Files
SugarDB/sugardb/api_generic.go
2025-04-14 01:05:48 +01:00

839 lines
25 KiB
Go

// Copyright 2024 Kelvin Clement Mwinuka
//
// 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
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sugardb
import (
"fmt"
"strconv"
"strings"
"github.com/echovault/sugardb/internal"
)
// SetWriteOption constants
type SetWriteOpt string
const (
SETNX SetWriteOpt = "NX"
SETXX SetWriteOpt = "XX"
)
// SetWriteOption modifies the behavior of Set.
//
// SETNX - Only set if the key does not exist.
//
// SETXX - Only set if the key exists.
type SetWriteOption interface {
IsSetWriteOpt() SetWriteOpt
}
func (w SetWriteOpt) IsSetWriteOpt() SetWriteOpt { return w }
// SetExOption constants
type SetExOpt string
const (
SETEX SetExOpt = "EX"
SETPX SetExOpt = "PX"
SETEXAT SetExOpt = "EXAT"
SETPXAT SetExOpt = "PXAT"
)
// SetExOption modifies the behavior of Set.
//
// SETEX - Expire the key after the specified number of seconds (positive integer).
//
// SETPX - Expire the key after the specified number of milliseconds (positive integer).
//
// SETEXAT - Expire at the exact time in unix seconds (positive integer).
//
// SETPXAT - Expire at the exact time in unix milliseconds (positive integer).
type SetExOption interface {
IsSetExOpt() SetExOpt
}
func (x SetExOpt) IsSetExOpt() SetExOpt { return x }
// SETOptions is a struct wrapper for all optional parameters of the Set command.
//
// `WriteOpt` - SetWriteOption - One of SETNX or SETXX.
//
// `ExpireOpt` - SetExOption - One of SETEX, SETPX, SETEXAT, or SETPXAT.
//
// `ExpireTime` - int - Time in seconds or milliseconds depending on what ExpireOpt was provided.
//
// `GET` - bool - Whether to return previous value if there was one.
type SETOptions struct {
WriteOpt SetWriteOption
ExpireOpt SetExOption
ExpireTime int
Get bool
}
// ExpireOptions constants
type ExOpt string
const (
NX ExOpt = "NX"
XX ExOpt = "XX"
LT ExOpt = "LT"
GT ExOpt = "GT"
)
// ExpireOptions modifies the behavior of Expire, PExpire, ExpireAt, PExpireAt.
//
// NX - Only set the expiry time if the key has no associated expiry.
//
// XX - Only set the expiry time if the key already has an expiry time.
//
// GT - Only set the expiry time if the new expiry time is greater than the current one.
//
// LT - Only set the expiry time if the new expiry time is less than the current one.
//
// NX, GT, and LT are mutually exclusive. XX can additionally be passed in with either GT or LT.
//
// Hash only: NX, XX, GT, and LT are all mutually exclusive.
type ExpireOptions interface {
IsExOpt() ExOpt
}
func (x ExOpt) IsExOpt() ExOpt { return x }
// GetExOption constants
type GetExOpt string
const (
EX GetExOpt = "EX"
PX GetExOpt = "PX"
EXAT GetExOpt = "EXAT"
PXAT GetExOpt = "PXAT"
PERSIST GetExOpt = "PERSIST"
)
// GetExOption modifies the behavior of GetEx.
//
// EX - Set the specified expire time, in seconds.
//
// PX - Set the specified expire time, in milliseconds.
//
// EXAT - Set the specified Unix time at which the key will expire, in seconds.
//
// PXAT - Set the specified Unix time at which the key will expire, in milliseconds.
//
// PERSIST - Remove the time to live associated with the key.
type GetExOption interface {
isGetExOpt() GetExOpt
}
func (x GetExOpt) isGetExOpt() GetExOpt { return x }
// COPYOptions is a struct wrapper for all optional parameters of the Copy command.
//
// `Database` - string - Logical database index
//
// `Replace` - bool - Whether to replace the destination key if it exists
type COPYOptions struct {
Database string
Replace bool
}
// Set creates or modifies the value at the given key.
//
// Parameters:
//
// `key` - string - the key to create or update.
//
// `value` - string - the value to place at the key.
//
// 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> already exists" - when the NX flag is set to true and the key already exists.
func (server *SugarDB) Set(key, value string, options SETOptions) (string, bool, error) {
cmd := []string{"SET", key, value}
if options.WriteOpt != nil {
cmd = append(cmd, fmt.Sprint(options.WriteOpt))
}
if options.ExpireOpt != nil {
cmd = append(cmd, []string{fmt.Sprint(options.ExpireOpt), strconv.Itoa(options.ExpireTime)}...)
}
if options.Get {
cmd = append(cmd, "GET")
}
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return "", false, err
}
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
// keys are created.
//
// Parameters:
//
// `kvPairs` - map[string]string - a map representing all the keys and values to be set.
//
// Returns: true if the set is successful.
//
// Errors:
//
// "key <key> already exists" - when the NX flag is set to true and the key already exists.
func (server *SugarDB) MSet(kvPairs map[string]string) (bool, error) {
cmd := []string{"MSET"}
for k, v := range kvPairs {
cmd = append(cmd, []string{k, v}...)
}
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return false, err
}
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.
//
// Parameters:
//
// `key` - string - the key whose value should be retrieved.
//
// Returns: A string representing the value at the specified key. If the value does not exist, an empty
// string is returned.
func (server *SugarDB) Get(key string) (string, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"GET", key}), nil, false, true)
if err != nil {
return "", err
}
return internal.ParseStringResponse(b)
}
// MGet get multiple values from the list of provided keys. The index of each value corresponds to the index of its key
// in the parameter slice. Values that do not exist will be an empty string.
//
// Parameters:
//
// `keys` - []string - a string slice of all the keys.
//
// Returns: a string slice of all the values.
func (server *SugarDB) MGet(keys ...string) ([]string, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand(append([]string{"MGET"}, keys...)), nil, false, true)
if err != nil {
return []string{}, err
}
return internal.ParseStringArrayResponse(b)
}
// Del removes the given keys from the store.
//
// Parameters:
//
// `keys` - []string - the keys to delete from the store.
//
// Returns: The number of keys that were successfully deleted.
func (server *SugarDB) Del(keys ...string) (int, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand(append([]string{"DEL"}, keys...)), nil, false, true)
if err != nil {
return 0, err
}
return internal.ParseIntegerResponse(b)
}
// Persist removes the expiry associated with a key and makes it permanent.
// Has no effect on a key that is already persistent.
//
// Parameters:
//
// `key` - string - the key to persist.
//
// Returns: true if the keys is successfully persisted.
func (server *SugarDB) Persist(key string) (bool, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"PERSIST", key}), nil, false, true)
if err != nil {
return false, err
}
return internal.ParseBooleanResponse(b)
}
// ExpireTime return the current key's expiry time in unix epoch seconds.
//
// Parameters:
//
// `key` - string.
//
// Returns: -2 if the keys does not exist, -1 if the key exists but has no expiry time, seconds if the key has an expiry.
func (server *SugarDB) ExpireTime(key string) (int, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"EXPIRETIME", key}), nil, false, true)
if err != nil {
return 0, err
}
return internal.ParseIntegerResponse(b)
}
// PExpireTime return the current key's expiry time in unix epoch milliseconds.
//
// Parameters:
//
// `key` - string.
//
// Returns: -2 if the keys does not exist, -1 if the key exists but has no expiry time, seconds if the key has an expiry.
func (server *SugarDB) PExpireTime(key string) (int, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"PEXPIRETIME", key}), nil, false, true)
if err != nil {
return 0, err
}
return internal.ParseIntegerResponse(b)
}
// TTL return the current key's expiry time from now in seconds.
//
// Parameters:
//
// `key` - string.
//
// Returns: -2 if the keys does not exist, -1 if the key exists but has no expiry time, seconds if the key has an expiry.
func (server *SugarDB) TTL(key string) (int, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"TTL", key}), nil, false, true)
if err != nil {
return 0, err
}
return internal.ParseIntegerResponse(b)
}
// PTTL return the current key's expiry time from now in milliseconds.
//
// Parameters:
//
// `key` - string.
//
// Returns: -2 if the keys does not exist, -1 if the key exists but has no expiry time, seconds if the key has an expiry.
func (server *SugarDB) PTTL(key string) (int, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"PTTL", key}), nil, false, true)
if err != nil {
return 0, err
}
return internal.ParseIntegerResponse(b)
}
// Expire set the given key's expiry in seconds from now.
// This command turns a persistent key into a volatile one.
//
// Parameters:
//
// `key` - string.
//
// `seconds` - int - number of seconds from now.
//
// `options` - ExpireOptions - One of NX, GT, LT. XX can be passed with GT OR LT optionally.
//
// Returns: true if the key's expiry was successfully updated.
func (server *SugarDB) Expire(key string, seconds int, options ...ExpireOptions) (bool, error) {
cmd := []string{"EXPIRE", key, strconv.Itoa(seconds)}
for _, opt := range options {
if opt != nil {
cmd = append(cmd, fmt.Sprint(opt))
}
}
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return false, err
}
return internal.ParseBooleanResponse(b)
}
// PExpire set the given key's expiry in milliseconds from now.
// This command turns a persistent key into a volatile one.
//
// Parameters:
//
// `key` - string.
//
// `milliseconds` - int - number of milliseconds from now.
//
// `options` - PExpireOptions
//
// Returns: true if the key's expiry was successfully updated.
func (server *SugarDB) PExpire(key string, milliseconds int, options ...ExpireOptions) (bool, error) {
cmd := []string{"PEXPIRE", key, strconv.Itoa(milliseconds)}
for _, opt := range options {
if opt != nil {
cmd = append(cmd, fmt.Sprint(opt))
}
}
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return false, err
}
return internal.ParseBooleanResponse(b)
}
// ExpireAt sets the given key's expiry in unix epoch seconds.
// This command turns a persistent key into a volatile one.
//
// Parameters:
//
// `key` - string.
//
// `unixSeconds` - int - number of seconds from now.
//
// `options` - ExpireAtOptions
//
// Returns: true if the key's expiry was successfully updated.
func (server *SugarDB) ExpireAt(key string, unixSeconds int, options ...ExpireOptions) (int, error) {
cmd := []string{"EXPIREAT", key, strconv.Itoa(unixSeconds)}
for _, opt := range options {
if opt != nil {
cmd = append(cmd, fmt.Sprint(opt))
}
}
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return 0, err
}
return internal.ParseIntegerResponse(b)
}
// PExpireAt set the given key's expiry in unix epoch milliseconds.
// This command turns a persistent key into a volatile one.
//
// Parameters:
//
// `key` - string.
//
// `unixMilliseconds` - int - number of seconds from now.
//
// `options` - PExpireAtOptions
//
// Returns: true if the key's expiry was successfully updated.
func (server *SugarDB) PExpireAt(key string, unixMilliseconds int, options ...ExpireOptions) (int, error) {
cmd := []string{"PEXPIREAT", key, strconv.Itoa(unixMilliseconds)}
for _, opt := range options {
if opt != nil {
cmd = append(cmd, fmt.Sprint(opt))
}
}
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return 0, err
}
return internal.ParseIntegerResponse(b)
}
// Incr increments the value at the given key if it's an integer.
// If the key does not exist, it's created with an initial value of 0 before incrementing.
//
// Parameters:
//
// `key` - string
//
// Returns: The new value as an integer.
func (server *SugarDB) Incr(key string) (int, error) {
// Construct the command
cmd := []string{"INCR", key}
// Execute the command
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return 0, err
}
// Parse the integer response
return internal.ParseIntegerResponse(b)
}
// Decr decrements the value at the given key if it's an integer.
// If the key does not exist, it's created with an initial value of 0 before incrementing.
//
// Parameters:
//
// `key` - string
//
// Returns: The new value as an integer.
func (server *SugarDB) Decr(key string) (int, error) {
// Construct the command
cmd := []string{"DECR", key}
// Execute the command
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return 0, err
}
// Parse the integer response
return internal.ParseIntegerResponse(b)
}
// IncrBy increments the integer value of the specified key by the given increment.
// If the key does not exist, it is created with an initial value of 0 before incrementing.
// If the value stored at the key is not an integer, an error is returned.
//
// Parameters:
//
// `key` - string - The key whose value is to be incremented.
//
// `increment` - int - The amount by which to increment the key's value. This can be a positive or negative integer.
//
// Returns: The new value of the key after the increment operation as an integer.
func (server *SugarDB) IncrBy(key string, value string) (int, error) {
// Construct the command
cmd := []string{"INCRBY", key, value}
// Execute the command
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return 0, err
}
// Parse the integer response
return internal.ParseIntegerResponse(b)
}
// IncrByFloat increments the floating-point value of the specified key by the given increment.
// If the key does not exist, it is created with an initial value of 0 before incrementing.
// If the value stored at the key is not a float, an error is returned.
//
// Parameters:
//
// `key` - string - The key whose value is to be incremented.
//
// `increment` - float64 - The amount by which to increment the key's value. This can be a positive or negative float.
//
// Returns: The new value of the key after the increment operation as a float64.
func (server *SugarDB) IncrByFloat(key string, value string) (float64, error) {
// Construct the command
cmd := []string{"INCRBYFLOAT", key, value}
// Execute the command
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return 0, err
}
// Parse the float response
return internal.ParseFloatResponse(b)
}
// DecrBy decrements the integer value of the specified key by the given increment.
// If the key does not exist, it is created with an initial value of 0 before decrementing.
// If the value stored at the key is not an integer, an error is returned.
//
// Parameters:
//
// `key` - string - The key whose value is to be decremented.
//
// `increment` - int - The amount by which to decrement the key's value. This can be a positive or negative integer.
//
// Returns: The new value of the key after the decrement operation as an integer.
func (server *SugarDB) DecrBy(key string, value string) (int, error) {
// Construct the command
cmd := []string{"DECRBY", key, value}
// Execute the command
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return 0, err
}
// Parse the integer response
return internal.ParseIntegerResponse(b)
}
// Rename renames the key from oldKey to newKey.
// If the oldKey does not exist, an error is returned.
//
// Parameters:
//
// `oldKey` - string - The key to be renamed.
//
// `newKey` - string - The new name for the key.
//
// Returns: A string indicating the success of the operation.
func (server *SugarDB) Rename(oldKey string, newKey string) (string, error) {
// Construct the command
cmd := []string{"RENAME", oldKey, newKey}
// Execute the command
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return "", err
}
// Parse the simple string response
return internal.ParseStringResponse(b)
}
// RenameNX renames the specified key with the new name only if the new name does not already exist.
//
// Parameters:
//
// `oldKey` - string - The key to be renamed.
//
// `newKey` - string - The new name for the key.
//
// Returns: A string indicating the success of the operation.
func (server *SugarDB) RenameNX(oldKey string, newKey string) (string, error) {
// Construct the command
cmd := []string{"RENAMENX", oldKey, newKey}
// Execute the command
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return "", err
}
// Parse the simple string response
return internal.ParseStringResponse(b)
}
// RandomKey returns a random key from the current active database.
// If no keys present in db returns an empty string.
func (server *SugarDB) RandomKey() (string, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"RANDOMKEY"}), nil, false, true)
if err != nil {
return "", err
}
return internal.ParseStringResponse(b)
}
// DBSize returns the number of keys in the currently-selected database.
// Returns: An integer number of keys
func (server *SugarDB) DBSize() (int, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"DBSIZE"}), nil, false, true)
if err != nil {
return 0, err
}
return internal.ParseIntegerResponse(b)
}
// GetDel retrieves the value at the provided key and deletes that key.
//
// Parameters:
//
// `key` - string - the key whose value should be retrieved and then deleted.
//
// Returns: A string representing the value at the specified key. If the value does not exist, an empty
// string is returned.
func (server *SugarDB) GetDel(key string) (string, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"GETDEL", key}), nil, false, true)
if err != nil {
return "", err
}
return internal.ParseStringResponse(b)
}
// GetEx retrieves the value of the provided key and optionally sets its expiration
//
// Parameters:
//
// `key` - string - the key whose value should be retrieved and expiry set.
//
// `option` - GetExOption - one of EX, PX, EXAT, PXAT, PERSIST. Can be nil.
//
// `unixtime` - int - Number of seconds or milliseconds from now.
//
// Returns: A string representing the value at the specified key. If the value does not exist, an empty string is returned.
func (server *SugarDB) GetEx(key string, option GetExOption, unixtime int) (string, error) {
cmd := make([]string, 2)
cmd[0] = "GETEX"
cmd[1] = key
if option != nil {
opt := fmt.Sprint(option)
cmd = append(cmd, opt)
}
if unixtime != 0 {
cmd = append(cmd, strconv.Itoa(unixtime))
}
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return "", err
}
return internal.ParseStringResponse(b)
}
// Touch Alters the last access time or access count of the key(s) depending on whether LFU or LRU strategy was used.
// A key is ignored if it does not exist.
//
// Parameters:
//
// `keys` - ...string - the keys whose access time or access count should be incremented based on eviction policy.
//
// Returns: An integer representing the number of keys successfully touched. If a key doesn't exist it is simply ignored.
func (server *SugarDB) Touch(keys ...string) (int, error) {
cmd := make([]string, len(keys)+1)
cmd[0] = "TOUCH"
for i, k := range keys {
cmd[i+1] = k
}
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return -1, err
}
return internal.ParseIntegerResponse(b)
}
// ObjectFreq retrieves the access frequency count of an object stored at <key>.
// The command is only available when the maxmemory-policy configuration directive is set to one of the LFU policies.
//
// Parameters:
//
// `key` - string - the key whose access frequency should be retrieved.
//
// Returns: An integer representing the access frequency. If the key doesn't exist -1 and an error is returned.
func (server *SugarDB) ObjectFreq(key string) (int, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"OBJECTFREQ", key}), nil, false, true)
if err != nil {
return -1, err
}
return internal.ParseIntegerResponse(b)
}
// ObjectIdleTime retrieves the time in seconds since the last access to the value stored at <key>.
// The command is only available when the maxmemory-policy configuration directive is set to one of the LRU policies.
//
// Parameters:
//
// `key` - string - the key whose last access time should be retrieved.
//
// Returns: A float64 representing the seconds since the key was last accessed. If the key doesn't exist -1 and an error is returned.
func (server *SugarDB) ObjectIdleTime(key string) (float64, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"OBJECTIDLETIME", key}), nil, false, true)
if err != nil {
return -1, err
}
return internal.ParseFloatResponse(b)
}
// Type returns the string representation of the type of the value stored at key.
// The different types that can be returned are: string, integer, float, list, set, zset, and hash.
//
// Parameters:
//
// `key` - string - the key whose type should be returned
//
// Returns: A string representation of the type of the value stored at key, if the key doesn't exist an empty string and error is returned
func (server *SugarDB) Type(key string) (string, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"TYPE", key}), nil, false, true)
if err != nil {
return "", err
}
return internal.ParseStringResponse(b)
}
// Copy copies a value of a source key to destination key.
//
// Parameters:
//
// `source` - string - the source key from which data is to be copied
//
// `destination` - string - the destination key where data should be copied
//
// Returns: 1 if the copy is successful. 0 if the copy is unsuccessful
func (server *SugarDB) Copy(sourceKey, destinationKey string, options COPYOptions) (int, error) {
cmd := []string{"COPY", sourceKey, destinationKey}
if options.Database != "" {
cmd = append(cmd, "db", options.Database)
}
if options.Replace {
cmd = append(cmd, "replace")
}
b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true)
if err != nil {
return 0, err
}
return internal.ParseIntegerResponse(b)
}
// Move key from currently selected database to specified destination database and return 1.
// When key already exists in the destination database, or it does not exist in the source database, it does nothing and returns 0.
//
// Parameters:
//
// `key` - string - the key that should be moved.
//
// `destinationDB` - int - the database the key should be moved to.
//
// Returns: 1 if successful, 0 if unsuccessful.
func (server *SugarDB) Move(key string, destinationDB int) (int, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"Move", key, strconv.Itoa(destinationDB)}), nil, false, true)
if err != nil {
return 0, err
}
return internal.ParseIntegerResponse(b)
}
// Exists returns the number of keys that exist from the provided list of keys.
// Note: Duplicate keys in the argument list are each counted separately.
//
// Parameters:
//
// `keys` - ...string - the keys whose existence should be checked.
//
// Returns: An integer representing the number of keys that exist.
func (server *SugarDB) Exists(keys ...string) (int, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand(append([]string{"EXISTS"}, keys...)), nil, false, true)
if err != nil {
return 0, err
}
return internal.ParseIntegerResponse(b)
}
// Keys returns all of the keys matching the glob pattern of the given key.
// Parameters:
//
// `pattern` - string - pattern of key to match on
//
// Returns: A string slice of all the matching keys. If there are no keys matching the pattern, an empty slice is returned.
func (server *SugarDB) Keys(pattern string) ([]string, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"KEYS", pattern}), nil, false, true)
if err != nil {
return nil, err
}
return internal.ParseStringArrayResponse(b)
}