]> git.saurik.com Git - redis.git/commitdiff
Add commands SETBIT/GETBIT
authorPieter Noordhuis <pcnoordhuis@gmail.com>
Thu, 9 Dec 2010 15:39:33 +0000 (16:39 +0100)
committerPieter Noordhuis <pcnoordhuis@gmail.com>
Thu, 9 Dec 2010 15:39:33 +0000 (16:39 +0100)
src/redis.c
src/redis.h
src/sds.c
src/sds.h
src/t_string.c
tests/unit/basic.tcl

index 5b39c011f537e5e4e6845e483c3b4b90449e1bc1..6d803269ef9c307d2fcbdb4ba8776f2dd933355e 100644 (file)
@@ -78,6 +78,8 @@ struct redisCommand readonlyCommandTable[] = {
     {"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},
     {"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},
index e012db4c4da9ba009464bcd6cd01e4365f23c0c4..b1f992d3eda9929df84ecff995eff6ff1cba67be 100644 (file)
@@ -887,6 +887,8 @@ void setexCommand(redisClient *c);
 void getCommand(redisClient *c);
 void delCommand(redisClient *c);
 void existsCommand(redisClient *c);
+void setbitCommand(redisClient *c);
+void getbitCommand(redisClient *c);
 void incrCommand(redisClient *c);
 void decrCommand(redisClient *c);
 void incrbyCommand(redisClient *c);
index 2d063c4a4b7d9ad0ba87b244fb641838b1d99e98..ff47728571c0c878fe52baf4f540ed02b66fae2e 100644 (file)
--- a/src/sds.c
+++ b/src/sds.c
@@ -155,6 +155,32 @@ sds sdscpy(sds s, char *t) {
     return sdscpylen(s, t, strlen(t));
 }
 
+sds sdssetbit(sds s, size_t bit, int on) {
+    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
+    int byte = bit >> 3;
+    int reqlen = byte+1;
+
+    if (reqlen > sh->len) {
+        size_t totlen;
+
+        s = sdsMakeRoomFor(s,reqlen-sh->len);
+        if (s == NULL) return NULL;
+        sh = (void*)(s-(sizeof(struct sdshdr)));
+
+        /* Make sure added region doesn't contain garbage */
+        totlen = sh->len+sh->free;
+        memset(s+sh->len,0,sh->free+1);
+        sh->len = reqlen;
+        sh->free = totlen-sh->len;
+    }
+
+    bit = 7 - (bit & 0x7);
+    on &= 0x1;
+    s[byte] |= on << bit;
+    s[byte] &= ~((!on) << bit);
+    return s;
+}
+
 sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
     va_list cpy;
     char *buf, *t;
index ae0f84fb5c4ec239698d09b9e167ccfce6dbc946..61ef36b62dda6ef89384d1c8b6ac920a547280fd 100644 (file)
--- a/src/sds.h
+++ b/src/sds.h
@@ -53,6 +53,7 @@ sds sdscatlen(sds s, void *t, size_t len);
 sds sdscat(sds s, char *t);
 sds sdscpylen(sds s, char *t, size_t len);
 sds sdscpy(sds s, char *t);
+sds sdssetbit(sds s, size_t bit, int on);
 
 sds sdscatvprintf(sds s, const char *fmt, va_list ap);
 #ifdef __GNUC__
index 39ee506d5e9586851ad8bf603859ca2c0b2d15b7..4b6fe79208736c98940c6cf6ef44c99fb4670c6a 100644 (file)
@@ -1,3 +1,4 @@
+#include <limits.h>
 #include "redis.h"
 
 /*-----------------------------------------------------------------------------
@@ -80,6 +81,87 @@ void getsetCommand(redisClient *c) {
     removeExpire(c->db,c->argv[1]);
 }
 
+static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
+    long long loffset;
+    char *err = "bit offset is not an integer or out of range";
+
+    if (getLongLongFromObjectOrReply(c,o,&loffset,err) != REDIS_OK)
+        return REDIS_ERR;
+
+    /* Limit offset to SIZE_T_MAX or 1GB in bytes */
+    if ((loffset < 0) ||
+        ((unsigned long long)loffset >= (unsigned)SIZE_T_MAX) ||
+        ((unsigned long long)loffset >> 3) >= (1024*1024*1024))
+    {
+        addReplyError(c,err);
+        return REDIS_ERR;
+    }
+
+    *offset = (size_t)loffset;
+    return REDIS_OK;
+}
+
+void setbitCommand(redisClient *c) {
+    robj *o;
+    size_t bitoffset;
+    int on;
+
+    if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
+        return;
+
+    on = ((sds)c->argv[3]->ptr)[0] - '0';
+    if (sdslen(c->argv[3]->ptr) != 1 || (on & ~1)) {
+        addReplyError(c,"bit should be 0 or 1");
+        return;
+    }
+
+    o = lookupKeyWrite(c->db,c->argv[1]);
+    if (o == NULL) {
+        sds value = sdssetbit(sdsempty(),bitoffset,on);
+        o = createObject(REDIS_STRING,value);
+        dbAdd(c->db,c->argv[1],o);
+    } else {
+        if (checkType(c,o,REDIS_STRING)) return;
+
+        /* Create a copy when the object is shared or encoded. */
+        if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
+            robj *decoded = getDecodedObject(o);
+            o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
+            decrRefCount(decoded);
+            dbReplace(c->db,c->argv[1],o);
+        }
+
+        o->ptr = sdssetbit(o->ptr,bitoffset,on);
+    }
+    touchWatchedKey(c->db,c->argv[1]);
+    server.dirty++;
+    addReply(c,shared.cone);
+}
+
+void getbitCommand(redisClient *c) {
+    robj *o;
+    size_t bitoffset, byte, bitmask;
+    int on = 0;
+    char llbuf[32];
+
+    if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
+        return;
+
+    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
+        checkType(c,o,REDIS_STRING)) return;
+
+    byte = bitoffset >> 3;
+    bitmask = 1 << (7 - (bitoffset & 0x7));
+    if (o->encoding != REDIS_ENCODING_RAW) {
+        if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
+            on = llbuf[byte] & bitmask;
+    } else {
+        if (byte < sdslen(o->ptr))
+            on = ((sds)o->ptr)[byte] & bitmask;
+    }
+    addReply(c, on ? shared.cone : shared.czero);
+}
+
 void mgetCommand(redisClient *c) {
     int j;
 
index 4c6662c67defb0cd5b313eae2cb5bf75638c4ee8..3032cca908935affb367bbd88753b7941c869b0f 100644 (file)
@@ -374,4 +374,88 @@ start_server {tags {"basic"}} {
         r set mystring "foozzz0123456789 baz"
         r strlen mystring
     }
+
+    test "SETBIT against non-existing key" {
+        r del mykey
+
+        # Setting 2nd bit to on is integer 64, ascii "@"
+        assert_equal 1 [r setbit mykey 1 1]
+        assert_equal "@" [r get mykey]
+    }
+
+    test "SETBIT against string-encoded key" {
+        # Single byte with 2nd bit set
+        r set mykey "@"
+
+        # 64 + 32 = 96 => ascii "`" (backtick)
+        assert_equal 1 [r setbit mykey 2 1]
+        assert_equal "`" [r get mykey]
+    }
+
+    test "SETBIT against integer-encoded key" {
+        r set mykey 1
+        assert_encoding int mykey
+
+        # Ascii "1" is integer 49 = 00 11 00 01
+        # Setting 7th bit = 51 => ascii "3"
+        assert_equal 1 [r setbit mykey 6 1]
+        assert_equal "3" [r get mykey]
+    }
+
+    test "SETBIT against key with wrong type" {
+        r del mykey
+        r lpush mykey "foo"
+        assert_error "*wrong kind*" {r setbit mykey 0 1}
+    }
+
+    test "SETBIT with out of range bit offset" {
+        r del mykey
+        assert_error "*out of range*" {r setbit mykey [expr 8*1024*1024*1024] 1}
+        assert_error "*out of range*" {r setbit mykey -1 1}
+    }
+
+    test "SETBIT with non-bit argument" {
+        r del mykey
+        assert_error "*0 or 1*" {r setbit mykey 0 -1}
+        assert_error "*0 or 1*" {r setbit mykey 0  2}
+        assert_error "*0 or 1*" {r setbit mykey 0 10}
+        assert_error "*0 or 1*" {r setbit mykey 0 01}
+    }
+
+    test "GETBIT against non-existing key" {
+        r del mykey
+        assert_equal 0 [r getbit mykey 0]
+    }
+
+    test "GETBIT against string-encoded key" {
+        # Single byte with 2nd and 3rd bit set
+        r set mykey "`"
+
+        # In-range
+        assert_equal 0 [r getbit mykey 0]
+        assert_equal 1 [r getbit mykey 1]
+        assert_equal 1 [r getbit mykey 2]
+        assert_equal 0 [r getbit mykey 3]
+
+        # Out-range
+        assert_equal 0 [r getbit mykey 8]
+        assert_equal 0 [r getbit mykey 100]
+        assert_equal 0 [r getbit mykey 10000]
+    }
+
+    test "GETBIT against integer-encoded key" {
+        r set mykey 1
+        assert_encoding int mykey
+
+        # Ascii "1" is integer 49 = 00 11 00 01
+        assert_equal 0 [r getbit mykey 0]
+        assert_equal 0 [r getbit mykey 1]
+        assert_equal 1 [r getbit mykey 2]
+        assert_equal 1 [r getbit mykey 3]
+
+        # Out-range
+        assert_equal 0 [r getbit mykey 8]
+        assert_equal 0 [r getbit mykey 100]
+        assert_equal 0 [r getbit mykey 10000]
+    }
 }