Skip to content

Commit 0a8a756

Browse files
Go: Implement Bitfield and Bitfield ReadOnly commands (valkey-io#2999)
* Added Bitfield and Bitfield ReadOnly commands Signed-off-by: Niharika Bhavaraju <[email protected]>
1 parent e99cd3e commit 0a8a756

File tree

6 files changed

+470
-0
lines changed

6 files changed

+470
-0
lines changed

go/api/base_client.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6819,3 +6819,108 @@ func (client *baseClient) XRevRangeWithOptions(
68196819
}
68206820
return handleMapOfArrayOfStringArrayOrNilResponse(result)
68216821
}
6822+
6823+
// Reads or modifies the array of bits representing the string that is held at key
6824+
// based on the specified sub commands.
6825+
//
6826+
// See [valkey.io] for details.
6827+
//
6828+
// Parameters:
6829+
//
6830+
// key - The key of the string.
6831+
// subCommands - The subCommands to be performed on the binary value of the string at
6832+
// key, which could be any of the following:
6833+
// - [BitFieldGet].
6834+
// - [BitFieldSet].
6835+
// - [BitFieldIncrby].
6836+
// - [BitFieldOverflow].
6837+
// Use `options.NewBitFieldGet()` to specify a BitField GET command.
6838+
// Use `options.NewBitFieldSet()` to specify a BitField SET command.
6839+
// Use `options.NewBitFieldIncrby()` to specify a BitField INCRYBY command.
6840+
// Use `options.BitFieldOverflow()` to specify a BitField OVERFLOW command.
6841+
//
6842+
// Return value:
6843+
//
6844+
// Result from the executed subcommands.
6845+
// - BitFieldGet returns the value in the binary representation of the string.
6846+
// - BitFieldSet returns the previous value before setting the new value in the binary representation.
6847+
// - BitFieldIncrBy returns the updated value after increasing or decreasing the bits.
6848+
// - BitFieldOverflow controls the behavior of subsequent operations and returns
6849+
// a result based on the specified overflow type (WRAP, SAT, FAIL).
6850+
//
6851+
// Example:
6852+
//
6853+
// commands := []options.BitFieldSubCommands{
6854+
// options.BitFieldGet(options.SignedInt, 8, 16),
6855+
// options.BitFieldOverflow(options.SAT),
6856+
// options.NewBitFieldSet(options.UnsignedInt, 4, 0, 7),
6857+
// options.BitFieldIncrBy(options.SignedInt, 5, 100, 1),
6858+
// }
6859+
// result, err := client.BitField("mykey", commands)
6860+
// result: [{0 false} {7 false} {15 false}]
6861+
//
6862+
// [valkey.io]: https://valkey.io/commands/bitfield/
6863+
func (client *baseClient) BitField(key string, subCommands []options.BitFieldSubCommands) ([]Result[int64], error) {
6864+
args := make([]string, 0, 10)
6865+
args = append(args, key)
6866+
6867+
for _, cmd := range subCommands {
6868+
cmdArgs, err := cmd.ToArgs()
6869+
if err != nil {
6870+
return nil, err
6871+
}
6872+
args = append(args, cmdArgs...)
6873+
}
6874+
6875+
result, err := client.executeCommand(C.BitField, args)
6876+
if err != nil {
6877+
return nil, err
6878+
}
6879+
return handleIntOrNilArrayResponse(result)
6880+
}
6881+
6882+
// Reads the array of bits representing the string that is held at key
6883+
// based on the specified sub commands.
6884+
//
6885+
// See [valkey.io] for details.
6886+
//
6887+
// Parameters:
6888+
//
6889+
// key - The key of the string.
6890+
// subCommands - The read-only subCommands to be performed on the binary value
6891+
// of the string at key, which could be:
6892+
// - [BitFieldGet].
6893+
// Use `options.NewBitFieldGet()` to specify a BitField GET command.
6894+
//
6895+
// Return value:
6896+
//
6897+
// Result from the executed GET subcommands.
6898+
// - BitFieldGet returns the value in the binary representation of the string.
6899+
//
6900+
// Example:
6901+
//
6902+
// commands := []options.BitFieldROCommands{
6903+
// options.BitFieldGet(options.SignedInt, 8, 16),
6904+
// }
6905+
// result, err := client.BitFieldRO("mykey", commands)
6906+
// result: [{42 false}]
6907+
//
6908+
// [valkey.io]: https://valkey.io/commands/bitfield_ro/
6909+
func (client *baseClient) BitFieldRO(key string, commands []options.BitFieldROCommands) ([]Result[int64], error) {
6910+
args := make([]string, 0, 10)
6911+
args = append(args, key)
6912+
6913+
for _, cmd := range commands {
6914+
cmdArgs, err := cmd.ToArgs()
6915+
if err != nil {
6916+
return nil, err
6917+
}
6918+
args = append(args, cmdArgs...)
6919+
}
6920+
6921+
result, err := client.executeCommand(C.BitFieldReadOnly, args)
6922+
if err != nil {
6923+
return nil, err
6924+
}
6925+
return handleIntOrNilArrayResponse(result)
6926+
}

