summaryrefslogtreecommitdiff
path: root/src/bitops.c
diff options
context:
space:
mode:
authorHuang Zhw <huang_zhw@126.com>2021-09-12 16:31:22 +0800
committerGitHub <noreply@github.com>2021-09-12 11:31:22 +0300
commit75dd230994275c4448510d93d6c86541038574d2 (patch)
tree3c57ef0dfc0901cc5165b0f029224cf41b1a366d /src/bitops.c
parent418c2e79313b367e64e47d38edd59f9f22a3b4fa (diff)
downloadredis-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.c131
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).