Skip to content

Commit b095c94

Browse files
Merge pull request valkey-io#3384 from niharikabhavaraju/niharika-bitopcmd
Go: Implement Bitop command
2 parents ae7d4e2 + b9fcc86 commit b095c94

File tree

6 files changed

+295
-0
lines changed

6 files changed

+295
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* Go: Add `FLUSHALL` ([#3117](https://github.com/valkey-io/valkey-glide/pull/3117))
2323
* Go: Add `FLUSHDB` ([#3117](https://github.com/valkey-io/valkey-glide/pull/3117))
2424
* Go: Add password update api ([#3346](https://github.com/valkey-io/valkey-glide/pull/3346))
25+
* Go: Add `BITOP` ([#3384](https://github.com/valkey-io/valkey-glide/pull/3384))
2526
* Go: Add `GeoHash` ([#3439](https://github.com/valkey-io/valkey-glide/pull/3439))
2627
* Go/Core: Move FFI to a dedicated folder for reusability ([#3372](https://github.com/valkey-io/valkey-glide/pull/3372))
2728
* Go: Add `GeoPos` ([#3409](https://github.com/valkey-io/valkey-glide/pull/3409))

go/api/base_client.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5512,6 +5512,39 @@ func (client *baseClient) BitCount(key string) (int64, error) {
55125512
return handleIntResponse(result)
55135513
}
55145514

5515+
// Perform a bitwise operation between multiple keys (containing string values) and store the result in the destination.
5516+
//
5517+
// Note:
5518+
//
5519+
// When in cluster mode, `destination` and all `keys` must map to the same hash slot.
5520+
//
5521+
// Parameters:
5522+
//
5523+
// bitwiseOperation - The bitwise operation to perform.
5524+
// destination - The key that will store the resulting string.
5525+
// keys - The list of keys to perform the bitwise operation on.
5526+
//
5527+
// Return value:
5528+
//
5529+
// The size of the string stored in destination.
5530+
//
5531+
// [valkey.io]: https://valkey.io/commands/bitop/
5532+
func (client *baseClient) BitOp(bitwiseOperation options.BitOpType, destination string, keys []string) (int64, error) {
5533+
bitOp, err := options.NewBitOp(bitwiseOperation, destination, keys)
5534+
if err != nil {
5535+
return defaultIntResponse, err
5536+
}
5537+
args, err := bitOp.ToArgs()
5538+
if err != nil {
5539+
return defaultIntResponse, err
5540+
}
5541+
result, err := client.executeCommand(C.BitOp, args)
5542+
if err != nil {
5543+
return defaultIntResponse, &errors.RequestError{Msg: "Bitop command execution failed"}
5544+
}
5545+
return handleIntResponse(result)
5546+
}
5547+
55155548
// Counts the number of set bits (population counting) in a string stored at key. The
55165549
// offsets start and end are zero-based indexes, with `0` being the first element of the
55175550
// list, `1` being the next element and so on. These offsets can also be negative numbers

go/api/bitmap_commands.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ type BitmapCommands interface {
2121
BitField(key string, subCommands []options.BitFieldSubCommands) ([]Result[int64], error)
2222

2323
BitFieldRO(key string, commands []options.BitFieldROCommands) ([]Result[int64], error)
24+
25+
BitOp(bitwiseOperation options.BitOpType, destination string, keys []string) (int64, error)
2426
}

go/api/bitmap_commands_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,103 @@ func ExampleGlideClusterClient_BitFieldRO() {
200200

201201
// output: [{24 false}]
202202
}
203+
204+
func ExampleGlideClient_BitOp() {
205+
var client *GlideClient = getExampleGlideClient()
206+
207+
bitopkey1 := "{bitop_test}key1"
208+
bitopkey2 := "{bitop_test}key2"
209+
destKey := "{bitop_test}dest"
210+
211+
// Set initial values
212+
client.Set(bitopkey1, "foobar")
213+
client.Set(bitopkey2, "abcdef")
214+
215+
// Perform BITOP AND
216+
result, err := client.BitOp(options.AND, destKey, []string{bitopkey1, bitopkey2})
217+
if err != nil {
218+
fmt.Println("BitOp AND failed:", err)
219+
} else {
220+
fmt.Println("BitOp AND Result:", result)
221+
}
222+
223+
// Perform BITOP OR
224+
result, err = client.BitOp(options.OR, destKey, []string{bitopkey1, bitopkey2})
225+
if err != nil {
226+
fmt.Println("BitOp OR failed:", err)
227+
} else {
228+
fmt.Println("BitOp OR Result:", result)
229+
}
230+
231+
// Perform BITOP XOR
232+
result, err = client.BitOp(options.XOR, destKey, []string{bitopkey1, bitopkey2})
233+
if err != nil {
234+
fmt.Println("BitOp XOR failed:", err)
235+
} else {
236+
fmt.Println("BitOp XOR Result:", result)
237+
}
238+
239+
// Perform BITOP NOT (only one source key allowed)
240+
result, err = client.BitOp(options.NOT, destKey, []string{bitopkey1})
241+
if err != nil {
242+
fmt.Println("BitOp NOT failed:", err)
243+
} else {
244+
fmt.Println("BitOp NOT Result:", result)
245+
}
246+
247+
// Output:
248+
// BitOp AND Result: 6
249+
// BitOp OR Result: 6
250+
// BitOp XOR Result: 6
251+
// BitOp NOT Result: 6
252+
}
253+
254+
func ExampleGlideClusterClient_BitOp() {
255+
var client *GlideClusterClient = getExampleGlideClusterClient() // example helper function
256+
257+
bitopkey1 := "{bitop_test}key1"
258+
bitopkey2 := "{bitop_test}key2"
259+
destKey := "{bitop_test}dest"
260+
261+
// Set initial values
262+
client.Set(bitopkey1, "foobar")
263+
client.Set(bitopkey2, "abcdef")
264+
265+
// Perform BITOP AND
266+
result, err := client.BitOp(options.AND, destKey, []string{bitopkey1, bitopkey2})
267+
if err != nil {
268+
fmt.Println("BitOp AND failed:", err)
269+
} else {
270+
fmt.Println("BitOp AND Result:", result)
271+
}
272+
273+
// Perform BITOP OR
274+
result, err = client.BitOp(options.OR, destKey, []string{bitopkey1, bitopkey2})
275+
if err != nil {
276+
fmt.Println("BitOp OR failed:", err)
277+
} else {
278+
fmt.Println("BitOp OR Result:", result)
279+
}
280+
281+
// Perform BITOP XOR
282+
result, err = client.BitOp(options.XOR, destKey, []string{bitopkey1, bitopkey2})
283+
if err != nil {
284+
fmt.Println("BitOp XOR failed:", err)
285+
} else {
286+
fmt.Println("BitOp XOR Result:", result)
287+
}
288+
289+
// Perform BITOP NOT (only one source key allowed)
290+
result, err = client.BitOp(options.NOT, destKey, []string{bitopkey1})
291+
if err != nil {
292+
fmt.Println("BitOp NOT failed:", err)
293+
} else {
294+
fmt.Println("BitOp NOT Result:", result)
295+
}
296+
297+
// Output:
298+
// BitOp AND Result: 6
299+
// BitOp OR Result: 6
300+
// BitOp XOR Result: 6
301+
// BitOp NOT Result: 6
302+
}

go/api/options/bitop_options.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
2+
3+
package options
4+
5+
import (
6+
"github.com/valkey-io/valkey-glide/go/api/errors"
7+
)
8+
9+
type BitOpType string
10+
11+
const (
12+
AND BitOpType = "AND"
13+
OR BitOpType = "OR"
14+
XOR BitOpType = "XOR"
15+
NOT BitOpType = "NOT"
16+
)
17+
18+
// BitOp represents a BITOP operation.
19+
type BitOp struct {
20+
Operation BitOpType
21+
DestKey string
22+
SrcKeys []string
23+
}
24+
25+
// NewBitOp validates and creates a new BitOp command.
26+
func NewBitOp(operation BitOpType, destKey string, srcKeys []string) (*BitOp, error) {
27+
if operation == NOT {
28+
if len(srcKeys) != 1 {
29+
return nil, &errors.RequestError{Msg: "BITOP NOT requires exactly 1 source key"}
30+
}
31+
} else {
32+
if len(srcKeys) < 2 {
33+
return nil, &errors.RequestError{Msg: "BITOP requires at least 2 source keys"}
34+
}
35+
}
36+
37+
return &BitOp{
38+
Operation: operation,
39+
DestKey: destKey,
40+
SrcKeys: srcKeys,
41+
}, nil
42+
}
43+
44+
// ToArgs converts the BitOp command to arguments.
45+
func (cmd *BitOp) ToArgs() ([]string, error) {
46+
args := []string{string(cmd.Operation), cmd.DestKey}
47+
args = append(args, cmd.SrcKeys...)
48+
return args, nil
49+
}

go/integTest/shared_commands_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7577,6 +7577,116 @@ func (suite *GlideTestSuite) TestBitCountWithOptions_StartEndBit() {
75777577
})
75787578
}
75797579

7580+
func (suite *GlideTestSuite) TestBitOp_AND() {
7581+
suite.runWithDefaultClients(func(client api.BaseClient) {
7582+
bitopkey1 := "{bitop_test}" + uuid.New().String()
7583+
bitopkey2 := "{bitop_test}" + uuid.New().String()
7584+
destKey := "{bitop_test}" + uuid.New().String()
7585+
7586+
_, err := client.Set(bitopkey1, "foobar")
7587+
assert.NoError(suite.T(), err)
7588+
7589+
_, err = client.Set(bitopkey2, "abcdef")
7590+
assert.NoError(suite.T(), err)
7591+
7592+
result, err := client.BitOp(options.AND, destKey, []string{bitopkey1, bitopkey2})
7593+
assert.NoError(suite.T(), err)
7594+
assert.GreaterOrEqual(suite.T(), result, int64(0))
7595+
7596+
bitResult, err := client.Get(destKey)
7597+
assert.NoError(suite.T(), err)
7598+
assert.NotEmpty(suite.T(), bitResult.Value())
7599+
})
7600+
}
7601+
7602+
func (suite *GlideTestSuite) TestBitOp_OR() {
7603+
suite.runWithDefaultClients(func(client api.BaseClient) {
7604+
key1 := "{bitop_test}" + uuid.New().String()
7605+
key2 := "{bitop_test}" + uuid.New().String()
7606+
destKey := "{bitop_test}" + uuid.New().String()
7607+
7608+
_, err := client.Set(key1, "foo")
7609+
assert.NoError(suite.T(), err)
7610+
7611+
_, err = client.Set(key2, "bar")
7612+
assert.NoError(suite.T(), err)
7613+
7614+
result, err := client.BitOp(options.OR, destKey, []string{key1, key2})
7615+
assert.NoError(suite.T(), err)
7616+
assert.GreaterOrEqual(suite.T(), result, int64(0))
7617+
7618+
bitResult, err := client.Get(destKey)
7619+
assert.NoError(suite.T(), err)
7620+
assert.NotEmpty(suite.T(), bitResult.Value())
7621+
})
7622+
}
7623+
7624+
func (suite *GlideTestSuite) TestBitOp_XOR() {
7625+
suite.runWithDefaultClients(func(client api.BaseClient) {
7626+
key1 := "{bitop_test}" + uuid.New().String()
7627+
key2 := "{bitop_test}" + uuid.New().String()
7628+
destKey := "{bitop_test}" + uuid.New().String()
7629+
7630+
_, err := client.Set(key1, "foo")
7631+
assert.NoError(suite.T(), err)
7632+
7633+
_, err = client.Set(key2, "bar")
7634+
assert.NoError(suite.T(), err)
7635+
7636+
result, err := client.BitOp(options.XOR, destKey, []string{key1, key2})
7637+
assert.NoError(suite.T(), err)
7638+
assert.GreaterOrEqual(suite.T(), result, int64(0))
7639+
7640+
bitResult, err := client.Get(destKey)
7641+
assert.NoError(suite.T(), err)
7642+
assert.NotEmpty(suite.T(), bitResult.Value())
7643+
})
7644+
}
7645+
7646+
func (suite *GlideTestSuite) TestBitOp_NOT() {
7647+
suite.runWithDefaultClients(func(client api.BaseClient) {
7648+
srcKey := "{bitop_test}" + uuid.New().String()
7649+
destKey := "{bitop_test}" + uuid.New().String()
7650+
7651+
_, err := client.Set(srcKey, "foobar")
7652+
assert.NoError(suite.T(), err)
7653+
7654+
result, err := client.BitOp(options.NOT, destKey, []string{srcKey})
7655+
assert.NoError(suite.T(), err)
7656+
assert.GreaterOrEqual(suite.T(), result, int64(0))
7657+
7658+
bitResult, err := client.Get(destKey)
7659+
assert.NoError(suite.T(), err)
7660+
assert.NotEmpty(suite.T(), bitResult.Value())
7661+
})
7662+
}
7663+
7664+
func (suite *GlideTestSuite) TestBitOp_InvalidArguments() {
7665+
suite.runWithDefaultClients(func(client api.BaseClient) {
7666+
destKey := "{bitop_test}" + uuid.New().String()
7667+
key1 := "{bitop_test}" + uuid.New().String()
7668+
key2 := "{bitop_test}" + uuid.New().String()
7669+
7670+
_, err := client.Set(key1, "foo")
7671+
assert.NoError(suite.T(), err)
7672+
7673+
_, err = client.Set(key2, "bar")
7674+
assert.NoError(suite.T(), err)
7675+
7676+
_, err = client.BitOp(options.AND, destKey, []string{key1})
7677+
assert.NotNil(suite.T(), err)
7678+
7679+
_, err = client.BitOp(options.OR, destKey, []string{key1})
7680+
assert.NotNil(suite.T(), err)
7681+
7682+
_, err = client.BitOp(options.XOR, destKey, []string{key1})
7683+
assert.NotNil(suite.T(), err)
7684+
7685+
_, err = client.BitOp(options.NOT, destKey, []string{key1, key2})
7686+
assert.NotNil(suite.T(), err)
7687+
})
7688+
}
7689+
75807690
func (suite *GlideTestSuite) TestXPendingAndXClaim() {
75817691
suite.runWithDefaultClients(func(client api.BaseClient) {
75827692
// 1. Arrange the data

0 commit comments

Comments
 (0)