Skip to content

Commit 6a9bd16

Browse files
nkrokerclaude
andcommitted
fix(bitfield): support Redis positional '#N' offset syntax
'#N' expands to N * bit_width. Matches Redis BITFIELD behavior. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 04d4e7f commit 6a9bd16

2 files changed

Lines changed: 42 additions & 2 deletions

File tree

src/commands/cmd_bit.cc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,14 @@ class CommandBitfield : public Commander {
302302
}
303303
cmd.encoding = encoding.GetValue();
304304

305-
// parse offset
306-
if (!GetBitOffsetFromArgument(group[2], &cmd.offset).IsOK()) {
305+
// parse offset — support Redis '#N' positional syntax: #N means N * bit_width
306+
if (!group[2].empty() && group[2][0] == '#') {
307+
auto pos_parse = ParseInt<uint32_t>(group[2].substr(1), 10);
308+
if (!pos_parse) {
309+
return {Status::RedisParseErr, "bit offset is not an integer or out of range"};
310+
}
311+
cmd.offset = *pos_parse * cmd.encoding.Bits();
312+
} else if (!GetBitOffsetFromArgument(group[2], &cmd.offset).IsOK()) {
307313
return {Status::RedisParseErr, "bit offset is not an integer or out of range"};
308314
}
309315

tests/gocase/unit/type/bitmap/bitmap_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,40 @@ func TestBitmap(t *testing.T) {
379379
require.ErrorContains(t, rdb.Do(ctx, "BITFIELD_RO", "str", "INCRBY", "u8", "32", 2).Err(), "BITFIELD_RO only supports the GET subcommand")
380380
})
381381

382+
t.Run("BITFIELD positional offset #N syntax", func(t *testing.T) {
383+
require.NoError(t, rdb.Del(ctx, "bf_pos").Err())
384+
385+
// #0 with u16 = offset 0, #1 = offset 16, #2 = offset 32
386+
res := rdb.Do(ctx, "BITFIELD", "bf_pos", "SET", "u16", "#0", 100)
387+
require.NoError(t, res.Err())
388+
require.EqualValues(t, []interface{}{int64(0)}, res.Val())
389+
390+
res = rdb.Do(ctx, "BITFIELD", "bf_pos", "SET", "u16", "#1", 200)
391+
require.NoError(t, res.Err())
392+
require.EqualValues(t, []interface{}{int64(0)}, res.Val())
393+
394+
res = rdb.Do(ctx, "BITFIELD", "bf_pos", "GET", "u16", "#0", "GET", "u16", "#1")
395+
require.NoError(t, res.Err())
396+
require.EqualValues(t, []interface{}{int64(100), int64(200)}, res.Val())
397+
398+
// INCRBY with #N
399+
res = rdb.Do(ctx, "BITFIELD", "bf_pos", "INCRBY", "u16", "#0", 1)
400+
require.NoError(t, res.Err())
401+
require.EqualValues(t, []interface{}{int64(101)}, res.Val())
402+
403+
// OVERFLOW SAT with #N
404+
res = rdb.Do(ctx, "BITFIELD", "bf_pos", "OVERFLOW", "SAT", "INCRBY", "u16", "#1", 65535)
405+
require.NoError(t, res.Err())
406+
require.EqualValues(t, []interface{}{int64(65535)}, res.Val())
407+
408+
// BITFIELD_RO GET with #N
409+
for _, command := range []string{"BITFIELD", "BITFIELD_RO"} {
410+
res = rdb.Do(ctx, command, "bf_pos", "GET", "u16", "#0")
411+
require.NoError(t, res.Err())
412+
require.EqualValues(t, []interface{}{int64(101)}, res.Val())
413+
}
414+
})
415+
382416
t.Run("BITPOS BIT option check", func(t *testing.T) {
383417
require.NoError(t, rdb.Set(ctx, "mykey", "\x00\xff\xf0", 0).Err())
384418
cmd := rdb.BitPosSpan(ctx, "mykey", 1, 7, 15, "bit")

0 commit comments

Comments
 (0)