File: | src/sort.c |
Warning: | line 320, column 14 Value stored to 'vectorlen' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 "server.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, int writeflag) { |
62 | char *p, *f, *k; |
63 | sds spat, ssub; |
64 | robj *keyobj, *fieldobj = NULL((void*)0), *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((void*)0); |
87 | } |
88 | |
89 | /* Find out if we're dealing with a hash dereference. */ |
90 | if ((f = strstr(p+1, "->")) != NULL((void*)0) && *(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((void*)0),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 | if (!writeflag) |
110 | o = lookupKeyRead(db,keyobj); |
111 | else |
112 | o = lookupKeyWrite(db,keyobj); |
113 | if (o == NULL((void*)0)) goto noobj; |
114 | |
115 | if (fieldobj) { |
116 | if (o->type != OBJ_HASH4) goto noobj; |
117 | |
118 | /* Retrieve value from hash by the field name. The returned object |
119 | * is a new object with refcount already incremented. */ |
120 | o = hashTypeGetValueObject(o, fieldobj->ptr); |
121 | } else { |
122 | if (o->type != OBJ_STRING0) goto noobj; |
123 | |
124 | /* Every object that this function returns needs to have its refcount |
125 | * increased. sortCommand decreases it again. */ |
126 | incrRefCount(o); |
127 | } |
128 | decrRefCount(keyobj); |
129 | if (fieldobj) decrRefCount(fieldobj); |
130 | return o; |
131 | |
132 | noobj: |
133 | decrRefCount(keyobj); |
134 | if (fieldlen) decrRefCount(fieldobj); |
135 | return NULL((void*)0); |
136 | } |
137 | |
138 | /* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with |
139 | * the additional parameter is not standard but a BSD-specific we have to |
140 | * pass sorting parameters via the global 'server' structure */ |
141 | int sortCompare(const void *s1, const void *s2) { |
142 | const redisSortObject *so1 = s1, *so2 = s2; |
143 | int cmp; |
144 | |
145 | if (!server.sort_alpha) { |
146 | /* Numeric sorting. Here it's trivial as we precomputed scores */ |
147 | if (so1->u.score > so2->u.score) { |
148 | cmp = 1; |
149 | } else if (so1->u.score < so2->u.score) { |
150 | cmp = -1; |
151 | } else { |
152 | /* Objects have the same score, but we don't want the comparison |
153 | * to be undefined, so we compare objects lexicographically. |
154 | * This way the result of SORT is deterministic. */ |
155 | cmp = compareStringObjects(so1->obj,so2->obj); |
156 | } |
157 | } else { |
158 | /* Alphanumeric sorting */ |
159 | if (server.sort_bypattern) { |
160 | if (!so1->u.cmpobj || !so2->u.cmpobj) { |
161 | /* At least one compare object is NULL */ |
162 | if (so1->u.cmpobj == so2->u.cmpobj) |
163 | cmp = 0; |
164 | else if (so1->u.cmpobj == NULL((void*)0)) |
165 | cmp = -1; |
166 | else |
167 | cmp = 1; |
168 | } else { |
169 | /* We have both the objects, compare them. */ |
170 | if (server.sort_store) { |
171 | cmp = compareStringObjects(so1->u.cmpobj,so2->u.cmpobj); |
172 | } else { |
173 | /* Here we can use strcoll() directly as we are sure that |
174 | * the objects are decoded string objects. */ |
175 | cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr); |
176 | } |
177 | } |
178 | } else { |
179 | /* Compare elements directly. */ |
180 | if (server.sort_store) { |
181 | cmp = compareStringObjects(so1->obj,so2->obj); |
182 | } else { |
183 | cmp = collateStringObjects(so1->obj,so2->obj); |
184 | } |
185 | } |
186 | } |
187 | return server.sort_desc ? -cmp : cmp; |
188 | } |
189 | |
190 | /* The SORT command is the most complex command in Redis. Warning: this code |
191 | * is optimized for speed and a bit less for readability */ |
192 | void sortCommand(client *c) { |
193 | list *operations; |
194 | unsigned int outputlen = 0; |
195 | int desc = 0, alpha = 0; |
196 | long limit_start = 0, limit_count = -1, start, end; |
197 | int j, dontsort = 0, vectorlen; |
198 | int getop = 0; /* GET operation counter */ |
199 | int int_conversion_error = 0; |
200 | int syntax_error = 0; |
201 | robj *sortval, *sortby = NULL((void*)0), *storekey = NULL((void*)0); |
202 | redisSortObject *vector; /* Resulting vector to sort */ |
203 | |
204 | /* Create a list of operations to perform for every sorted element. |
205 | * Operations can be GET */ |
206 | operations = listCreate(); |
207 | listSetFreeMethod(operations,zfree)((operations)->free = (zfree)); |
208 | j = 2; /* options start at argv[2] */ |
209 | |
210 | /* The SORT command has an SQL-alike syntax, parse it */ |
211 | while(j < c->argc) { |
212 | int leftargs = c->argc-j-1; |
213 | if (!strcasecmp(c->argv[j]->ptr,"asc")) { |
214 | desc = 0; |
215 | } else if (!strcasecmp(c->argv[j]->ptr,"desc")) { |
216 | desc = 1; |
217 | } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) { |
218 | alpha = 1; |
219 | } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) { |
220 | if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL((void*)0)) |
221 | != C_OK0) || |
222 | (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL((void*)0)) |
223 | != C_OK0)) |
224 | { |
225 | syntax_error++; |
226 | break; |
227 | } |
228 | j+=2; |
229 | } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) { |
230 | storekey = c->argv[j+1]; |
231 | j++; |
232 | } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) { |
233 | sortby = c->argv[j+1]; |
234 | /* If the BY pattern does not contain '*', i.e. it is constant, |
235 | * we don't need to sort nor to lookup the weight keys. */ |
236 | if (strchr(c->argv[j+1]->ptr,'*') == NULL((void*)0)) { |
237 | dontsort = 1; |
238 | } else { |
239 | /* If BY is specified with a real patter, we can't accept |
240 | * it in cluster mode. */ |
241 | if (server.cluster_enabled) { |
242 | addReplyError(c,"BY option of SORT denied in Cluster mode."); |
243 | syntax_error++; |
244 | break; |
245 | } |
246 | } |
247 | j++; |
248 | } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) { |
249 | if (server.cluster_enabled) { |
250 | addReplyError(c,"GET option of SORT denied in Cluster mode."); |
251 | syntax_error++; |
252 | break; |
253 | } |
254 | listAddNodeTail(operations,createSortOperation( |
255 | SORT_OP_GET0,c->argv[j+1])); |
256 | getop++; |
257 | j++; |
258 | } else { |
259 | addReplyErrorObject(c,shared.syntaxerr); |
260 | syntax_error++; |
261 | break; |
262 | } |
263 | j++; |
264 | } |
265 | |
266 | /* Handle syntax errors set during options parsing. */ |
267 | if (syntax_error) { |
268 | listRelease(operations); |
269 | return; |
270 | } |
271 | |
272 | /* Lookup the key to sort. It must be of the right types */ |
273 | if (!storekey) |
274 | sortval = lookupKeyRead(c->db,c->argv[1]); |
275 | else |
276 | sortval = lookupKeyWrite(c->db,c->argv[1]); |
277 | if (sortval && sortval->type != OBJ_SET2 && |
278 | sortval->type != OBJ_LIST1 && |
279 | sortval->type != OBJ_ZSET3) |
280 | { |
281 | listRelease(operations); |
282 | addReplyErrorObject(c,shared.wrongtypeerr); |
283 | return; |
284 | } |
285 | |
286 | /* Now we need to protect sortval incrementing its count, in the future |
287 | * SORT may have options able to overwrite/delete keys during the sorting |
288 | * and the sorted key itself may get destroyed */ |
289 | if (sortval) |
290 | incrRefCount(sortval); |
291 | else |
292 | sortval = createQuicklistObject(); |
293 | |
294 | |
295 | /* When sorting a set with no sort specified, we must sort the output |
296 | * so the result is consistent across scripting and replication. |
297 | * |
298 | * The other types (list, sorted set) will retain their native order |
299 | * even if no sort order is requested, so they remain stable across |
300 | * scripting and replication. */ |
301 | if (dontsort && |
302 | sortval->type == OBJ_SET2 && |
303 | (storekey || c->flags & CLIENT_LUA(1<<8))) |
304 | { |
305 | /* Force ALPHA sorting */ |
306 | dontsort = 0; |
307 | alpha = 1; |
308 | sortby = NULL((void*)0); |
309 | } |
310 | |
311 | /* Destructively convert encoded sorted sets for SORT. */ |
312 | if (sortval->type == OBJ_ZSET3) |
313 | zsetConvert(sortval, OBJ_ENCODING_SKIPLIST7); |
314 | |
315 | /* Objtain the length of the object to sort. */ |
316 | switch(sortval->type) { |
317 | case OBJ_LIST1: vectorlen = listTypeLength(sortval); break; |
318 | case OBJ_SET2: vectorlen = setTypeSize(sortval); break; |
319 | case OBJ_ZSET3: vectorlen = dictSize(((zset*)sortval->ptr)->dict)((((zset*)sortval->ptr)->dict)->ht[0].used+(((zset*) sortval->ptr)->dict)->ht[1].used); break; |
320 | default: vectorlen = 0; serverPanic("Bad SORT type")_serverPanic("sort.c",320,"Bad SORT type"),__builtin_unreachable (); /* Avoid GCC warning */ |
Value stored to 'vectorlen' is never read | |
321 | } |
322 | |
323 | /* Perform LIMIT start,count sanity checking. */ |
324 | start = (limit_start < 0) ? 0 : limit_start; |
325 | end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1; |
326 | if (start >= vectorlen) { |
327 | start = vectorlen-1; |
328 | end = vectorlen-2; |
329 | } |
330 | if (end >= vectorlen) end = vectorlen-1; |
331 | |
332 | /* Whenever possible, we load elements into the output array in a more |
333 | * direct way. This is possible if: |
334 | * |
335 | * 1) The object to sort is a sorted set or a list (internally sorted). |
336 | * 2) There is nothing to sort as dontsort is true (BY <constant string>). |
337 | * |
338 | * In this special case, if we have a LIMIT option that actually reduces |
339 | * the number of elements to fetch, we also optimize to just load the |
340 | * range we are interested in and allocating a vector that is big enough |
341 | * for the selected range length. */ |
342 | if ((sortval->type == OBJ_ZSET3 || sortval->type == OBJ_LIST1) && |
343 | dontsort && |
344 | (start != 0 || end != vectorlen-1)) |
345 | { |
346 | vectorlen = end-start+1; |
347 | } |
348 | |
349 | /* Load the sorting vector with all the objects to sort */ |
350 | vector = zmalloc(sizeof(redisSortObject)*vectorlen); |
351 | j = 0; |
352 | |
353 | if (sortval->type == OBJ_LIST1 && dontsort) { |
354 | /* Special handling for a list, if 'dontsort' is true. |
355 | * This makes sure we return elements in the list original |
356 | * ordering, accordingly to DESC / ASC options. |
357 | * |
358 | * Note that in this case we also handle LIMIT here in a direct |
359 | * way, just getting the required range, as an optimization. */ |
360 | if (end >= start) { |
361 | listTypeIterator *li; |
362 | listTypeEntry entry; |
363 | li = listTypeInitIterator(sortval, |
364 | desc ? (long)(listTypeLength(sortval) - start - 1) : start, |
365 | desc ? LIST_HEAD0 : LIST_TAIL1); |
366 | |
367 | while(j < vectorlen && listTypeNext(li,&entry)) { |
368 | vector[j].obj = listTypeGet(&entry); |
369 | vector[j].u.score = 0; |
370 | vector[j].u.cmpobj = NULL((void*)0); |
371 | j++; |
372 | } |
373 | listTypeReleaseIterator(li); |
374 | /* Fix start/end: output code is not aware of this optimization. */ |
375 | end -= start; |
376 | start = 0; |
377 | } |
378 | } else if (sortval->type == OBJ_LIST1) { |
379 | listTypeIterator *li = listTypeInitIterator(sortval,0,LIST_TAIL1); |
380 | listTypeEntry entry; |
381 | while(listTypeNext(li,&entry)) { |
382 | vector[j].obj = listTypeGet(&entry); |
383 | vector[j].u.score = 0; |
384 | vector[j].u.cmpobj = NULL((void*)0); |
385 | j++; |
386 | } |
387 | listTypeReleaseIterator(li); |
388 | } else if (sortval->type == OBJ_SET2) { |
389 | setTypeIterator *si = setTypeInitIterator(sortval); |
390 | sds sdsele; |
391 | while((sdsele = setTypeNextObject(si)) != NULL((void*)0)) { |
392 | vector[j].obj = createObject(OBJ_STRING0,sdsele); |
393 | vector[j].u.score = 0; |
394 | vector[j].u.cmpobj = NULL((void*)0); |
395 | j++; |
396 | } |
397 | setTypeReleaseIterator(si); |
398 | } else if (sortval->type == OBJ_ZSET3 && dontsort) { |
399 | /* Special handling for a sorted set, if 'dontsort' is true. |
400 | * This makes sure we return elements in the sorted set original |
401 | * ordering, accordingly to DESC / ASC options. |
402 | * |
403 | * Note that in this case we also handle LIMIT here in a direct |
404 | * way, just getting the required range, as an optimization. */ |
405 | |
406 | zset *zs = sortval->ptr; |
407 | zskiplist *zsl = zs->zsl; |
408 | zskiplistNode *ln; |
409 | sds sdsele; |
410 | int rangelen = vectorlen; |
411 | |
412 | /* Check if starting point is trivial, before doing log(N) lookup. */ |
413 | if (desc) { |
414 | long zsetlen = dictSize(((zset*)sortval->ptr)->dict)((((zset*)sortval->ptr)->dict)->ht[0].used+(((zset*) sortval->ptr)->dict)->ht[1].used); |
415 | |
416 | ln = zsl->tail; |
417 | if (start > 0) |
418 | ln = zslGetElementByRank(zsl,zsetlen-start); |
419 | } else { |
420 | ln = zsl->header->level[0].forward; |
421 | if (start > 0) |
422 | ln = zslGetElementByRank(zsl,start+1); |
423 | } |
424 | |
425 | while(rangelen--) { |
426 | serverAssertWithInfo(c,sortval,ln != NULL)((ln != ((void*)0))?(void)0 : (_serverAssertWithInfo(c,sortval ,"ln != NULL","sort.c",426),__builtin_unreachable())); |
427 | sdsele = ln->ele; |
428 | vector[j].obj = createStringObject(sdsele,sdslen(sdsele)); |
429 | vector[j].u.score = 0; |
430 | vector[j].u.cmpobj = NULL((void*)0); |
431 | j++; |
432 | ln = desc ? ln->backward : ln->level[0].forward; |
433 | } |
434 | /* Fix start/end: output code is not aware of this optimization. */ |
435 | end -= start; |
436 | start = 0; |
437 | } else if (sortval->type == OBJ_ZSET3) { |
438 | dict *set = ((zset*)sortval->ptr)->dict; |
439 | dictIterator *di; |
440 | dictEntry *setele; |
441 | sds sdsele; |
442 | di = dictGetIterator(set); |
443 | while((setele = dictNext(di)) != NULL((void*)0)) { |
444 | sdsele = dictGetKey(setele)((setele)->key); |
445 | vector[j].obj = createStringObject(sdsele,sdslen(sdsele)); |
446 | vector[j].u.score = 0; |
447 | vector[j].u.cmpobj = NULL((void*)0); |
448 | j++; |
449 | } |
450 | dictReleaseIterator(di); |
451 | } else { |
452 | serverPanic("Unknown type")_serverPanic("sort.c",452,"Unknown type"),__builtin_unreachable (); |
453 | } |
454 | serverAssertWithInfo(c,sortval,j == vectorlen)((j == vectorlen)?(void)0 : (_serverAssertWithInfo(c,sortval, "j == vectorlen","sort.c",454),__builtin_unreachable())); |
455 | |
456 | /* Now it's time to load the right scores in the sorting vector */ |
457 | if (!dontsort) { |
458 | for (j = 0; j < vectorlen; j++) { |
459 | robj *byval; |
460 | if (sortby) { |
461 | /* lookup value to sort by */ |
462 | byval = lookupKeyByPattern(c->db,sortby,vector[j].obj,storekey!=NULL((void*)0)); |
463 | if (!byval) continue; |
464 | } else { |
465 | /* use object itself to sort by */ |
466 | byval = vector[j].obj; |
467 | } |
468 | |
469 | if (alpha) { |
470 | if (sortby) vector[j].u.cmpobj = getDecodedObject(byval); |
471 | } else { |
472 | if (sdsEncodedObject(byval)(byval->encoding == 0 || byval->encoding == 8)) { |
473 | char *eptr; |
474 | |
475 | vector[j].u.score = strtod(byval->ptr,&eptr); |
476 | if (eptr[0] != '\0' || errno(*__errno_location ()) == ERANGE34 || |
477 | isnan(vector[j].u.score)__builtin_isnan (vector[j].u.score)) |
478 | { |
479 | int_conversion_error = 1; |
480 | } |
481 | } else if (byval->encoding == OBJ_ENCODING_INT1) { |
482 | /* Don't need to decode the object if it's |
483 | * integer-encoded (the only encoding supported) so |
484 | * far. We can just cast it */ |
485 | vector[j].u.score = (long)byval->ptr; |
486 | } else { |
487 | serverAssertWithInfo(c,sortval,1 != 1)((1 != 1)?(void)0 : (_serverAssertWithInfo(c,sortval,"1 != 1" ,"sort.c",487),__builtin_unreachable())); |
488 | } |
489 | } |
490 | |
491 | /* when the object was retrieved using lookupKeyByPattern, |
492 | * its refcount needs to be decreased. */ |
493 | if (sortby) { |
494 | decrRefCount(byval); |
495 | } |
496 | } |
497 | |
498 | server.sort_desc = desc; |
499 | server.sort_alpha = alpha; |
500 | server.sort_bypattern = sortby ? 1 : 0; |
501 | server.sort_store = storekey ? 1 : 0; |
502 | if (sortby && (start != 0 || end != vectorlen-1)) |
503 | pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end); |
504 | else |
505 | qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare); |
506 | } |
507 | |
508 | /* Send command output to the output buffer, performing the specified |
509 | * GET/DEL/INCR/DECR operations if any. */ |
510 | outputlen = getop ? getop*(end-start+1) : end-start+1; |
511 | if (int_conversion_error) { |
512 | addReplyError(c,"One or more scores can't be converted into double"); |
513 | } else if (storekey == NULL((void*)0)) { |
514 | /* STORE option not specified, sent the sorting result to client */ |
515 | addReplyArrayLen(c,outputlen); |
516 | for (j = start; j <= end; j++) { |
517 | listNode *ln; |
518 | listIter li; |
519 | |
520 | if (!getop) addReplyBulk(c,vector[j].obj); |
521 | listRewind(operations,&li); |
522 | while((ln = listNext(&li))) { |
523 | redisSortOperation *sop = ln->value; |
524 | robj *val = lookupKeyByPattern(c->db,sop->pattern, |
525 | vector[j].obj,storekey!=NULL((void*)0)); |
526 | |
527 | if (sop->type == SORT_OP_GET0) { |
528 | if (!val) { |
529 | addReplyNull(c); |
530 | } else { |
531 | addReplyBulk(c,val); |
532 | decrRefCount(val); |
533 | } |
534 | } else { |
535 | /* Always fails */ |
536 | serverAssertWithInfo(c,sortval,sop->type == SORT_OP_GET)((sop->type == 0)?(void)0 : (_serverAssertWithInfo(c,sortval ,"sop->type == SORT_OP_GET","sort.c",536),__builtin_unreachable ())); |
537 | } |
538 | } |
539 | } |
540 | } else { |
541 | robj *sobj = createQuicklistObject(); |
542 | |
543 | /* STORE option specified, set the sorting result as a List object */ |
544 | for (j = start; j <= end; j++) { |
545 | listNode *ln; |
546 | listIter li; |
547 | |
548 | if (!getop) { |
549 | listTypePush(sobj,vector[j].obj,LIST_TAIL1); |
550 | } else { |
551 | listRewind(operations,&li); |
552 | while((ln = listNext(&li))) { |
553 | redisSortOperation *sop = ln->value; |
554 | robj *val = lookupKeyByPattern(c->db,sop->pattern, |
555 | vector[j].obj,storekey!=NULL((void*)0)); |
556 | |
557 | if (sop->type == SORT_OP_GET0) { |
558 | if (!val) val = createStringObject("",0); |
559 | |
560 | /* listTypePush does an incrRefCount, so we should take care |
561 | * care of the incremented refcount caused by either |
562 | * lookupKeyByPattern or createStringObject("",0) */ |
563 | listTypePush(sobj,val,LIST_TAIL1); |
564 | decrRefCount(val); |
565 | } else { |
566 | /* Always fails */ |
567 | serverAssertWithInfo(c,sortval,sop->type == SORT_OP_GET)((sop->type == 0)?(void)0 : (_serverAssertWithInfo(c,sortval ,"sop->type == SORT_OP_GET","sort.c",567),__builtin_unreachable ())); |
568 | } |
569 | } |
570 | } |
571 | } |
572 | if (outputlen) { |
573 | setKey(c,c->db,storekey,sobj); |
574 | notifyKeyspaceEvent(NOTIFY_LIST(1<<4),"sortstore",storekey, |
575 | c->db->id); |
576 | server.dirty += outputlen; |
577 | } else if (dbDelete(c->db,storekey)) { |
578 | signalModifiedKey(c,c->db,storekey); |
579 | notifyKeyspaceEvent(NOTIFY_GENERIC(1<<2),"del",storekey,c->db->id); |
580 | server.dirty++; |
581 | } |
582 | decrRefCount(sobj); |
583 | addReplyLongLong(c,outputlen); |
584 | } |
585 | |
586 | /* Cleanup */ |
587 | for (j = 0; j < vectorlen; j++) |
588 | decrRefCount(vector[j].obj); |
589 | |
590 | decrRefCount(sortval); |
591 | listRelease(operations); |
592 | for (j = 0; j < vectorlen; j++) { |
593 | if (alpha && vector[j].u.cmpobj) |
594 | decrRefCount(vector[j].u.cmpobj); |
595 | } |
596 | zfree(vector); |
597 | } |