diff --git a/src/redis.c b/src/redis.c index aee5ce837..035ccea8c 100644 --- a/src/redis.c +++ b/src/redis.c @@ -74,13 +74,14 @@ struct redisCommand readonlyCommandTable[] = { {"setnx",setnxCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0}, {"setex",setexCommand,4,REDIS_CMD_DENYOOM,NULL,0,0,0}, {"append",appendCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"substr",substrCommand,4,0,NULL,1,1,1}, {"strlen",strlenCommand,2,0,NULL,1,1,1}, {"del",delCommand,-2,0,NULL,0,0,0}, {"exists",existsCommand,2,0,NULL,1,1,1}, {"setbit",setbitCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, {"getbit",getbitCommand,3,0,NULL,1,1,1}, {"setrange",setrangeCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"getrange",getrangeCommand,4,0,NULL,1,1,1}, + {"substr",getrangeCommand,4,0,NULL,1,1,1}, {"incr",incrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1}, {"decr",decrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1}, {"mget",mgetCommand,-2,0,NULL,1,-1,1}, diff --git a/src/redis.h b/src/redis.h index 865409937..927a4f1dd 100644 --- a/src/redis.h +++ b/src/redis.h @@ -890,6 +890,7 @@ void existsCommand(redisClient *c); void setbitCommand(redisClient *c); void getbitCommand(redisClient *c); void setrangeCommand(redisClient *c); +void getrangeCommand(redisClient *c); void incrCommand(redisClient *c); void decrCommand(redisClient *c); void incrbyCommand(redisClient *c); @@ -967,7 +968,6 @@ void discardCommand(redisClient *c); void blpopCommand(redisClient *c); void brpopCommand(redisClient *c); void appendCommand(redisClient *c); -void substrCommand(redisClient *c); void strlenCommand(redisClient *c); void zrankCommand(redisClient *c); void zrevrankCommand(redisClient *c); diff --git a/src/t_string.c b/src/t_string.c index e442a49fd..b71bfe99b 100644 --- a/src/t_string.c +++ b/src/t_string.c @@ -257,6 +257,43 @@ void setrangeCommand(redisClient *c) { addReplyLongLong(c,sdslen(o->ptr)); } +void getrangeCommand(redisClient *c) { + robj *o; + long start, end; + char *str, llbuf[32]; + size_t strlen; + + if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK) + return; + if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK) + return; + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,o,REDIS_STRING)) return; + + if (o->encoding == REDIS_ENCODING_INT) { + str = llbuf; + strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr); + } else { + str = o->ptr; + strlen = sdslen(str); + } + + /* Convert negative indexes */ + if (start < 0) start = strlen+start; + if (end < 0) end = strlen+end; + if (start < 0) start = 0; + if (end < 0) end = 0; + if ((unsigned)end >= strlen) end = strlen-1; + + /* Precondition: end >= 0 && end < strlen, so the only condition where + * nothing can be returned is: start > end. */ + if (start > end) { + addReply(c,shared.nullbulk); + } else { + addReplyBulkCBuffer(c,(char*)str+start,end-start+1); + } +} + void mgetCommand(redisClient *c) { int j; @@ -400,43 +437,6 @@ void appendCommand(redisClient *c) { addReplyLongLong(c,totlen); } -void substrCommand(redisClient *c) { - robj *o; - long start = atoi(c->argv[2]->ptr); - long end = atoi(c->argv[3]->ptr); - size_t rangelen, strlen; - sds range; - - if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || - checkType(c,o,REDIS_STRING)) return; - - o = getDecodedObject(o); - strlen = sdslen(o->ptr); - - /* convert negative indexes */ - if (start < 0) start = strlen+start; - if (end < 0) end = strlen+end; - if (start < 0) start = 0; - if (end < 0) end = 0; - - /* indexes sanity checks */ - if (start > end || (size_t)start >= strlen) { - /* Out of range start or start > end result in null reply */ - addReply(c,shared.nullbulk); - decrRefCount(o); - return; - } - if ((size_t)end >= strlen) end = strlen-1; - rangelen = (end-start)+1; - - /* Return the result */ - addReplySds(c,sdscatprintf(sdsempty(),"$%zu\r\n",rangelen)); - range = sdsnewlen((char*)o->ptr+start,rangelen); - addReplySds(c,range); - addReply(c,shared.crlf); - decrRefCount(o); -} - void strlenCommand(redisClient *c) { robj *o; diff --git a/tests/unit/basic.tcl b/tests/unit/basic.tcl index ecd2040aa..f082b05da 100644 --- a/tests/unit/basic.tcl +++ b/tests/unit/basic.tcl @@ -562,4 +562,40 @@ start_server {tags {"basic"}} { r set mykey "hello" assert_error "*maximum allowed size*" {r setrange mykey [expr 512*1024*1024-4] world} } + + test {SUBSTR basics} { + set res {} + r set foo "Hello World" + lappend res [r substr foo 0 3] + lappend res [r substr foo 0 -1] + lappend res [r substr foo -4 -1] + lappend res [r substr foo 5 3] + lappend res [r substr foo 5 5000] + lappend res [r substr foo -5000 10000] + set _ $res + } {Hell {Hello World} orld {} { World} {Hello World}} + + test {SUBSTR against integer encoded values} { + r set foo 123 + r substr foo 0 -2 + } {12} + + test {SUBSTR fuzzing} { + set err {} + for {set i 0} {$i < 1000} {incr i} { + set bin [randstring 0 1024 binary] + set _start [set start [randomInt 1500]] + set _end [set end [randomInt 1500]] + if {$_start < 0} {set _start "end-[abs($_start)-1]"} + if {$_end < 0} {set _end "end-[abs($_end)-1]"} + set s1 [string range $bin $_start $_end] + r set bin $bin + set s2 [r substr bin $start $end] + if {$s1 != $s2} { + set err "String mismatch" + break + } + } + set _ $err + } {} } diff --git a/tests/unit/other.tcl b/tests/unit/other.tcl index 2e6c0ae17..c142ba7f0 100644 --- a/tests/unit/other.tcl +++ b/tests/unit/other.tcl @@ -216,42 +216,6 @@ start_server {tags {"other"}} { set _ $err } {} - test {SUBSTR basics} { - set res {} - r set foo "Hello World" - lappend res [r substr foo 0 3] - lappend res [r substr foo 0 -1] - lappend res [r substr foo -4 -1] - lappend res [r substr foo 5 3] - lappend res [r substr foo 5 5000] - lappend res [r substr foo -5000 10000] - set _ $res - } {Hell {Hello World} orld {} { World} {Hello World}} - - test {SUBSTR against integer encoded values} { - r set foo 123 - r substr foo 0 -2 - } {12} - - test {SUBSTR fuzzing} { - set err {} - for {set i 0} {$i < 1000} {incr i} { - set bin [randstring 0 1024 binary] - set _start [set start [randomInt 1500]] - set _end [set end [randomInt 1500]] - if {$_start < 0} {set _start "end-[abs($_start)-1]"} - if {$_end < 0} {set _end "end-[abs($_end)-1]"} - set s1 [string range $bin $_start $_end] - r set bin $bin - set s2 [r substr bin $start $end] - if {$s1 != $s2} { - set err "String mismatch" - break - } - } - set _ $err - } {} - # Leave the user with a clean DB before to exit test {FLUSHDB} { set aux {}