Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go: Implement GetBit, SetBit, BitCount and Wait commands #2918

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type BaseClient interface {
ConnectionManagementCommands
HyperLogLogCommands
GenericBaseCommands
BitmapCommands
// Close terminates the client by closing all associated resources.
Close()
}
Expand Down Expand Up @@ -1441,3 +1442,54 @@ func (client *baseClient) ZCard(key string) (Result[int64], error) {

return handleLongResponse(result)
}

func (client *baseClient) SetBit(key string, offset int64, value int64) (Result[int64], error) {
result, err := client.executeCommand(C.SetBit, []string{key, utils.IntToString(offset), utils.IntToString(value)})
if err != nil {
return CreateNilInt64Result(), err
}
return handleLongResponse(result)
}

func (client *baseClient) GetBit(key string, offset int64) (Result[int64], error) {
result, err := client.executeCommand(C.GetBit, []string{key, utils.IntToString(offset)})
if err != nil {
return CreateNilInt64Result(), err
}
return handleLongResponse(result)
}

func (client *baseClient) Wait(numberOfReplicas int64, timeout int64) (Result[int64], error) {
if numberOfReplicas <= 0 {
return CreateNilInt64Result(), fmt.Errorf("Number of Replicas should be greater than 0")
}
if timeout < 0 {
return CreateNilInt64Result(), fmt.Errorf("Timeout cannot be lesser than 0")
}
result, err := client.executeCommand(C.Wait, []string{utils.IntToString(numberOfReplicas), utils.IntToString(timeout)})
if err != nil {
return CreateNilInt64Result(), err
}
return handleLongResponse(result)
}

func (client *baseClient) BitCount(key string) (Result[int64], error) {
result, err := client.executeCommand(C.BitCount, []string{key})
if err != nil {
return CreateNilInt64Result(), err
}
return handleLongResponse(result)
}

func (client *baseClient) BitCountWithOptions(key string, opts *options.BitCountOptions) (Result[int64], error) {
optionArgs, err := opts.ToArgs()
if err != nil {
return CreateNilInt64Result(), err
}
commandArgs := append([]string{key}, optionArgs...)
result, err := client.executeCommand(C.BitCount, commandArgs)
if err != nil {
return CreateNilInt64Result(), err
}
return handleLongResponse(result)
}
99 changes: 99 additions & 0 deletions go/api/bitmap_commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

package api

import "github.com/valkey-io/valkey-glide/go/glide/api/options"

