void spopCommand(redisClient *c) { robj *set, *ele, *aux; 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) { ele = createStringObjectFromLongLong(llele); set->ptr = intsetRemove(set->ptr,llele,NULL); } else { incrRefCount(ele); setTypeRemove(set,ele); } notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id); /* Replicate/AOF this command as an SREM operation */ aux = createStringObject("SREM",4); rewriteClientCommandVector(c,3,aux,c->argv[1],ele); decrRefCount(ele); decrRefCount(aux); addReplyBulk(c,ele); if (setTypeSize(set) == 0) { dbDelete(c->db,c->argv[1]); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id); } signalModifiedKey(c->db,c->argv[1]); server.dirty++; }
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 spopCommand(client *c) { robj *set, *ele, *aux; sds sdsele; int64_t llele; int encoding; if (c->argc == 3) { spopWithCountCommand(c); return; } else if (c->argc > 3) { addReply(c,shared.syntaxerr); return; } /* Make sure a key with the name inputted exists, and that it's type is * indeed a set */ if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,set,OBJ_SET)) return; /* Get a random element from the set */ encoding = setTypeRandomElement(set,&sdsele,&llele); /* Remove the element from the set */ if (encoding == OBJ_ENCODING_INTSET) { ele = createStringObjectFromLongLong(llele); set->ptr = intsetRemove(set->ptr,llele,NULL); } else { ele = createStringObject(sdsele,sdslen(sdsele)); setTypeRemove(set,ele->ptr); } notifyKeyspaceEvent(NOTIFY_SET,"spop",c->argv[1],c->db->id); /* Replicate/AOF this command as an SREM operation */ aux = createStringObject("SREM",4); rewriteClientCommandVector(c,3,aux,c->argv[1],ele); decrRefCount(aux); /* Add the element to the reply */ addReplyBulk(c,ele); decrRefCount(ele); /* Delete the set if it's empty */ if (setTypeSize(set) == 0) { dbDelete(c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id); } /* Set has been modified */ signalModifiedKey(c->db,c->argv[1]); server.dirty++; }
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++; }
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 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); }