mirror of
				https://github.com/EchoVault/SugarDB.git
				synced 2025-10-31 11:26:52 +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
	 Kelvin Clement Mwinuka
					Kelvin Clement Mwinuka