mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-04 07:36:27 +08:00
Added ZRemRangeByLex and ZRemRangeByRank to embedded API.
This commit is contained in:
3
Makefile
3
Makefile
@@ -14,7 +14,8 @@ build:
|
||||
env CGO_ENABLED=1 CC=x86_64-linux-musl-gcc GOOS=linux GOARCH=amd64 DEST=bin/linux/x86_64 make build-server
|
||||
|
||||
run:
|
||||
make build && docker-compose up --build
|
||||
make build && \
|
||||
docker-compose up --build
|
||||
|
||||
test-unit:
|
||||
env RACE=false OUT=internal/modules/admin/testdata make build-modules-test && \
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@ package echovault
|
||||
import (
|
||||
"github.com/echovault/echovault/internal"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SetOptions modifies the behaviour for the Set command
|
||||
@@ -124,12 +125,12 @@ func (server *EchoVault) Set(key, value string, options SetOptions) (string, err
|
||||
//
|
||||
// `kvPairs` - map[string]string - a map representing all the keys and values to be set.
|
||||
//
|
||||
// Returns: "OK" if the set is successful.
|
||||
// Returns: true if the set is successful.
|
||||
//
|
||||
// Errors:
|
||||
//
|
||||
// "key <key> does already exists" - when the NX flag is set to true and the key already exists.
|
||||
func (server *EchoVault) MSet(kvPairs map[string]string) (string, error) {
|
||||
// "key <key> already exists" - when the NX flag is set to true and the key already exists.
|
||||
func (server *EchoVault) MSet(kvPairs map[string]string) (bool, error) {
|
||||
cmd := []string{"MSET"}
|
||||
|
||||
for k, v := range kvPairs {
|
||||
@@ -138,10 +139,15 @@ func (server *EchoVault) MSet(kvPairs map[string]string) (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)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return strings.EqualFold(s, "ok"), nil
|
||||
}
|
||||
|
||||
// Get retrieves the value at the provided key.
|
||||
@@ -279,7 +285,7 @@ func (server *EchoVault) PTTL(key string) (int, error) {
|
||||
// `options` - ExpireOptions
|
||||
//
|
||||
// Returns: true if the key's expiry was successfully updated.
|
||||
func (server *EchoVault) Expire(key string, seconds int, options ExpireOptions) (int, error) {
|
||||
func (server *EchoVault) Expire(key string, seconds int, options ExpireOptions) (bool, error) {
|
||||
cmd := []string{"EXPIRE", key, strconv.Itoa(seconds)}
|
||||
|
||||
switch {
|
||||
@@ -295,10 +301,10 @@ func (server *EchoVault) Expire(key string, seconds int, options ExpireOptions)
|
||||
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
return internal.ParseIntegerResponse(b)
|
||||
return internal.ParseBooleanResponse(b)
|
||||
}
|
||||
|
||||
// PExpire set the given key's expiry in milliseconds from now.
|
||||
@@ -313,7 +319,7 @@ func (server *EchoVault) Expire(key string, seconds int, options ExpireOptions)
|
||||
// `options` - PExpireOptions
|
||||
//
|
||||
// Returns: true if the key's expiry was successfully updated.
|
||||
func (server *EchoVault) PExpire(key string, milliseconds int, options PExpireOptions) (int, error) {
|
||||
func (server *EchoVault) PExpire(key string, milliseconds int, options PExpireOptions) (bool, error) {
|
||||
cmd := []string{"PEXPIRE", key, strconv.Itoa(milliseconds)}
|
||||
|
||||
switch {
|
||||
@@ -329,10 +335,10 @@ func (server *EchoVault) PExpire(key string, milliseconds int, options PExpireOp
|
||||
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
return internal.ParseIntegerResponse(b)
|
||||
return internal.ParseBooleanResponse(b)
|
||||
}
|
||||
|
||||
// ExpireAt set the given key's expiry in unix epoch seconds.
|
||||
|
@@ -80,7 +80,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
time int
|
||||
expireOpts ExpireOptions
|
||||
pexpireOpts PExpireOptions
|
||||
want int
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@@ -92,7 +92,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetValues: map[string]internal.KeyData{
|
||||
"key1": {Value: "value1", ExpireAt: time.Time{}},
|
||||
},
|
||||
want: 1,
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -104,7 +104,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetValues: map[string]internal.KeyData{
|
||||
"key2": {Value: "value2", ExpireAt: time.Time{}},
|
||||
},
|
||||
want: 1,
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -116,11 +116,11 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetValues: map[string]internal.KeyData{
|
||||
"key3": {Value: "value3", ExpireAt: time.Time{}},
|
||||
},
|
||||
want: 1,
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Return 0 when NX flag is provided and key already has an expiry time",
|
||||
name: "Return false when NX flag is provided and key already has an expiry time",
|
||||
cmd: "EXPIRE",
|
||||
key: "key4",
|
||||
time: 1000,
|
||||
@@ -128,7 +128,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetValues: map[string]internal.KeyData{
|
||||
"key4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||
},
|
||||
want: 0,
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -140,11 +140,11 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetValues: map[string]internal.KeyData{
|
||||
"key5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||
},
|
||||
want: 1,
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Return 0 when key does not have an expiry and the XX flag is provided",
|
||||
name: "Return false when key does not have an expiry and the XX flag is provided",
|
||||
cmd: "EXPIRE",
|
||||
time: 1000,
|
||||
expireOpts: ExpireOptions{XX: true},
|
||||
@@ -152,7 +152,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetValues: map[string]internal.KeyData{
|
||||
"key6": {Value: "value6", ExpireAt: time.Time{}},
|
||||
},
|
||||
want: 0,
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -164,11 +164,11 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetValues: map[string]internal.KeyData{
|
||||
"key7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||
},
|
||||
want: 1,
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Return 0 when GT flag is passed and current expiry time is greater than provided time",
|
||||
name: "Return false when GT flag is passed and current expiry time is greater than provided time",
|
||||
cmd: "EXPIRE",
|
||||
key: "key8",
|
||||
time: 1000,
|
||||
@@ -176,11 +176,11 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetValues: map[string]internal.KeyData{
|
||||
"key8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||
},
|
||||
want: 0,
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Return 0 when GT flag is passed and key does not have an expiry time",
|
||||
name: "Return false when GT flag is passed and key does not have an expiry time",
|
||||
cmd: "EXPIRE",
|
||||
key: "key9",
|
||||
time: 1000,
|
||||
@@ -188,7 +188,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetValues: map[string]internal.KeyData{
|
||||
"key9": {Value: "value9", ExpireAt: time.Time{}},
|
||||
},
|
||||
want: 0,
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -200,11 +200,11 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetValues: map[string]internal.KeyData{
|
||||
"key10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||
},
|
||||
want: 1,
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Return 0 when LT flag is passed and current expiry time is less than provided time",
|
||||
name: "Return false when LT flag is passed and current expiry time is less than provided time",
|
||||
cmd: "EXPIRE",
|
||||
key: "key11",
|
||||
time: 50000,
|
||||
@@ -212,7 +212,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetValues: map[string]internal.KeyData{
|
||||
"key11": {Value: "value11", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||
},
|
||||
want: 0,
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
@@ -223,7 +223,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
||||
presetKeyData(server, context.Background(), k, d)
|
||||
}
|
||||
}
|
||||
var got int
|
||||
var got bool
|
||||
var err error
|
||||
if strings.EqualFold(tt.cmd, "PEXPIRE") {
|
||||
got, err = server.PExpire(tt.key, tt.time, tt.pexpireOpts)
|
||||
@@ -752,13 +752,13 @@ func TestEchoVault_MSET(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
kvPairs map[string]string
|
||||
want string
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Set multiple keys",
|
||||
kvPairs: map[string]string{"key1": "value1", "key2": "10", "key3": "3.142"},
|
||||
want: "OK",
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
@@ -87,6 +87,8 @@ type ZMPopOptions struct {
|
||||
//
|
||||
// ByLex returns the elements within the lexicographical ranges specified.
|
||||
//
|
||||
// Rev reverses the result from the previous filters.
|
||||
//
|
||||
// Offset specifies the offset to from which to start the ZRange process.
|
||||
//
|
||||
// Count specifies the number of elements to return.
|
||||
@@ -94,6 +96,7 @@ type ZRangeOptions struct {
|
||||
WithScores bool
|
||||
ByScore bool
|
||||
ByLex bool
|
||||
Rev bool
|
||||
Offset uint
|
||||
Count uint
|
||||
}
|
||||
@@ -864,6 +867,62 @@ func (server *EchoVault) ZRemRangeByScore(key string, min float64, max float64)
|
||||
return internal.ParseIntegerResponse(b)
|
||||
}
|
||||
|
||||
// ZRemRangeByLex Removes the elements that are lexicographically between min and max.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// `key` - string - The keys to the sorted set.
|
||||
//
|
||||
// `min` - string - The minimum lexicographic boundary.
|
||||
//
|
||||
// `max` - string - The maximum lexicographic boundary.
|
||||
//
|
||||
// Returns: The number of elements that were successfully removed.
|
||||
//
|
||||
// Errors:
|
||||
//
|
||||
// "value at <key> is not a sorted set" - when a key exists but is not a sorted set.
|
||||
func (server *EchoVault) ZRemRangeByLex(key, min, max string) (int, error) {
|
||||
b, err := server.handleCommand(
|
||||
server.context, internal.EncodeCommand([]string{"ZREMRANGEBYLEX", key, min, max}),
|
||||
nil,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return internal.ParseIntegerResponse(b)
|
||||
}
|
||||
|
||||
// ZRemRangeByRank Removes the elements that are ranked between min and max.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// `key` - string - The keys to the sorted set.
|
||||
//
|
||||
// `min` - int - The minimum rank boundary.
|
||||
//
|
||||
// `max` - int - The maximum rank boundary.
|
||||
//
|
||||
// Returns: The number of elements that were successfully removed.
|
||||
//
|
||||
// Errors:
|
||||
//
|
||||
// "value at <key> is not a sorted set" - when a key exists but is not a sorted set.
|
||||
func (server *EchoVault) ZRemRangeByRank(key string, min, max int) (int, error) {
|
||||
b, err := server.handleCommand(
|
||||
server.context, internal.EncodeCommand([]string{"ZREMRANGEBYRANK", key, strconv.Itoa(min), strconv.Itoa(max)}),
|
||||
nil,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return internal.ParseIntegerResponse(b)
|
||||
}
|
||||
|
||||
// ZRange Returns the range of elements in the sorted set.
|
||||
//
|
||||
// Parameters:
|
||||
@@ -889,6 +948,8 @@ func (server *EchoVault) ZRange(key, start, stop string, options ZRangeOptions)
|
||||
cmd = append(cmd, "BYSCORE")
|
||||
case options.ByLex:
|
||||
cmd = append(cmd, "BYLEX")
|
||||
case options.Rev:
|
||||
cmd = append(cmd, "REV")
|
||||
default:
|
||||
cmd = append(cmd, "BYSCORE")
|
||||
}
|
||||
@@ -941,6 +1002,8 @@ func (server *EchoVault) ZRangeStore(destination, source, start, stop string, op
|
||||
cmd = append(cmd, "BYSCORE")
|
||||
case options.ByLex:
|
||||
cmd = append(cmd, "BYLEX")
|
||||
case options.Rev:
|
||||
cmd = append(cmd, "REV")
|
||||
default:
|
||||
cmd = append(cmd, "BYSCORE")
|
||||
}
|
||||
|
@@ -333,8 +333,6 @@ func Test_AdminCommand(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Test MODULE LOAD command", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
port, err := internal.GetFreePort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -503,8 +501,6 @@ func Test_AdminCommand(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Test MODULE UNLOAD command", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
port, err := internal.GetFreePort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -527,6 +523,7 @@ func Test_AdminCommand(t *testing.T) {
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
respConn := resp.NewConn(conn)
|
||||
@@ -692,8 +689,6 @@ func Test_AdminCommand(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Test MODULE LIST command", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
port, err := internal.GetFreePort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -716,6 +711,7 @@ func Test_AdminCommand(t *testing.T) {
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
respConn := resp.NewConn(conn)
|
||||
|
@@ -359,7 +359,7 @@ func handleExpire(params internal.HandlerFuncParams) ([]byte, error) {
|
||||
}
|
||||
|
||||
if _, err = params.KeyLock(params.Context, key); err != nil {
|
||||
return nil, err
|
||||
return []byte(":0\r\n"), err
|
||||
}
|
||||
defer params.KeyUnlock(params.Context, key)
|
||||
|
||||
@@ -496,7 +496,7 @@ PXAT - Expire at the exat time in unix milliseconds (positive integer).`,
|
||||
Command: "mset",
|
||||
Module: constants.GenericModule,
|
||||
Categories: []string{constants.WriteCategory, constants.SlowCategory},
|
||||
Description: "(MSET key value [key value ...]) Automatically generic or modify multiple key/value pairs.",
|
||||
Description: "(MSET key value [key value ...]) Automatically set or modify multiple key/value pairs.",
|
||||
Sync: true,
|
||||
KeyExtractionFunc: msetKeyFunc,
|
||||
HandlerFunc: handleMSet,
|
||||
|
@@ -1769,7 +1769,7 @@ The elements are ordered from lowest score to highest score`,
|
||||
Categories: []string{constants.SortedSetCategory, constants.ReadCategory, constants.SlowCategory},
|
||||
Description: `(ZLEXCOUNT key min max) Returns the number of elements in within the sorted set within the
|
||||
lexicographical range between min and max. Returns 0, if the keys does not exist or if all the members do not have
|
||||
the same score. If the value held at key is not a sorted set, an error is returned`,
|
||||
the same score. If the value held at key is not a sorted set, an error is returned.`,
|
||||
Sync: false,
|
||||
KeyExtractionFunc: zlexcountKeyFunc,
|
||||
HandlerFunc: handleZLEXCOUNT,
|
||||
@@ -1779,7 +1779,7 @@ the same score. If the value held at key is not a sorted set, an error is return
|
||||
Module: constants.SortedSetModule,
|
||||
Categories: []string{constants.SortedSetCategory, constants.ReadCategory, constants.SlowCategory},
|
||||
Description: `(ZRANGE key start stop [BYSCORE | BYLEX] [REV] [LIMIT offset count]
|
||||
[WITHSCORES]) Returns the range of elements in the sorted set`,
|
||||
[WITHSCORES]) Returns the range of elements in the sorted set.`,
|
||||
Sync: false,
|
||||
KeyExtractionFunc: zrangeKeyCount,
|
||||
HandlerFunc: handleZRANGE,
|
||||
@@ -1789,7 +1789,7 @@ the same score. If the value held at key is not a sorted set, an error is return
|
||||
Module: constants.SortedSetModule,
|
||||
Categories: []string{constants.SortedSetCategory, constants.WriteCategory, constants.SlowCategory},
|
||||
Description: `ZRANGESTORE destination source start stop [BYSCORE | BYLEX] [REV] [LIMIT offset count]
|
||||
[WITHSCORES] Retrieve the range of elements in the sorted set and store it in destination`,
|
||||
[WITHSCORES] Retrieve the range of elements in the sorted set and store it in destination.`,
|
||||
Sync: true,
|
||||
KeyExtractionFunc: zrangeStoreKeyFunc,
|
||||
HandlerFunc: handleZRANGESTORE,
|
||||
|
Reference in New Issue
Block a user