{"sync",syncCommand,1,"ars",0,NULL,0,0,0,0,0},
{"flushdb",flushdbCommand,1,"w",0,NULL,0,0,0,0,0},
{"flushall",flushallCommand,1,"w",0,NULL,0,0,0,0,0},
- {"sort",sortCommand,-2,"wm",0,NULL,1,1,1,0,0},
+ {"sort",sortCommand,-2,"wmS",0,NULL,1,1,1,0,0},
{"info",infoCommand,-1,"r",0,NULL,0,0,0,0,0},
{"monitor",monitorCommand,1,"ars",0,NULL,0,0,0,0,0},
{"ttl",ttlCommand,2,"r",0,NULL,1,1,1,0,0},
list *unblocked_clients; /* list of clients to unblock before next loop */
/* Sort parameters - qsort_r() is only available under BSD so we
* have to take this state global, in order to pass it to sortCompare() */
+ int sort_dontsort;
int sort_desc;
int sort_alpha;
int sort_bypattern;
lua_pushstring(lua,"sort");
lua_gettable(lua,-2); /* Stack: array, table, table.sort */
lua_pushvalue(lua,-3); /* Stack: array, table, table.sort, array */
- lua_call(lua,1,0); /* Stack: array (sorted), table */
+ if (lua_pcall(lua,1,0,0)) {
+ /* Stack: array, table, error */
+
+ /* We are not interested in the error, we assume that the problem is
+ * that there are 'false' elements inside the array, so we try
+ * again with a slower function but able to handle this case, that
+ * is: table.sort(table, __redis__compare_helper) */
+ lua_pop(lua,1); /* Stack: array, table */
+ lua_pushstring(lua,"sort"); /* Stack: array, table, sort */
+ lua_gettable(lua,-2); /* Stack: array, table, table.sort */
+ lua_pushvalue(lua,-3); /* Stack: array, table, table.sort, array */
+ lua_getglobal(lua,"__redis__compare_helper");
+ /* Stack: array, table, table.sort, array, __redis__compare_helper */
+ lua_call(lua,2,0);
+ }
+ /* Stack: array (sorted), table */
lua_pop(lua,1); /* Stack: array (sorted) */
}
* reply as expected. */
if ((cmd->flags & REDIS_CMD_SORT_FOR_SCRIPT) &&
(reply[0] == '*' && reply[1] != '-')) {
- luaSortArray(lua);
+ /* Skip this step if command is SORT but output was already sorted */
+ if (cmd->proc != sortCommand || server.sort_dontsort)
+ luaSortArray(lua);
}
sdsfree(reply);
lua_setglobal(lua,"math");
+ /* Add a helper funciton that we use to sort the multi bulk output of non
+ * deterministic commands, when containing 'false' elements. */
+ {
+ char *compare_func = "function __redis__compare_helper(a,b)\n"
+ " if a == false then a = '' end\n"
+ " if b == false then b = '' end\n"
+ " return a<b\n"
+ "end\n";
+ luaL_loadbuffer(lua,compare_func,strlen(compare_func),"cmp_func_def");
+ lua_pcall(lua,0,0,0);
+ }
+
/* Create the (non connected) client that we use to execute Redis commands
* inside the Lua interpreter.
* Note: there is no need to create it again when this function is called
#include "redis.h"
#include "pqsort.h" /* Partial qsort for SORT+LIMIT */
+#include <math.h> /* isnan() */
redisSortOperation *createSortOperation(int type, robj *pattern) {
redisSortOperation *so = zmalloc(sizeof(*so));
} else if (so1->u.score < so2->u.score) {
cmp = -1;
} else {
- cmp = 0;
+ /* Objects have the same score, but we don't want the comparison
+ * to be undefined, so we compare objects lexicographycally.
+ * This way the result of SORT is deterministic. */
+ cmp = compareStringObjects(so1->obj,so2->obj);
}
} else {
/* Alphanumeric sorting */
long limit_start = 0, limit_count = -1, start, end;
int j, dontsort = 0, vectorlen;
int getop = 0; /* GET operation counter */
+ int int_convertion_error = 0;
robj *sortval, *sortby = NULL, *storekey = NULL;
redisSortObject *vector; /* Resulting vector to sort */
if (sortby) vector[j].u.cmpobj = getDecodedObject(byval);
} else {
if (byval->encoding == REDIS_ENCODING_RAW) {
- vector[j].u.score = strtod(byval->ptr,NULL);
+ char *eptr;
+
+ vector[j].u.score = strtod(byval->ptr,&eptr);
+ if (eptr[0] != '\0' || errno == ERANGE ||
+ isnan(vector[j].u.score))
+ {
+ int_convertion_error = 1;
+ }
} else if (byval->encoding == REDIS_ENCODING_INT) {
/* Don't need to decode the object if it's
* integer-encoded (the only encoding supported) so
}
if (end >= vectorlen) end = vectorlen-1;
+ server.sort_dontsort = dontsort;
if (dontsort == 0) {
server.sort_desc = desc;
server.sort_alpha = alpha;
/* Send command output to the output buffer, performing the specified
* GET/DEL/INCR/DECR operations if any. */
outputlen = getop ? getop*(end-start+1) : end-start+1;
- if (storekey == NULL) {
+ if (int_convertion_error) {
+ addReplyError(c,"One or more scores can't be converted into double");
+ } else if (storekey == NULL) {
/* STORE option not specified, sent the sorting result to client */
addReplyMultiBulkLen(c,outputlen);
for (j = start; j <= end; j++) {
test "SORT with STORE does not create empty lists (github issue 224)" {
r flushdb
r lpush foo bar
- r sort foo limit 10 10 store zap
+ r sort foo alpha limit 10 10 store zap
r exists zap
} {0}