xref: /redis-3.2.3/src/sort.c (revision 22c9c407)
1 #include "redis.h"
2 #include "pqsort.h" /* Partial qsort for SORT+LIMIT */
3 #include <math.h> /* isnan() */
4 
5 redisSortOperation *createSortOperation(int type, robj *pattern) {
6     redisSortOperation *so = zmalloc(sizeof(*so));
7     so->type = type;
8     so->pattern = pattern;
9     return so;
10 }
11 
12 /* Return the value associated to the key with a name obtained
13  * substituting the first occurence of '*' in 'pattern' with 'subst'.
14  * The returned object will always have its refcount increased by 1
15  * when it is non-NULL. */
16 robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
17     char *p, *f;
18     sds spat, ssub;
19     robj keyobj, fieldobj, *o;
20     int prefixlen, sublen, postfixlen, fieldlen;
21     /* Expoit the internal sds representation to create a sds string allocated on the stack in order to make this function faster */
22     struct {
23         int len;
24         int free;
25         char buf[REDIS_SORTKEY_MAX+1];
26     } keyname, fieldname;
27 
28     /* If the pattern is "#" return the substitution object itself in order
29      * to implement the "SORT ... GET #" feature. */
30     spat = pattern->ptr;
31     if (spat[0] == '#' && spat[1] == '\0') {
32         incrRefCount(subst);
33         return subst;
34     }
35 
36     /* The substitution object may be specially encoded. If so we create
37      * a decoded object on the fly. Otherwise getDecodedObject will just
38      * increment the ref count, that we'll decrement later. */
39     subst = getDecodedObject(subst);
40 
41     ssub = subst->ptr;
42     if (sdslen(spat)+sdslen(ssub)-1 > REDIS_SORTKEY_MAX) return NULL;
43     p = strchr(spat,'*');
44     if (!p) {
45         decrRefCount(subst);
46         return NULL;
47     }
48 
49     /* Find out if we're dealing with a hash dereference. */
50     if ((f = strstr(p+1, "->")) != NULL) {
51         fieldlen = sdslen(spat)-(f-spat);
52         /* this also copies \0 character */
53         memcpy(fieldname.buf,f+2,fieldlen-1);
54         fieldname.len = fieldlen-2;
55     } else {
56         fieldlen = 0;
57     }
58 
59     prefixlen = p-spat;
60     sublen = sdslen(ssub);
61     postfixlen = sdslen(spat)-(prefixlen+1)-fieldlen;
62     memcpy(keyname.buf,spat,prefixlen);
63     memcpy(keyname.buf+prefixlen,ssub,sublen);
64     memcpy(keyname.buf+prefixlen+sublen,p+1,postfixlen);
65     keyname.buf[prefixlen+sublen+postfixlen] = '\0';
66     keyname.len = prefixlen+sublen+postfixlen;
67     decrRefCount(subst);
68 
69     /* Lookup substituted key */
70     initStaticStringObject(keyobj,((char*)&keyname)+(sizeof(struct sdshdr)));
71     o = lookupKeyRead(db,&keyobj);
72     if (o == NULL) return NULL;
73 
74     if (fieldlen > 0) {
75         if (o->type != REDIS_HASH || fieldname.len < 1) return NULL;
76 
77         /* Retrieve value from hash by the field name. This operation
78          * already increases the refcount of the returned object. */
79         initStaticStringObject(fieldobj,((char*)&fieldname)+(sizeof(struct sdshdr)));
80         o = hashTypeGetObject(o, &fieldobj);
81     } else {
82         if (o->type != REDIS_STRING) return NULL;
83 
84         /* Every object that this function returns needs to have its refcount
85          * increased. sortCommand decreases it again. */
86         incrRefCount(o);
87     }
88 
89     return o;
90 }
91 
92 /* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with
93  * the additional parameter is not standard but a BSD-specific we have to
94  * pass sorting parameters via the global 'server' structure */
95 int sortCompare(const void *s1, const void *s2) {
96     const redisSortObject *so1 = s1, *so2 = s2;
97     int cmp;
98 
99     if (!server.sort_alpha) {
100         /* Numeric sorting. Here it's trivial as we precomputed scores */
101         if (so1->u.score > so2->u.score) {
102             cmp = 1;
103         } else if (so1->u.score < so2->u.score) {
104             cmp = -1;
105         } else {
106             /* Objects have the same score, but we don't want the comparison
107              * to be undefined, so we compare objects lexicographycally.
108              * This way the result of SORT is deterministic. */
109             cmp = compareStringObjects(so1->obj,so2->obj);
110         }
111     } else {
112         /* Alphanumeric sorting */
113         if (server.sort_bypattern) {
114             if (!so1->u.cmpobj || !so2->u.cmpobj) {
115                 /* At least one compare object is NULL */
116                 if (so1->u.cmpobj == so2->u.cmpobj)
117                     cmp = 0;
118                 else if (so1->u.cmpobj == NULL)
119                     cmp = -1;
120                 else
121                     cmp = 1;
122             } else {
123                 /* We have both the objects, use strcoll */
124                 cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr);
125             }
126         } else {
127             /* Compare elements directly. */
128             cmp = compareStringObjects(so1->obj,so2->obj);
129         }
130     }
131     return server.sort_desc ? -cmp : cmp;
132 }
133 
134 /* The SORT command is the most complex command in Redis. Warning: this code
135  * is optimized for speed and a bit less for readability */
136 void sortCommand(redisClient *c) {
137     list *operations;
138     unsigned int outputlen = 0;
139     int desc = 0, alpha = 0;
140     long limit_start = 0, limit_count = -1, start, end;
141     int j, dontsort = 0, vectorlen;
142     int getop = 0; /* GET operation counter */
143     int int_convertion_error = 0;
144     robj *sortval, *sortby = NULL, *storekey = NULL;
145     redisSortObject *vector; /* Resulting vector to sort */
146 
147     /* Lookup the key to sort. It must be of the right types */
148     sortval = lookupKeyRead(c->db,c->argv[1]);
149     if (sortval && sortval->type != REDIS_SET && sortval->type != REDIS_LIST &&
150         sortval->type != REDIS_ZSET)
151     {
152         addReply(c,shared.wrongtypeerr);
153         return;
154     }
155 
156     /* Create a list of operations to perform for every sorted element.
157      * Operations can be GET/DEL/INCR/DECR */
158     operations = listCreate();
159     listSetFreeMethod(operations,zfree);
160     j = 2;
161 
162     /* Now we need to protect sortval incrementing its count, in the future
163      * SORT may have options able to overwrite/delete keys during the sorting
164      * and the sorted key itself may get destroied */
165     if (sortval)
166         incrRefCount(sortval);
167     else
168         sortval = createListObject();
169 
170     /* The SORT command has an SQL-alike syntax, parse it */
171     while(j < c->argc) {
172         int leftargs = c->argc-j-1;
173         if (!strcasecmp(c->argv[j]->ptr,"asc")) {
174             desc = 0;
175         } else if (!strcasecmp(c->argv[j]->ptr,"desc")) {
176             desc = 1;
177         } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) {
178             alpha = 1;
179         } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) {
180             if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) ||
181                 (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return;
182             j+=2;
183         } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) {
184             storekey = c->argv[j+1];
185             j++;
186         } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) {
187             sortby = c->argv[j+1];
188             /* If the BY pattern does not contain '*', i.e. it is constant,
189              * we don't need to sort nor to lookup the weight keys. */
190             if (strchr(c->argv[j+1]->ptr,'*') == NULL) dontsort = 1;
191             j++;
192         } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {
193             listAddNodeTail(operations,createSortOperation(
194                 REDIS_SORT_GET,c->argv[j+1]));
195             getop++;
196             j++;
197         } else {
198             decrRefCount(sortval);
199             listRelease(operations);
200             addReply(c,shared.syntaxerr);
201             return;
202         }
203         j++;
204     }
205 
206     /* If we have STORE we need to force sorting for deterministic output
207      * and replication. We use alpha sorting since this is guaranteed to
208      * work with any input. */
209     if (storekey && dontsort) {
210         dontsort = 0;
211         alpha = 1;
212         sortby = NULL;
213     }
214 
215     /* Destructively convert encoded sorted sets for SORT. */
216     if (sortval->type == REDIS_ZSET)
217         zsetConvert(sortval, REDIS_ENCODING_SKIPLIST);
218 
219     /* Load the sorting vector with all the objects to sort */
220     switch(sortval->type) {
221     case REDIS_LIST: vectorlen = listTypeLength(sortval); break;
222     case REDIS_SET: vectorlen =  setTypeSize(sortval); break;
223     case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break;
224     default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */
225     }
226     vector = zmalloc(sizeof(redisSortObject)*vectorlen);
227     j = 0;
228 
229     if (sortval->type == REDIS_LIST) {
230         listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL);
231         listTypeEntry entry;
232         while(listTypeNext(li,&entry)) {
233             vector[j].obj = listTypeGet(&entry);
234             vector[j].u.score = 0;
235             vector[j].u.cmpobj = NULL;
236             j++;
237         }
238         listTypeReleaseIterator(li);
239     } else if (sortval->type == REDIS_SET) {
240         setTypeIterator *si = setTypeInitIterator(sortval);
241         robj *ele;
242         while((ele = setTypeNextObject(si)) != NULL) {
243             vector[j].obj = ele;
244             vector[j].u.score = 0;
245             vector[j].u.cmpobj = NULL;
246             j++;
247         }
248         setTypeReleaseIterator(si);
249     } else if (sortval->type == REDIS_ZSET) {
250         dict *set = ((zset*)sortval->ptr)->dict;
251         dictIterator *di;
252         dictEntry *setele;
253         di = dictGetIterator(set);
254         while((setele = dictNext(di)) != NULL) {
255             vector[j].obj = dictGetKey(setele);
256             vector[j].u.score = 0;
257             vector[j].u.cmpobj = NULL;
258             j++;
259         }
260         dictReleaseIterator(di);
261     } else {
262         redisPanic("Unknown type");
263     }
264     redisAssertWithInfo(c,sortval,j == vectorlen);
265 
266     /* Now it's time to load the right scores in the sorting vector */
267     if (dontsort == 0) {
268         for (j = 0; j < vectorlen; j++) {
269             robj *byval;
270             if (sortby) {
271                 /* lookup value to sort by */
272                 byval = lookupKeyByPattern(c->db,sortby,vector[j].obj);
273                 if (!byval) continue;
274             } else {
275                 /* use object itself to sort by */
276                 byval = vector[j].obj;
277             }
278 
279             if (alpha) {
280                 if (sortby) vector[j].u.cmpobj = getDecodedObject(byval);
281             } else {
282                 if (byval->encoding == REDIS_ENCODING_RAW) {
283                     char *eptr;
284 
285                     vector[j].u.score = strtod(byval->ptr,&eptr);
286                     if (eptr[0] != '\0' || errno == ERANGE ||
287                         isnan(vector[j].u.score))
288                     {
289                         int_convertion_error = 1;
290                     }
291                 } else if (byval->encoding == REDIS_ENCODING_INT) {
292                     /* Don't need to decode the object if it's
293                      * integer-encoded (the only encoding supported) so
294                      * far. We can just cast it */
295                     vector[j].u.score = (long)byval->ptr;
296                 } else {
297                     redisAssertWithInfo(c,sortval,1 != 1);
298                 }
299             }
300 
301             /* when the object was retrieved using lookupKeyByPattern,
302              * its refcount needs to be decreased. */
303             if (sortby) {
304                 decrRefCount(byval);
305             }
306         }
307     }
308 
309     /* We are ready to sort the vector... perform a bit of sanity check
310      * on the LIMIT option too. We'll use a partial version of quicksort. */
311     start = (limit_start < 0) ? 0 : limit_start;
312     end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1;
313     if (start >= vectorlen) {
314         start = vectorlen-1;
315         end = vectorlen-2;
316     }
317     if (end >= vectorlen) end = vectorlen-1;
318 
319     server.sort_dontsort = dontsort;
320     if (dontsort == 0) {
321         server.sort_desc = desc;
322         server.sort_alpha = alpha;
323         server.sort_bypattern = sortby ? 1 : 0;
324         if (sortby && (start != 0 || end != vectorlen-1))
325             pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end);
326         else
327             qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare);
328     }
329 
330     /* Send command output to the output buffer, performing the specified
331      * GET/DEL/INCR/DECR operations if any. */
332     outputlen = getop ? getop*(end-start+1) : end-start+1;
333     if (int_convertion_error) {
334         addReplyError(c,"One or more scores can't be converted into double");
335     } else if (storekey == NULL) {
336         /* STORE option not specified, sent the sorting result to client */
337         addReplyMultiBulkLen(c,outputlen);
338         for (j = start; j <= end; j++) {
339             listNode *ln;
340             listIter li;
341 
342             if (!getop) addReplyBulk(c,vector[j].obj);
343             listRewind(operations,&li);
344             while((ln = listNext(&li))) {
345                 redisSortOperation *sop = ln->value;
346                 robj *val = lookupKeyByPattern(c->db,sop->pattern,
347                     vector[j].obj);
348 
349                 if (sop->type == REDIS_SORT_GET) {
350                     if (!val) {
351                         addReply(c,shared.nullbulk);
352                     } else {
353                         addReplyBulk(c,val);
354                         decrRefCount(val);
355                     }
356                 } else {
357                     /* Always fails */
358                     redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET);
359                 }
360             }
361         }
362     } else {
363         robj *sobj = createZiplistObject();
364 
365         /* STORE option specified, set the sorting result as a List object */
366         for (j = start; j <= end; j++) {
367             listNode *ln;
368             listIter li;
369 
370             if (!getop) {
371                 listTypePush(sobj,vector[j].obj,REDIS_TAIL);
372             } else {
373                 listRewind(operations,&li);
374                 while((ln = listNext(&li))) {
375                     redisSortOperation *sop = ln->value;
376                     robj *val = lookupKeyByPattern(c->db,sop->pattern,
377                         vector[j].obj);
378 
379                     if (sop->type == REDIS_SORT_GET) {
380                         if (!val) val = createStringObject("",0);
381 
382                         /* listTypePush does an incrRefCount, so we should take care
383                          * care of the incremented refcount caused by either
384                          * lookupKeyByPattern or createStringObject("",0) */
385                         listTypePush(sobj,val,REDIS_TAIL);
386                         decrRefCount(val);
387                     } else {
388                         /* Always fails */
389                         redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET);
390                     }
391                 }
392             }
393         }
394         if (outputlen) {
395             setKey(c->db,storekey,sobj);
396             server.dirty += outputlen;
397         } else if (dbDelete(c->db,storekey)) {
398             signalModifiedKey(c->db,storekey);
399             server.dirty++;
400         }
401         decrRefCount(sobj);
402         addReplyLongLong(c,outputlen);
403     }
404 
405     /* Cleanup */
406     if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET)
407         for (j = 0; j < vectorlen; j++)
408             decrRefCount(vector[j].obj);
409     decrRefCount(sortval);
410     listRelease(operations);
411     for (j = 0; j < vectorlen; j++) {
412         if (alpha && vector[j].u.cmpobj)
413             decrRefCount(vector[j].u.cmpobj);
414     }
415     zfree(vector);
416 }
417 
418 
419