-
Notifications
You must be signed in to change notification settings - Fork 0
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
[internal review] Go: implement standalone custom command #152
Changes from all commits
ebebd1f
ea052c1
f622145
8fb90d8
da56350
0853aee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,8 +5,8 @@ package api | |
// #cgo LDFLAGS: -L../target/release -lglide_rs | ||
// #include "../lib.h" | ||
// | ||
// void successCallback(uintptr_t channelPtr, char *message); | ||
// void failureCallback(uintptr_t channelPtr, char *errMessage, RequestErrorType errType); | ||
// void successCallback(void *channelPtr, char *message); | ||
// void failureCallback(void *channelPtr, char *errMessage, RequestErrorType errType); | ||
import "C" | ||
|
||
import ( | ||
|
@@ -16,14 +16,30 @@ import ( | |
"google.golang.org/protobuf/proto" | ||
) | ||
|
||
// BaseClient defines an interface for methods common to both [RedisClient] and [RedisClusterClient]. | ||
type BaseClient interface { | ||
// Close terminates the client by closing all associated resources. | ||
Close() | ||
} | ||
|
||
type payload struct { | ||
value string | ||
error error | ||
} | ||
|
||
//export successCallback | ||
func successCallback(channelPtr C.uintptr_t, cResponse *C.char) { | ||
// TODO: Implement when we implement the command logic | ||
func successCallback(channelPtr unsafe.Pointer, cResponse *C.char) { | ||
// TODO: call lib.rs function to free response | ||
response := C.GoString(cResponse) | ||
resultChannel := *(*chan payload)(channelPtr) | ||
resultChannel <- payload{value: response, error: nil} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it sync or async operation? |
||
} | ||
|
||
//export failureCallback | ||
func failureCallback(channelPtr C.uintptr_t, cErrorMessage *C.char, cErrorType C.RequestErrorType) { | ||
// TODO: Implement when we implement the command logic | ||
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)} | ||
} | ||
|
||
type clientConfiguration interface { | ||
|
@@ -71,3 +87,39 @@ func (client *baseClient) Close() { | |
C.close_client(client.coreClient) | ||
client.coreClient = nil | ||
} | ||
|
||
func (client *baseClient) executeCommand(requestType C.RequestType, args []string) (interface{}, error) { | ||
if client.coreClient == nil { | ||
return nil, &ClosingError{"The client is closed."} | ||
} | ||
|
||
cArgs := 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]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You changed callback singature to take channel pointer as |
||
|
||
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 | ||
} | ||
return cArgs | ||
} | ||
|
||
func freeCStrings(cArgs []*C.char) { | ||
for _, arg := range cArgs { | ||
C.free(unsafe.Pointer(arg)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,8 @@ | |
|
||
package api | ||
|
||
// #cgo LDFLAGS: -L../target/release -lglide_rs | ||
// #include "../lib.h" | ||
import "C" | ||
|
||
// RedisClient is a client used for connection to standalone Redis servers. | ||
|
@@ -20,3 +22,18 @@ func NewRedisClient(config *RedisClientConfiguration) (*RedisClient, error) { | |
|
||
return &RedisClient{client}, nil | ||
} | ||
|
||
// CustomCommand executes a single command, specified by args, without checking inputs. Every part of the command, including | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider creating a list of interfaces as we did in java client. |
||
// the command name and subcommands, should be added as a separate value in args. The returning value depends on the executed | ||
// command. | ||
// | ||
// This function should only be used for single-response commands. Commands that don't return response (such as SUBSCRIBE), or | ||
// that return potentially more than a single response (such as XREAD), or that change the client's behavior (such as entering | ||
// pub/sub mode on RESP2 connections) shouldn't be called using this function. | ||
// | ||
// For example, to return a list of all pub/sub clients: | ||
// | ||
// client.CustomCommand([]string{"CLIENT", "LIST","TYPE", "PUBSUB"}) | ||
func (client *RedisClient) CustomCommand(args []string) (interface{}, error) { | ||
return client.executeCommand(C.CustomCommand, args) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add non-null and non-empty check for |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 | ||
|
||
package integTest | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/aws/glide-for-redis/go/glide/api" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func (suite *GlideTestSuite) TestCustomCommandInfo() { | ||
client := suite.defaultClient() | ||
result, err := client.CustomCommand([]string{"INFO"}) | ||
|
||
assert.Nil(suite.T(), err) | ||
assert.IsType(suite.T(), "", result) | ||
strResult := result.(string) | ||
assert.True(suite.T(), strings.Contains(strResult, "# Stats")) | ||
} | ||
|
||
func (suite *GlideTestSuite) TestCustomCommandPing() { | ||
client := suite.defaultClient() | ||
result, err := client.CustomCommand([]string{"PING"}) | ||
|
||
assert.Nil(suite.T(), err) | ||
assert.Equal(suite.T(), "PONG", result) | ||
} | ||
|
||
func (suite *GlideTestSuite) TestCustomCommandClientInfo() { | ||
clientName := "TEST_CLIENT_NAME" | ||
config := api.NewRedisClientConfiguration(). | ||
WithAddress(&api.NodeAddress{Port: suite.standalonePorts[0]}). | ||
WithClientName(clientName) | ||
client := suite.client(config) | ||
|
||
result, err := client.CustomCommand([]string{"CLIENT", "INFO"}) | ||
|
||
assert.Nil(suite.T(), err) | ||
assert.IsType(suite.T(), "", result) | ||
strResult := result.(string) | ||
assert.True(suite.T(), strings.Contains(strResult, fmt.Sprintf("name=%s", clientName))) | ||
} | ||
|
||
func (suite *GlideTestSuite) TestCustomCommand_invalidCommand() { | ||
client := suite.defaultClient() | ||
result, err := client.CustomCommand([]string{"pewpew"}) | ||
|
||
assert.Nil(suite.T(), result) | ||
assert.NotNil(suite.T(), err) | ||
assert.IsType(suite.T(), &api.RequestError{}, err) | ||
} | ||
|
||
func (suite *GlideTestSuite) TestCustomCommand_invalidArgs() { | ||
client := suite.defaultClient() | ||
result, err := client.CustomCommand([]string{"ping", "pang", "pong"}) | ||
|
||
assert.Nil(suite.T(), result) | ||
assert.NotNil(suite.T(), err) | ||
assert.IsType(suite.T(), &api.RequestError{}, err) | ||
} | ||
|
||
func (suite *GlideTestSuite) TestCustomCommand_closedClient() { | ||
client := suite.defaultClient() | ||
client.Close() | ||
|
||
result, err := client.CustomCommand([]string{"ping"}) | ||
|
||
assert.Nil(suite.T(), result) | ||
assert.NotNil(suite.T(), err) | ||
assert.IsType(suite.T(), &api.ClosingError{}, err) | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -32,7 +32,7 @@ typedef struct ConnectionResponse { | |||||
* `channel_address` is the address of the Go channel used by the callback to send the error message back to the caller of the command. | ||||||
* `message` is the value returned by the Redis command. | ||||||
*/ | ||||||
typedef void (*SuccessCallback)(uintptr_t channel_address, const char *message); | ||||||
typedef void (*SuccessCallback)(const void *channel_address, const char *message); | ||||||
|
||||||
/** | ||||||
* Failure callback that is called when a Redis command fails. | ||||||
|
@@ -41,7 +41,7 @@ typedef void (*SuccessCallback)(uintptr_t channel_address, const char *message); | |||||
* `error_message` is the error message returned by Redis for the failed command. It should be manually freed after this callback is invoked, otherwise a memory leak will occur. | ||||||
* `error_type` is the type of error returned by glide-core, depending on the `RedisError` returned. | ||||||
*/ | ||||||
typedef void (*FailureCallback)(uintptr_t channel_address, | ||||||
typedef void (*FailureCallback)(const void *channel_address, | ||||||
const char *error_message, | ||||||
RequestErrorType error_type); | ||||||
|
||||||
|
@@ -96,3 +96,94 @@ void close_client(const void *client_ptr); | |||||
* * The contained `connection_error_message` must not be null. | ||||||
*/ | ||||||
void free_connection_response(struct ConnectionResponse *connection_response_ptr); | ||||||
|
||||||
enum RequestType { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to move enum(s) to another file(s) and On another hand we have permission to use cbindgen which can generate entire header automatically. |
||||||
CustomCommand = 1, | ||||||
GetString = 2, | ||||||
SetString = 3, | ||||||
Ping = 4, | ||||||
Info = 5, | ||||||
Del = 6, | ||||||
Select = 7, | ||||||
ConfigGet = 8, | ||||||
ConfigSet = 9, | ||||||
ConfigResetStat = 10, | ||||||
ConfigRewrite = 11, | ||||||
ClientGetName = 12, | ||||||
ClientGetRedir = 13, | ||||||
ClientId = 14, | ||||||
ClientInfo = 15, | ||||||
ClientKill = 16, | ||||||
ClientList = 17, | ||||||
ClientNoEvict = 18, | ||||||
ClientNoTouch = 19, | ||||||
ClientPause = 20, | ||||||
ClientReply = 21, | ||||||
ClientSetInfo = 22, | ||||||
ClientSetName = 23, | ||||||
ClientUnblock = 24, | ||||||
ClientUnpause = 25, | ||||||
Expire = 26, | ||||||
HashSet = 27, | ||||||
HashGet = 28, | ||||||
HashDel = 29, | ||||||
HashExists = 30, | ||||||
MGet = 31, | ||||||
MSet = 32, | ||||||
Incr = 33, | ||||||
IncrBy = 34, | ||||||
Decr = 35, | ||||||
IncrByFloat = 36, | ||||||
DecrBy = 37, | ||||||
HashGetAll = 38, | ||||||
HashMSet = 39, | ||||||
HashMGet = 40, | ||||||
HashIncrBy = 41, | ||||||
HashIncrByFloat = 42, | ||||||
LPush = 43, | ||||||
LPop = 44, | ||||||
RPush = 45, | ||||||
RPop = 46, | ||||||
LLen = 47, | ||||||
LRem = 48, | ||||||
LRange = 49, | ||||||
LTrim = 50, | ||||||
SAdd = 51, | ||||||
SRem = 52, | ||||||
SMembers = 53, | ||||||
SCard = 54, | ||||||
PExpireAt = 55, | ||||||
PExpire = 56, | ||||||
ExpireAt = 57, | ||||||
Exists = 58, | ||||||
Unlink = 59, | ||||||
TTL = 60, | ||||||
Zadd = 61, | ||||||
Zrem = 62, | ||||||
Zrange = 63, | ||||||
Zcard = 64, | ||||||
Zcount = 65, | ||||||
ZIncrBy = 66, | ||||||
ZScore = 67, | ||||||
Type = 68, | ||||||
HLen = 69, | ||||||
Echo = 70, | ||||||
ZPopMin = 71, | ||||||
Strlen = 72, | ||||||
Lindex = 73, | ||||||
ZPopMax = 74, | ||||||
XRead = 75, | ||||||
XAdd = 76, | ||||||
XReadGroup = 77, | ||||||
XAck = 78, | ||||||
XTrim = 79, | ||||||
XGroupCreate = 80, | ||||||
XGroupDestroy = 81, | ||||||
}; | ||||||
typedef uint32_t RequestType; | ||||||
|
||||||
void command(const void *client_ptr, | ||||||
uintptr_t channel, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe
Suggested change
|
||||||
RequestType command_type, | ||||||
uintptr_t arg_count, | ||||||
const char *const *args); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.