go/api/bitmap_commands.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ type BitmapCommands interface {
1717
BitCount(key string) (int64, error)
1818

1919
BitCountWithOptions(key string, options *options.BitCountOptions) (int64, error)
20+
21+
BitField(key string, subCommands []options.BitFieldSubCommands) ([]Result[int64], error)
22+
23+
BitFieldRO(key string, commands []options.BitFieldROCommands) ([]Result[int64], error)
2024
}

go/api/glide_client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type GlideClientCommands interface {
1818
BaseClient
1919
GenericCommands
2020
ServerManagementCommands
21+
BitmapCommands
2122
}
2223

2324
// GlideClient implements standalone mode operations by extending baseClient functionality.

go/api/options/bitfield_options.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package options
2+
3+
import (
4+
"github.com/valkey-io/valkey-glide/go/glide/utils"
5+
)
6+
7+
// Subcommands for bitfield operations.
8+
type BitFieldSubCommands interface {
9+
ToArgs() ([]string, error)
10+
}
11+
12+
// Subcommands for bitfieldReadOnly.
13+
type BitFieldROCommands interface {
14+
dummy()
15+
ToArgs() ([]string, error)
16+
}
17+
18+
type EncType string
19+
20+
const (
21+
SignedInt EncType = "i"
22+
UnsignedInt EncType = "u"
23+
)
24+
25+
type OverflowType string
26+
27+
const (
28+
WRAP OverflowType = "WRAP"
29+
SAT OverflowType = "SAT"
30+
FAIL OverflowType = "FAIL"
31+
)
32+
33+
// BitFieldGet represents a GET operation to get the value in the binary
34+
// representation of the string stored in key based on EncType and Offset.
35+
type BitFieldGet struct {
36+
EncType EncType
37+
Bits int64
38+
Offset int64
39+
UseHash bool
40+
}
41+
42+
// NewBitFieldGet creates a new BitField GET command
43+
func NewBitFieldGet(encType EncType, bits int64, offset int64) *BitFieldGet {
44+
return &BitFieldGet{
45+
EncType: encType,
46+
Bits: bits,
47+
Offset: offset,
48+
}
49+
}
50+
51+
// ToArgs converts the GET command to arguments
52+
func (cmd *BitFieldGet) ToArgs() ([]string, error) {
53+
args := []string{"GET"}
54+
args = append(args, string(cmd.EncType)+utils.IntToString(cmd.Bits))
55+
if cmd.UseHash {
56+
args = append(args, "#"+utils.IntToString(cmd.Offset))
57+
} else {
58+
args = append(args, utils.IntToString(cmd.Offset))
59+
}
60+
return args, nil
61+
}
62+
63+
func (cmd *BitFieldGet) dummy() {}
64+
65+
// BitFieldSet represents a SET operation to set the bits in the binary
66+
// representation of the string stored in key based on EncType and Offset.
67+
type BitFieldSet struct {
68+
EncType EncType
69+
Bits int64
70+
Offset int64
71+
Value int64
72+
UseHash bool
73+
}
74+
75+
// NewBitFieldSet creates a new BitField SET command
76+
func NewBitFieldSet(encType EncType, bits int64, offset int64, value int64) *BitFieldSet {
77+
return &BitFieldSet{
78+
EncType: encType,
79+
Bits: bits,
80+
Offset: offset,
81+
Value: value,
82+
}
83+
}
84+
85+
// ToArgs converts the SET command to arguments
86+
func (cmd *BitFieldSet) ToArgs() ([]string, error) {
87+
args := []string{"SET"}
88+
args = append(args, string(cmd.EncType)+utils.IntToString(cmd.Bits))
89+
if cmd.UseHash {
90+
args = append(args, "#"+utils.IntToString(cmd.Offset))
91+
} else {
92+
args = append(args, utils.IntToString(cmd.Offset))
93+
}
94+
args = append(args, utils.IntToString(cmd.Value))
95+
return args, nil
96+
}
97+
98+
// BitFieldIncrBy represents a INCRBY subcommand for increasing or decreasing the bits in the binary
99+
// representation of the string stored in key based on EncType and Offset.
100+
type BitFieldIncrBy struct {
101+
EncType EncType
102+
Bits int64
103+
Offset int64
104+
Increment int64
105+
UseHash bool
106+
}
107+
108+
// NewBitFieldIncrBy creates a new BitField INCRBY command
109+
func NewBitFieldIncrBy(encType EncType, bits int64, offset int64, increment int64) *BitFieldIncrBy {
110+
return &BitFieldIncrBy{
111+
EncType: encType,
112+
Bits: bits,
113+
Offset: offset,
114+
Increment: increment,
115+
}
116+
}
117+
118+
// ToArgs converts the INCRBY command to arguments
119+
func (cmd *BitFieldIncrBy) ToArgs() ([]string, error) {
120+
args := []string{"INCRBY"}
121+
args = append(args, string(cmd.EncType)+utils.IntToString(cmd.Bits))
122+
if cmd.UseHash {
123+
args = append(args, "#"+utils.IntToString(cmd.Offset))
124+
} else {
125+
args = append(args, utils.IntToString(cmd.Offset))
126+
}
127+
args = append(args, utils.IntToString(cmd.Increment))
128+
return args, nil
129+
}
130+
131+
// BitFieldOverflow represents a OVERFLOW subcommand that determines the result of the SET
132+
// or INCRBY commands when an under or overflow occurs.
133+
type BitFieldOverflow struct {
134+
Overflow OverflowType
135+
}
136+
137+
// NewBitFieldOverflow creates a new BitField OVERFLOW command
138+
func NewBitFieldOverflow(overflow OverflowType) *BitFieldOverflow {
139+
return &BitFieldOverflow{
140+
Overflow: overflow,
141+
}
142+
}
143+
144+
// ToArgs converts the OVERFLOW command to arguments
145+
func (cmd *BitFieldOverflow) ToArgs() ([]string, error) {
146+
return []string{"OVERFLOW", string(cmd.Overflow)}, nil
147+
}

go/api/response_handlers.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,32 @@ func handleIntArrayResponse(response *C.struct_CommandResponse) ([]int64, error)
323323
return slice, nil
324324
}
325325

326+
func handleIntOrNilArrayResponse(response *C.struct_CommandResponse) ([]Result[int64], error) {
327+
defer C.free_command_response(response)
328+
329+
typeErr := checkResponseType(response, C.Array, false)
330+
if typeErr != nil {
331+
return nil, typeErr
332+
}
333+
334+
slice := make([]Result[int64], 0, response.array_value_len)
335+
for _, v := range unsafe.Slice(response.array_value, response.array_value_len) {
336+
if v.response_type == C.Null {
337+
slice = append(slice, CreateNilInt64Result())
338+
continue
339+
}
340+
341+
err := checkResponseType(&v, C.Int, false)
342+
if err != nil {
343+
return nil, err
344+
}
345+
346+
slice = append(slice, CreateInt64Result(int64(v.int_value)))
347+
}
348+
349+
return slice, nil
350+
}
351+
326352
func handleFloatResponse(response *C.struct_CommandResponse) (float64, error) {
327353
defer C.free_command_response(response)
328354

0 commit comments

Comments
 (0)