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(REDIS_NOTIFY_LIST,"sortstore",storekey, 508 c->db->id); 509 server.dirty += outputlen; 510 } else if (dbDelete(c->db,storekey)) { 511 signalModifiedKey(c->db,storekey); 512 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",storekey,c->db->id); 513 server.dirty++; 514 } 515 decrRefCount(sobj); 516 addReplyLongLong(c,outputlen); 517 } 518 519 /* Cleanup */ 520 if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET) 521 for (j = 0; j < vectorlen; j++) 522 decrRefCount(vector[j].obj); 523 decrRefCount(sortval); 524 listRelease(operations); 525 for (j = 0; j < vectorlen; j++) { 526 if (alpha && vector[j].u.cmpobj) 527 decrRefCount(vector[j].u.cmpobj); 528 } 529 zfree(vector); 530 } 531