Skip to content

Commit

Permalink
Go client: Preparing the skeleton code to create the client connectio…
Browse files Browse the repository at this point in the history
…n and invoke set and get commands

Signed-off-by: Janhavi Gupta <[email protected]>
  • Loading branch information
janhavigupta007 committed Aug 6, 2024
1 parent 2fe8c50 commit e7a3714
Show file tree
Hide file tree
Showing 27 changed files with 451 additions and 528 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ on:
- .github/workflows/lint-rust/action.yml
- .github/workflows/install-valkey/action.yml
- .github/json_matrices/build-matrix.json
workflow_dispatch:

concurrency:
group: go-${{ github.head_ref || github.ref }}
cancel-in-progress: true
Expand Down
1 change: 1 addition & 0 deletions go/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ lib.h

# benchmarking results
benchmarks/results/**
benchmarks/gobenchmarks.json
1 change: 1 addition & 0 deletions go/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ redis = { path = "../submodules/redis-rs/redis", features = ["aio", "tokio-comp"
glide-core = { path = "../glide-core", features = ["socket-layer"] }
tokio = { version = "^1", features = ["rt", "macros", "rt-multi-thread", "time"] }
protobuf = { version = "3.3.0", features = [] }
derivative = "2.2.0"

[profile.release]
lto = true
Expand Down
18 changes: 9 additions & 9 deletions go/DEVELOPER.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Developer Guide

This document describes how to set up your development environment to build and test the GLIDE for Redis Go wrapper.
This document describes how to set up your development environment to build and test the Valkey GLIDE Go wrapper.

### Development Overview

We're excited to share that the GLIDE Go client is currently in development! However, it's important to note that this client is a work in progress and is not yet complete or fully tested. Your contributions and feedback are highly encouraged as we work towards refining and improving this implementation. Thank you for your interest and understanding as we continue to develop this Go wrapper.

The GLIDE for Redis Go wrapper consists of both Go and Rust code. The Go and Rust components communicate in two ways:
The Valkey GLIDE Go wrapper consists of both Go and Rust code. The Go and Rust components communicate in two ways:
1. Using the [protobuf](https://github.com/protocolbuffers/protobuf) protocol.
2. Using shared C objects. [cgo](https://pkg.go.dev/cmd/cgo) is used to interact with the C objects from Go code.

Expand All @@ -25,11 +25,10 @@ Software Dependencies
- openssl
- openssl-dev
- rustup
- redis

**Redis installation**
**Valkey installation**

To install redis-server and redis-cli on your host, follow the [Redis Installation Guide](https://redis.io/docs/install/install-redis/).
To install valkey-server and valkey-cli on your host, follow the [Valkey Installation Guide](https://github.com/valkey-io/valkey).

**Dependencies installation for Ubuntu**

Expand Down Expand Up @@ -102,7 +101,8 @@ Before starting this step, make sure you've installed all software requirements.

1. Clone the repository:
```bash
git clone https://github.com/valkey-io/valkey-glide.git
VERSION=0.1.0 # You can modify this to other released version or set it to "main" to get the unstable branch
git clone --branch ${VERSION} https://github.com/valkey-io/valkey-glide.git
cd valkey-glide
```
2. Initialize git submodule:
Expand All @@ -116,16 +116,16 @@ Before starting this step, make sure you've installed all software requirements.
```
4. If on CentOS or Ubuntu, add the glide-rs library to LD_LIBRARY_PATH:
```bash
# Replace "<path to glide-for-redis>" with the path to the glide-for-redis root, eg "$HOME/Projects/glide-for-redis"
GLIDE_ROOT_FOLDER_PATH=<path to glide-for-redis>
# Replace "<path to valkey-glide>" with the path to the valkey-glide root, eg "$HOME/Projects/valkey-glide"
GLIDE_ROOT_FOLDER_PATH=<path to valkey-glide>
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GLIDE_ROOT_FOLDER_PATH/go/target/release/deps/
```
5. Build the Go wrapper:
```bash
make build
```
6. Run tests:
1. Ensure that you have installed redis-server and redis-cli on your host. You can find the Redis installation guide at the following link: [Redis Installation Guide](https://redis.io/docs/install/install-redis/install-redis-on-linux/).
1. Ensure that you have installed valkey-server and valkey-cli on your host. You can find the Valkey installation guide at the following link: [Valkey Installation Guide](https://github.com/valkey-io/valkey).
2. Execute the following command from the go folder:
```bash
go test -race ./...
Expand Down
106 changes: 38 additions & 68 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
// Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

package api

// #cgo LDFLAGS: -L../target/release -lglide_rs
// #include "../lib.h"
//
// void successCallback(void *channelPtr, char *message);
// void successCallback(void *channelPtr, struct CommandResponse *message);
// void failureCallback(void *channelPtr, char *errMessage, RequestErrorType errType);
import "C"

import (
"unsafe"

"github.com/aws/glide-for-redis/go/glide/protobuf"
"github.com/valkey-io/valkey-glide/go/glide/protobuf"
"google.golang.org/protobuf/proto"
)

// BaseClient defines an interface for methods common to both [RedisClient] and [RedisClusterClient].
// BaseClient defines an interface for methods common to both [GlideClient] and [GlideClusterClient].
type BaseClient interface {
StringCommands

Expand All @@ -27,23 +27,21 @@ type BaseClient interface {
const OK = "OK"

type payload struct {
value string
value *C.struct_CommandResponse
error error
}

//export successCallback
func successCallback(channelPtr unsafe.Pointer, cResponse *C.char) {
// TODO: call lib.rs function to free response
response := C.GoString(cResponse)
func successCallback(channelPtr unsafe.Pointer, cResponse *C.struct_CommandResponse) {
response := cResponse
resultChannel := *(*chan payload)(channelPtr)
resultChannel <- payload{value: response, error: nil}
}

//export failureCallback
func failureCallback(channelPtr unsafe.Pointer, cErrorMessage *C.char, cErrorType C.RequestErrorType) {
// TODO: call lib.rs function to free response
resultChannel := *(*chan payload)(channelPtr)
resultChannel <- payload{value: "", error: goError(cErrorType, cErrorMessage)}
resultChannel <- payload{value: nil, error: goError(cErrorType, cErrorMessage)}
}

type clientConfiguration interface {
Expand All @@ -54,6 +52,10 @@ type baseClient struct {
coreClient unsafe.Pointer
}

// Creates a connection by invoking the `create_client` function from Rust library via FFI.
// Passes the pointers to callback functions which will be invoked when the command succeeds or fails.
// Once the connection is established, this function invokes `free_connection_response` exposed by rust library to free the
// connection_response to avoid any memory leaks.
func createClient(config clientConfiguration) (*baseClient, error) {
request := config.toProtobuf()
msg, err := proto.Marshal(request)
Expand Down Expand Up @@ -92,34 +94,41 @@ func (client *baseClient) Close() {
client.coreClient = nil
}

func (client *baseClient) executeCommand(requestType C.RequestType, args []string) (interface{}, error) {
func (client *baseClient) executeCommand(requestType C.RequestType, args []string) (*C.struct_CommandResponse, error) {
if client.coreClient == nil {
return nil, &ClosingError{"The client is closed."}
return nil, &ClosingError{"ExecuteCommand failed. The client is closed."}
}

cArgs := toCStrings(args)
cArgs, argLengths := toCStrings(args)
defer freeCStrings(cArgs)

resultChannel := make(chan payload)
resultChannelPtr := uintptr(unsafe.Pointer(&resultChannel))

C.command(client.coreClient, C.uintptr_t(resultChannelPtr), requestType, C.uintptr_t(len(args)), &cArgs[0])

C.command(
client.coreClient,
C.uintptr_t(resultChannelPtr),
uint32(requestType),
C.size_t(len(args)),
&cArgs[0],
&argLengths[0],
)
payload := <-resultChannel
if payload.error != nil {
return nil, payload.error
}

return payload.value, nil
}

func toCStrings(args []string) []*C.char {
cArgs := make([]*C.char, len(args))
for i, arg := range args {
cString := C.CString(arg)
cArgs[i] = cString
// TODO: Handle passing the arguments as strings without assuming null termination assumption.
func toCStrings(args []string) ([]*C.char, []C.ulong) {
cStrings := make([]*C.char, len(args))
stringLengths := make([]C.ulong, len(args))
for i, str := range args {
cStrings[i] = C.CString(str)
stringLengths[i] = C.size_t(len(str))
}
return cArgs
return cStrings, stringLengths
}

func freeCStrings(cArgs []*C.char) {
Expand All @@ -128,65 +137,26 @@ func freeCStrings(cArgs []*C.char) {
}
}

// Set the given key with the given value. The return value is a response from Redis containing the string "OK".
//
// See [redis.io] for details.
//
// For example:
//
// result := client.Set("key", "value")
//
// [redis.io]: https://redis.io/commands/set/
func (client *baseClient) Set(key string, value string) (string, error) {
result, err := client.executeCommand(C.SetString, []string{key, value})
result, err := client.executeCommand(C.Set, []string{key, value})
if err != nil {
return "", err
}

return handleStringResponse(result)
return handleStringResponse(result), nil
}

// SetWithOptions sets the given key with the given value using the given options. The return value is dependent on the passed
// options. If the value is successfully set, "OK" is returned. If value isn't set because of [OnlyIfExists] or
// [OnlyIfDoesNotExist] conditions, an zero-value string is returned (""). If [api.SetOptions.ReturnOldValue] is set, the old
// value is returned.
//
// See [redis.io] for details.
//
// For example:
//
// result, err := client.SetWithOptions("key", "value", &api.SetOptions{
// ConditionalSet: api.OnlyIfExists,
// Expiry: &api.Expiry{
// Type: api.Seconds,
// Count: uint64(5),
// },
// })
//
// [redis.io]: https://redis.io/commands/set/
func (client *baseClient) SetWithOptions(key string, value string, options *SetOptions) (string, error) {
result, err := client.executeCommand(C.SetString, append([]string{key, value}, options.toArgs()...))
result, err := client.executeCommand(C.Set, append([]string{key, value}, options.toArgs()...))
if err != nil {
return "", nil
return "", err
}

return handleStringResponse(result)
return handleStringOrNullResponse(result), nil
}

// Get a pointer to the value associated with the given key, or nil if no such value exists.
//
// See [redis.io] for details.
//
// For example:
//
// result := client.Set("key", "value")
//
// [redis.io]: https://redis.io/commands/set/
func (client *baseClient) Get(key string) (string, error) {
result, err := client.executeCommand(C.GetString, []string{key})
result, err := client.executeCommand(C.Get, []string{key})
if err != nil {
return "", err
}

return handleStringResponse(result)
return handleStringOrNullResponse(result), nil
}
18 changes: 0 additions & 18 deletions go/api/cluster_client.go

This file was deleted.

12 changes: 6 additions & 6 deletions go/api/command_options.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
// Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

package api

import "strconv"

// SetOptions represents optional arguments for the [api.StringCommands.SetWithOptions] command.
//
// See [redis.io]
// See [valkey.io]
//
// [redis.io]: https://redis.io/commands/set/
// [valkey.io]: https://valkey.io/commands/set/
type SetOptions struct {
// If ConditionalSet is not set the value will be set regardless of prior value existence. If value isn't set because of
// the condition, [api.StringCommands.SetWithOptions] will return a zero-value string ("").
ConditionalSet ConditionalSet
// Set command to return the old value stored at the given key, or a zero-value string ("") if the key did not exist. An
// error is returned and [api.StringCommands.SetWithOptions] is aborted if the value stored at key is not a string.
// Equivalent to GET in the Redis API.
// Equivalent to GET in the valkey API.
ReturnOldValue bool
// If not set, no expiry time will be set for the value.
Expiry *Expiry
Expand Down Expand Up @@ -47,9 +47,9 @@ const returnOldValue = "GET"
type ConditionalSet string

const (
// OnlyIfExists only sets the key if it already exists. Equivalent to "XX" in the Redis API.
// OnlyIfExists only sets the key if it already exists. Equivalent to "XX" in the valkey API.
OnlyIfExists ConditionalSet = "XX"
// OnlyIfDoesNotExist only sets the key if it does not already exist. Equivalent to "NX" in the Redis API.
// OnlyIfDoesNotExist only sets the key if it does not already exist. Equivalent to "NX" in the valkey API.
OnlyIfDoesNotExist ConditionalSet = "NX"
)

Expand Down
30 changes: 15 additions & 15 deletions go/api/commands.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
// Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

package api

// StringCommands defines an interface for the "String Commands" group of Redis commands for standalone and cluster clients.
// StringCommands defines an interface for the "String Commands" group of commands for standalone and cluster clients.
//
// See [redis.io] for details.
// See [valkey.io] for details.
//
// [redis.io]: https://redis.io/commands/?group=string
// [valkey.io]: https://valkey.io/commands/?group=string
type StringCommands interface {
// Set the given key with the given value. The return value is a response from Redis containing the string "OK".
// Set the given key with the given value. The return value is a response from Valkey containing the string "OK".
//
// See [redis.io] for details.
// See [valkey.io] for details.
//
// For example:
//
// result := client.Set("key", "value")
// result, err := client.Set("key", "value")
//
// [redis.io]: https://redis.io/commands/set/
// [valkey.io]: https://valkey.io/commands/set/
Set(key string, value string) (string, error)

// SetWithOptions sets the given key with the given value using the given options. The return value is dependent on the
// passed options. If the value is successfully set, "OK" is returned. If value isn't set because of [OnlyIfExists] or
// [OnlyIfDoesNotExist] conditions, a zero-value string is returned (""). If [SetOptions#ReturnOldValue] is set, the old
// [OnlyIfDoesNotExist] conditions, an empty string is returned (""). If [SetOptions#ReturnOldValue] is set, the old
// value is returned.
//
// See [redis.io] for details.
// See [valkey.io] for details.
//
// For example:
//
Expand All @@ -36,17 +36,17 @@ type StringCommands interface {
// },
// })
//
// [redis.io]: https://redis.io/commands/set/
// [valkey.io]: https://valkey.io/commands/set/
SetWithOptions(key string, value string, options *SetOptions) (string, error)

// Get a pointer to the value associated with the given key, or nil if no such value exists
// Get string value associated with the given key, or an empty string is returned ("") if no such value exists
//
// See [redis.io] for details.
// See [valkey.io] for details.
//
// For example:
//
// result := client.Set("key", "value")
// result, err := client.Get("key")
//
// [redis.io]: https://redis.io/commands/set/
// [valkey.io]: https://valkey.io/commands/get/
Get(key string) (string, error)
}
Loading

0 comments on commit e7a3714

Please sign in to comment.