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 = createQuicklistObject(); 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 /* When sorting a set with no sort specified, we must sort the output 289 * so the result is consistent across scripting and replication. 290 * 291 * The other types (list, sorted set) will retain their native order 292 * even if no sort order is requested, so they remain stable across 293 * scripting and replication. */ 294 if (dontsort && 295 sortval->type == REDIS_SET && 296 (storekey || c->flags & REDIS_LUA_CLIENT)) 297 { 298 /* Force ALPHA sorting */ 299 dontsort = 0; 300 alpha = 1; 301 sortby = NULL; 302 } 303 304 /* Destructively convert encoded sorted sets for SORT. */ 305 if (sortval->type == REDIS_ZSET) 306 zsetConvert(sortval, REDIS_ENCODING_SKIPLIST); 307 308 /* Objtain the length of the object to sort. */ 309 switch(sortval->type) { 310 case REDIS_LIST: vectorlen = listTypeLength(sortval); break; 311 case REDIS_SET: vectorlen = setTypeSize(sortval); break; 312 case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break; 313 default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */ 314 } 315 316 /* Perform LIMIT start,count sanity checking. */ 317 start = (limit_start < 0) ? 0 : limit_start; 318 end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1; 319 if (start >= vectorlen) { 320 start = vectorlen-1; 321 end = vectorlen-2; 322 } 323 if (end >= vectorlen) end = vectorlen-1; 324 325 /* Optimization: 326 * 327 * 1) if the object to sort is a sorted set. 328 * 2) There is nothing to sort as dontsort is true (BY <constant string>). 329 * 3) We have a LIMIT option that actually reduces the number of elements 330 * to fetch. 331 * 332 * In this case to load all the objects in the vector is a huge waste of 333 * resources. We just allocate a vector that is big enough for the selected 334 * range length, and make sure to load just this part in the vector. */ 335 if (sortval->type == REDIS_ZSET && 336 dontsort && 337 (start != 0 || end != vectorlen-1)) 338 { 339 vectorlen = end-start+1; 340 } 341 342 /* Load the sorting vector with all the objects to sort */ 343 vector = zmalloc(sizeof(redisSortObject)*vectorlen); 344 j = 0; 345 346 if (sortval->type == REDIS_LIST) { 347 listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL); 348 listTypeEntry entry; 349 while(listTypeNext(li,&entry)) { 350 vector[j].obj = listTypeGet(&entry); 351 vector[j].u.score = 0; 352 vector[j].u.cmpobj = NULL; 353 j++; 354 } 355 listTypeReleaseIterator(li); 356 } else if (sortval->type == REDIS_SET) { 357 setTypeIterator *si = setTypeInitIterator(sortval); 358 robj *ele; 359 while((ele = setTypeNextObject(si)) != NULL) { 360 vector[j].obj = ele; 361 vector[j].u.score = 0; 362 vector[j].u.cmpobj = NULL; 363 j++; 364 } 365 setTypeReleaseIterator(si); 366 } else if (sortval->type == REDIS_ZSET && dontsort) { 367 /* Special handling for a sorted set, if 'dontsort' is true. 368 * This makes sure we return elements in the sorted set original 369 * ordering, accordingly to DESC / ASC options. 370 * 371 * Note that in this case we also handle LIMIT here in a direct 372 * way, just getting the required range, as an optimization. */ 373 374 zset *zs = sortval->ptr; 375 zskiplist *zsl = zs->zsl; 376 zskiplistNode *ln; 377 robj *ele; 378 int rangelen = vectorlen; 379 380 /* Check if starting point is trivial, before doing log(N) lookup. */ 381 if (desc) { 382 long zsetlen = dictSize(((zset*)sortval->ptr)->dict); 383 384 ln = zsl->tail; 385 if (start > 0) 386 ln = zslGetElementByRank(zsl,zsetlen-start); 387 } else { 388 ln = zsl->header->level[0].forward; 389 if (start > 0) 390 ln = zslGetElementByRank(zsl,start+1); 391 } 392 393 while(rangelen--) { 394 redisAssertWithInfo(c,sortval,ln != NULL); 395 ele = ln->obj; 396 vector[j].obj = ele; 397 vector[j].u.score = 0; 398 vector[j].u.cmpobj = NULL; 399 j++; 400 ln = desc ? ln->backward : ln->level[0].forward; 401 } 402 /* The code producing the output does not know that in the case of 403 * sorted set, 'dontsort', and LIMIT, we are able to get just the 404 * range, already sorted, so we need to adjust "start" and "end" 405 * to make sure start is set to 0. */ 406 end -= start; 407 start = 0; 408 } else if (sortval->type == REDIS_ZSET) { 409 dict *set = ((zset*)sortval->ptr)->dict; 410 dictIterator *di; 411 dictEntry *setele; 412 di = dictGetIterator(set); 413 while((setele = dictNext(di)) != NULL) { 414 vector[j].obj = dictGetKey(setele); 415 vector[j].u.score = 0; 416 vector[j].u.cmpobj = NULL; 417 j++; 418 } 419 dictReleaseIterator(di); 420 } else { 421 redisPanic("Unknown type"); 422 } 423 printf("j: %d; vectorlen: %d\n", j, vectorlen); 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 = createQuicklistObject(); 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