SORT命令在Redis中算是比较复杂的,该函数的可读性挺差,主要是考虑了性能优化的因素。有得有失:对机器友好,但对人不友好。
- /* The SORT command is the most complex command in Redis. Warning: this code
- * is optimized for speed and a bit less for readability */
- void sortCommand(client *c) {
- list *operations;
- unsigned int outputlen = 0;
- int desc = 0, alpha = 0;
- long limit_start = 0, limit_count = -1, start, end;
- int j, dontsort = 0, vectorlen;
- int getop = 0; /* GET operation counter */
- int int_conversion_error = 0;
- int syntax_error = 0;
- robj *sortval, *sortby = NULL, *storekey = NULL;
- redisSortObject *vector; /* Resulting vector to sort */
-
- /* Create a list of operations to perform for every sorted element.
- * Operations can be GET */
- operations = listCreate();
- listSetFreeMethod(operations,zfree);
- j = 2; /* options start at argv[2] */
-
- /* The SORT command has an SQL-alike syntax, parse it */
- while(j < c->argc) {
- int leftargs = c->argc-j-1;
- if (!strcasecmp(c->argv[j]->ptr,"asc")) {
- desc = 0;
- } else if (!strcasecmp(c->argv[j]->ptr,"desc")) {
- desc = 1;
- } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) {
- alpha = 1;
- } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) {
- if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL)
- != C_OK) ||
- (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL)
- != C_OK))
- {
- syntax_error++;
- break;
- }
- j+=2;
- } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) {
- storekey = c->argv[j+1];
- j++;
- } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) {
- sortby = c->argv[j+1];
- /* If the BY pattern does not contain '*', i.e. it is constant,
- * we don't need to sort nor to lookup the weight keys. */
- if (strchr(c->argv[j+1]->ptr,'*') == NULL) {
- dontsort = 1;
- } else {
- /* If BY is specified with a real patter, we can't accept
- * it in cluster mode. */
- if (server.cluster_enabled) {
- addReplyError(c,"BY option of SORT denied in Cluster mode.");
- syntax_error++;
- break;
- }
- }
- j++;
- } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {
- if (server.cluster_enabled) {
- addReplyError(c,"GET option of SORT denied in Cluster mode.");
- syntax_error++;
- break;
- }
- listAddNodeTail(operations,createSortOperation(
- SORT_OP_GET,c->argv[j+1]));
- getop++;
- j++;
- } else {
- addReplyErrorObject(c,shared.syntaxerr);
- syntax_error++;
- break;
- }
- j++;
- }
-
- /* Handle syntax errors set during options parsing. */
- if (syntax_error) {
- listRelease(operations);
- return;
- }
-
- /* Lookup the key to sort. It must be of the right types */
- if (!storekey)
- sortval = lookupKeyRead(c->db,c->argv[1]);
- else
- sortval = lookupKeyWrite(c->db,c->argv[1]);
- if (sortval && sortval->type != OBJ_SET &&
- sortval->type != OBJ_LIST &&
- sortval->type != OBJ_ZSET)
- {
- listRelease(operations);
- addReplyErrorObject(c,shared.wrongtypeerr);
- return;
- }
-
- /* Now we need to protect sortval incrementing its count, in the future
- * SORT may have options able to overwrite/delete keys during the sorting
- * and the sorted key itself may get destroyed */
- if (sortval)
- incrRefCount(sortval);
- else
- sortval = createQuicklistObject();
-
-
- /* When sorting a set with no sort specified, we must sort the output
- * so the result is consistent across scripting and replication.
- *
- * The other types (list, sorted set) will retain their native order
- * even if no sort order is requested, so they remain stable across
- * scripting and replication. */
- if (dontsort &&
- sortval->type == OBJ_SET &&
- (storekey || c->flags & CLIENT_LUA))
- {
- /* Force ALPHA sorting */
- dontsort = 0;
- alpha = 1;
- sortby = NULL;
- }
-
- /* Destructively convert encoded sorted sets for SORT. */
- if (sortval->type == OBJ_ZSET)
- zsetConvert(sortval, OBJ_ENCODING_SKIPLIST);
-
- /* Objtain the length of the object to sort. */
- switch(sortval->type) {
- case OBJ_LIST: vectorlen = listTypeLength(sortval); break;
- case OBJ_SET: vectorlen = setTypeSize(sortval); break;
- case OBJ_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break;
- default: vectorlen = 0; serverPanic("Bad SORT type"); /* Avoid GCC warning */
- }
-
- /* Perform LIMIT start,count sanity checking. */
- start = (limit_start < 0) ? 0 : limit_start;
- end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1;
- if (start >= vectorlen) {
- start = vectorlen-1;
- end = vectorlen-2;
- }
- if (end >= vectorlen) end = vectorlen-1;
-
- /* Whenever possible, we load elements into the output array in a more
- * direct way. This is possible if:
- *
- * 1) The object to sort is a sorted set or a list (internally sorted).
- * 2) There is nothing to sort as dontsort is true (BY <constant string>).
- *
- * In this special case, if we have a LIMIT option that actually reduces
- * the number of elements to fetch, we also optimize to just load the
- * range we are interested in and allocating a vector that is big enough
- * for the selected range length. */
- if ((sortval->type == OBJ_ZSET || sortval->type == OBJ_LIST) &&
- dontsort &&
- (start != 0 || end != vectorlen-1))
- {
- vectorlen = end-start+1;
- }
-
- /* Load the sorting vector with all the objects to sort */
- vector = zmalloc(sizeof(redisSortObject)*vectorlen);
- j = 0;
-
- if (sortval->type == OBJ_LIST && dontsort) {
- /* Special handling for a list, if 'dontsort' is true.
- * This makes sure we return elements in the list original
- * ordering, accordingly to DESC / ASC options.
- *
- * Note that in this case we also handle LIMIT here in a direct
- * way, just getting the required range, as an optimization. */
- if (end >= start) {
- listTypeIterator *li;
- listTypeEntry entry;
- li = listTypeInitIterator(sortval,
- desc ? (long)(listTypeLength(sortval) - start - 1) : start,
- desc ? LIST_HEAD : LIST_TAIL);
-
- while(j < vectorlen && listTypeNext(li,&entry)) {
- vector[j].obj = listTypeGet(&entry);
- vector[j].u.score = 0;
- vector[j].u.cmpobj = NULL;
- j++;
- }
- listTypeReleaseIterator(li);
- /* Fix start/end: output code is not aware of this optimization. */
- end -= start;
- start = 0;
- }
- } else if (sortval->type == OBJ_LIST) {
- listTypeIterator *li = listTypeInitIterator(sortval,0,LIST_TAIL);
- listTypeEntry entry;
- while(listTypeNext(li,&entry)) {
- vector[j].obj = listTypeGet(&entry);
- vector[j].u.score = 0;
- vector[j].u.cmpobj = NULL;
- j++;
- }
- listTypeReleaseIterator(li);
- } else if (sortval->type == OBJ_SET) {
- setTypeIterator *si = setTypeInitIterator(sortval);
- sds sdsele;
- while((sdsele = setTypeNextObject(si)) != NULL) {
- vector[j].obj = createObject(OBJ_STRING,sdsele);
- vector[j].u.score = 0;
- vector[j].u.cmpobj = NULL;
- j++;
- }
- setTypeReleaseIterator(si);
- } else if (sortval->type == OBJ_ZSET && dontsort) {
- /* Special handling for a sorted set, if 'dontsort' is true.
- * This makes sure we return elements in the sorted set original
- * ordering, accordingly to DESC / ASC options.
- *
- * Note that in this case we also handle LIMIT here in a direct
- * way, just getting the required range, as an optimization. */
-
- zset *zs = sortval->ptr;
- zskiplist *zsl = zs->zsl;
- zskiplistNode *ln;
- sds sdsele;
- int rangelen = vectorlen;
-
- /* Check if starting point is trivial, before doing log(N) lookup. */
- if (desc) {
- long zsetlen = dictSize(((zset*)sortval->ptr)->dict);
-
- ln = zsl->tail;
- if (start > 0)
- ln = zslGetElementByRank(zsl,zsetlen-start);
- } else {
- ln = zsl->header->level[0].forward;
- if (start > 0)
- ln = zslGetElementByRank(zsl,start+1);
- }
-
- while(rangelen--) {
- serverAssertWithInfo(c,sortval,ln != NULL);
- sdsele = ln->ele;
- vector[j].obj = createStringObject(sdsele,sdslen(sdsele));
- vector[j].u.score = 0;
- vector[j].u.cmpobj = NULL;
- j++;
- ln = desc ? ln->backward : ln->level[0].forward;
- }
- /* Fix start/end: output code is not aware of this optimization. */
- end -= start;
- start = 0;
- } else if (sortval->type == OBJ_ZSET) {
- dict *set = ((zset*)sortval->ptr)->dict;
- dictIterator *di;
- dictEntry *setele;
- sds sdsele;
- di = dictGetIterator(set);
- while((setele = dictNext(di)) != NULL) {
- sdsele = dictGetKey(setele);
- vector[j].obj = createStringObject(sdsele,sdslen(sdsele));
- vector[j].u.score = 0;
- vector[j].u.cmpobj = NULL;
- j++;
- }
- dictReleaseIterator(di);
- } else {
- serverPanic("Unknown type");
- }
- serverAssertWithInfo(c,sortval,j == vectorlen);
-
- /* Now it's time to load the right scores in the sorting vector */
- if (!dontsort) {
- for (j = 0; j < vectorlen; j++) {
- robj *byval;
- if (sortby) {
- /* lookup value to sort by */
- byval = lookupKeyByPattern(c->db,sortby,vector[j].obj,storekey!=NULL);
- if (!byval) continue;
- } else {
- /* use object itself to sort by */
- byval = vector[j].obj;
- }
-
- if (alpha) {
- if (sortby) vector[j].u.cmpobj = getDecodedObject(byval);
- } else {
- if (sdsEncodedObject(byval)) {
- char *eptr;
-
- vector[j].u.score = strtod(byval->ptr,&eptr);
- if (eptr[0] != '\0' || errno == ERANGE ||
- isnan(vector[j].u.score))
- {
- int_conversion_error = 1;
- }
- } else if (byval->encoding == OBJ_ENCODING_INT) {
- /* Don't need to decode the object if it's
- * integer-encoded (the only encoding supported) so
- * far. We can just cast it */
- vector[j].u.score = (long)byval->ptr;
- } else {
- serverAssertWithInfo(c,sortval,1 != 1);
- }
- }
-
- /* when the object was retrieved using lookupKeyByPattern,
- * its refcount needs to be decreased. */
- if (sortby) {
- decrRefCount(byval);
- }
- }
-
- server.sort_desc = desc;
- server.sort_alpha = alpha;
- server.sort_bypattern = sortby ? 1 : 0;
- server.sort_store = storekey ? 1 : 0;
- if (sortby && (start != 0 || end != vectorlen-1))
- pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end);
- else
- qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare);
- }