]> git.saurik.com Git - redis.git/blame_incremental - src/sort.c
When closing the MDB DBI, do it in a transaction.
[redis.git] / src / sort.c
... / ...
CommitLineData
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
36zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank);
37
38redisSortOperation *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 occurence 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. */
61robj *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
129noobj:
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 */
138int 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 lexicographycally.
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 */
179void 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 destroied */
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 server.dirty += outputlen;
508 } else if (dbDelete(c->db,storekey)) {
509 signalModifiedKey(c->db,storekey);
510 server.dirty++;
511 }
512 decrRefCount(sobj);
513 addReplyLongLong(c,outputlen);
514 }
515
516 /* Cleanup */
517 if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET)
518 for (j = 0; j < vectorlen; j++)
519 decrRefCount(vector[j].obj);
520 decrRefCount(sortval);
521 listRelease(operations);
522 for (j = 0; j < vectorlen; j++) {
523 if (alpha && vector[j].u.cmpobj)
524 decrRefCount(vector[j].u.cmpobj);
525 }
526 zfree(vector);
527}
528
529