diff options
author | Huang Zhw <huang_zhw@126.com> | 2021-09-12 16:31:22 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-12 11:31:22 +0300 |
commit | 75dd230994275c4448510d93d6c86541038574d2 (patch) | |
tree | 3c57ef0dfc0901cc5165b0f029224cf41b1a366d /src/bitops.c | |
parent | 418c2e79313b367e64e47d38edd59f9f22a3b4fa (diff) | |
download | redis-75dd230994275c4448510d93d6c86541038574d2.tar.gz |
bitpos/bitcount add bit index (#9324)
Make bitpos/bitcount support bit index:
```
BITPOS key bit [start [end [BIT|BYTE]]]
BITCOUNT key [start end [BIT|BYTE]]
```
The default behavior is `BYTE`, so these commands are still compatible with old.
Diffstat (limited to 'src/bitops.c')
-rw-r--r-- | src/bitops.c | 131 |
1 files changed, 107 insertions, 24 deletions
diff --git a/src/bitops.c b/src/bitops.c index 7c46241c3..44ed684bd 100644 --- a/src/bitops.c +++ b/src/bitops.c @@ -789,12 +789,15 @@ void bitopCommand(client *c) { addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */ } -/* BITCOUNT key [start end] */ +/* BITCOUNT key [start end [BIT|BYTE]] */ void bitcountCommand(client *c) { robj *o; - long start, end, strlen; + long long start, end; + long strlen; unsigned char *p; char llbuf[LONG_STR_SIZE]; + int isbit = 0; + unsigned char first_byte_neg_mask = 0, last_byte_neg_mask = 0; /* Lookup, check for type, and return 0 for non existing keys. */ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || @@ -802,21 +805,41 @@ void bitcountCommand(client *c) { p = getObjectReadOnlyString(o,&strlen,llbuf); /* Parse start/end range if any. */ - if (c->argc == 4) { - if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK) + if (c->argc == 4 || c->argc == 5) { + long long totlen = strlen; + /* Make sure we will not overflow */ + serverAssert(totlen <= LLONG_MAX >> 3); + if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK) return; - if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK) + if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK) return; /* Convert negative indexes */ if (start < 0 && end < 0 && start > end) { addReply(c,shared.czero); return; } - if (start < 0) start = strlen+start; - if (end < 0) end = strlen+end; + if (c->argc == 5) { + if (!strcasecmp(c->argv[4]->ptr,"bit")) isbit = 1; + else if (!strcasecmp(c->argv[4]->ptr,"byte")) isbit = 0; + else { + addReplyErrorObject(c,shared.syntaxerr); + return; + } + } + if (isbit) totlen <<= 3; + if (start < 0) start = totlen+start; + if (end < 0) end = totlen+end; if (start < 0) start = 0; if (end < 0) end = 0; - if (end >= strlen) end = strlen-1; + if (end >= totlen) end = totlen-1; + if (isbit && start <= end) { + /* Before converting bit offset to byte offset, create negative masks + * for the edges. */ + first_byte_neg_mask = ~((1<<(8-(start&7)))-1) & 0xFF; + last_byte_neg_mask = (1<<(7-(end&7)))-1; + start >>= 3; + end >>= 3; + } } else if (c->argc == 2) { /* The whole string. */ start = 0; @@ -832,19 +855,30 @@ void bitcountCommand(client *c) { if (start > end) { addReply(c,shared.czero); } else { - long bytes = end-start+1; - - addReplyLongLong(c,redisPopcount(p+start,bytes)); + long bytes = (long)(end-start+1); + long long count = redisPopcount(p+start,bytes); + if (first_byte_neg_mask != 0 || last_byte_neg_mask != 0) { + unsigned char firstlast[2] = {0, 0}; + /* We may count bits of first byte and last byte which are out of + * range. So we need to subtract them. Here we use a trick. We set + * bits in the range to zero. So these bit will not be excluded. */ + if (first_byte_neg_mask != 0) firstlast[0] = p[start] & first_byte_neg_mask; + if (last_byte_neg_mask != 0) firstlast[1] = p[end] & last_byte_neg_mask; + count -= redisPopcount(firstlast,2); + } + addReplyLongLong(c,count); } } -/* BITPOS key bit [start [end]] */ +/* BITPOS key bit [start [end [BIT|BYTE]]] */ void bitposCommand(client *c) { robj *o; - long bit, start, end, strlen; + long long start, end; + long bit, strlen; unsigned char *p; char llbuf[LONG_STR_SIZE]; - int end_given = 0; + int isbit = 0, end_given = 0; + unsigned char first_byte_neg_mask = 0, last_byte_neg_mask = 0; /* Parse the bit argument to understand what we are looking for, set * or clear bits. */ @@ -856,7 +890,7 @@ void bitposCommand(client *c) { } /* If the key does not exist, from our point of view it is an infinite - * array of 0 bits. If the user is looking for the fist clear bit return 0, + * array of 0 bits. If the user is looking for the first clear bit return 0, * If the user is looking for the first set bit, return -1. */ if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) { addReplyLongLong(c, bit ? -1 : 0); @@ -866,22 +900,43 @@ void bitposCommand(client *c) { p = getObjectReadOnlyString(o,&strlen,llbuf); /* Parse start/end range if any. */ - if (c->argc == 4 || c->argc == 5) { - if (getLongFromObjectOrReply(c,c->argv[3],&start,NULL) != C_OK) + if (c->argc == 4 || c->argc == 5 || c->argc == 6) { + long long totlen = strlen; + /* Make sure we will not overflow */ + serverAssert(totlen <= LLONG_MAX >> 3); + if (getLongLongFromObjectOrReply(c,c->argv[3],&start,NULL) != C_OK) return; - if (c->argc == 5) { - if (getLongFromObjectOrReply(c,c->argv[4],&end,NULL) != C_OK) + if (c->argc == 6) { + if (!strcasecmp(c->argv[5]->ptr,"bit")) isbit = 1; + else if (!strcasecmp(c->argv[5]->ptr,"byte")) isbit = 0; + else { + addReplyErrorObject(c,shared.syntaxerr); + return; + } + } + if (c->argc >= 5) { + if (getLongLongFromObjectOrReply(c,c->argv[4],&end,NULL) != C_OK) return; end_given = 1; } else { - end = strlen-1; + if (isbit) end = (totlen<<3) + 7; + else end = totlen-1; } + if (isbit) totlen <<= 3; /* Convert negative indexes */ - if (start < 0) start = strlen+start; - if (end < 0) end = strlen+end; + if (start < 0) start = totlen+start; + if (end < 0) end = totlen+end; if (start < 0) start = 0; if (end < 0) end = 0; - if (end >= strlen) end = strlen-1; + if (end >= totlen) end = totlen-1; + if (isbit && start <= end) { + /* Before converting bit offset to byte offset, create negative masks + * for the edges. */ + first_byte_neg_mask = ~((1<<(8-(start&7)))-1) & 0xFF; + last_byte_neg_mask = (1<<(7-(end&7)))-1; + start >>= 3; + end >>= 3; + } } else if (c->argc == 3) { /* The whole string. */ start = 0; @@ -898,8 +953,36 @@ void bitposCommand(client *c) { addReplyLongLong(c, -1); } else { long bytes = end-start+1; - long long pos = redisBitpos(p+start,bytes,bit); + long long pos; + unsigned char tmpchar; + if (first_byte_neg_mask) { + if (bit) tmpchar = p[start] & ~first_byte_neg_mask; + else tmpchar = p[start] | first_byte_neg_mask; + /* Special case, there is only one byte */ + if (last_byte_neg_mask && bytes == 1) { + if (bit) tmpchar = tmpchar & ~last_byte_neg_mask; + else tmpchar = tmpchar | last_byte_neg_mask; + } + pos = redisBitpos(&tmpchar,1,bit); + /* If there are no more bytes or we get valid pos, we can exit early */ + if (bytes == 1 || (pos != -1 && pos != 8)) goto result; + start++; + bytes--; + } + /* If the last byte has not bits in the range, we should exclude it */ + long curbytes = bytes - (last_byte_neg_mask ? 1 : 0); + if (curbytes > 0) { + pos = redisBitpos(p+start,curbytes,bit); + /* If there is no more bytes or we get valid pos, we can exit early */ + if (bytes == curbytes || (pos != -1 && pos != (long long)curbytes<<3)) goto result; + start += curbytes; + bytes -= curbytes; + } + if (bit) tmpchar = p[end] & ~last_byte_neg_mask; + else tmpchar = p[end] | last_byte_neg_mask; + pos = redisBitpos(&tmpchar,1,bit); + result: /* If we are looking for clear bits, and the user specified an exact * range with start-end, we can't consider the right of the range as * zero padded (as we do when no explicit end is given). |