2012-11-08 12:25:23 -05:00
|
|
|
/* SORT command and helper functions.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
|
|
|
*
|
|
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
|
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* * Neither the name of Redis nor the names of its contributors may be used
|
|
|
|
* to endorse or promote products derived from this software without
|
|
|
|
* specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2010-06-21 18:07:48 -04:00
|
|
|
#include "redis.h"
|
|
|
|
#include "pqsort.h" /* Partial qsort for SORT+LIMIT */
|
2012-02-01 09:22:28 -05:00
|
|
|
#include <math.h> /* isnan() */
|
2010-06-21 18:07:48 -04:00
|
|
|
|
2012-10-03 05:41:08 -04:00
|
|
|
zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank);
|
|
|
|
|
2010-06-21 18:07:48 -04:00
|
|
|
redisSortOperation *createSortOperation(int type, robj *pattern) {
|
|
|
|
redisSortOperation *so = zmalloc(sizeof(*so));
|
|
|
|
so->type = type;
|
|
|
|
so->pattern = pattern;
|
|
|
|
return so;
|
|
|
|
}
|
|
|
|
|
2012-04-17 07:05:09 -04:00
|
|
|
/* Return the value associated to the key with a name obtained using
|
|
|
|
* the following rules:
|
|
|
|
*
|
2013-01-16 12:00:20 -05:00
|
|
|
* 1) The first occurrence of '*' in 'pattern' is substituted with 'subst'.
|
2012-04-17 07:05:09 -04:00
|
|
|
*
|
|
|
|
* 2) If 'pattern' matches the "->" string, everything on the left of
|
2013-12-05 10:35:32 -05:00
|
|
|
* 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
|
2012-04-17 07:05:09 -04:00
|
|
|
* 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.
|
|
|
|
*
|
2010-06-21 18:07:48 -04:00
|
|
|
* The returned object will always have its refcount increased by 1
|
|
|
|
* when it is non-NULL. */
|
|
|
|
robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
|
2012-04-17 07:05:09 -04:00
|
|
|
char *p, *f, *k;
|
2010-06-21 18:07:48 -04:00
|
|
|
sds spat, ssub;
|
2012-04-18 05:37:14 -04:00
|
|
|
robj *keyobj, *fieldobj = NULL, *o;
|
2010-06-21 18:07:48 -04:00
|
|
|
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;
|
2012-04-17 07:05:09 -04:00
|
|
|
|
|
|
|
/* If we can't find '*' in the pattern we return NULL as to GET a
|
|
|
|
* fixed key does not make sense. */
|
2010-06-21 18:07:48 -04:00
|
|
|
p = strchr(spat,'*');
|
|
|
|
if (!p) {
|
|
|
|
decrRefCount(subst);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Find out if we're dealing with a hash dereference. */
|
2012-04-17 07:05:09 -04:00
|
|
|
if ((f = strstr(p+1, "->")) != NULL && *(f+2) != '\0') {
|
|
|
|
fieldlen = sdslen(spat)-(f-spat)-2;
|
|
|
|
fieldobj = createStringObject(f+2,fieldlen);
|
2010-06-21 18:07:48 -04:00
|
|
|
} else {
|
|
|
|
fieldlen = 0;
|
|
|
|
}
|
|
|
|
|
2012-04-17 07:05:09 -04:00
|
|
|
/* Perform the '*' substitution. */
|
2010-06-21 18:07:48 -04:00
|
|
|
prefixlen = p-spat;
|
|
|
|
sublen = sdslen(ssub);
|
2012-04-17 07:05:09 -04:00
|
|
|
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() */
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
/* Lookup substituted key */
|
2012-04-17 07:05:09 -04:00
|
|
|
o = lookupKeyRead(db,keyobj);
|
|
|
|
if (o == NULL) goto noobj;
|
2010-06-21 18:07:48 -04:00
|
|
|
|
2012-04-18 05:37:14 -04:00
|
|
|
if (fieldobj) {
|
2012-04-17 07:05:09 -04:00
|
|
|
if (o->type != REDIS_HASH) goto noobj;
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
/* Retrieve value from hash by the field name. This operation
|
|
|
|
* already increases the refcount of the returned object. */
|
2012-04-17 07:05:09 -04:00
|
|
|
o = hashTypeGetObject(o, fieldobj);
|
2010-06-21 18:07:48 -04:00
|
|
|
} else {
|
2012-04-17 07:05:09 -04:00
|
|
|
if (o->type != REDIS_STRING) goto noobj;
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
/* Every object that this function returns needs to have its refcount
|
|
|
|
* increased. sortCommand decreases it again. */
|
|
|
|
incrRefCount(o);
|
|
|
|
}
|
2012-04-17 07:05:09 -04:00
|
|
|
decrRefCount(keyobj);
|
2012-04-18 05:37:14 -04:00
|
|
|
if (fieldobj) decrRefCount(fieldobj);
|
2010-06-21 18:07:48 -04:00
|
|
|
return o;
|
2012-04-17 07:05:09 -04:00
|
|
|
|
|
|
|
noobj:
|
|
|
|
decrRefCount(keyobj);
|
|
|
|
if (fieldlen) decrRefCount(fieldobj);
|
|
|
|
return NULL;
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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 {
|
2012-02-01 09:22:28 -05:00
|
|
|
/* Objects have the same score, but we don't want the comparison
|
2013-01-16 12:00:20 -05:00
|
|
|
* to be undefined, so we compare objects lexicographically.
|
2012-02-01 09:22:28 -05:00
|
|
|
* This way the result of SORT is deterministic. */
|
|
|
|
cmp = compareStringObjects(so1->obj,so2->obj);
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
} 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 {
|
2013-07-12 06:02:36 -04:00
|
|
|
/* 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);
|
|
|
|
}
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Compare elements directly. */
|
2013-07-12 06:02:36 -04:00
|
|
|
if (server.sort_store) {
|
|
|
|
cmp = compareStringObjects(so1->obj,so2->obj);
|
|
|
|
} else {
|
|
|
|
cmp = collateStringObjects(so1->obj,so2->obj);
|
|
|
|
}
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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 sortCommand(redisClient *c) {
|
|
|
|
list *operations;
|
|
|
|
unsigned int outputlen = 0;
|
|
|
|
int desc = 0, alpha = 0;
|
2011-12-19 06:29:46 -05:00
|
|
|
long limit_start = 0, limit_count = -1, start, end;
|
2010-06-21 18:07:48 -04:00
|
|
|
int j, dontsort = 0, vectorlen;
|
|
|
|
int getop = 0; /* GET operation counter */
|
2012-02-01 09:22:28 -05:00
|
|
|
int int_convertion_error = 0;
|
2014-03-10 11:10:50 -04:00
|
|
|
int syntax_error = 0;
|
2010-06-21 18:07:48 -04:00
|
|
|
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]);
|
2012-10-03 05:41:08 -04:00
|
|
|
if (sortval && sortval->type != REDIS_SET &&
|
|
|
|
sortval->type != REDIS_LIST &&
|
|
|
|
sortval->type != REDIS_ZSET)
|
2010-06-21 18:07:48 -04:00
|
|
|
{
|
|
|
|
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);
|
2012-10-03 05:41:08 -04:00
|
|
|
j = 2; /* options start at argv[2] */
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
/* Now we need to protect sortval incrementing its count, in the future
|
|
|
|
* SORT may have options able to overwrite/delete keys during the sorting
|
2013-01-16 12:00:20 -05:00
|
|
|
* and the sorted key itself may get destroyed */
|
2011-12-01 10:07:55 -05:00
|
|
|
if (sortval)
|
|
|
|
incrRefCount(sortval);
|
|
|
|
else
|
|
|
|
sortval = createListObject();
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
/* 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) {
|
2014-03-10 10:44:41 -04:00
|
|
|
if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL)
|
|
|
|
!= REDIS_OK) ||
|
|
|
|
(getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL)
|
|
|
|
!= REDIS_OK))
|
|
|
|
{
|
2014-03-10 11:10:50 -04:00
|
|
|
syntax_error++;
|
|
|
|
break;
|
2014-03-10 10:44:41 -04:00
|
|
|
}
|
2010-06-21 18:07:48 -04:00
|
|
|
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. */
|
2014-03-10 11:28:18 -04:00
|
|
|
if (strchr(c->argv[j+1]->ptr,'*') == NULL) {
|
|
|
|
dontsort = 1;
|
|
|
|
} else {
|
|
|
|
/* If BY is specified with a real patter, we can't accept
|
|
|
|
* it in cluster mode. */
|
|
|
|
if (server.cluster_enabled) {
|
|
|
|
addReplyError(c,"BY option of SORT denied in Cluster mode.");
|
|
|
|
syntax_error++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2010-06-21 18:07:48 -04:00
|
|
|
j++;
|
|
|
|
} else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {
|
2014-03-10 11:10:50 -04:00
|
|
|
if (server.cluster_enabled) {
|
|
|
|
addReplyError(c,"GET option of SORT denied in Cluster mode.");
|
|
|
|
syntax_error++;
|
|
|
|
break;
|
|
|
|
}
|
2010-06-21 18:07:48 -04:00
|
|
|
listAddNodeTail(operations,createSortOperation(
|
|
|
|
REDIS_SORT_GET,c->argv[j+1]));
|
|
|
|
getop++;
|
|
|
|
j++;
|
|
|
|
} else {
|
|
|
|
addReply(c,shared.syntaxerr);
|
2014-03-10 11:10:50 -04:00
|
|
|
syntax_error++;
|
|
|
|
break;
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
|
2014-03-10 11:10:50 -04:00
|
|
|
/* Handle syntax errors set during options parsing. */
|
|
|
|
if (syntax_error) {
|
|
|
|
decrRefCount(sortval);
|
|
|
|
listRelease(operations);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-10-23 11:52:35 -04:00
|
|
|
/* When sorting a set with no sort specified, we must sort the output
|
|
|
|
* so the result is consistent across scripting and replication.
|
Scripting: Force SORT BY constant determinism inside SORT itself.
SORT is able to return (faster than when ordering) unordered output if
the "BY" clause is used with a constant value. However we try to play
well with scripting requirements of determinism providing always sorted
outputs when SORT (and other similar commands) are called by Lua
scripts.
However we used the general mechanism in place in scripting in order to
reorder SORT output, that is, if the command has the "S" flag set, the
Lua scripting engine will take an additional step when converting a
multi bulk reply to Lua value, calling a Lua sorting function.
This is suboptimal as we can do it faster inside SORT itself.
This is also broken as issue #545 shows us: basically when SORT is used
with a constant BY, and additionally also GET is used, the Lua scripting
engine was trying to order the output as a flat array, while it was
actually a list of key-value pairs.
What we do know is to recognized if the caller of SORT is the Lua client
(since we can check this using the REDIS_LUA_CLIENT flag). If so, and if
a "don't sort" condition is triggered by the BY option with a constant
string, we force the lexicographical sorting.
This commit fixes this bug and improves the performance, and at the same
time simplifies the implementation. This does not mean I'm smart today,
it means I was stupid when I committed the original implementation ;)
2012-09-04 19:12:41 -04:00
|
|
|
*
|
2014-10-23 11:52:35 -04:00
|
|
|
* 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. */
|
2014-12-11 09:57:11 -05:00
|
|
|
if (dontsort &&
|
|
|
|
sortval->type == REDIS_SET &&
|
|
|
|
(storekey || c->flags & REDIS_LUA_CLIENT))
|
2012-10-03 05:41:08 -04:00
|
|
|
{
|
|
|
|
/* Force ALPHA sorting */
|
2012-02-01 11:05:45 -05:00
|
|
|
dontsort = 0;
|
|
|
|
alpha = 1;
|
|
|
|
sortby = NULL;
|
|
|
|
}
|
|
|
|
|
2011-03-14 08:30:06 -04:00
|
|
|
/* Destructively convert encoded sorted sets for SORT. */
|
2011-12-01 10:07:55 -05:00
|
|
|
if (sortval->type == REDIS_ZSET)
|
|
|
|
zsetConvert(sortval, REDIS_ENCODING_SKIPLIST);
|
2011-03-14 08:30:06 -04:00
|
|
|
|
2012-10-03 05:41:08 -04:00
|
|
|
/* Objtain the length of the object to sort. */
|
2010-06-21 18:07:48 -04:00
|
|
|
switch(sortval->type) {
|
|
|
|
case REDIS_LIST: vectorlen = listTypeLength(sortval); break;
|
2010-08-21 05:15:31 -04:00
|
|
|
case REDIS_SET: vectorlen = setTypeSize(sortval); break;
|
2010-06-21 18:07:48 -04:00
|
|
|
case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break;
|
|
|
|
default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */
|
|
|
|
}
|
2012-10-03 05:41:08 -04:00
|
|
|
|
|
|
|
/* Perform LIMIT start,count sanity checking. */
|
|
|
|
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;
|
|
|
|
|
|
|
|
/* Optimization:
|
|
|
|
*
|
|
|
|
* 1) if the object to sort is a sorted set.
|
|
|
|
* 2) There is nothing to sort as dontsort is true (BY <constant string>).
|
|
|
|
* 3) We have a LIMIT option that actually reduces the number of elements
|
|
|
|
* to fetch.
|
|
|
|
*
|
|
|
|
* In this case to load all the objects in the vector is a huge waste of
|
|
|
|
* resources. We just allocate a vector that is big enough for the selected
|
|
|
|
* range length, and make sure to load just this part in the vector. */
|
|
|
|
if (sortval->type == REDIS_ZSET &&
|
|
|
|
dontsort &&
|
|
|
|
(start != 0 || end != vectorlen-1))
|
|
|
|
{
|
|
|
|
vectorlen = end-start+1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Load the sorting vector with all the objects to sort */
|
2010-06-21 18:07:48 -04:00
|
|
|
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);
|
2010-08-21 05:15:31 -04:00
|
|
|
} else if (sortval->type == REDIS_SET) {
|
2010-08-21 05:25:13 -04:00
|
|
|
setTypeIterator *si = setTypeInitIterator(sortval);
|
2010-08-21 05:15:31 -04:00
|
|
|
robj *ele;
|
2010-12-09 15:11:56 -05:00
|
|
|
while((ele = setTypeNextObject(si)) != NULL) {
|
2010-08-21 05:15:31 -04:00
|
|
|
vector[j].obj = ele;
|
|
|
|
vector[j].u.score = 0;
|
|
|
|
vector[j].u.cmpobj = NULL;
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
setTypeReleaseIterator(si);
|
2012-10-03 05:41:08 -04:00
|
|
|
} else if (sortval->type == REDIS_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;
|
|
|
|
robj *ele;
|
|
|
|
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--) {
|
|
|
|
redisAssertWithInfo(c,sortval,ln != NULL);
|
|
|
|
ele = ln->obj;
|
|
|
|
vector[j].obj = ele;
|
|
|
|
vector[j].u.score = 0;
|
|
|
|
vector[j].u.cmpobj = NULL;
|
|
|
|
j++;
|
|
|
|
ln = desc ? ln->backward : ln->level[0].forward;
|
|
|
|
}
|
|
|
|
/* The code producing the output does not know that in the case of
|
|
|
|
* sorted set, 'dontsort', and LIMIT, we are able to get just the
|
|
|
|
* range, already sorted, so we need to adjust "start" and "end"
|
|
|
|
* to make sure start is set to 0. */
|
|
|
|
end -= start;
|
|
|
|
start = 0;
|
2010-08-21 05:15:31 -04:00
|
|
|
} else if (sortval->type == REDIS_ZSET) {
|
|
|
|
dict *set = ((zset*)sortval->ptr)->dict;
|
2010-06-21 18:07:48 -04:00
|
|
|
dictIterator *di;
|
|
|
|
dictEntry *setele;
|
|
|
|
di = dictGetIterator(set);
|
|
|
|
while((setele = dictNext(di)) != NULL) {
|
2011-11-08 11:07:55 -05:00
|
|
|
vector[j].obj = dictGetKey(setele);
|
2010-06-21 18:07:48 -04:00
|
|
|
vector[j].u.score = 0;
|
|
|
|
vector[j].u.cmpobj = NULL;
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
dictReleaseIterator(di);
|
2010-08-21 05:15:31 -04:00
|
|
|
} else {
|
|
|
|
redisPanic("Unknown type");
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
2011-10-04 12:43:03 -04:00
|
|
|
redisAssertWithInfo(c,sortval,j == vectorlen);
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
/* 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 {
|
2012-06-05 15:50:10 -04:00
|
|
|
if (sdsEncodedObject(byval)) {
|
2012-02-01 09:22:28 -05:00
|
|
|
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;
|
|
|
|
}
|
2010-06-21 18:07:48 -04:00
|
|
|
} 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 {
|
2011-10-04 12:43:03 -04:00
|
|
|
redisAssertWithInfo(c,sortval,1 != 1);
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* when the object was retrieved using lookupKeyByPattern,
|
|
|
|
* its refcount needs to be decreased. */
|
|
|
|
if (sortby) {
|
|
|
|
decrRefCount(byval);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dontsort == 0) {
|
|
|
|
server.sort_desc = desc;
|
|
|
|
server.sort_alpha = alpha;
|
|
|
|
server.sort_bypattern = sortby ? 1 : 0;
|
2013-07-12 06:02:36 -04:00
|
|
|
server.sort_store = storekey ? 1 : 0;
|
2010-06-21 18:07:48 -04:00
|
|
|
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;
|
2012-02-01 09:22:28 -05:00
|
|
|
if (int_convertion_error) {
|
|
|
|
addReplyError(c,"One or more scores can't be converted into double");
|
|
|
|
} else if (storekey == NULL) {
|
2010-06-21 18:07:48 -04:00
|
|
|
/* STORE option not specified, sent the sorting result to client */
|
2010-09-02 06:38:34 -04:00
|
|
|
addReplyMultiBulkLen(c,outputlen);
|
2010-06-21 18:07:48 -04:00
|
|
|
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 {
|
2011-10-04 12:43:03 -04:00
|
|
|
/* Always fails */
|
|
|
|
redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET);
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} 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 {
|
2011-10-04 12:43:03 -04:00
|
|
|
/* Always fails */
|
|
|
|
redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET);
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-01-30 01:36:49 -05:00
|
|
|
if (outputlen) {
|
|
|
|
setKey(c->db,storekey,sobj);
|
2013-01-25 07:19:08 -05:00
|
|
|
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"sortstore",storekey,
|
|
|
|
c->db->id);
|
2012-01-30 01:36:49 -05:00
|
|
|
server.dirty += outputlen;
|
|
|
|
} else if (dbDelete(c->db,storekey)) {
|
|
|
|
signalModifiedKey(c->db,storekey);
|
2013-01-25 07:19:08 -05:00
|
|
|
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",storekey,c->db->id);
|
2012-01-30 01:36:49 -05:00
|
|
|
server.dirty++;
|
|
|
|
}
|
2011-06-14 09:34:27 -04:00
|
|
|
decrRefCount(sobj);
|
2010-09-02 08:30:56 -04:00
|
|
|
addReplyLongLong(c,outputlen);
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Cleanup */
|
2010-08-21 05:15:31 -04:00
|
|
|
if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET)
|
2010-06-21 18:07:48 -04:00
|
|
|
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);
|
|
|
|
}
|