mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-05 07:56:52 +08:00
Return ok boolean instead of OK string in embedded api methods that return ok status. Updated tests to match new return types
This commit is contained in:
@@ -59,11 +59,4 @@ func main() {
|
||||
<-cancelCh
|
||||
|
||||
server.ShutDown()
|
||||
|
||||
// TODO: For example purposes only! Delete before PR!
|
||||
// vault, err := echovault.NewEchoVault()
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// newValue, err := vault.HIncrByFloat("key", "field", 7.75)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/echovault/echovault/internal"
|
||||
"github.com/tidwall/resp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ACLLoadOptions modifies the behaviour of the ACLLoad function.
|
||||
@@ -150,8 +151,8 @@ func (server *EchoVault) ACLUsers() ([]string, error) {
|
||||
//
|
||||
// `user` - User - The user object to add/update.
|
||||
//
|
||||
// Returns: "OK" if the user is successfully created/updated.
|
||||
func (server *EchoVault) ACLSetUser(user User) (string, error) {
|
||||
// Returns: true if the user is successfully created/updated.
|
||||
func (server *EchoVault) ACLSetUser(user User) (bool, error) {
|
||||
cmd := []string{"ACL", "SETUSER", user.Username}
|
||||
|
||||
if user.Enabled {
|
||||
@@ -238,10 +239,11 @@ func (server *EchoVault) ACLSetUser(user User) (string, error) {
|
||||
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, err
|
||||
}
|
||||
|
||||
return internal.ParseStringResponse(b)
|
||||
s, err := internal.ParseStringResponse(b)
|
||||
return strings.EqualFold(s, "ok"), err
|
||||
}
|
||||
|
||||
// ACLGetUser gets the ACL configuration of the name with the given username.
|
||||
@@ -323,14 +325,15 @@ func (server *EchoVault) ACLGetUser(username string) (map[string][]string, error
|
||||
//
|
||||
// `usernames` - ...string - A string of usernames to delete from the ACL module.
|
||||
//
|
||||
// Returns: "OK" if the deletion is successful.
|
||||
func (server *EchoVault) ACLDelUser(usernames ...string) (string, error) {
|
||||
// Returns: true if the deletion is successful.
|
||||
func (server *EchoVault) ACLDelUser(usernames ...string) (bool, error) {
|
||||
cmd := append([]string{"ACL", "DELUSER"}, usernames...)
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, err
|
||||
}
|
||||
return internal.ParseStringResponse(b)
|
||||
s, err := internal.ParseStringResponse(b)
|
||||
return strings.EqualFold(s, "ok"), err
|
||||
}
|
||||
|
||||
// ACLList lists all the currently loaded ACL users and their rules.
|
||||
@@ -349,8 +352,8 @@ func (server *EchoVault) ACLList() ([]string, error) {
|
||||
//
|
||||
// `options` - ACLLoadOptions - modifies the load behaviour.
|
||||
//
|
||||
// Returns: "OK" if the load is successful.
|
||||
func (server *EchoVault) ACLLoad(options ACLLoadOptions) (string, error) {
|
||||
// Returns: true if the load is successful.
|
||||
func (server *EchoVault) ACLLoad(options ACLLoadOptions) (bool, error) {
|
||||
cmd := []string{"ACL", "LOAD"}
|
||||
switch {
|
||||
case options.Merge:
|
||||
@@ -363,19 +366,21 @@ func (server *EchoVault) ACLLoad(options ACLLoadOptions) (string, error) {
|
||||
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, err
|
||||
}
|
||||
|
||||
return internal.ParseStringResponse(b)
|
||||
s, err := internal.ParseStringResponse(b)
|
||||
return strings.EqualFold(s, "ok"), err
|
||||
}
|
||||
|
||||
// ACLSave saves the current ACL configuration to the configured ACL file.
|
||||
//
|
||||
// Returns: "OK" if the save is successful.
|
||||
func (server *EchoVault) ACLSave() (string, error) {
|
||||
// Returns: true if the save is successful.
|
||||
func (server *EchoVault) ACLSave() (bool, error) {
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"ACL", "SAVE"}), nil, false, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, err
|
||||
}
|
||||
return internal.ParseStringResponse(b)
|
||||
s, err := internal.ParseStringResponse(b)
|
||||
return strings.EqualFold(s, "ok"), err
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Copyright 2024 Kelvin Clement Mwinuka
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");s
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
|
@@ -78,14 +78,14 @@ type PExpireAtOptions ExpireOptions
|
||||
//
|
||||
// `options` - SetOptions.
|
||||
//
|
||||
// Returns: "OK" if the set is successful, If the "Get" flag in SetOptions is set to true, the previous value is returned.
|
||||
// Returns: true if the set is successful, If the "Get" flag in SetOptions is set to true, the previous value is returned.
|
||||
//
|
||||
// Errors:
|
||||
//
|
||||
// "key <key> does not exist"" - when the XX flag is set to true and the key does not exist.
|
||||
//
|
||||
// "key <key> does already exists" - when the NX flag is set to true and the key already exists.
|
||||
func (server *EchoVault) Set(key, value string, options SetOptions) (string, error) {
|
||||
func (server *EchoVault) Set(key, value string, options SetOptions) (string, bool, error) {
|
||||
cmd := []string{"SET", key, value}
|
||||
|
||||
switch {
|
||||
@@ -112,10 +112,18 @@ func (server *EchoVault) Set(key, value string, options SetOptions) (string, err
|
||||
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
return internal.ParseStringResponse(b)
|
||||
previousValue, err := internal.ParseStringResponse(b)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if !options.GET {
|
||||
previousValue = ""
|
||||
}
|
||||
|
||||
return previousValue, true, nil
|
||||
}
|
||||
|
||||
// MSet set multiple values at multiple keys with one command. Existing keys are overwritten and non-existent
|
||||
|
@@ -609,7 +609,8 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
key string
|
||||
value string
|
||||
options SetOptions
|
||||
want string
|
||||
wantOk bool
|
||||
wantPrev string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@@ -618,7 +619,8 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
key: "key1",
|
||||
value: "value1",
|
||||
options: SetOptions{},
|
||||
want: "OK",
|
||||
wantOk: true,
|
||||
wantPrev: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -627,7 +629,8 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
key: "key2",
|
||||
value: "value2",
|
||||
options: SetOptions{NX: true},
|
||||
want: "OK",
|
||||
wantOk: true,
|
||||
wantPrev: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -638,11 +641,12 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
ExpireAt: time.Time{},
|
||||
},
|
||||
},
|
||||
key: "key3",
|
||||
value: "value3",
|
||||
options: SetOptions{NX: true},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
key: "key3",
|
||||
value: "value3",
|
||||
options: SetOptions{NX: true},
|
||||
wantOk: false,
|
||||
wantPrev: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Set new key value when key exists with XX flag passed",
|
||||
@@ -652,11 +656,12 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
ExpireAt: time.Time{},
|
||||
},
|
||||
},
|
||||
key: "key4",
|
||||
value: "value4",
|
||||
options: SetOptions{XX: true},
|
||||
want: "OK",
|
||||
wantErr: false,
|
||||
key: "key4",
|
||||
value: "value4",
|
||||
options: SetOptions{XX: true},
|
||||
wantOk: true,
|
||||
wantPrev: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Return error when setting non-existent key with XX flag",
|
||||
@@ -664,7 +669,8 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
key: "key5",
|
||||
value: "value5",
|
||||
options: SetOptions{XX: true},
|
||||
want: "",
|
||||
wantOk: false,
|
||||
wantPrev: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -673,7 +679,8 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
key: "key6",
|
||||
value: "value6",
|
||||
options: SetOptions{EX: 100},
|
||||
want: "OK",
|
||||
wantOk: true,
|
||||
wantPrev: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -682,7 +689,8 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
key: "key7",
|
||||
value: "value7",
|
||||
options: SetOptions{PX: 4096},
|
||||
want: "OK",
|
||||
wantOk: true,
|
||||
wantPrev: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -691,7 +699,8 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
key: "key8",
|
||||
value: "value8",
|
||||
options: SetOptions{EXAT: int(mockClock.Now().Add(200 * time.Second).Unix())},
|
||||
want: "OK",
|
||||
wantOk: true,
|
||||
wantPrev: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -700,7 +709,8 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
value: "value9",
|
||||
options: SetOptions{PXAT: int(mockClock.Now().Add(4096 * time.Millisecond).UnixMilli())},
|
||||
presetValues: nil,
|
||||
want: "OK",
|
||||
wantOk: true,
|
||||
wantPrev: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -711,11 +721,12 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
ExpireAt: time.Time{},
|
||||
},
|
||||
},
|
||||
key: "key10",
|
||||
value: "value10",
|
||||
options: SetOptions{GET: true, EX: 1000},
|
||||
want: "previous-value",
|
||||
wantErr: false,
|
||||
key: "key10",
|
||||
value: "value10",
|
||||
options: SetOptions{GET: true, EX: 1000},
|
||||
wantOk: true,
|
||||
wantPrev: "previous-value",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Return nil when GET value is passed and no previous value exists",
|
||||
@@ -723,7 +734,8 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
key: "key11",
|
||||
value: "value11",
|
||||
options: SetOptions{GET: true, EX: 1000},
|
||||
want: "",
|
||||
wantOk: true,
|
||||
wantPrev: "",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
@@ -734,13 +746,16 @@ func TestEchoVault_SET(t *testing.T) {
|
||||
presetKeyData(server, context.Background(), k, d)
|
||||
}
|
||||
}
|
||||
got, err := server.Set(tt.key, tt.value, tt.options)
|
||||
previousValue, ok, err := server.Set(tt.key, tt.value, tt.options)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("SET() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("SET() got = %v, want %v", got, tt.want)
|
||||
if ok != tt.wantOk {
|
||||
t.Errorf("SET() ok got = %v, want %v", ok, tt.wantOk)
|
||||
}
|
||||
if previousValue != tt.wantPrev {
|
||||
t.Errorf("SET() previous value got = %v, want %v", previousValue, tt.wantPrev)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/echovault/echovault/internal"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LLen returns the length of the list.
|
||||
@@ -101,29 +102,33 @@ func (server *EchoVault) LIndex(key string, index uint) (string, error) {
|
||||
//
|
||||
// `value` - string - the new value to place at the given index.
|
||||
//
|
||||
// Returns: "OK" if the update is successful.
|
||||
// Returns: true if the update is successful.
|
||||
//
|
||||
// Errors:
|
||||
//
|
||||
// "LSet command on non-list item" - when the provided key exists but is not a list.
|
||||
//
|
||||
// "index must be within list range" - when the index is not within the list boundary.
|
||||
func (server *EchoVault) LSet(key string, index int, value string) (string, error) {
|
||||
func (server *EchoVault) LSet(key string, index int, value string) (bool, error) {
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"LSET", key, strconv.Itoa(index), value}), nil, false, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, err
|
||||
}
|
||||
return internal.ParseStringResponse(b)
|
||||
s, err := internal.ParseStringResponse(b)
|
||||
return strings.EqualFold(s, "ok"), err
|
||||
}
|
||||
|
||||
// LTrim work similarly to LRange but instead of returning the new list, it replaces the original list with the
|
||||
// trimmed list.
|
||||
func (server *EchoVault) LTrim(key string, start int, end int) (string, error) {
|
||||
//
|
||||
// Returns: true if the trim is successful.
|
||||
func (server *EchoVault) LTrim(key string, start int, end int) (bool, error) {
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"LTRIM", key, strconv.Itoa(start), strconv.Itoa(end)}), nil, false, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, err
|
||||
}
|
||||
return internal.ParseStringResponse(b)
|
||||
s, err := internal.ParseStringResponse(b)
|
||||
return strings.EqualFold(s, "ok"), err
|
||||
}
|
||||
|
||||
// LRem removes 'count' instances of the specified element from the list.
|
||||
@@ -136,17 +141,23 @@ func (server *EchoVault) LTrim(key string, start int, end int) (string, error) {
|
||||
//
|
||||
// `value` - string - the element to remove.
|
||||
//
|
||||
// Returns: "OK" if the removal was successful.
|
||||
// Returns: true if the removal was successful.
|
||||
//
|
||||
// Errors:
|
||||
//
|
||||
// "LRem command on non-list item" - when the provided key exists but is not a list.
|
||||
func (server *EchoVault) LRem(key string, count int, value string) (string, error) {
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"LREM", key, strconv.Itoa(count), value}), nil, false, true)
|
||||
func (server *EchoVault) LRem(key string, count int, value string) (bool, error) {
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{
|
||||
"LREM", key, strconv.Itoa(count), value}),
|
||||
nil,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, err
|
||||
}
|
||||
return internal.ParseStringResponse(b)
|
||||
s, err := internal.ParseStringResponse(b)
|
||||
return strings.EqualFold(s, "ok"), err
|
||||
}
|
||||
|
||||
// LMove moves an element from one list to another.
|
||||
@@ -163,19 +174,20 @@ func (server *EchoVault) LRem(key string, count int, value string) (string, erro
|
||||
// `whereTo` - string - either "LEFT" or "RIGHT". If "LEFT", the element is added to the beginning of the destination list.
|
||||
// If "RIGHT", the element is added to the end of the destination list.
|
||||
//
|
||||
// Returns: "OK" if the removal was successful.
|
||||
// Returns: true if the removal was successful.
|
||||
//
|
||||
// Errors:
|
||||
//
|
||||
// "both source and destination must be lists" - when either source or destination are not lists.
|
||||
//
|
||||
// "wherefrom and whereto arguments must be either LEFT or RIGHT" - if whereFrom or whereTo are not either "LEFT" or "RIGHT".
|
||||
func (server *EchoVault) LMove(source, destination, whereFrom, whereTo string) (string, error) {
|
||||
func (server *EchoVault) LMove(source, destination, whereFrom, whereTo string) (bool, error) {
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"LMOVE", source, destination, whereFrom, whereTo}), nil, false, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, err
|
||||
}
|
||||
return internal.ParseStringResponse(b)
|
||||
s, err := internal.ParseStringResponse(b)
|
||||
return strings.EqualFold(s, "ok"), err
|
||||
}
|
||||
|
||||
// LPop pops an element from the start of the list and return it.
|
||||
|
@@ -176,7 +176,7 @@ func TestEchoVault_LMOVE(t *testing.T) {
|
||||
destination string
|
||||
whereFrom string
|
||||
whereTo string
|
||||
want string
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@@ -190,7 +190,7 @@ func TestEchoVault_LMOVE(t *testing.T) {
|
||||
destination: "destination1",
|
||||
whereFrom: "LEFT",
|
||||
whereTo: "LEFT",
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -204,7 +204,7 @@ func TestEchoVault_LMOVE(t *testing.T) {
|
||||
destination: "destination2",
|
||||
whereFrom: "LEFT",
|
||||
whereTo: "RIGHT",
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -218,7 +218,7 @@ func TestEchoVault_LMOVE(t *testing.T) {
|
||||
destination: "destination3",
|
||||
whereFrom: "RIGHT",
|
||||
whereTo: "LEFT",
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -232,7 +232,7 @@ func TestEchoVault_LMOVE(t *testing.T) {
|
||||
destination: "destination4",
|
||||
whereFrom: "RIGHT",
|
||||
whereTo: "RIGHT",
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -245,7 +245,7 @@ func TestEchoVault_LMOVE(t *testing.T) {
|
||||
destination: "destination5",
|
||||
whereFrom: "LEFT",
|
||||
whereTo: "LEFT",
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -259,7 +259,7 @@ func TestEchoVault_LMOVE(t *testing.T) {
|
||||
destination: "destination6",
|
||||
whereFrom: "LEFT",
|
||||
whereTo: "LEFT",
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -272,7 +272,7 @@ func TestEchoVault_LMOVE(t *testing.T) {
|
||||
destination: "destination7",
|
||||
whereFrom: "LEFT",
|
||||
whereTo: "LEFT",
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -286,7 +286,7 @@ func TestEchoVault_LMOVE(t *testing.T) {
|
||||
destination: "destination8",
|
||||
whereFrom: "LEFT",
|
||||
whereTo: "LEFT",
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -297,7 +297,7 @@ func TestEchoVault_LMOVE(t *testing.T) {
|
||||
destination: "destination9",
|
||||
whereFrom: "LEFT",
|
||||
whereTo: "LEFT",
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -308,7 +308,7 @@ func TestEchoVault_LMOVE(t *testing.T) {
|
||||
destination: "destination10",
|
||||
whereFrom: "LEFT",
|
||||
whereTo: "LEFT",
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
@@ -664,7 +664,7 @@ func TestEchoVault_LREM(t *testing.T) {
|
||||
key string
|
||||
count int
|
||||
value string
|
||||
want string
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@@ -674,7 +674,7 @@ func TestEchoVault_LREM(t *testing.T) {
|
||||
key: "key1",
|
||||
count: 3,
|
||||
value: "4",
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -684,7 +684,7 @@ func TestEchoVault_LREM(t *testing.T) {
|
||||
key: "key2",
|
||||
count: -3,
|
||||
value: "4",
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -694,7 +694,7 @@ func TestEchoVault_LREM(t *testing.T) {
|
||||
key: "LremKey8",
|
||||
count: 0,
|
||||
value: "value1",
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
@@ -729,7 +729,7 @@ func TestEchoVault_LSET(t *testing.T) {
|
||||
key string
|
||||
index int
|
||||
value string
|
||||
want string
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@@ -739,7 +739,7 @@ func TestEchoVault_LSET(t *testing.T) {
|
||||
key: "key1",
|
||||
index: 3,
|
||||
value: "new-value",
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -749,7 +749,7 @@ func TestEchoVault_LSET(t *testing.T) {
|
||||
key: "key2",
|
||||
index: 0,
|
||||
value: "new-value",
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -759,7 +759,7 @@ func TestEchoVault_LSET(t *testing.T) {
|
||||
key: "key3",
|
||||
index: 1,
|
||||
value: "new-value",
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -769,7 +769,7 @@ func TestEchoVault_LSET(t *testing.T) {
|
||||
key: "key4",
|
||||
index: 0,
|
||||
value: "element",
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -779,7 +779,7 @@ func TestEchoVault_LSET(t *testing.T) {
|
||||
key: "key5",
|
||||
index: 0,
|
||||
value: "element",
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -789,7 +789,7 @@ func TestEchoVault_LSET(t *testing.T) {
|
||||
key: "key6",
|
||||
index: 3,
|
||||
value: "element",
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -799,7 +799,7 @@ func TestEchoVault_LSET(t *testing.T) {
|
||||
key: "key7",
|
||||
index: -1,
|
||||
value: "element",
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
@@ -834,7 +834,7 @@ func TestEchoVault_LTRIM(t *testing.T) {
|
||||
key string
|
||||
start int
|
||||
end int
|
||||
want string
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@@ -847,7 +847,7 @@ func TestEchoVault_LTRIM(t *testing.T) {
|
||||
key: "key1",
|
||||
start: 3,
|
||||
end: 6,
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -857,7 +857,7 @@ func TestEchoVault_LTRIM(t *testing.T) {
|
||||
key: "key2",
|
||||
start: 5,
|
||||
end: -1,
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -867,7 +867,7 @@ func TestEchoVault_LTRIM(t *testing.T) {
|
||||
key: "key3",
|
||||
start: 3,
|
||||
end: 1,
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -877,7 +877,7 @@ func TestEchoVault_LTRIM(t *testing.T) {
|
||||
key: "key4",
|
||||
start: 0,
|
||||
end: 2,
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -887,7 +887,7 @@ func TestEchoVault_LTRIM(t *testing.T) {
|
||||
key: "key5",
|
||||
start: 0,
|
||||
end: 3,
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -897,7 +897,7 @@ func TestEchoVault_LTRIM(t *testing.T) {
|
||||
key: "key6",
|
||||
start: -1,
|
||||
end: 3,
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
@@ -907,7 +907,7 @@ func TestEchoVault_LTRIM(t *testing.T) {
|
||||
key: "key7",
|
||||
start: 10,
|
||||
end: 11,
|
||||
want: "",
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/echovault/echovault/internal"
|
||||
"github.com/tidwall/resp"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type conn struct {
|
||||
@@ -176,14 +177,15 @@ func (server *EchoVault) PUnsubscribe(tag string, patterns ...string) {
|
||||
//
|
||||
// `message` - string - The message to publish to the specified channel.
|
||||
//
|
||||
// Returns: "OK" when the publish is successful. This does not indicate whether each subscriber has received the message,
|
||||
// Returns: true when the publish is successful. This does not indicate whether each subscriber has received the message,
|
||||
// only that the message has been published.
|
||||
func (server *EchoVault) Publish(channel, message string) (string, error) {
|
||||
func (server *EchoVault) Publish(channel, message string) (bool, error) {
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"PUBLISH", channel, message}), nil, false, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, err
|
||||
}
|
||||
return internal.ParseStringResponse(b)
|
||||
s, err := internal.ParseStringResponse(b)
|
||||
return strings.EqualFold(s, "ok"), err
|
||||
}
|
||||
|
||||
// PubSubChannels returns the list of channels & patterns that match the glob pattern provided.
|
||||
|
@@ -86,6 +86,8 @@ func (server *EchoVault) SDiff(keys ...string) ([]string, error) {
|
||||
|
||||
// SDiffStore works like SDiff but instead of returning the resulting set elements, the resulting set is stored
|
||||
// at the 'destination' key.
|
||||
//
|
||||
// Returns: an integer representing the cardinality of the new set.
|
||||
func (server *EchoVault) SDiffStore(destination string, keys ...string) (int, error) {
|
||||
cmd := append([]string{"SDIFFSTORE", destination}, keys...)
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
|
||||
@@ -334,6 +336,8 @@ func (server *EchoVault) SUnion(keys ...string) ([]string, error) {
|
||||
|
||||
// SUnionStore store works like SUnion but instead of returning the resulting elements, it stores the resulting
|
||||
// set at the 'destination' key. The return value is an integer representing the cardinality of the new set.
|
||||
//
|
||||
// Returns: an integer representing the cardinality of the new union set.
|
||||
func (server *EchoVault) SUnionStore(destination string, keys ...string) (int, error) {
|
||||
cmd := append([]string{"SUNIONSTORE", destination}, keys...)
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
|
||||
|
@@ -490,11 +490,13 @@ func handleSave(params internal.HandlerFuncParams) ([]byte, error) {
|
||||
func Commands() []internal.Command {
|
||||
return []internal.Command{
|
||||
{
|
||||
Command: "auth",
|
||||
Module: constants.ACLModule,
|
||||
Categories: []string{constants.ConnectionCategory, constants.SlowCategory},
|
||||
Description: "(AUTH [username] password) Authenticates the connection",
|
||||
Sync: false,
|
||||
Command: "auth",
|
||||
Module: constants.ACLModule,
|
||||
Categories: []string{constants.ConnectionCategory, constants.SlowCategory},
|
||||
Description: `(AUTH [username] password)
|
||||
Authenticates the connection. If the username is not provided, the connection will be authenticated against the
|
||||
default ACL user. Otherwise, it is authenticated against the ACL user with the provided username.`,
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
@@ -522,8 +524,8 @@ func Commands() []internal.Command {
|
||||
Command: "cat",
|
||||
Module: constants.ACLModule,
|
||||
Categories: []string{constants.SlowCategory},
|
||||
Description: `(ACL CAT [category]) List all the categories.
|
||||
If the optional category is provided, list all the commands in the category`,
|
||||
Description: `(ACL CAT [category]) Lists all the categories.
|
||||
If the optional category is provided, lists all the commands in the category.`,
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
@@ -538,7 +540,7 @@ If the optional category is provided, list all the commands in the category`,
|
||||
Command: "users",
|
||||
Module: constants.ACLModule,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||
Description: "(ACL USERS) List all usernames of the configured ACL users",
|
||||
Description: "(ACL USERS) Lists all usernames of the configured ACL users.",
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
@@ -568,7 +570,7 @@ If the optional category is provided, list all the commands in the category`,
|
||||
Command: "getuser",
|
||||
Module: constants.ACLModule,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||
Description: "(ACL GETUSER username) List the ACL rules of a user",
|
||||
Description: "(ACL GETUSER username) List the ACL rules of a user.",
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
@@ -580,11 +582,12 @@ If the optional category is provided, list all the commands in the category`,
|
||||
HandlerFunc: handleGetUser,
|
||||
},
|
||||
{
|
||||
Command: "deluser",
|
||||
Module: constants.ACLModule,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||
Description: "(ACL DELUSER username [username ...]) Deletes users and terminates their connections. Cannot delete default user",
|
||||
Sync: true,
|
||||
Command: "deluser",
|
||||
Module: constants.ACLModule,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||
Description: `(ACL DELUSER username [username ...])
|
||||
Deletes users and terminates their connections. Cannot delete default user.`,
|
||||
Sync: true,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
@@ -598,7 +601,7 @@ If the optional category is provided, list all the commands in the category`,
|
||||
Command: "whoami",
|
||||
Module: constants.ACLModule,
|
||||
Categories: []string{constants.FastCategory},
|
||||
Description: "(ACL WHOAMI) Returns the authenticated user of the current connection",
|
||||
Description: "(ACL WHOAMI) Returns the authenticated user of the current connection.",
|
||||
Sync: true,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
@@ -613,7 +616,7 @@ If the optional category is provided, list all the commands in the category`,
|
||||
Command: "list",
|
||||
Module: constants.ACLModule,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||
Description: "(ACL LIST) Dumps effective acl rules in acl config file format",
|
||||
Description: "(ACL LIST) Dumps effective acl rules in ACL DSL format.",
|
||||
Sync: true,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
@@ -646,7 +649,7 @@ When 'REPLACE' is passed, users from config file who share a username with users
|
||||
Command: "save",
|
||||
Module: constants.ACLModule,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||
Description: "(ACL SAVE) Saves the effective ACL rules the configured ACL config file",
|
||||
Description: "(ACL SAVE) Saves the effective ACL rules the configured ACL config file.",
|
||||
Sync: true,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
|
@@ -195,7 +195,7 @@ func Commands() []internal.Command {
|
||||
Command: "commands",
|
||||
Module: constants.AdminModule,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory},
|
||||
Description: "Get a list of all the commands in available on the echovault with categories and descriptions",
|
||||
Description: "Get a list of all the commands in available on the echovault with categories and descriptions.",
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
@@ -232,8 +232,8 @@ func Commands() []internal.Command {
|
||||
{
|
||||
Command: "count",
|
||||
Module: constants.AdminModule,
|
||||
Categories: []string{constants.SlowCategory},
|
||||
Description: "Get the dumber of commands in the echovault",
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory},
|
||||
Description: "Get the dumber of commands in the echovault instance.",
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
@@ -245,9 +245,9 @@ func Commands() []internal.Command {
|
||||
{
|
||||
Command: "list",
|
||||
Module: constants.AdminModule,
|
||||
Categories: []string{constants.SlowCategory},
|
||||
Description: `(COMMAND LIST [FILTERBY <ACLCAT category | PATTERN pattern | MODULE module>]) Get the list of command names.
|
||||
Allows for filtering by ACL category or glob pattern.`,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory},
|
||||
Description: `(COMMAND LIST [FILTERBY <ACLCAT category | PATTERN pattern | MODULE module>])
|
||||
Get the list of command names. Allows for filtering by ACL category or glob pattern.`,
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
@@ -262,7 +262,7 @@ Allows for filtering by ACL category or glob pattern.`,
|
||||
Command: "save",
|
||||
Module: constants.AdminModule,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||
Description: "(SAVE) Trigger a snapshot save",
|
||||
Description: "(SAVE) Trigger a snapshot save.",
|
||||
Sync: true,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
@@ -299,7 +299,7 @@ Allows for filtering by ACL category or glob pattern.`,
|
||||
Command: "rewriteaof",
|
||||
Module: constants.AdminModule,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||
Description: "(REWRITEAOF) Trigger re-writing of append process",
|
||||
Description: "(REWRITEAOF) Trigger re-writing of append process.",
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
@@ -352,11 +352,12 @@ module's key extraction and handler functions.`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Command: "unload",
|
||||
Module: constants.AdminModule,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||
Description: `(MODULE UNLOAD name) Unloads a module based on the its name as displayed by the MODULE LIST command.`,
|
||||
Sync: true,
|
||||
Command: "unload",
|
||||
Module: constants.AdminModule,
|
||||
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||
Description: `(MODULE UNLOAD name)
|
||||
Unloads a module based on the its name as displayed by the MODULE LIST command.`,
|
||||
Sync: true,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||
|
@@ -35,11 +35,13 @@ func handlePing(params internal.HandlerFuncParams) ([]byte, error) {
|
||||
func Commands() []internal.Command {
|
||||
return []internal.Command{
|
||||
{
|
||||
Command: "ping",
|
||||
Module: constants.ConnectionModule,
|
||||
Categories: []string{constants.FastCategory, constants.ConnectionCategory},
|
||||
Description: "(PING [value]) Ping the echovault. If a value is provided, the value will be echoed.",
|
||||
Sync: false,
|
||||
Command: "ping",
|
||||
Module: constants.ConnectionModule,
|
||||
Categories: []string{constants.ConnectionCategory, constants.FastCategory},
|
||||
Description: `(PING [message])
|
||||
Ping the echovault server. If a message is provided, the message will be echoed back to the client.
|
||||
Otherwise, the server will return "PONG".`,
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
|
@@ -511,19 +511,21 @@ func handlePop(params internal.HandlerFuncParams) ([]byte, error) {
|
||||
func Commands() []internal.Command {
|
||||
return []internal.Command{
|
||||
{
|
||||
Command: "lpush",
|
||||
Module: constants.ListModule,
|
||||
Categories: []string{constants.ListCategory, constants.WriteCategory, constants.FastCategory},
|
||||
Description: "(LPUSH key element [element ...]) Prepends one or more values to the beginning of a list, creates the list if it does not exist.",
|
||||
Command: "lpush",
|
||||
Module: constants.ListModule,
|
||||
Categories: []string{constants.ListCategory, constants.WriteCategory, constants.FastCategory},
|
||||
Description: `(LPUSH key element [element ...])
|
||||
Prepends one or more values to the beginning of a list, creates the list if it does not exist.`,
|
||||
Sync: true,
|
||||
KeyExtractionFunc: lpushKeyFunc,
|
||||
HandlerFunc: handleLPush,
|
||||
},
|
||||
{
|
||||
Command: "lpushx",
|
||||
Module: constants.ListModule,
|
||||
Categories: []string{constants.ListCategory, constants.WriteCategory, constants.FastCategory},
|
||||
Description: "(LPUSHX key element [element ...]) Prepends a value to the beginning of a list only if the list exists.",
|
||||
Command: "lpushx",
|
||||
Module: constants.ListModule,
|
||||
Categories: []string{constants.ListCategory, constants.WriteCategory, constants.FastCategory},
|
||||
Description: `(LPUSHX key element [element ...])
|
||||
Prepends a value to the beginning of a list only if the list exists.`,
|
||||
Sync: true,
|
||||
KeyExtractionFunc: lpushKeyFunc,
|
||||
HandlerFunc: handleLPush,
|
||||
@@ -558,7 +560,7 @@ func Commands() []internal.Command {
|
||||
{
|
||||
Command: "lindex",
|
||||
Module: constants.ListModule,
|
||||
Categories: []string{constants.ListCategory, constants.ReadCategory, constants.SlowCategory},
|
||||
Categories: []string{constants.ListCategory, constants.ReadCategory, constants.FastCategory},
|
||||
Description: "(LINDEX key index) Gets list element by index.",
|
||||
Sync: false,
|
||||
KeyExtractionFunc: lindexKeyFunc,
|
||||
@@ -567,7 +569,7 @@ func Commands() []internal.Command {
|
||||
{
|
||||
Command: "lset",
|
||||
Module: constants.ListModule,
|
||||
Categories: []string{constants.ListCategory, constants.WriteCategory, constants.SlowCategory},
|
||||
Categories: []string{constants.ListCategory, constants.WriteCategory, constants.FastCategory},
|
||||
Description: "(LSET key index element) Sets the value of an element in a list by its index.",
|
||||
Sync: true,
|
||||
KeyExtractionFunc: lsetKeyFunc,
|
||||
@@ -586,16 +588,17 @@ func Commands() []internal.Command {
|
||||
Command: "lrem",
|
||||
Module: constants.ListModule,
|
||||
Categories: []string{constants.ListCategory, constants.WriteCategory, constants.SlowCategory},
|
||||
Description: "(LREM key count element) Remove elements from list.",
|
||||
Description: "(LREM key count element) Remove <count> elements from list.",
|
||||
Sync: true,
|
||||
KeyExtractionFunc: lremKeyFunc,
|
||||
HandlerFunc: handleLRem,
|
||||
},
|
||||
{
|
||||
Command: "lmove",
|
||||
Module: constants.ListModule,
|
||||
Categories: []string{constants.ListCategory, constants.WriteCategory, constants.SlowCategory},
|
||||
Description: "(LMOVE source destination <LEFT | RIGHT> <LEFT | RIGHT>) Move element from one list to the other specifying left/right for both lists.",
|
||||
Command: "lmove",
|
||||
Module: constants.ListModule,
|
||||
Categories: []string{constants.ListCategory, constants.WriteCategory, constants.SlowCategory},
|
||||
Description: `(LMOVE source destination <LEFT | RIGHT> <LEFT | RIGHT>)
|
||||
Move element from one list to the other specifying left/right for both lists.`,
|
||||
Sync: true,
|
||||
KeyExtractionFunc: lmoveKeyFunc,
|
||||
HandlerFunc: handleLMove,
|
||||
|
@@ -793,10 +793,11 @@ func handleSUNIONSTORE(params internal.HandlerFuncParams) ([]byte, error) {
|
||||
func Commands() []internal.Command {
|
||||
return []internal.Command{
|
||||
{
|
||||
Command: "sadd",
|
||||
Module: constants.SetModule,
|
||||
Categories: []string{constants.SetCategory, constants.WriteCategory, constants.FastCategory},
|
||||
Description: "(SADD key member [member...]) Add one or more members to the set. If the set does not exist, it's created.",
|
||||
Command: "sadd",
|
||||
Module: constants.SetModule,
|
||||
Categories: []string{constants.SetCategory, constants.WriteCategory, constants.FastCategory},
|
||||
Description: `(SADD key member [member...])
|
||||
Add one or more members to the set. If the set does not exist, it's created.`,
|
||||
Sync: true,
|
||||
KeyExtractionFunc: saddKeyFunc,
|
||||
HandlerFunc: handleSADD,
|
||||
@@ -826,7 +827,7 @@ All keys that are non-existed or hold values that are not sets will be skipped.`
|
||||
Module: constants.SetModule,
|
||||
Categories: []string{constants.SetCategory, constants.WriteCategory, constants.SlowCategory},
|
||||
Description: `(SDIFFSTORE destination key [key...]) Works the same as SDIFF but also stores the result at 'destination'.
|
||||
Returns the cardinality of the new set`,
|
||||
Returns the cardinality of the new set.`,
|
||||
Sync: true,
|
||||
KeyExtractionFunc: sdiffstoreKeyFunc,
|
||||
HandlerFunc: handleSDIFFSTORE,
|
||||
@@ -841,10 +842,11 @@ Returns the cardinality of the new set`,
|
||||
HandlerFunc: handleSINTER,
|
||||
},
|
||||
{
|
||||
Command: "sintercard",
|
||||
Module: constants.SetModule,
|
||||
Categories: []string{constants.SetCategory, constants.ReadCategory, constants.SlowCategory},
|
||||
Description: "(SINTERCARD key [key...] [LIMIT limit]) Returns the cardinality of the intersection between multiple sets.",
|
||||
Command: "sintercard",
|
||||
Module: constants.SetModule,
|
||||
Categories: []string{constants.SetCategory, constants.ReadCategory, constants.SlowCategory},
|
||||
Description: `(SINTERCARD key [key...] [LIMIT limit])
|
||||
Returns the cardinality of the intersection between multiple sets.`,
|
||||
Sync: false,
|
||||
KeyExtractionFunc: sintercardKeyFunc,
|
||||
HandlerFunc: handleSINTERCARD,
|
||||
|
Reference in New Issue
Block a user