// Supports commands and transactions for the "Bitmap" group of commands for standalone and cluster clients.
//
// See [valkey.io] for details.
//
// [valkey.io]: https://valkey.io/commands/#bitmap
type BitmapCommands interface {
// Sets or clears the bit at offset in the string value stored at key.
// The offset is a zero-based index, with 0 being the first element of
// the list, 1 being the next element, and so on. The offset must be
// less than 2^32 and greater than or equal to 0 If a key is
Comment on lines +14 to +16
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// The offset is a zero-based index, with 0 being the first element of
// the list, 1 being the next element, and so on. The offset must be
// less than 2^32 and greater than or equal to 0 If a key is
// The offset is a zero-based index, with `0` being the first element of
// the list, `1` being the next element, and so on. The offset must be
// less than `2^32` and greater than or equal to `0` If a key is

Consider copying docs from node client

// non-existent then the bit at offset is set to value and the preceding
// bits are set to 0.
//
// Parameters:
// key - The key of the string.
// offset - The index of the bit to be set.
// value - The bit value to set at offset The value must be 0 or 1.
//
// Return value:
// The bit value that was previously stored at offset.
//
// Example:
// result, err := client.SetBit("key",1 , 1)
// result.Value(): 1
// result.IsNil(): false
//
// [valkey.io]: https://valkey.io/commands/setbit/
SetBit(key string, offset int64, value int64) (Result[int64], error)

// Returns the bit value at offset in the string value stored at key.
// offset should be greater than or equal to zero.
//
// Parameters:
// key - The key of the string.
// offset - The index of the bit to return.
//
// Return value:
// The bit at offset of the string. Returns zero if the key is empty or if the positive
// offset exceeds the length of the string.
//
// Example:
// result, err := client.GetBit("key1",1,1)
// result.Value(): 1
// result.IsNil(): false
//
// [valkey.io]: https://valkey.io/commands/getbit/
GetBit(key string, offset int64) (Result[int64], error)

// Counts the number of set bits (population counting) in a string stored at key.
//
// Parameters:
// key - The key for the string to count the set bits of.
//
// Return value:
// The number of set bits in the string. Returns zero if the key is missing as it is
// treated as an empty string.
//
// Example:
// result, err := client.BitCount("mykey")
// result.Value(): 26
// result.IsNil(): false
//
// [valkey.io]: https://valkey.io/commands/bitcount/
BitCount(key string) (Result[int64], error)

// Counts the number of set bits (population counting) in a string stored at key. The
// offsets start and end are zero-based indexes, with 0 being the first element of the
// list, 1 being the next element and so on. These offsets can also be negative numbers
// indicating offsets starting at the end of the list, with -1 being the last element
// of the list, -2 being the penultimate, and so on.
//
// Parameters:
// key - The key for the string to count the set bits of.
// options - Start is the starting offset and end is the ending offset. BitmapIndexType
// is The index offset type. Could be either {@link BitmapIndexType#BIT} or
// {@link BitmapIndexType#BYTE}.
Comment on lines +81 to +82
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix links (those are javadoc style)

//
// Return value:
// The number of set bits in the string interval specified by start, end, and options.
// Returns zero if the key is missing as it is treated as an empty string.
//
// Example:
// opts := &options.BitCountOptions{}
// opts.SetStart(1)
// opts, err := opts.SetEnd(1)
// opts, err = opts.SetBitmapIndexType(options.BYTE)
// result, err := client.BitCount("mykey",options)
// result.Value(): 6
// result.IsNil(): false
//
// [valkey.io]: https://valkey.io/commands/bitcount/
BitCountWithOptions(key string, options *options.BitCountOptions) (Result[int64], error)
}
22 changes: 22 additions & 0 deletions go/api/generic_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,4 +428,26 @@ type GenericBaseCommands interface {
//
// [valkey.io]: https://valkey.io/commands/renamenx/
Renamenx(key string, newKey string) (Result[bool], error)

// Wait blocks the current client until all the previous write commands are successfully
// transferred and acknowledged by at least the specified number of replicas or if the timeout is reached,
// whichever is earlier
//
// Parameters:
// numberOfReplicas - The number of replicas to reach.
// timeout - The timeout value specified in milliseconds. A value of 0 will
// block indefinitely.
//
// Return value:
// The number of replicas reached by all the writes performed in the context of the current connection.
//
// Example:
// result, err := client.Wait(1, 1000)
// if err != nil {
// // handle error
// }
// fmt.Println(result.Value()) // Output: 1 // if cluster has 2 replicasets
//
// [valkey.io]: https://valkey.io/commands/wait/
Wait(numberOfReplicas int64, timeout int64) (Result[int64], error)
}
70 changes: 70 additions & 0 deletions go/api/options/bitcount_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

package options

import (
"fmt"

"github.com/valkey-io/valkey-glide/go/glide/utils"
)

type BitmapIndexType string

const (
BYTE BitmapIndexType = "BYTE"
BIT BitmapIndexType = "BIT"
)

// Optional arguments to `BitCount` in [BitMapCommands]
type BitCountOptions struct {
Start *int64
End *int64
BitMapIndexType BitmapIndexType
}

// SetStart defines start byte to calculate bitcount in bitcount command.
func (options *BitCountOptions) SetStart(start int64) *BitCountOptions {
options.Start = &start
return options
}

// SetEnd defines start byte to calculate bitcount in bitcount command.
func (options *BitCountOptions) SetEnd(end int64) (*BitCountOptions, error) {
if options.Start == nil {
return options, fmt.Errorf("End value cannot be set without start value, use SetStart()")
}
options.End = &end
return options, nil
}

// SetBitmapIndexType to specify start and end are in BYTE or BIT
func (options *BitCountOptions) SetBitmapIndexType(bitMapIndexType BitmapIndexType) (*BitCountOptions, error) {
if options.Start == nil || options.End == nil {
return options, fmt.Errorf(
"SetBitmapIndexType value cannot be set without start and end values, use SetStart() and SetEnd()",
Comment on lines +42 to +44
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an option you can create factory methods for BitCountOptions: BitCountOptionsWithStart, BitCountOptionsWithStartAndEnd, BitCountOptionsWithStartEndAndType or so. This will make API more aligned with others.
You can use builder pattern as an alternative.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I don't insist on changes.

)
}
if bitMapIndexType != BIT && bitMapIndexType != BYTE {
return options, fmt.Errorf("Invalid argument: BIT and BYTE are the only allowed values")
}
options.BitMapIndexType = bitMapIndexType
return options, nil
}

// ToArgs converts the options to a list of arguments.
func (opts *BitCountOptions) ToArgs() ([]string, error) {
args := []string{}
var err error

if opts.Start != nil {
args = append(args, utils.IntToString(*opts.Start))
if opts.End != nil {
args = append(args, utils.IntToString(*opts.End))
if opts.BitMapIndexType != "" {
args = append(args, string(opts.BitMapIndexType))
}
}
}

return args, err
}
Loading
Loading