summaryrefslogtreecommitdiff
path: root/examples/redis-unstable/src/sort.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/redis-unstable/src/sort.c')
-rw-r--r--examples/redis-unstable/src/sort.c665
1 files changed, 0 insertions, 665 deletions
diff --git a/examples/redis-unstable/src/sort.c b/examples/redis-unstable/src/sort.c
deleted file mode 100644
index 997ca62..0000000
--- a/examples/redis-unstable/src/sort.c
+++ /dev/null
@@ -1,665 +0,0 @@
-/* SORT command and helper functions.
- *
- * Copyright (c) 2009-Present, Redis Ltd.
- * All rights reserved.
- *
- * Licensed under your choice of (a) the Redis Source Available License 2.0
- * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
- * GNU Affero General Public License v3 (AGPLv3).
- */
-
-#include "fast_float_strtod.h"
-#include "server.h"
-#include "pqsort.h" /* Partial qsort for SORT+LIMIT */
-#include <math.h> /* isnan() */
-#include "cluster.h"
-
-zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank);
-
-redisSortOperation *createSortOperation(int type, robj *pattern) {
- redisSortOperation *so = zmalloc(sizeof(*so));
- so->type = type;
- so->pattern = pattern;
- return so;
-}
-
-/* Return the value associated to the key with a name obtained using
- * the following rules:
- *
- * 1) The first occurrence of '*' in 'pattern' is substituted with 'subst'.
- *
- * 2) If 'pattern' matches the "->" string, everything on the left of
- * the arrow is treated as the name of a hash field, and the part on the
- * left as the key name containing a hash. The value of the specified
- * field is returned.
- *
- * 3) If 'pattern' equals "#", the function simply returns 'subst' itself so
- * that the SORT command can be used like: SORT key GET # to retrieve
- * the Set/List elements directly.
- *
- * The returned object will always have its refcount increased by 1
- * when it is non-NULL. */
-robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
- kvobj *kv;
- char *p, *f, *k;
- sds spat, ssub;
- robj *keyobj, *fieldobj = NULL, *val;
-
- int prefixlen, sublen, postfixlen, fieldlen;
-
- /* If the pattern is "#" return the substitution object itself in order
- * to implement the "SORT ... GET #" feature. */
- spat = pattern->ptr;
- if (spat[0] == '#' && spat[1] == '\0') {
- incrRefCount(subst);
- return subst;
- }
-
- /* The substitution object may be specially encoded. If so we create
- * a decoded object on the fly. Otherwise getDecodedObject will just
- * increment the ref count, that we'll decrement later. */
- subst = getDecodedObject(subst);
- ssub = subst->ptr;
-
- /* If we can't find '*' in the pattern we return NULL as to GET a
- * fixed key does not make sense. */
- p = strchr(spat,'*');
- if (!p) {
- decrRefCount(subst);
- return NULL;
- }
-
- /* Find out if we're dealing with a hash dereference. */
- if ((f = strstr(p+1, "->")) != NULL && *(f+2) != '\0') {
- fieldlen = sdslen(spat)-(f-spat)-2;
- fieldobj = createStringObject(f+2,fieldlen);
- } else {
- fieldlen = 0;
- }
-
- /* Perform the '*' substitution. */
- prefixlen = p-spat;
- sublen = sdslen(ssub);
- postfixlen = sdslen(spat)-(prefixlen+1)-(fieldlen ? fieldlen+2 : 0);
- keyobj = createStringObject(NULL,prefixlen+sublen+postfixlen);
- k = keyobj->ptr;
- memcpy(k,spat,prefixlen);
- memcpy(k+prefixlen,ssub,sublen);
- memcpy(k+prefixlen+sublen,p+1,postfixlen);
- decrRefCount(subst); /* Incremented by decodeObject() */
-
- /* Lookup substituted key */
- kv = lookupKeyRead(db, keyobj);
- if (kv == NULL) goto noobj;
-
- if (fieldobj) {
- if (kv->type != OBJ_HASH) goto noobj;
-
- /* Retrieve value from hash by the field name. The returned object
- * is a new object with refcount already incremented. */
- int isHashDeleted;
- hashTypeGetValueObject(db, kv, fieldobj->ptr, HFE_LAZY_EXPIRE, &val, NULL, &isHashDeleted);
- kv = val;
-
- if (isHashDeleted)
- goto noobj;
-
- } else {
- if (kv->type != OBJ_STRING) goto noobj;
-
- /* Every object that this function returns needs to have its refcount
- * increased. sortCommand decreases it again. */
- incrRefCount(kv);
- }
- decrRefCount(keyobj);
- if (fieldobj) decrRefCount(fieldobj);
- return kv;
-
-noobj:
- decrRefCount(keyobj);
- if (fieldlen) decrRefCount(fieldobj);
- return NULL;
-}
-
-/* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with
- * the additional parameter is not standard but a BSD-specific we have to
- * pass sorting parameters via the global 'server' structure */
-int sortCompare(const void *s1, const void *s2) {
- const redisSortObject *so1 = s1, *so2 = s2;
- int cmp;
-
- if (!server.sort_alpha) {
- /* Numeric sorting. Here it's trivial as we precomputed scores */
- if (so1->u.score > so2->u.score) {
- cmp = 1;
- } else if (so1->u.score < so2->u.score) {
- cmp = -1;
- } else {
- /* Objects have the same score, but we don't want the comparison
- * to be undefined, so we compare objects lexicographically.
- * This way the result of SORT is deterministic. */
- cmp = compareStringObjects(so1->obj,so2->obj);
- }
- } else {
- /* Alphanumeric sorting */
- if (server.sort_bypattern) {
- if (!so1->u.cmpobj || !so2->u.cmpobj) {
- /* At least one compare object is NULL */
- if (so1->u.cmpobj == so2->u.cmpobj)
- cmp = 0;
- else if (so1->u.cmpobj == NULL)
- cmp = -1;
- else
- cmp = 1;
- } else {
- /* We have both the objects, compare them. */
- if (server.sort_store) {
- cmp = compareStringObjects(so1->u.cmpobj,so2->u.cmpobj);
- } else {
- /* Here we can use strcoll() directly as we are sure that
- * the objects are decoded string objects. */
- cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr);
- }
- }
- } else {
- /* Compare elements directly. */
- if (server.sort_store) {
- cmp = compareStringObjects(so1->obj,so2->obj);
- } else {
- cmp = collateStringObjects(so1->obj,so2->obj);
- }
- }
- }
- return server.sort_desc ? -cmp : cmp;
-}
-
-/* The SORT command is the most complex command in Redis. Warning: this code
- * is optimized for speed and a bit less for readability */
-void sortCommandGeneric(client *c, int readonly) {
- 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_conversion_error = 0;
- int syntax_error = 0;
- robj *sortval, *sortby = NULL, *storekey = NULL;
- size_t oldsize = 0;
- redisSortObject *vector; /* Resulting vector to sort */
- int user_has_full_key_access = 0; /* ACL - used in order to verify 'get' and 'by' options can be used */
- /* Create a list of operations to perform for every sorted element.
- * Operations can be GET */
- operations = listCreate();
- listSetFreeMethod(operations,zfree);
- j = 2; /* options start at argv[2] */
-
- user_has_full_key_access = ACLUserCheckCmdWithUnrestrictedKeyAccess(c->user, c->cmd, c->argv, c->argc, CMD_KEY_ACCESS);
-
- /* 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)
- != C_OK) ||
- (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL)
- != C_OK))
- {
- syntax_error++;
- break;
- }
- j+=2;
- } else if (readonly == 0 && !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;
- } else {
- /* If BY is specified with a real pattern, we can't accept it in cluster mode,
- * unless we can make sure the keys formed by the pattern are in the same slot
- * as the key to sort. */
- if (server.cluster_enabled && patternHashSlot(sortby->ptr, sdslen(sortby->ptr)) != getKeySlot(c->argv[1]->ptr)) {
- addReplyError(c, "BY option of SORT denied in Cluster mode when "
- "keys formed by the pattern may be in different slots.");
- syntax_error++;
- break;
- }
-
- /* If the BY pattern slot is not equal with the slot of keys, we will record
- * an incompatible behavior as above comments. */
- if (server.cluster_compatibility_sample_ratio && !server.cluster_enabled &&
- SHOULD_CLUSTER_COMPATIBILITY_SAMPLE())
- {
- if (patternHashSlot(sortby->ptr, sdslen(sortby->ptr)) !=
- (int)keyHashSlot(c->argv[1]->ptr, sdslen(c->argv[1]->ptr)))
- server.stat_cluster_incompatible_ops++;
- }
-
- /* If BY is specified with a real pattern, we can't accept
- * it if no full ACL key access is applied for this command. */
- if (!user_has_full_key_access) {
- addReplyError(c,"BY option of SORT denied due to insufficient ACL permissions.");
- syntax_error++;
- break;
- }
- }
- j++;
- } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {
- /* If GET is specified with a real pattern, we can't accept it in cluster mode,
- * unless we can make sure the keys formed by the pattern are in the same slot
- * as the key to sort. The pattern # represents the key itself, so just skip
- * pattern slot check. */
- if (server.cluster_enabled &&
- strcmp(c->argv[j+1]->ptr, "#") &&
- patternHashSlot(c->argv[j+1]->ptr, sdslen(c->argv[j+1]->ptr)) != getKeySlot(c->argv[1]->ptr))
- {
- addReplyError(c, "GET option of SORT denied in Cluster mode when "
- "keys formed by the pattern may be in different slots.");
- syntax_error++;
- break;
- }
-
- /* If the GET pattern slot is not equal with the slot of keys, we will record
- * an incompatible behavior as above comments. */
- if (server.cluster_compatibility_sample_ratio && !server.cluster_enabled &&
- strcmp(c->argv[j+1]->ptr, "#") &&
- SHOULD_CLUSTER_COMPATIBILITY_SAMPLE())
- {
- if (patternHashSlot(c->argv[j+1]->ptr, sdslen(c->argv[j+1]->ptr)) !=
- (int)keyHashSlot(c->argv[1]->ptr, sdslen(c->argv[1]->ptr)))
- server.stat_cluster_incompatible_ops++;
- }
-
- if (!user_has_full_key_access) {
- addReplyError(c,"GET option of SORT denied due to insufficient ACL permissions.");
- syntax_error++;
- break;
- }
- listAddNodeTail(operations,createSortOperation(
- SORT_OP_GET,c->argv[j+1]));
- getop++;
- j++;
- } else {
- addReplyErrorObject(c,shared.syntaxerr);
- syntax_error++;
- break;
- }
- j++;
- }
-
- /* Handle syntax errors set during options parsing. */
- if (syntax_error) {
- listRelease(operations);
- return;
- }
-
- /* Lookup the key to sort. It must be of the right types */
- sortval = lookupKeyRead(c->db, c->argv[1]);
- if (sortval && sortval->type != OBJ_SET &&
- sortval->type != OBJ_LIST &&
- sortval->type != OBJ_ZSET)
- {
- listRelease(operations);
- addReplyErrorObject(c,shared.wrongtypeerr);
- return;
- }
-
- /* 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 destroyed */
- if (sortval)
- incrRefCount(sortval);
- else
- sortval = createQuicklistObject(server.list_max_listpack_size, server.list_compress_depth);
-
- /* When sorting a set with no sort specified, we must sort the output
- * so the result is consistent across scripting and replication.
- *
- * The other types (list, sorted set) will retain their native order
- * even if no sort order is requested, so they remain stable across
- * scripting and replication. */
- if (dontsort &&
- sortval->type == OBJ_SET &&
- (storekey || c->flags & CLIENT_SCRIPT))
- {
- /* Force ALPHA sorting */
- dontsort = 0;
- alpha = 1;
- sortby = NULL;
- }
-
- /* Destructively convert encoded sorted sets for SORT. */
- if (sortval->type == OBJ_ZSET) {
- if (server.memory_tracking_per_slot)
- oldsize = zsetAllocSize(sortval);
- zsetConvert(sortval, OBJ_ENCODING_SKIPLIST);
- if (server.memory_tracking_per_slot)
- updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, zsetAllocSize(sortval));
- }
-
- /* Obtain the length of the object to sort. */
- switch(sortval->type) {
- case OBJ_LIST: vectorlen = listTypeLength(sortval); break;
- case OBJ_SET: vectorlen = setTypeSize(sortval); break;
- case OBJ_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break;
- default: vectorlen = 0; serverPanic("Bad SORT type"); /* Avoid GCC warning */
- }
-
- /* Perform LIMIT start,count sanity checking.
- * And avoid integer overflow by limiting inputs to object sizes. */
- start = min(max(limit_start, 0), vectorlen);
- limit_count = min(max(limit_count, -1), vectorlen);
- 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;
-
- /* Whenever possible, we load elements into the output array in a more
- * direct way. This is possible if:
- *
- * 1) The object to sort is a sorted set or a list (internally sorted).
- * 2) There is nothing to sort as dontsort is true (BY <constant string>).
- *
- * In this special case, if we have a LIMIT option that actually reduces
- * the number of elements to fetch, we also optimize to just load the
- * range we are interested in and allocating a vector that is big enough
- * for the selected range length. */
- if ((sortval->type == OBJ_ZSET || sortval->type == OBJ_LIST) &&
- dontsort &&
- (start != 0 || end != vectorlen-1))
- {
- vectorlen = end-start+1;
- }
-
- /* Load the sorting vector with all the objects to sort */
- vector = zmalloc(sizeof(redisSortObject)*vectorlen);
- j = 0;
-
- if (sortval->type == OBJ_LIST && dontsort) {
- /* Special handling for a list, if 'dontsort' is true.
- * This makes sure we return elements in the list original
- * ordering, accordingly to DESC / ASC options.
- *
- * Note that in this case we also handle LIMIT here in a direct
- * way, just getting the required range, as an optimization. */
- if (end >= start) {
- listTypeIterator li;
- listTypeEntry entry;
- listTypeInitIterator(&li, sortval,
- desc ? (long)(listTypeLength(sortval) - start - 1) : start,
- desc ? LIST_HEAD : LIST_TAIL);
-
- while(j < vectorlen && listTypeNext(&li, &entry)) {
- vector[j].obj = listTypeGet(&entry);
- vector[j].u.score = 0;
- vector[j].u.cmpobj = NULL;
- j++;
- }
- listTypeResetIterator(&li);
- /* Fix start/end: output code is not aware of this optimization. */
- end -= start;
- start = 0;
- }
- } else if (sortval->type == OBJ_LIST) {
- listTypeIterator li;
- listTypeEntry entry;
- listTypeInitIterator(&li, sortval, 0, LIST_TAIL);
- while(listTypeNext(&li, &entry)) {
- vector[j].obj = listTypeGet(&entry);
- vector[j].u.score = 0;
- vector[j].u.cmpobj = NULL;
- j++;
- }
- listTypeResetIterator(&li);
- } else if (sortval->type == OBJ_SET) {
- if (server.memory_tracking_per_slot)
- oldsize = setTypeAllocSize(sortval);
- setTypeIterator si;
- sds sdsele;
- setTypeInitIterator(&si, sortval);
- while((sdsele = setTypeNextObject(&si)) != NULL) {
- vector[j].obj = createObject(OBJ_STRING,sdsele);
- vector[j].u.score = 0;
- vector[j].u.cmpobj = NULL;
- j++;
- }
- setTypeResetIterator(&si);
- if (server.memory_tracking_per_slot)
- updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, setTypeAllocSize(sortval));
- } else if (sortval->type == OBJ_ZSET && dontsort) {
- /* Special handling for a sorted set, if 'dontsort' is true.
- * This makes sure we return elements in the sorted set original
- * ordering, accordingly to DESC / ASC options.
- *
- * Note that in this case we also handle LIMIT here in a direct
- * way, just getting the required range, as an optimization. */
-
- zset *zs = sortval->ptr;
- zskiplist *zsl = zs->zsl;
- zskiplistNode *ln;
- sds sdsele;
- int rangelen = vectorlen;
-
- /* Check if starting point is trivial, before doing log(N) lookup. */
- if (desc) {
- long zsetlen = dictSize(((zset*)sortval->ptr)->dict);
-
- ln = zsl->tail;
- if (start > 0)
- ln = zslGetElementByRank(zsl,zsetlen-start);
- } else {
- ln = zsl->header->level[0].forward;
- if (start > 0)
- ln = zslGetElementByRank(zsl,start+1);
- }
-
- while(rangelen--) {
- serverAssertWithInfo(c,sortval,ln != NULL);
- sdsele = zslGetNodeElement(ln);
- vector[j].obj = createStringObject(sdsele,sdslen(sdsele));
- vector[j].u.score = 0;
- vector[j].u.cmpobj = NULL;
- j++;
- ln = desc ? ln->backward : ln->level[0].forward;
- }
- /* Fix start/end: output code is not aware of this optimization. */
- end -= start;
- start = 0;
- } else if (sortval->type == OBJ_ZSET) {
- dict *set = ((zset*)sortval->ptr)->dict;
- dictIterator di;
- dictEntry *setele;
- sds sdsele;
-
- if (server.memory_tracking_per_slot)
- oldsize = zsetAllocSize(sortval);
- dictInitIterator(&di, set);
- while((setele = dictNext(&di)) != NULL) {
- sdsele = zslGetNodeElement(dictGetKey(setele));
- vector[j].obj = createStringObject(sdsele,sdslen(sdsele));
- vector[j].u.score = 0;
- vector[j].u.cmpobj = NULL;
- j++;
- }
- dictResetIterator(&di);
- if (server.memory_tracking_per_slot)
- updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, zsetAllocSize(sortval));
- } else {
- serverPanic("Unknown type");
- }
- serverAssertWithInfo(c,sortval,j == vectorlen);
-
- /* Now it's time to load the right scores in the sorting vector */
- if (!dontsort) {
- 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 (sdsEncodedObject(byval)) {
- char *eptr;
-
- vector[j].u.score = fast_float_strtod(byval->ptr,&eptr);
- if (eptr[0] != '\0' || errno == ERANGE ||
- isnan(vector[j].u.score))
- {
- int_conversion_error = 1;
- }
- } else if (byval->encoding == OBJ_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 {
- serverAssertWithInfo(c,sortval,1 != 1);
- }
- }
-
- /* when the object was retrieved using lookupKeyByPattern,
- * its refcount needs to be decreased. */
- if (sortby) {
- decrRefCount(byval);
- }
- }
-
- server.sort_desc = desc;
- server.sort_alpha = alpha;
- server.sort_bypattern = sortby ? 1 : 0;
- server.sort_store = storekey ? 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_conversion_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 */
- addReplyArrayLen(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 == SORT_OP_GET) {
- if (!val) {
- addReplyNull(c);
- } else {
- addReplyBulk(c,val);
- decrRefCount(val);
- }
- } else {
- /* Always fails */
- serverAssertWithInfo(c,sortval,sop->type == SORT_OP_GET);
- }
- }
- }
- } else {
- /* We can't predict the size and encoding of the stored list, we
- * assume it's a large list and then convert it at the end if needed. */
- robj *sobj = createQuicklistObject(server.list_max_listpack_size, server.list_compress_depth);
-
- /* 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,LIST_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 == SORT_OP_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,LIST_TAIL);
- decrRefCount(val);
- } else {
- /* Always fails */
- serverAssertWithInfo(c,sortval,sop->type == SORT_OP_GET);
- }
- }
- }
- }
-
- if (outputlen) {
- listTypeTryConversion(sobj,LIST_CONV_AUTO,NULL,NULL);
- setKey(c, c->db, storekey, &sobj, 0);
- /* Ownership of sobj transferred to the db. Set to NULL to prevent
- * freeing it below. */
- sobj = NULL;
- notifyKeyspaceEvent(NOTIFY_LIST,"sortstore",storekey,
- c->db->id);
- server.dirty += outputlen;
- /* Ownership of sobj transferred to the db. No need to free it. */
- } else {
- if (dbDelete(c->db, storekey)) {
- keyModified(c, c->db, storekey, NULL, 1);
- notifyKeyspaceEvent(NOTIFY_GENERIC, "del", storekey, c->db->id);
- server.dirty++;
- }
- decrRefCount(sobj);
- }
-
- addReplyLongLong(c,outputlen);
- }
-
- /* Cleanup */
- 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);
-}
-
-/* SORT wrapper function for read-only mode. */
-void sortroCommand(client *c) {
- sortCommandGeneric(c, 1);
-}
-
-void sortCommand(client *c) {
- sortCommandGeneric(c, 0);
-}