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 a hash field, and the part on the 52 * left as the key name containing a 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, compare them. */ 167 if (server.sort_store) { 168 cmp = compareStringObjects(so1->u.cmpobj,so2->u.cmpobj); 169 } else { 170 /* Here we can use strcoll() directly as we are sure that 171 * the objects are decoded string objects. */ 172 cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr); 173 } 174 } 175 } else { 176 /* Compare elements directly. */ 177 if (server.sort_store) { 178 cmp = compareStringObjects(so1->obj,so2->obj); 179 } else { 180 cmp = collateStringObjects(so1->obj,so2->obj); 181 } 182 } 183 } 184 return server.sort_desc ? -cmp : cmp; 185 } 186 187 /* The SORT command is the most complex command in Redis. Warning: this code 188 * is optimized for speed and a bit less for readability */ 189 void sortCommand(redisClient *c) { 190 list *operations; 191 unsigned int outputlen = 0; 192 int desc = 0, alpha = 0; 193 long limit_start = 0, limit_count = -1, start, end; 194 int j, dontsort = 0, vectorlen; 195 int getop = 0; /* GET operation counter */ 196 int int_convertion_error = 0; 197 int syntax_error = 0; 198 robj *sortval, *sortby = NULL, *storekey = NULL; 199 redisSortObject *vector; /* Resulting vector to sort */ 200 201 /* Lookup the key to sort. It must be of the right types */ 202 sortval = lookupKeyRead(c->db,c->argv[1]); 203 if (sortval && sortval->type != REDIS_SET && 204 sortval->type != REDIS_LIST && 205 sortval->type != REDIS_ZSET) 206 { 207 addReply(c,shared.wrongtypeerr); 208 return; 209 } 210 211 /* Create a list of operations to perform for every sorted element. 212 * Operations can be GET/DEL/INCR/DECR */ 213 operations = listCreate(); 214 listSetFreeMethod(operations,zfree); 215 j = 2; /* options start at argv[2] */ 216 217 /* Now we need to protect sortval incrementing its count, in the future 218 * SORT may have options able to overwrite/delete keys during the sorting 219 * and the sorted key itself may get destroyed */ 220 if (sortval) 221 incrRefCount(sortval); 222 else 223 sortval = createListObject(); 224 225 /* The SORT command has an SQL-alike syntax, parse it */ 226 while(j < c->argc) { 227 int leftargs = c->argc-j-1; 228 if (!strcasecmp(c->argv[j]->ptr,"asc")) { 229 desc = 0; 230 } else if (!strcasecmp(c->argv[j]->ptr,"desc")) { 231 desc = 1; 232 } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) { 233 alpha = 1; 234 } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) { 235 if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) 236 != REDIS_OK) || 237 (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) 238 != REDIS_OK)) 239 { 240 syntax_error++; 241 break; 242 } 243 j+=2; 244 } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) { 245 storekey = c->argv[j+1]; 246 j++; 247 } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) { 248 sortby = c->argv[j+1]; 249 /* If the BY pattern does not contain '*', i.e. it is constant, 250 * we don't need to sort nor to lookup the weight keys. */ 251 if (strchr(c->argv[j+1]->ptr,'*') == NULL) { 252 dontsort = 1; 253 } else { 254 /* If BY is specified with a real patter, we can't accept 255 * it in cluster mode. */ 256 if (server.cluster_enabled) { 257 addReplyError(c,"BY option of SORT denied in Cluster mode."); 258 syntax_error++; 259 break; 260 } 261 } 262 j++; 263 } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) { 264 if (server.cluster_enabled) { 265 addReplyError(c,"GET option of SORT denied in Cluster mode."); 266 syntax_error++; 267 break; 268 } 269 listAddNodeTail(operations,createSortOperation( 270 REDIS_SORT_GET,c->argv[j+1])); 271 getop++; 272 j++; 273 } else { 274 addReply(c,shared.syntaxerr); 275 syntax_error++; 276 break; 277 } 278 j++; 279 } 280 281 /* Handle syntax errors set during options parsing. */ 282 if (syntax_error) { 283 decrRefCount(sortval); 284 listRelease(operations); 285 return; 286 } 287 288 /* For the STORE option, or when SORT is called from a Lua script, 289 * we want to force a specific ordering even when no explicit ordering 290 * was asked (SORT BY nosort). This guarantees that replication / AOF 291 * is deterministic. 292 * 293 * However in the case 'dontsort' is true, but the type to sort is a 294 * sorted set, we don't need to do anything as ordering is guaranteed 295 * in this special case. */ 296 if ((storekey || c->flags & REDIS_LUA_CLIENT) && 297 (dontsort && sortval->type != REDIS_ZSET)) 298 { 299 /* Force ALPHA sorting */ 300 dontsort = 0; 301 alpha = 1; 302 sortby = NULL; 303 } 304 305 /* Destructively convert encoded sorted sets for SORT. */ 306 if (sortval->type == REDIS_ZSET) 307 zsetConvert(sortval, REDIS_ENCODING_SKIPLIST); 308 309 /* Objtain the length of the object to sort. */ 310 switch(sortval->type) { 311 case REDIS_LIST: vectorlen = listTypeLength(sortval); break; 312 case REDIS_SET: vectorlen = setTypeSize(sortval); break; 313 case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break; 314 default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */ 315 } 316 317 /* Perform LIMIT start,count sanity checking. */ 318 start = (limit_start < 0) ? 0 : limit_start; 319 end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1; 320 if (start >= vectorlen) { 321 start = vectorlen-1; 322 end = vectorlen-2; 323 } 324 if (end >= vectorlen) end = vectorlen-1; 325 326 /* Optimization: 327 * 328 * 1) if the object to sort is a sorted set. 329 * 2) There is nothing to sort as dontsort is true (BY <constant string>). 330 * 3) We have a LIMIT option that actually reduces the number of elements 331 * to fetch. 332 * 333 * In this case to load all the objects in the vector is a huge waste of 334 * resources. We just allocate a vector that is big enough for the selected 335 * range length, and make sure to load just this part in the vector. */ 336 if (sortval->type == REDIS_ZSET && 337 dontsort && 338 (start != 0 || end != vectorlen-1)) 339 { 340 vectorlen = end-start+1; 341 } 342 343 /* Load the sorting vector with all the objects to sort */ 344 vector = zmalloc(sizeof(redisSortObject)*vectorlen); 345 j = 0; 346 347 if (sortval->type == REDIS_LIST) { 348 listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL); 349 listTypeEntry entry; 350 while(listTypeNext(li,&entry)) { 351 vector[j].obj = listTypeGet(&entry); 352 vector[j].u.score = 0; 353 vector[j].u.cmpobj = NULL; 354 j++; 355 } 356 listTypeReleaseIterator(li); 357 } else if (sortval->type == REDIS_SET) { 358 setTypeIterator *si = setTypeInitIterator(sortval); 359 robj *ele; 360 while((ele = setTypeNextObject(si)) != NULL) { 361 vector[j].obj = ele; 362 vector[j].u.score = 0; 363 vector[j].u.cmpobj = NULL; 364 j++; 365 } 366 setTypeReleaseIterator(si); 367 } else if (sortval->type == REDIS_ZSET && dontsort) { 368 /* Special handling for a sorted set, if 'dontsort' is true. 369 * This makes sure we return elements in the sorted set original 370 * ordering, accordingly to DESC / ASC options. 371 * 372 * Note that in this case we also handle LIMIT here in a direct 373 * way, just getting the required range, as an optimization. */ 374 375 zset *zs = sortval->ptr; 376 zskiplist *zsl = zs->zsl; 377 zskiplistNode *ln; 378 robj *ele; 379 int rangelen = vectorlen; 380 381 /* Check if starting point is trivial, before doing log(N) lookup. */ 382 if (desc) { 383 long zsetlen = dictSize(((zset*)sortval->ptr)->dict); 384 385 ln = zsl->tail; 386 if (start > 0) 387 ln = zslGetElementByRank(zsl,zsetlen-start); 388 } else { 389 ln = zsl->header->level[0].forward; 390 if (start > 0) 391 ln = zslGetElementByRank(zsl,start+1); 392 } 393 394 while(rangelen--) { 395 redisAssertWithInfo(c,sortval,ln != NULL); 396 ele = ln->obj; 397 vector[j].obj = ele; 398 vector[j].u.score = 0; 399 vector[j].u.cmpobj = NULL; 400 j++; 401 ln = desc ? ln->backward : ln->level[0].forward; 402 } 403 /* The code producing the output does not know that in the case of 404 * sorted set, 'dontsort', and LIMIT, we are able to get just the 405 * range, already sorted, so we need to adjust "start" and "end" 406 * to make sure start is set to 0. */ 407 end -= start; 408 start = 0; 409 } else if (sortval->type == REDIS_ZSET) { 410 dict *set = ((zset*)sortval->ptr)->dict; 411 dictIterator *di; 412 dictEntry *setele; 413 di = dictGetIterator(set); 414 while((setele = dictNext(di)) != NULL) { 415 vector[j].obj = dictGetKey(setele); 416 vector[j].u.score = 0; 417 vector[j].u.cmpobj = NULL; 418 j++; 419 } 420 dictReleaseIterator(di); 421 } else { 422 redisPanic("Unknown type"); 423 } 424 redisAssertWithInfo(c,sortval,j == vectorlen); 425 426 /* Now it's time to load the right scores in the sorting vector */ 427 if (dontsort == 0) { 428 for (j = 0; j < vectorlen; j++) { 429 robj *byval; 430 if (sortby) { 431 /* lookup value to sort by */ 432 byval = lookupKeyByPattern(c->db,sortby,vector[j].obj); 433 if (!byval) continue; 434 } else { 435 /* use object itself to sort by */ 436 byval = vector[j].obj; 437 } 438 439 if (alpha) { 440 if (sortby) vector[j].u.cmpobj = getDecodedObject(byval); 441 } else { 442 if (sdsEncodedObject(byval)) { 443 char *eptr; 444 445 vector[j].u.score = strtod(byval->ptr,&eptr); 446 if (eptr[0] != '\0' || errno == ERANGE || 447 isnan(vector[j].u.score)) 448 { 449 int_convertion_error = 1; 450 } 451 } else if (byval->encoding == REDIS_ENCODING_INT) { 452 /* Don't need to decode the object if it's 453 * integer-encoded (the only encoding supported) so 454 * far. We can just cast it */ 455 vector[j].u.score = (long)byval->ptr; 456 } else { 457 redisAssertWithInfo(c,sortval,1 != 1); 458 } 459 } 460 461 /* when the object was retrieved using lookupKeyByPattern, 462 * its refcount needs to be decreased. */ 463 if (sortby) { 464 decrRefCount(byval); 465 } 466 } 467 } 468 469 if (dontsort == 0) { 470 server.sort_desc = desc; 471 server.sort_alpha = alpha; 472 server.sort_bypattern = sortby ? 1 : 0; 473 server.sort_store = storekey ? 1 : 0; 474 if (sortby && (start != 0 || end != vectorlen-1)) 475 pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end); 476 else 477 qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare); 478 } 479 480 /* Send command output to the output buffer, performing the specified 481 * GET/DEL/INCR/DECR operations if any. */ 482 outputlen = getop ? getop*(end-start+1) : end-start+1; 483 if (int_convertion_error) { 484 addReplyError(c,"One or more scores can't be converted into double"); 485 } else if (storekey == NULL) { 486 /* STORE option not specified, sent the sorting result to client */ 487 addReplyMultiBulkLen(c,outputlen); 488 for (j = start; j <= end; j++) { 489 listNode *ln; 490 listIter li; 491 492 if (!getop) addReplyBulk(c,vector[j].obj); 493 listRewind(operations,&li); 494 while((ln = listNext(&li))) { 495 redisSortOperation *sop = ln->value; 496 robj *val = lookupKeyByPattern(c->db,sop->pattern, 497 vector[j].obj); 498 499 if (sop->type == REDIS_SORT_GET) { 500 if (!val) { 501 addReply(c,shared.nullbulk); 502 } else { 503 addReplyBulk(c,val); 504 decrRefCount(val); 505 } 506 } else { 507 /* Always fails */ 508 redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); 509 } 510 } 511 } 512 } else { 513 robj *sobj = createZiplistObject(); 514 515 /* STORE option specified, set the sorting result as a List object */ 516 for (j = start; j <= end; j++) { 517 listNode *ln; 518 listIter li; 519 520 if (!getop) { 521 listTypePush(sobj,vector[j].obj,REDIS_TAIL); 522 } else { 523 listRewind(operations,&li); 524 while((ln = listNext(&li))) { 525 redisSortOperation *sop = ln->value; 526 robj *val = lookupKeyByPattern(c->db,sop->pattern, 527 vector[j].obj); 528 529 if (sop->type == REDIS_SORT_GET) { 530 if (!val) val = createStringObject("",0); 531 532 /* listTypePush does an incrRefCount, so we should take care 533 * care of the incremented refcount caused by either 534 * lookupKeyByPattern or createStringObject("",0) */ 535 listTypePush(sobj,val,REDIS_TAIL); 536 decrRefCount(val); 537 } else { 538 /* Always fails */ 539 redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); 540 } 541 } 542 } 543 } 544 if (outputlen) { 545 setKey(c->db,storekey,sobj); 546 notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"sortstore",storekey, 547 c->db->id); 548 server.dirty += outputlen; 549 } else if (dbDelete(c->db,storekey)) { 550 signalModifiedKey(c->db,storekey); 551 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",storekey,c->db->id); 552 server.dirty++; 553 } 554 decrRefCount(sobj); 555 addReplyLongLong(c,outputlen); 556 } 557 558 /* Cleanup */ 559 if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET) 560 for (j = 0; j < vectorlen; j++) 561 decrRefCount(vector[j].obj); 562 decrRefCount(sortval); 563 listRelease(operations); 564 for (j = 0; j < vectorlen; j++) { 565 if (alpha && vector[j].u.cmpobj) 566 decrRefCount(vector[j].u.cmpobj); 567 } 568 zfree(vector); 569 } 570