diff --git a/examples/tinygo-outbound-redis/go.mod b/examples/tinygo-outbound-redis/go.mod index d36f33b768..7e287959e8 100644 --- a/examples/tinygo-outbound-redis/go.mod +++ b/examples/tinygo-outbound-redis/go.mod @@ -1,12 +1,9 @@ module github.com/fermyon/spin/templates/spin-http-tinygo-outbound-http -go 1.17 +go 1.18 require github.com/fermyon/spin/sdk/go v0.0.0 -require ( - github.com/julienschmidt/httprouter v1.3.0 // indirect - golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb -) +require github.com/julienschmidt/httprouter v1.3.0 // indirect replace github.com/fermyon/spin/sdk/go v0.0.0 => ../../sdk/go/ diff --git a/examples/tinygo-outbound-redis/go.sum b/examples/tinygo-outbound-redis/go.sum index 326a9b6bc5..096c54e630 100644 --- a/examples/tinygo-outbound-redis/go.sum +++ b/examples/tinygo-outbound-redis/go.sum @@ -1,35 +1,2 @@ -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w= -golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/examples/tinygo-outbound-redis/main.go b/examples/tinygo-outbound-redis/main.go index 6bd8fe1eb3..9623e4d182 100644 --- a/examples/tinygo-outbound-redis/main.go +++ b/examples/tinygo-outbound-redis/main.go @@ -1,13 +1,13 @@ package main import ( + "fmt" "net/http" "os" - "strconv" "reflect" - "fmt" + "sort" + "strconv" - "golang.org/x/exp/slices" spin_http "github.com/fermyon/spin/sdk/go/http" "github.com/fermyon/spin/sdk/go/redis" ) @@ -15,7 +15,7 @@ import ( func init() { // handler for the http trigger - spin_http.Handle(func(w http.ResponseWriter, r *http.Request) { + spin_http.Handle(func(w http.ResponseWriter, _ *http.Request) { // addr is the environment variable set in `spin.toml` that points to the // address of the Redis server. @@ -28,19 +28,21 @@ func init() { // payload is the data publish to the redis channel. payload := []byte(`Hello redis from tinygo!`) - if err := redis.Publish(addr, channel, payload); err != nil { + rdb := redis.NewClient(addr) + + if err := rdb.Publish(channel, payload); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // set redis `mykey` = `myvalue` - if err := redis.Set(addr, "mykey", []byte("myvalue")); err != nil { + if err := rdb.Set("mykey", []byte("myvalue")); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // get redis payload for `mykey` - if payload, err := redis.Get(addr, "mykey"); err != nil { + if payload, err := rdb.Get("mykey"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } else { @@ -50,7 +52,7 @@ func init() { } // incr `spin-go-incr` by 1 - if payload, err := redis.Incr(addr, "spin-go-incr"); err != nil { + if payload, err := rdb.Incr("spin-go-incr"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } else { @@ -60,7 +62,7 @@ func init() { } // delete `spin-go-incr` and `mykey` - if payload, err := redis.Del(addr, []string{"spin-go-incr", "mykey", "non-existing-key"}); err != nil { + if payload, err := rdb.Del("spin-go-incr", "mykey", "non-existing-key"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { w.Write([]byte("deleted keys num: ")) @@ -68,19 +70,19 @@ func init() { w.Write([]byte("\n")) } - if _, err := redis.Sadd(addr, "myset", []string{"foo", "bar"}); err != nil { + if _, err := rdb.Sadd("myset", "foo", "bar"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } { expected := []string{"bar", "foo"} - payload, err := redis.Smembers(addr, "myset") + payload, err := rdb.Smembers("myset") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - slices.Sort(payload) + sort.Strings(payload) if !reflect.DeepEqual(payload, expected) { http.Error( w, @@ -95,14 +97,14 @@ func init() { } } - if _, err := redis.Srem(addr, "myset", []string{"bar"}); err != nil { + if _, err := rdb.Srem("myset", "bar"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } { expected := []string{"foo"} - if payload, err := redis.Smembers(addr, "myset"); err != nil { + if payload, err := rdb.Smembers("myset"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } else if !reflect.DeepEqual(payload, expected) { @@ -119,27 +121,24 @@ func init() { } } - message := redis.RedisParameter{Kind: redis.RedisParameterKindBinary, Val: []byte("message")} - hello := redis.RedisParameter{Kind: redis.RedisParameterKindBinary, Val: []byte("hello")} - if _, err := redis.Execute(addr, "set", []redis.RedisParameter{message, hello}); err != nil { + if _, err := rdb.Execute("set", "message", "hello"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - world := redis.RedisParameter{Kind: redis.RedisParameterKindBinary, Val: []byte(" world")} - if _, err := redis.Execute(addr, "append", []redis.RedisParameter{message, world}); err != nil { + if _, err := rdb.Execute("append", "message", " world"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - if payload, err := redis.Execute(addr, "get", []redis.RedisParameter{message}); err != nil { + if payload, err := rdb.Execute("get", "message"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } else if !reflect.DeepEqual( payload, - []redis.RedisResult{redis.RedisResult{ - Kind: redis.RedisResultKindBinary, - Val: []byte("hello world"), + []*redis.Result{{ + Kind: redis.ResultKindBinary, + Val: []byte("hello world"), }}) { http.Error(w, "unexpected GET result", http.StatusInternalServerError) return diff --git a/sdk/go/redis/internals.go b/sdk/go/redis/internals.go index 7eed45840f..e34b6418cb 100644 --- a/sdk/go/redis/internals.go +++ b/sdk/go/redis/internals.go @@ -6,9 +6,48 @@ package redis import "C" import ( "errors" + "fmt" "unsafe" ) +// argumentKind represents a type of a argument for executing a Redis command. +type argumentKind uint8 + +const ( + argumentKindInt argumentKind = iota + argumentKindBinary +) + +// argument represents an argument for a Redis command. +type argument struct { + kind argumentKind + val any +} + +func createParameter(x any) (*argument, error) { + var p argument + switch v := x.(type) { + case int: + p.kind = argumentKindInt + p.val = int64(v) + case int32: + p.kind = argumentKindInt + p.val = int64(v) + case int64: + p.kind = argumentKindInt + p.val = v + case string: + p.kind = argumentKindBinary + p.val = []byte(v) + case []byte: + p.kind = argumentKindBinary + p.val = v + default: + return nil, fmt.Errorf("unsupported parameter type: %T", x) + } + return &p, nil +} + //export spin_redis_handle_redis_message func handleRedisMessage(payload *C.spin_redis_payload_t) C.spin_redis_error_t { bytes := C.GoBytes(unsafe.Pointer(payload.ptr), C.int(payload.len)) @@ -68,7 +107,7 @@ func del(addr string, keys []string) (int64, error) { return int64(cpayload), toErr(err) } -func sadd(addr string, key string, values []string) (int64, error) { +func sadd(addr, key string, values []string) (int64, error) { caddr := redisStr(addr) ckey := redisStr(key) cvalues := redisListStr(values) @@ -79,7 +118,7 @@ func sadd(addr string, key string, values []string) (int64, error) { return int64(cpayload), toErr(err) } -func smembers(addr string, key string) ([]string, error) { +func smembers(addr, key string) ([]string, error) { caddr := redisStr(addr) ckey := redisStr(key) @@ -89,7 +128,7 @@ func smembers(addr string, key string) ([]string, error) { return fromRedisListStr(&cpayload), toErr(err) } -func srem(addr string, key string, values []string) (int64, error) { +func srem(addr, key string, values []string) (int64, error) { caddr := redisStr(addr) ckey := redisStr(key) cvalues := redisListStr(values) @@ -100,33 +139,7 @@ func srem(addr string, key string, values []string) (int64, error) { return int64(cpayload), toErr(err) } -type RedisParameterKind uint8 - -const ( - RedisParameterKindInt64 = iota - RedisParameterKindBinary -) - -type RedisParameter struct { - Kind RedisParameterKind - Val interface{} -} - -type RedisResultKind uint8 - -const ( - RedisResultKindNil = iota - RedisResultKindStatus - RedisResultKindInt64 - RedisResultKindBinary -) - -type RedisResult struct { - Kind RedisResultKind - Val interface{} -} - -func execute(addr string, command string, arguments []RedisParameter) ([]RedisResult, error) { +func execute(addr, command string, arguments []*argument) ([]*Result, error) { caddr := redisStr(addr) ccommand := redisStr(command) carguments := redisListParameter(arguments) @@ -142,8 +155,10 @@ func redisStr(x string) C.outbound_redis_string_t { } func redisListStr(xs []string) C.outbound_redis_list_string_t { - var cxs []C.outbound_redis_string_t - + if len(xs) == 0 { + return C.outbound_redis_list_string_t{} + } + cxs := make([]C.outbound_redis_string_t, 0, len(xs)) for i := 0; i < len(xs); i++ { cxs = append(cxs, redisStr(xs[i])) } @@ -152,62 +167,63 @@ func redisListStr(xs []string) C.outbound_redis_list_string_t { func fromRedisListStr(list *C.outbound_redis_list_string_t) []string { listLen := int(list.len) - var result []string + result := make([]string, 0, listLen) slice := unsafe.Slice(list.ptr, listLen) for i := 0; i < listLen; i++ { - string := slice[i] - result = append(result, C.GoStringN(string.ptr, C.int(string.len))) + str := slice[i] + result = append(result, C.GoStringN(str.ptr, C.int(str.len))) } - return result } -func redisParameter(x RedisParameter) C.outbound_redis_redis_parameter_t { - +func redisParameter(x *argument) C.outbound_redis_redis_parameter_t { var ret C.outbound_redis_redis_parameter_t - switch x.Kind { - case RedisParameterKindInt64: - *(*C.int64_t)(unsafe.Pointer(&ret.val)) = x.Val.(int64) - case RedisParameterKindBinary: - value := x.Val.([]byte) + switch x.kind { + case argumentKindInt: + *(*C.int64_t)(unsafe.Pointer(&ret.val)) = x.val.(int64) + case argumentKindBinary: + value := x.val.([]byte) payload := C.outbound_redis_payload_t{ptr: &value[0], len: C.size_t(len(value))} *(*C.outbound_redis_payload_t)(unsafe.Pointer(&ret.val)) = payload } - ret.tag = C.uint8_t(x.Kind) + ret.tag = C.uint8_t(x.kind) return ret } -func redisListParameter(xs []RedisParameter) C.outbound_redis_list_redis_parameter_t { - var cxs []C.outbound_redis_redis_parameter_t +func redisListParameter(xs []*argument) C.outbound_redis_list_redis_parameter_t { + if len(xs) == 0 { + return C.outbound_redis_list_redis_parameter_t{} + } + cxs := make([]C.outbound_redis_redis_parameter_t, 0, len(xs)) for i := 0; i < len(xs); i++ { cxs = append(cxs, redisParameter(xs[i])) } return C.outbound_redis_list_redis_parameter_t{ptr: &cxs[0], len: C.size_t(len(cxs))} } -func fromRedisResult(result *C.outbound_redis_redis_result_t) RedisResult { - var val interface{} - switch result.tag { - case 0: val = nil - case 1: { - string := (*C.outbound_redis_string_t)(unsafe.Pointer(&result.val)) - val = C.GoStringN(string.ptr, C.int(string.len)) - } - case 2: val = int64(*(*C.int64_t)(unsafe.Pointer(&result.val))) - case 3: { +func fromRedisResult(result *C.outbound_redis_redis_result_t) *Result { + var val any + switch ResultKind(result.tag) { + case ResultKindNil: + val = nil + case ResultKindStatus: + str := (*C.outbound_redis_string_t)(unsafe.Pointer(&result.val)) + val = C.GoStringN(str.ptr, C.int(str.len)) + case ResultKindInt64: + val = int64(*(*C.int64_t)(unsafe.Pointer(&result.val))) + case ResultKindBinary: payload := (*C.outbound_redis_payload_t)(unsafe.Pointer(&result.val)) val = C.GoBytes(unsafe.Pointer(payload.ptr), C.int(payload.len)) } - } - return RedisResult{Kind: RedisResultKind(result.tag), Val: val} + return &Result{Kind: ResultKind(result.tag), Val: val} } -func fromRedisListResult(list *C.outbound_redis_list_redis_result_t) []RedisResult { +func fromRedisListResult(list *C.outbound_redis_list_redis_result_t) []*Result { listLen := int(list.len) - var result []RedisResult + result := make([]*Result, 0, listLen) slice := unsafe.Slice(list.ptr, listLen) for i := 0; i < listLen; i++ { diff --git a/sdk/go/redis/internals_test.go b/sdk/go/redis/internals_test.go new file mode 100644 index 0000000000..191fa6eedb --- /dev/null +++ b/sdk/go/redis/internals_test.go @@ -0,0 +1,40 @@ +package redis + +import ( + "reflect" + "testing" +) + +func TestCreateParameter(t *testing.T) { + tests := []struct { + in any + want argumentKind + }{ + {in: "a", want: argumentKindBinary}, + {in: []byte("b"), want: argumentKindBinary}, + {in: 1, want: argumentKindInt}, + {in: int64(2), want: argumentKindInt}, + {in: int32(3), want: argumentKindInt}, + } + + for _, tc := range tests { + p, err := createParameter(tc.in) + if err != nil { + t.Error(err) + } + if p.kind != tc.want { + t.Errorf("want %s, got %s", tc.want, p.kind) + } + } +} + +func TestRedisListString(t *testing.T) { + list := []string{"a", "b", "c"} + + rlist := redisListStr(list) + got := fromRedisListStr(&rlist) + + if !reflect.DeepEqual(list, got) { + t.Errorf("want %s, got %s", list, got) + } +} diff --git a/sdk/go/redis/redis.go b/sdk/go/redis/redis.go index 8d41e0e668..a5b03736eb 100644 --- a/sdk/go/redis/redis.go +++ b/sdk/go/redis/redis.go @@ -3,6 +3,7 @@ package redis import ( + "errors" "fmt" "os" ) @@ -23,55 +24,116 @@ func Handle(fn func(payload []byte) error) { handler = fn } -// Publish a Redis message to the specificed channel and return an error, if any. -func Publish(addr, channel string, payload []byte) error { - return publish(addr, channel, payload) +// Client is a Redis client. +type Client struct { + addr string +} + +// NewClient returns a Redis client. +func NewClient(address string) *Client { + return &Client{addr: address} +} + +// Publish a Redis message to the specified channel. +func (c *Client) Publish(channel string, payload []byte) error { + if len(payload) == 0 { + return errors.New("payload is empty") + } + return publish(c.addr, channel, payload) } // Get the value of a key. An error is returned if the value stored at key is // not a string. -func Get(addr, key string) ([]byte, error) { - return get(addr, key) +func (c *Client) Get(key string) ([]byte, error) { + return get(c.addr, key) } // Set key to value. If key alreads holds a value, it is overwritten. -func Set(addr, key string, payload []byte) error { - return set(addr, key, payload) +func (c *Client) Set(key string, payload []byte) error { + if len(payload) == 0 { + return errors.New("payload is empty") + } + return set(c.addr, key, payload) } -// Increments the number stored at key by one. If the key does not exist, +// Incr increments the number stored at key by one. If the key does not exist, // it is set to 0 before performing the operation. An error is returned if // the key contains a value of the wrong type or contains a string that can not // be represented as integer. -func Incr(addr, key string) (int64, error) { - return incr(addr, key) +func (c *Client) Incr(key string) (int64, error) { + return incr(c.addr, key) +} + +// Del removes the specified keys. A key is ignored if it does not exist. +func (c *Client) Del(keys ...string) (int64, error) { + return del(c.addr, keys) } -// Removes the specified keys. A key is ignored if it does not exist. -func Del(addr string, keys []string) (int64, error) { - return del(addr, keys) +// Sadd adds the specified values to the set for the specified key, creating +// it if it does not already exist. +func (c *Client) Sadd(key string, values ...string) (int64, error) { + return sadd(c.addr, key, values) } -// Adds the specified values to the set for the specified key, creating it if it -// does not already exist. -func Sadd(addr string, key string, values []string) (int64, error) { - return sadd(addr, key, values) +// Smembers gets the elements of the set for the specified key. +func (c *Client) Smembers(key string) ([]string, error) { + return smembers(c.addr, key) } -// Get the elements of the set for the specified key. -func Smembers(addr string, key string) ([]string, error) { - return smembers(addr, key) +// Srem removes the specified elements from the set for the specified key. +// This has no effect if the key does not exist. +func (c *Client) Srem(key string, values ...string) (int64, error) { + return srem(c.addr, key, values) } -// Removes the specified elements from the set for the specified key. This has -// no effect if the key does not exist. -func Srem(addr string, key string, values []string) (int64, error) { - return srem(addr, key, values) +// ResultKind represents a result type returned from executing a Redis command. +type ResultKind uint8 + +const ( + ResultKindNil ResultKind = iota + ResultKindStatus + ResultKindInt64 + ResultKindBinary +) + +// String implements fmt.Stringer. +func (r ResultKind) String() string { + switch r { + case ResultKindNil: + return "nil" + case ResultKindStatus: + return "status" + case ResultKindInt64: + return "int64" + case ResultKindBinary: + return "binary" + default: + return "unknown" + } +} + +// GoString implements fmt.GoStringer. +func (r ResultKind) GoString() string { return r.String() } + +// Result represents a value returned from a Redis command. +type Result struct { + Kind ResultKind + Val any } -// Run the specified Redis command with the specified arguments, returning zero -// or more results. This is a general-purpose function which should work with -// any Redis command. -func Execute(addr string, command string, arguments []RedisParameter) ([]RedisResult, error) { - return execute(addr, command, arguments) +// Execute runs the specified Redis command with the specified arguments, +// returning zero or more results. This is a general-purpose function which +// should work with any Redis command. +// +// Arguments must be string, []byte, int, int64, or int32. +func (c *Client) Execute(command string, arguments ...any) ([]*Result, error) { + var params []*argument + for _, a := range arguments { + p, err := createParameter(a) + if err != nil { + return nil, err + } + params = append(params, p) + } + return execute(c.addr, command, params) }