More consistent BITPOS behavior with bit=0 and ranges.

With the new behavior it is possible to specify just the start in the
range (the end will be assumed to be the first byte), or it is possible
to specify both start and end.

This is useful to change the behavior of the command when looking for
zeros inside a string.

1) If the user specifies both start and end, and no 0 is found inside
   the range, the command returns -1.

2) If instead no range is specified, or just the start is given, even
   if in the actual string no 0 bit is found, the command returns the
   first bit on the right after the end of the string.

So for example if the string stored at key foo is "\xff\xff":

    BITPOS foo (returns 16)
    BITPOS foo 0 -1 (returns -1)
    BITPOS foo 0 (returns 16)

The idea is that when no end is given the user is just looking for the
first bit that is zero and can be set to 1 with SETBIT, as it is
"available". Instead when a specific range is given, we just look for a
zero within the boundaries of the range.
This commit is contained in:
antirez 2014-02-27 12:53:03 +01:00
parent 38c620b3b5
commit 0e31eaa27f

View File

@ -505,12 +505,13 @@ void bitcountCommand(redisClient *c) {
} }
} }
/* BITPOS key bit [start end] */ /* BITPOS key bit [start [end]] */
void bitposCommand(redisClient *c) { void bitposCommand(redisClient *c) {
robj *o; robj *o;
long bit, start, end, strlen; long bit, start, end, strlen;
unsigned char *p; unsigned char *p;
char llbuf[32]; char llbuf[32];
int end_given = 0;
/* Parse the bit argument to understand what we are looking for, set /* Parse the bit argument to understand what we are looking for, set
* or clear bits. */ * or clear bits. */
@ -541,11 +542,16 @@ void bitposCommand(redisClient *c) {
} }
/* Parse start/end range if any. */ /* Parse start/end range if any. */
if (c->argc == 5) { if (c->argc == 4 || c->argc == 5) {
if (getLongFromObjectOrReply(c,c->argv[3],&start,NULL) != REDIS_OK) if (getLongFromObjectOrReply(c,c->argv[3],&start,NULL) != REDIS_OK)
return; return;
if (getLongFromObjectOrReply(c,c->argv[4],&end,NULL) != REDIS_OK) if (c->argc == 5) {
return; if (getLongFromObjectOrReply(c,c->argv[4],&end,NULL) != REDIS_OK)
return;
end_given = 1;
} else {
end = strlen-1;
}
/* Convert negative indexes */ /* Convert negative indexes */
if (start < 0) start = strlen+start; if (start < 0) start = strlen+start;
if (end < 0) end = strlen+end; if (end < 0) end = strlen+end;
@ -570,14 +576,14 @@ void bitposCommand(redisClient *c) {
long bytes = end-start+1; long bytes = end-start+1;
long pos = redisBitpos(p+start,bytes,bit); long pos = redisBitpos(p+start,bytes,bit);
/* If we are looking for clear bits, and our range does not includes /* If we are looking for clear bits, and the user specified an exact
* the end of the string, but terminates before, we can't consider the * range with start-end, we can't consider the right of the range as
* right of the range as zero padded. * zero padded (as we do when no explicit end is given).
* *
* so if redisBitpos() returns the first bit outside the string, * So if redisBitpos() returns the first bit outside the range,
* we return -1 to the caller, to mean, in the specified range there * we return -1 to the caller, to mean, in the specified range there
* is not a single "0" bit. */ * is not a single "0" bit. */
if (end != strlen-1 && bit == 0 && pos == bytes*8) { if (end_given && bit == 0 && pos == bytes*8) {
addReplyLongLong(c,-1); addReplyLongLong(c,-1);
return; return;
} }