xref: /redis-3.2.3/src/sort.c (revision 40f822da)
1 /* SORT command and helper functions.
2  *
3  * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *   * Redistributions of source code must retain the above copyright notice,
10  *     this list of conditions and the following disclaimer.
11  *   * Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  *   * Neither the name of Redis nor the names of its contributors may be used
15  *     to endorse or promote products derived from this software without
16  *     specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 
32 #include "redis.h"
33 #include "pqsort.h" /* Partial qsort for SORT+LIMIT */
34 #include <math.h> /* isnan() */
35 
36 zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank);
37 
38 redisSortOperation *createSortOperation(int type, robj *pattern) {
39     redisSortOperation *so = zmalloc(sizeof(*so));
40     so->type = type;
41     so->pattern = pattern;
42     return so;
43 }
44 
45 /* Return the value associated to the key with a name obtained using
46  * the following rules:
47  *
48  * 1) The first occurrence of '*' in 'pattern' is substituted with 'subst'.
49  *
50  * 2) If 'pattern' matches the "->" string, everything on the left of
51  *    the arrow is treated as the name of an hash field, and the part on the
52  *    left as the key name containing an hash. The value of the specified
53  *    field is returned.
54  *
55  * 3) If 'pattern' equals "#", the function simply returns 'subst' itself so
56  *    that the SORT command can be used like: SORT key GET # to retrieve
57  *    the Set/List elements directly.
58  *
59  * The returned object will always have its refcount increased by 1
60  * when it is non-NULL. */
61 robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
62     char *p, *f, *k;
63     sds spat, ssub;
64     robj *keyobj, *fieldobj = NULL, *o;
65     int prefixlen, sublen, postfixlen, fieldlen;
66 
67     /* If the pattern is "#" return the substitution object itself in order
68      * to implement the "SORT ... GET #" feature. */
69     spat = pattern->ptr;
70     if (spat[0] == '#' && spat[1] == '\0') {
71         incrRefCount(subst);
72         return subst;
73     }
74 
75     /* The substitution object may be specially encoded. If so we create
76      * a decoded object on the fly. Otherwise getDecodedObject will just
77      * increment the ref count, that we'll decrement later. */
78     subst = getDecodedObject(subst);
79     ssub = subst->ptr;
80 
81     /* If we can't find '*' in the pattern we return NULL as to GET a
82      * fixed key does not make sense. */
83     p = strchr(spat,'*');
84     if (!p) {
85         decrRefCount(subst);
86         return NULL;
87     }
88 
89     /* Find out if we're dealing with a hash dereference. */
90     if ((f = strstr(p+1, "->")) != NULL && *(f+2) != '\0') {
91         fieldlen = sdslen(spat)-(f-spat)-2;
92         fieldobj = createStringObject(f+2,fieldlen);
93     } else {
94         fieldlen = 0;
95     }
96 
97     /* Perform the '*' substitution. */
98     prefixlen = p-spat;
99     sublen = sdslen(ssub);
100     postfixlen = sdslen(spat)-(prefixlen+1)-(fieldlen ? fieldlen+2 : 0);
101     keyobj = createStringObject(NULL,prefixlen+sublen+postfixlen);
102     k = keyobj->ptr;
103     memcpy(k,spat,prefixlen);
104     memcpy(k+prefixlen,ssub,sublen);
105     memcpy(k+prefixlen+sublen,p+1,postfixlen);
106     decrRefCount(subst); /* Incremented by decodeObject() */
107 
108     /* Lookup substituted key */
109     o = lookupKeyRead(db,keyobj);
110     if (o == NULL) goto noobj;
111 
112     if (fieldobj) {
113         if (o->type != REDIS_HASH) goto noobj;
114 
115         /* Retrieve value from hash by the field name. This operation
116          * already increases the refcount of the returned object. */
117         o = hashTypeGetObject(o, fieldobj);
118     } else {
119         if (o->type != REDIS_STRING) goto noobj;
120 
121         /* Every object that this function returns needs to have its refcount
122          * increased. sortCommand decreases it again. */
123         incrRefCount(o);
124     }
125     decrRefCount(keyobj);
126     if (fieldobj) decrRefCount(fieldobj);
127     return o;
128 
129 noobj:
130     decrRefCount(keyobj);
131     if (fieldlen) decrRefCount(fieldobj);
132     return NULL;
133 }
134 
135 /* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with
136  * the additional parameter is not standard but a BSD-specific we have to
137  * pass sorting parameters via the global 'server' structure */
138 int sortCompare(const void *s1, const void *s2) {
139     const redisSortObject *so1 = s1, *so2 = s2;
140     int cmp;
141 
142     if (!server.sort_alpha) {
143         /* Numeric sorting. Here it's trivial as we precomputed scores */
144         if (so1->u.score > so2->u.score) {
145             cmp = 1;
146         } else if (so1->u.score < so2->u.score) {
147             cmp = -1;
148         } else {
149             /* Objects have the same score, but we don't want the comparison
150              * to be undefined, so we compare objects lexicographically.
151              * This way the result of SORT is deterministic. */
152             cmp = compareStringObjects(so1->obj,so2->obj);
153         }
154     } else {
155         /* Alphanumeric sorting */
156         if (server.sort_bypattern) {
157             if (!so1->u.cmpobj || !so2->u.cmpobj) {
158                 /* At least one compare object is NULL */
159                 if (so1->u.cmpobj == so2->u.cmpobj)
160                     cmp = 0;
161                 else if (so1->u.cmpobj == NULL)
162                     cmp = -1;
163                 else
164                     cmp = 1;
165             } else {
166                 /* We have both the objects, use strcoll */
167                 cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr);
168             }
169         } else {
170             /* Compare elements directly. */
171             cmp = compareStringObjects(so1->obj,so2->obj);
172         }
173     }
174     return server.sort_desc ? -cmp : cmp;
175 }
176 
177 /* The SORT command is the most complex command in Redis. Warning: this code
178  * is optimized for speed and a bit less for readability */
179 void sortCommand(redisClient *c) {
180     list *operations;
181     unsigned int outputlen = 0;
182     int desc = 0, alpha = 0;
183     long limit_start = 0, limit_count = -1, start, end;
184     int j, dontsort = 0, vectorlen;
185     int getop = 0; /* GET operation counter */
186     int int_convertion_error = 0;
187     robj *sortval, *sortby = NULL, *storekey = NULL;
188     redisSortObject *vector; /* Resulting vector to sort */
189 
190     /* Lookup the key to sort. It must be of the right types */
191     sortval = lookupKeyRead(c->db,c->argv[1]);
192     if (sortval && sortval->type != REDIS_SET &&
193                    sortval->type != REDIS_LIST &&
194                    sortval->type != REDIS_ZSET)
195     {
196         addReply(c,shared.wrongtypeerr);
197         return;
198     }
199 
200     /* Create a list of operations to perform for every sorted element.
201      * Operations can be GET/DEL/INCR/DECR */
202     operations = listCreate();
203     listSetFreeMethod(operations,zfree);
204     j = 2; /* options start at argv[2] */
205 
206     /* Now we need to protect sortval incrementing its count, in the future
207      * SORT may have options able to overwrite/delete keys during the sorting
208      * and the sorted key itself may get destroyed */
209     if (sortval)
210         incrRefCount(sortval);
211     else
212         sortval = createListObject();
213 
214     /* The SORT command has an SQL-alike syntax, parse it */
215     while(j < c->argc) {
216         int leftargs = c->argc-j-1;
217         if (!strcasecmp(c->argv[j]->ptr,"asc")) {
218             desc = 0;
219         } else if (!strcasecmp(c->argv[j]->ptr,"desc")) {
220             desc = 1;
221         } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) {
222             alpha = 1;
223         } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) {
224             if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) ||
225                 (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return;
226             j+=2;
227         } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) {
228             storekey = c->argv[j+1];
229             j++;
230         } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) {
231             sortby = c->argv[j+1];
232             /* If the BY pattern does not contain '*', i.e. it is constant,
233              * we don't need to sort nor to lookup the weight keys. */
234             if (strchr(c->argv[j+1]->ptr,'*') == NULL) dontsort = 1;
235             j++;
236         } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {
237             listAddNodeTail(operations,createSortOperation(
238                 REDIS_SORT_GET,c->argv[j+1]));
239             getop++;
240             j++;
241         } else {
242             decrRefCount(sortval);
243             listRelease(operations);
244             addReply(c,shared.syntaxerr);
245             return;
246         }
247         j++;
248     }
249 
250     /* For the STORE option, or when SORT is called from a Lua script,
251      * we want to force a specific ordering even when no explicit ordering
252      * was asked (SORT BY nosort). This guarantees that replication / AOF
253      * is deterministic.
254      *
255      * However in the case 'dontsort' is true, but the type to sort is a
256      * sorted set, we don't need to do anything as ordering is guaranteed
257      * in this special case. */
258     if ((storekey || c->flags & REDIS_LUA_CLIENT) &&
259         (dontsort && sortval->type != REDIS_ZSET))
260     {
261         /* Force ALPHA sorting */
262         dontsort = 0;
263         alpha = 1;
264         sortby = NULL;
265     }
266 
267     /* Destructively convert encoded sorted sets for SORT. */
268     if (sortval->type == REDIS_ZSET)
269         zsetConvert(sortval, REDIS_ENCODING_SKIPLIST);
270 
271     /* Objtain the length of the object to sort. */
272     switch(sortval->type) {
273     case REDIS_LIST: vectorlen = listTypeLength(sortval); break;
274     case REDIS_SET: vectorlen =  setTypeSize(sortval); break;
275     case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break;
276     default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */
277     }
278 
279     /* Perform LIMIT start,count sanity checking. */
280     start = (limit_start < 0) ? 0 : limit_start;
281     end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1;
282     if (start >= vectorlen) {
283         start = vectorlen-1;
284         end = vectorlen-2;
285     }
286     if (end >= vectorlen) end = vectorlen-1;
287 
288     /* Optimization:
289      *
290      * 1) if the object to sort is a sorted set.
291      * 2) There is nothing to sort as dontsort is true (BY <constant string>).
292      * 3) We have a LIMIT option that actually reduces the number of elements
293      *    to fetch.
294      *
295      * In this case to load all the objects in the vector is a huge waste of
296      * resources. We just allocate a vector that is big enough for the selected
297      * range length, and make sure to load just this part in the vector. */
298     if (sortval->type == REDIS_ZSET &&
299         dontsort &&
300         (start != 0 || end != vectorlen-1))
301     {
302         vectorlen = end-start+1;
303     }
304 
305     /* Load the sorting vector with all the objects to sort */
306     vector = zmalloc(sizeof(redisSortObject)*vectorlen);
307     j = 0;
308 
309     if (sortval->type == REDIS_LIST) {
310         listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL);
311         listTypeEntry entry;
312         while(listTypeNext(li,&entry)) {
313             vector[j].obj = listTypeGet(&entry);
314             vector[j].u.score = 0;
315             vector[j].u.cmpobj = NULL;
316             j++;
317         }
318         listTypeReleaseIterator(li);
319     } else if (sortval->type == REDIS_SET) {
320         setTypeIterator *si = setTypeInitIterator(sortval);
321         robj *ele;
322         while((ele = setTypeNextObject(si)) != NULL) {
323             vector[j].obj = ele;
324             vector[j].u.score = 0;
325             vector[j].u.cmpobj = NULL;
326             j++;
327         }
328         setTypeReleaseIterator(si);
329     } else if (sortval->type == REDIS_ZSET && dontsort) {
330         /* Special handling for a sorted set, if 'dontsort' is true.
331          * This makes sure we return elements in the sorted set original
332          * ordering, accordingly to DESC / ASC options.
333          *
334          * Note that in this case we also handle LIMIT here in a direct
335          * way, just getting the required range, as an optimization. */
336 
337         zset *zs = sortval->ptr;
338         zskiplist *zsl = zs->zsl;
339         zskiplistNode *ln;
340         robj *ele;
341         int rangelen = vectorlen;
342 
343         /* Check if starting point is trivial, before doing log(N) lookup. */
344         if (desc) {
345             long zsetlen = dictSize(((zset*)sortval->ptr)->dict);
346 
347             ln = zsl->tail;
348             if (start > 0)
349                 ln = zslGetElementByRank(zsl,zsetlen-start);
350         } else {
351             ln = zsl->header->level[0].forward;
352             if (start > 0)
353                 ln = zslGetElementByRank(zsl,start+1);
354         }
355 
356         while(rangelen--) {
357             redisAssertWithInfo(c,sortval,ln != NULL);
358             ele = ln->obj;
359             vector[j].obj = ele;
360             vector[j].u.score = 0;
361             vector[j].u.cmpobj = NULL;
362             j++;
363             ln = desc ? ln->backward : ln->level[0].forward;
364         }
365         /* The code producing the output does not know that in the case of
366          * sorted set, 'dontsort', and LIMIT, we are able to get just the
367          * range, already sorted, so we need to adjust "start" and "end"
368          * to make sure start is set to 0. */
369         end -= start;
370         start = 0;
371     } else if (sortval->type == REDIS_ZSET) {
372         dict *set = ((zset*)sortval->ptr)->dict;
373         dictIterator *di;
374         dictEntry *setele;
375         di = dictGetIterator(set);
376         while((setele = dictNext(di)) != NULL) {
377             vector[j].obj = dictGetKey(setele);
378             vector[j].u.score = 0;
379             vector[j].u.cmpobj = NULL;
380             j++;
381         }
382         dictReleaseIterator(di);
383     } else {
384         redisPanic("Unknown type");
385     }
386     redisAssertWithInfo(c,sortval,j == vectorlen);
387 
388     /* Now it's time to load the right scores in the sorting vector */
389     if (dontsort == 0) {
390         for (j = 0; j < vectorlen; j++) {
391             robj *byval;
392             if (sortby) {
393                 /* lookup value to sort by */
394                 byval = lookupKeyByPattern(c->db,sortby,vector[j].obj);
395                 if (!byval) continue;
396             } else {
397                 /* use object itself to sort by */
398                 byval = vector[j].obj;
399             }
400 
401             if (alpha) {
402                 if (sortby) vector[j].u.cmpobj = getDecodedObject(byval);
403             } else {
404                 if (byval->encoding == REDIS_ENCODING_RAW) {
405                     char *eptr;
406 
407                     vector[j].u.score = strtod(byval->ptr,&eptr);
408                     if (eptr[0] != '\0' || errno == ERANGE ||
409                         isnan(vector[j].u.score))
410                     {
411                         int_convertion_error = 1;
412                     }
413                 } else if (byval->encoding == REDIS_ENCODING_INT) {
414                     /* Don't need to decode the object if it's
415                      * integer-encoded (the only encoding supported) so
416                      * far. We can just cast it */
417                     vector[j].u.score = (long)byval->ptr;
418                 } else {
419                     redisAssertWithInfo(c,sortval,1 != 1);
420                 }
421             }
422 
423             /* when the object was retrieved using lookupKeyByPattern,
424              * its refcount needs to be decreased. */
425             if (sortby) {
426                 decrRefCount(byval);
427             }
428         }
429     }
430 
431     if (dontsort == 0) {
432         server.sort_desc = desc;
433         server.sort_alpha = alpha;
434         server.sort_bypattern = sortby ? 1 : 0;
435         if (sortby && (start != 0 || end != vectorlen-1))
436             pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end);
437         else
438             qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare);
439     }
440 
441     /* Send command output to the output buffer, performing the specified
442      * GET/DEL/INCR/DECR operations if any. */
443     outputlen = getop ? getop*(end-start+1) : end-start+1;
444     if (int_convertion_error) {
445         addReplyError(c,"One or more scores can't be converted into double");
446     } else if (storekey == NULL) {
447         /* STORE option not specified, sent the sorting result to client */
448         addReplyMultiBulkLen(c,outputlen);
449         for (j = start; j <= end; j++) {
450             listNode *ln;
451             listIter li;
452 
453             if (!getop) addReplyBulk(c,vector[j].obj);
454             listRewind(operations,&li);
455             while((ln = listNext(&li))) {
456                 redisSortOperation *sop = ln->value;
457                 robj *val = lookupKeyByPattern(c->db,sop->pattern,
458                     vector[j].obj);
459 
460                 if (sop->type == REDIS_SORT_GET) {
461                     if (!val) {
462                         addReply(c,shared.nullbulk);
463                     } else {
464                         addReplyBulk(c,val);
465                         decrRefCount(val);
466                     }
467                 } else {
468                     /* Always fails */
469                     redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET);
470                 }
471             }
472         }
473     } else {
474         robj *sobj = createZiplistObject();
475 
476         /* STORE option specified, set the sorting result as a List object */
477         for (j = start; j <= end; j++) {
478             listNode *ln;
479             listIter li;
480 
481             if (!getop) {
482                 listTypePush(sobj,vector[j].obj,REDIS_TAIL);
483             } else {
484                 listRewind(operations,&li);
485                 while((ln = listNext(&li))) {
486                     redisSortOperation *sop = ln->value;
487                     robj *val = lookupKeyByPattern(c->db,sop->pattern,
488                         vector[j].obj);
489 
490                     if (sop->type == REDIS_SORT_GET) {
491                         if (!val) val = createStringObject("",0);
492 
493                         /* listTypePush does an incrRefCount, so we should take care
494                          * care of the incremented refcount caused by either
495                          * lookupKeyByPattern or createStringObject("",0) */
496                         listTypePush(sobj,val,REDIS_TAIL);
497                         decrRefCount(val);
498                     } else {
499                         /* Always fails */
500                         redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET);
501                     }
502                 }
503             }
504         }
505         if (outputlen) {
506             setKey(c->db,storekey,sobj);
507             notifyKeyspaceEvent("sortstore",storekey,c->db->id);
508             server.dirty += outputlen;
509         } else if (dbDelete(c->db,storekey)) {
510             signalModifiedKey(c->db,storekey);
511             notifyKeyspaceEvent("del",storekey,c->db->id);
512             server.dirty++;
513         }
514         decrRefCount(sobj);
515         addReplyLongLong(c,outputlen);
516     }
517 
518     /* Cleanup */
519     if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET)
520         for (j = 0; j < vectorlen; j++)
521             decrRefCount(vector[j].obj);
522     decrRefCount(sortval);
523     listRelease(operations);
524     for (j = 0; j < vectorlen; j++) {
525         if (alpha && vector[j].u.cmpobj)
526             decrRefCount(vector[j].u.cmpobj);
527     }
528     zfree(vector);
529 }
530