Skip to content

Commit 1be51b1

Browse files
sheharyaarjihuayumapleFU
authored
Add BIT support to BITPOS (#2170)
Signed-off-by: Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com> Co-authored-by: 纪华裕 <jihuayu123@gmail.com> Co-authored-by: mwish <maplewish117@gmail.com>
1 parent b5207f3 commit 1be51b1

File tree

7 files changed

+340
-43
lines changed

7 files changed

+340
-43
lines changed

src/commands/cmd_bit.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "commands/command_parser.h"
2323
#include "error_constants.h"
2424
#include "server/server.h"
25+
#include "status.h"
2526
#include "types/redis_bitmap.h"
2627

2728
namespace redis {
@@ -171,6 +172,10 @@ class CommandBitPos : public Commander {
171172
stop_ = *parse_stop;
172173
}
173174

175+
if (args.size() >= 6 && util::EqualICase(args[5], "BIT")) {
176+
is_bit_index_ = true;
177+
}
178+
174179
auto parse_arg = ParseInt<int64_t>(args[2], 10);
175180
if (!parse_arg) {
176181
return {Status::RedisParseErr, errValueNotInteger};
@@ -189,7 +194,7 @@ class CommandBitPos : public Commander {
189194
Status Execute(Server *srv, Connection *conn, std::string *output) override {
190195
int64_t pos = 0;
191196
redis::Bitmap bitmap_db(srv->storage, conn->GetNamespace());
192-
auto s = bitmap_db.BitPos(args_[1], bit_, start_, stop_, stop_given_, &pos);
197+
auto s = bitmap_db.BitPos(args_[1], bit_, start_, stop_, stop_given_, &pos, is_bit_index_);
193198
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
194199

195200
*output = redis::Integer(pos);
@@ -201,6 +206,7 @@ class CommandBitPos : public Commander {
201206
int64_t stop_ = -1;
202207
bool bit_ = false;
203208
bool stop_given_ = false;
209+
bool is_bit_index_ = false;
204210
};
205211

206212
class CommandBitOp : public Commander {

src/types/redis_bitmap.cc

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
#include "redis_bitmap.h"
2222

2323
#include <algorithm>
24+
#include <cstdint>
2425
#include <memory>
26+
#include <utility>
2527
#include <vector>
2628

2729
#include "common/bit_util.h"
@@ -307,7 +309,9 @@ rocksdb::Status Bitmap::BitCount(const Slice &user_key, int64_t start, int64_t s
307309
}
308310

309311
rocksdb::Status Bitmap::BitPos(const Slice &user_key, bool bit, int64_t start, int64_t stop, bool stop_given,
310-
int64_t *pos) {
312+
int64_t *pos, bool is_bit_index) {
313+
if (is_bit_index) DCHECK(stop_given);
314+
311315
std::string raw_value;
312316
std::string ns_key = AppendNamespacePrefix(user_key);
313317

@@ -323,11 +327,15 @@ rocksdb::Status Bitmap::BitPos(const Slice &user_key, bool bit, int64_t start, i
323327
if (metadata.Type() == kRedisString) {
324328
ss = std::nullopt;
325329
redis::BitmapString bitmap_string_db(storage_, namespace_);
326-
return bitmap_string_db.BitPos(raw_value, bit, start, stop, stop_given, pos);
330+
return bitmap_string_db.BitPos(raw_value, bit, start, stop, stop_given, pos, is_bit_index);
327331
}
328-
std::tie(start, stop) = BitmapString::NormalizeRange(start, stop, static_cast<int64_t>(metadata.size));
329-
auto u_start = static_cast<uint32_t>(start);
330-
auto u_stop = static_cast<uint32_t>(stop);
332+
333+
uint32_t to_bit_factor = is_bit_index ? 8 : 1;
334+
auto size = static_cast<int64_t>(metadata.size) * static_cast<int64_t>(to_bit_factor);
335+
336+
std::tie(start, stop) = BitmapString::NormalizeRange(start, stop, size);
337+
auto u_start = static_cast<uint64_t>(start);
338+
auto u_stop = static_cast<uint64_t>(stop);
331339
if (u_start > u_stop) {
332340
*pos = -1;
333341
return rocksdb::Status::OK();
@@ -341,13 +349,40 @@ rocksdb::Status Bitmap::BitPos(const Slice &user_key, bool bit, int64_t start, i
341349
return -1;
342350
};
343351

352+
auto bit_pos_in_byte_startstop = [](char byte, bool bit, uint32_t start, uint32_t stop) -> int {
353+
for (uint32_t i = start; i <= stop; i++) {
354+
if (bit && (byte & (1 << i)) != 0) return (int)i; // typecast to int since the value ranges from 0 to 7
355+
if (!bit && (byte & (1 << i)) == 0) return (int)i;
356+
}
357+
return -1;
358+
};
359+
344360
rocksdb::ReadOptions read_options;
345361
read_options.snapshot = ss->GetSnapShot();
346-
uint32_t start_index = u_start / kBitmapSegmentBytes;
347-
uint32_t stop_index = u_stop / kBitmapSegmentBytes;
362+
// if bit index, (Eg start = 1, stop = 35), then
363+
// u_start = 1/8 = 0, u_stop = 35/8 = 4 (in bytes)
364+
uint32_t start_segment_index = (u_start / to_bit_factor) / kBitmapSegmentBytes;
365+
uint32_t stop_segment_index = (u_stop / to_bit_factor) / kBitmapSegmentBytes;
366+
uint32_t start_bit_pos_in_byte = 0;
367+
uint32_t stop_bit_pos_in_byte = 0;
368+
369+
if (is_bit_index) {
370+
start_bit_pos_in_byte = u_start % 8;
371+
stop_bit_pos_in_byte = u_stop % 8;
372+
}
373+
374+
auto range_in_byte = [start_bit_pos_in_byte, stop_bit_pos_in_byte](
375+
uint32_t byte_start, uint32_t byte_end,
376+
uint32_t curr_byte) -> std::pair<uint32_t, uint32_t> {
377+
if (curr_byte == byte_start && curr_byte == byte_end) return {start_bit_pos_in_byte, stop_bit_pos_in_byte};
378+
if (curr_byte == byte_start) return {start_bit_pos_in_byte, 7};
379+
if (curr_byte == byte_end) return {0, stop_bit_pos_in_byte};
380+
return {0, 7};
381+
};
382+
348383
// Don't use multi get to prevent large range query, and take too much memory
349384
// Searching bits in segments [start_index, stop_index].
350-
for (uint32_t i = start_index; i <= stop_index; i++) {
385+
for (uint32_t i = start_segment_index; i <= stop_segment_index; i++) {
351386
rocksdb::PinnableSlice pin_value;
352387
std::string sub_key =
353388
InternalKey(ns_key, std::to_string(i * kBitmapSegmentBytes), metadata.version, storage_->IsSlotIdEncoded())
@@ -364,17 +399,33 @@ rocksdb::Status Bitmap::BitPos(const Slice &user_key, bool bit, int64_t start, i
364399
continue;
365400
}
366401
size_t byte_pos_in_segment = 0;
367-
if (i == start_index) byte_pos_in_segment = u_start % kBitmapSegmentBytes;
402+
size_t byte_with_bit_start = -1;
403+
size_t byte_with_bit_stop = -2;
404+
// if bit index, (Eg start = 1, stop = 35), then
405+
// byte_pos_in_segment should be calculated in bytes, hence divide by 8
406+
if (i == start_segment_index) {
407+
byte_pos_in_segment = (u_start / to_bit_factor) % kBitmapSegmentBytes;
408+
byte_with_bit_start = byte_pos_in_segment;
409+
}
368410
size_t stop_byte_in_segment = pin_value.size();
369-
if (i == stop_index) {
370-
DCHECK_LE(u_stop % kBitmapSegmentBytes + 1, pin_value.size());
371-
stop_byte_in_segment = u_stop % kBitmapSegmentBytes + 1;
411+
if (i == stop_segment_index) {
412+
DCHECK_LE((u_stop / to_bit_factor) % kBitmapSegmentBytes + 1, pin_value.size());
413+
stop_byte_in_segment = (u_stop / to_bit_factor) % kBitmapSegmentBytes + 1;
414+
byte_with_bit_stop = stop_byte_in_segment;
372415
}
373416
// Invariant:
374417
// 1. pin_value.size() <= kBitmapSegmentBytes.
375418
// 2. If it's the last segment, metadata.size % kBitmapSegmentBytes <= pin_value.size().
376419
for (; byte_pos_in_segment < stop_byte_in_segment; byte_pos_in_segment++) {
377-
int bit_pos_in_byte_value = bit_pos_in_byte(pin_value[byte_pos_in_segment], bit);
420+
int bit_pos_in_byte_value = -1;
421+
if (is_bit_index) {
422+
uint32_t start_bit = 0, stop_bit = 7;
423+
std::tie(start_bit, stop_bit) = range_in_byte(byte_with_bit_start, byte_with_bit_stop, byte_pos_in_segment);
424+
bit_pos_in_byte_value = bit_pos_in_byte_startstop(pin_value[byte_pos_in_segment], bit, start_bit, stop_bit);
425+
} else {
426+
bit_pos_in_byte_value = bit_pos_in_byte(pin_value[byte_pos_in_segment], bit);
427+
}
428+
378429
if (bit_pos_in_byte_value != -1) {
379430
*pos = static_cast<int64_t>(i * kBitmapSegmentBits + byte_pos_in_segment * 8 + bit_pos_in_byte_value);
380431
return rocksdb::Status::OK();
@@ -387,7 +438,7 @@ rocksdb::Status Bitmap::BitPos(const Slice &user_key, bool bit, int64_t start, i
387438
// 1. If it's the last segment, we've done searching in the above loop.
388439
// 2. If it's not the last segment, we can check if the segment is all 0.
389440
if (pin_value.size() < kBitmapSegmentBytes) {
390-
if (i == stop_index) {
441+
if (i == stop_segment_index) {
391442
continue;
392443
}
393444
*pos = static_cast<int64_t>(i * kBitmapSegmentBits + pin_value.size() * 8);

src/types/redis_bitmap.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ class Bitmap : public Database {
5050
rocksdb::Status GetString(const Slice &user_key, uint32_t max_btos_size, std::string *value);
5151
rocksdb::Status SetBit(const Slice &user_key, uint32_t bit_offset, bool new_bit, bool *old_bit);
5252
rocksdb::Status BitCount(const Slice &user_key, int64_t start, int64_t stop, bool is_bit_index, uint32_t *cnt);
53-
rocksdb::Status BitPos(const Slice &user_key, bool bit, int64_t start, int64_t stop, bool stop_given, int64_t *pos);
53+
rocksdb::Status BitPos(const Slice &user_key, bool bit, int64_t start, int64_t stop, bool stop_given, int64_t *pos,
54+
bool is_bit_index);
5455
rocksdb::Status BitOp(BitOpFlags op_flag, const std::string &op_name, const Slice &user_key,
5556
const std::vector<Slice> &op_keys, int64_t *len);
5657
rocksdb::Status Bitfield(const Slice &user_key, const std::vector<BitfieldOperation> &ops,

src/types/redis_bitmap_string.cc

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,31 +100,80 @@ rocksdb::Status BitmapString::BitCount(const std::string &raw_value, int64_t sta
100100
}
101101

102102
rocksdb::Status BitmapString::BitPos(const std::string &raw_value, bool bit, int64_t start, int64_t stop,
103-
bool stop_given, int64_t *pos) {
103+
bool stop_given, int64_t *pos, bool is_bit_index) {
104104
std::string_view string_value = std::string_view{raw_value}.substr(Metadata::GetOffsetAfterExpire(raw_value[0]));
105105
auto strlen = static_cast<int64_t>(string_value.size());
106106
/* Convert negative and out-of-bound indexes */
107-
std::tie(start, stop) = NormalizeRange(start, stop, strlen);
107+
108+
int64_t length = is_bit_index ? strlen * 8 : strlen;
109+
std::tie(start, stop) = NormalizeRange(start, stop, length);
108110

109111
if (start > stop) {
110112
*pos = -1;
111-
} else {
112-
int64_t bytes = stop - start + 1;
113-
*pos = util::msb::RawBitpos(reinterpret_cast<const uint8_t *>(string_value.data()) + start, bytes, bit);
114-
115-
/* If we are looking for clear bits, and the user specified an exact
116-
* range with start-end, we can't consider the right of the range as
117-
* zero padded (as we do when no explicit end is given).
118-
*
119-
* So if redisBitpos() returns the first bit outside the range,
120-
* we return -1 to the caller, to mean, in the specified range there
121-
* is not a single "0" bit. */
122-
if (stop_given && bit == 0 && *pos == bytes * 8) {
113+
return rocksdb::Status::OK();
114+
}
115+
116+
int64_t byte_start = is_bit_index ? start / 8 : start;
117+
int64_t byte_stop = is_bit_index ? stop / 8 : stop;
118+
int64_t bit_in_start_byte = is_bit_index ? start % 8 : 0;
119+
int64_t bit_in_stop_byte = is_bit_index ? stop % 8 : 7;
120+
int64_t bytes_cnt = byte_stop - byte_start + 1;
121+
122+
auto bit_pos_in_byte_startstop = [](char byte, bool bit, uint32_t start, uint32_t stop) -> int {
123+
for (uint32_t i = start; i <= stop; i++) {
124+
if (util::msb::GetBitFromByte(byte, i) == bit) {
125+
return (int)i;
126+
}
127+
}
128+
return -1;
129+
};
130+
131+
// if the bit start and bit end are in the same byte, we can process it manually
132+
if (is_bit_index && byte_start == byte_stop) {
133+
int res = bit_pos_in_byte_startstop(string_value[byte_start], bit, bit_in_start_byte, bit_in_stop_byte);
134+
if (res != -1) {
135+
*pos = res + byte_start * 8;
136+
return rocksdb::Status::OK();
137+
}
138+
*pos = -1;
139+
return rocksdb::Status::OK();
140+
}
141+
142+
if (is_bit_index && bit_in_start_byte != 0) {
143+
// process first byte
144+
int res = bit_pos_in_byte_startstop(string_value[byte_start], bit, bit_in_start_byte, 7);
145+
if (res != -1) {
146+
*pos = res + byte_start * 8;
147+
return rocksdb::Status::OK();
148+
}
149+
150+
byte_start++;
151+
bytes_cnt--;
152+
}
153+
154+
*pos = util::msb::RawBitpos(reinterpret_cast<const uint8_t *>(string_value.data()) + byte_start, bytes_cnt, bit);
155+
156+
if (is_bit_index && *pos != -1 && *pos != bytes_cnt * 8) {
157+
// if the pos is more than stop bit, then it is not in the range
158+
if (*pos > stop) {
123159
*pos = -1;
124160
return rocksdb::Status::OK();
125161
}
126-
if (*pos != -1) *pos += start * 8; /* Adjust for the bytes we skipped. */
127162
}
163+
164+
/* If we are looking for clear bits, and the user specified an exact
165+
* range with start-end, we tcan' consider the right of the range as
166+
* zero padded (as we do when no explicit end is given).
167+
*
168+
* So if redisBitpos() returns the first bit outside the range,
169+
* we return -1 to the caller, to mean, in the specified range there
170+
* is not a single "0" bit. */
171+
if (stop_given && bit == 0 && *pos == bytes_cnt * 8) {
172+
*pos = -1;
173+
return rocksdb::Status::OK();
174+
}
175+
if (*pos != -1) *pos += byte_start * 8; /* Adjust for the bytes we skipped. */
176+
128177
return rocksdb::Status::OK();
129178
}
130179

src/types/redis_bitmap_string.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class BitmapString : public Database {
3939
static rocksdb::Status BitCount(const std::string &raw_value, int64_t start, int64_t stop, bool is_bit_index,
4040
uint32_t *cnt);
4141
static rocksdb::Status BitPos(const std::string &raw_value, bool bit, int64_t start, int64_t stop, bool stop_given,
42-
int64_t *pos);
42+
int64_t *pos, bool is_bit_index);
4343
rocksdb::Status Bitfield(const Slice &ns_key, std::string *raw_value, const std::vector<BitfieldOperation> &ops,
4444
std::vector<std::optional<BitfieldValue>> *rets);
4545
static rocksdb::Status BitfieldReadOnly(const Slice &ns_key, const std::string &raw_value,

tests/cppunit/types/bitmap_test.cc

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ TEST_P(RedisBitmapTest, BitPosClearBit) {
179179
///
180180
/// String will set a empty string value when initializing, so, when first
181181
/// querying, it should return -1.
182-
bitmap_->BitPos(key_, false, 0, -1, /*stop_given=*/false, &pos);
182+
bitmap_->BitPos(key_, false, 0, -1, /*stop_given=*/false, &pos, /*bit_index=*/false);
183183
if (i == 0 && !use_bitmap) {
184184
EXPECT_EQ(pos, -1);
185185
} else {
@@ -201,7 +201,7 @@ TEST_P(RedisBitmapTest, BitPosSetBit) {
201201
int64_t pos = 0;
202202
int start_indexes[] = {0, 1, 124, 1025, 1027, 3 * 1024 + 1};
203203
for (size_t i = 0; i < sizeof(start_indexes) / sizeof(start_indexes[0]); i++) {
204-
bitmap_->BitPos(key_, true, start_indexes[i], -1, true, &pos);
204+
bitmap_->BitPos(key_, true, start_indexes[i], -1, true, &pos, /*bit_index=*/false);
205205
EXPECT_EQ(pos, offsets[i]);
206206
}
207207
auto s = bitmap_->Del(key_);
@@ -215,19 +215,19 @@ TEST_P(RedisBitmapTest, BitPosNegative) {
215215
}
216216
int64_t pos = 0;
217217
// First bit is negative
218-
bitmap_->BitPos(key_, false, 0, -1, true, &pos);
218+
bitmap_->BitPos(key_, false, 0, -1, true, &pos, /*bit_index=*/false);
219219
EXPECT_EQ(0, pos);
220220
// 8 * 1024 - 1 bit is positive
221-
bitmap_->BitPos(key_, true, 0, -1, true, &pos);
221+
bitmap_->BitPos(key_, true, 0, -1, true, &pos, /*bit_index=*/false);
222222
EXPECT_EQ(8 * 1024 - 1, pos);
223223
// First bit in 1023 byte is negative
224-
bitmap_->BitPos(key_, false, -1, -1, true, &pos);
224+
bitmap_->BitPos(key_, false, -1, -1, true, &pos, /*bit_index=*/false);
225225
EXPECT_EQ(8 * 1023, pos);
226226
// Last Bit in 1023 byte is positive
227-
bitmap_->BitPos(key_, true, -1, -1, true, &pos);
227+
bitmap_->BitPos(key_, true, -1, -1, true, &pos, /*bit_index=*/false);
228228
EXPECT_EQ(8 * 1024 - 1, pos);
229229
// Large negative number will be normalized.
230-
bitmap_->BitPos(key_, false, -10000, -10000, true, &pos);
230+
bitmap_->BitPos(key_, false, -10000, -10000, true, &pos, /*bit_index=*/false);
231231
EXPECT_EQ(0, pos);
232232

233233
auto s = bitmap_->Del(key_);
@@ -242,9 +242,9 @@ TEST_P(RedisBitmapTest, BitPosStopGiven) {
242242
EXPECT_FALSE(bit);
243243
}
244244
int64_t pos = 0;
245-
bitmap_->BitPos(key_, false, 0, 0, /*stop_given=*/true, &pos);
245+
bitmap_->BitPos(key_, false, 0, 0, /*stop_given=*/true, &pos, /*bit_index=*/false);
246246
EXPECT_EQ(-1, pos);
247-
bitmap_->BitPos(key_, false, 0, 0, /*stop_given=*/false, &pos);
247+
bitmap_->BitPos(key_, false, 0, 0, /*stop_given=*/false, &pos, /*bit_index=*/false);
248248
EXPECT_EQ(8, pos);
249249

250250
// Set a bit at 8 not affect that
@@ -253,9 +253,9 @@ TEST_P(RedisBitmapTest, BitPosStopGiven) {
253253
bitmap_->SetBit(key_, 8, true, &bit);
254254
EXPECT_FALSE(bit);
255255
}
256-
bitmap_->BitPos(key_, false, 0, 0, /*stop_given=*/true, &pos);
256+
bitmap_->BitPos(key_, false, 0, 0, /*stop_given=*/true, &pos, /*bit_index=*/false);
257257
EXPECT_EQ(-1, pos);
258-
bitmap_->BitPos(key_, false, 0, 1, /*stop_given=*/false, &pos);
258+
bitmap_->BitPos(key_, false, 0, 1, /*stop_given=*/false, &pos, /*bit_index=*/false);
259259
EXPECT_EQ(9, pos);
260260

261261
auto s = bitmap_->Del(key_);

0 commit comments

Comments
 (0)