cmd_set.go
cmd_set.go - Overview
This file implements the SET
command, which is responsible for setting or updating a key-value pair in the DiceDB store. It supports various options for setting expiration times, conditional setting based on key existence, and retrieving the value after setting it.
Detailed Documentation
cSET
var cSET = &CommandMeta{
Name: "SET",
Syntax: "SET key value [EX seconds] [PX milliseconds] [EXAT timestamp] [PXAT timestamp] [XX] [NX] [KEEPTTL] [GET]",
HelpShort: "SET puts or updates an existing <key, value> pair",
HelpLong: `
SET puts or updates an existing <key, value> pair.
SET identifies the type of the value based on the value itself. If the value is an integer,
it will be stored as an integer. Otherwise, it will be stored as a string.
- EX seconds: Set the expiration time in seconds
- PX milliseconds: Set the expiration time in milliseconds
- EXAT timestamp: Set the expiration time in seconds since epoch
- PXAT timestamp: Set the expiration time in milliseconds since epoch
- XX: Only set the key if it already exists
- NX: Only set the key if it does not already exist
- KEEPTTL: Keep the existing TTL of the key
- GET: Return the value of the key after setting it
Returns "OK" if the key was set or updated. Returns (nil) if the key was not set or updated.
Returns the value of the key if the GET option is provided.
`,
Examples: `
localhost:7379> SET k 43
OK OK
localhost:7379> SET k 43 EX 10
OK OK
localhost:7379> SET k 43 PX 10000
OK OK
localhost:7379> SET k 43 EXAT 1772377267
OK OK
localhost:7379> SET k 43 PXAT 1772377267000
OK OK
localhost:7379> SET k 43 XX
OK OK
localhost:7379> SET k 43 NX
OK (nil)
localhost:7379> SET k 43 KEEPTTL
OK OK
localhost:7379> SET k 43 GET
OK 43
`,
Eval: evalSET,
Execute: executeSET,
}
- Purpose: Defines the metadata for the
SET
command, including its name, syntax, help text, examples, evaluation function (evalSET
), and execution function (executeSET
). Name
: The name of the command ("SET").Syntax
: The command's syntax.HelpShort
: A short description of the command.HelpLong
: A detailed description of the command and its options.Examples
: Examples of how to use the command.Eval
: The function to evaluate the command (evalSET
).Execute
: The function to execute the command (executeSET
).
init()
func init() {
CommandRegistry.AddCommand(cSET)
}
- Purpose: Registers the
SET
command with theCommandRegistry
.
parseParams(args []string) (params map[types.Param]string, nonParams []string)
func parseParams(args []string) (params map[types.Param]string, nonParams []string) {
params = map[types.Param]string{}
for i := 0; i < len(args); i++ {
arg := types.Param(strings.ToUpper(args[i]))
switch arg {
case types.EX, types.PX, types.EXAT, types.PXAT:
params[arg] = args[i+1]
i++
case types.XX, types.NX, types.KEEPTTL, types.GET, types.LT, types.GT, types.CH, types.INCR:
params[arg] = "true"
default:
nonParams = append(nonParams, args[i])
}
}
return params, nonParams
}
- Purpose: Parses the parameters from the command arguments.
- Parameters:
args
([]string): The command arguments.
- Returns:
params
(map[types.Param]string): A map of parameters and their values.nonParams
([]string): A slice of arguments that are not parameters.
evalSET(c *Cmd, s *dstore.Store) (*CmdRes, error)
//nolint:gocyclo
func evalSET(c *Cmd, s *dstore.Store) (*CmdRes, error) {
if len(c.C.Args) <= 1 {
return cmdResNil, errors.ErrWrongArgumentCount("SET")
}
var key, value = c.C.Args[0], c.C.Args[1]
params, nonParams := parseParams(c.C.Args[2:])
if len(nonParams) > 0 {
return cmdResNil, errors.ErrInvalidSyntax("SET")
}
// Raise errors if incompatible parameters are provided
// in one command
if params[types.EX] != "" && params[types.PX] != "" {
return cmdResNil, errors.ErrInvalidSyntax("SET")
} else if params[types.EX] != "" && params[types.EXAT] != "" {
return cmdResNil, errors.ErrInvalidSyntax("SET")
} else if params[types.EX] != "" && params[types.PXAT] != "" {
return cmdResNil, errors.ErrInvalidSyntax("SET")
} else if params[types.PX] != "" && params[types.EXAT] != "" {
return cmdResNil, errors.ErrInvalidSyntax("SET")
} else if params[types.PX] != "" && params[types.PXAT] != "" {
return cmdResNil, errors.ErrInvalidSyntax("SET")
} else if params[types.EXAT] != "" && params[types.PXAT] != "" {
return cmdResNil, errors.ErrInvalidSyntax("SET")
} else if params[types.XX] != "" && params[types.NX] != "" {
return cmdResNil, errors.ErrInvalidSyntax("SET")
} else if params[types.KEEPTTL] != "" && (params[types.EX] != "" || params[types.PX] != "" || params[types.EXAT] != "" || params[types.PXAT] != "") {
return cmdResNil, errors.ErrInvalidSyntax("SET")
}
var err error
var exDurationSec, exDurationMs int64
// Default to -1 to indicate that the value is not set
// and the key will not expire
exDurationMs = -1
if params[types.EX] != "" {
exDurationSec, err = strconv.ParseInt(params[types.EX], 10, 64)
if err != nil {
return cmdResNil, errors.ErrInvalidValue("SET", "EX")
}
if exDurationSec <= 0 || exDurationSec >= MaxEXDurationSec {
return cmdResNil, errors.ErrInvalidValue("SET", "EX")
}
exDurationMs = exDurationSec * 1000
}
if params[types.PX] != "" {
exDurationMs, err = strconv.ParseInt(params[types.PX], 10, 64)
if err != nil {
return cmdResNil, errors.ErrInvalidValue("SET", "PX")
}
if exDurationMs <= 0 || exDurationMs >= MaxEXDurationSec {
return cmdResNil, errors.ErrInvalidValue("SET", "PX")
}
}
if params[types.EXAT] != "" {
tv, err := strconv.ParseInt(params[types.EXAT], 10, 64)
if err != nil {
return cmdResNil, errors.ErrInvalidValue("SET", "EXAT")
}
exDurationSec = tv - utils.GetCurrentTime().Unix()
if exDurationSec <= 0 || exDurationSec >= MaxEXDurationSec {
return cmdResNil, errors.ErrInvalidValue("SET", "EXAT")
}
exDurationMs = exDurationSec * 1000
}
if params[types.PXAT] != "" {
tv, err := strconv.ParseInt(params[types.PXAT], 10, 64)
if err != nil {
return cmdResNil, errors.ErrInvalidValue("SET", "PXAT")
}
exDurationMs = tv - utils.GetCurrentTime().UnixMilli()
if exDurationMs <= 0 || exDurationMs >= (MaxEXDurationSec*1000) {
return cmdResNil, errors.ErrInvalidValue("SET", "PXAT")
}
}
existingObj := s.Get(key)
// TODO: Add check for the type before doing the operation
// The scope of this is not clear and hence need some thought
// on how the database should react when a SET is called on
// a key that is not of the type that is being set.
// Example: existing key is of type string
// and now set is called with different type.
// Or, SET is called on a key that has some other type, like HLL
// Should we reject the operation?
// Or, should we convert the existing type to the new type?
// Or, should we just overwrite the value?
// If XX is provided and the key does not exist, return nil
// XX: only set the key if it already exists
// So, if it does not exist, we return nil and move on
if params[types.XX] != "" && existingObj == nil {
return cmdResNil, nil
}
// If NX is provided and the key already exists, return nil
// NX: only set the key if it does not already exist
// So, if it does exist, we return nil and move on
if params[types.NX] != "" && existingObj != nil {
return cmdResNil, nil
}
newObj := CreateObjectFromValue(s, value, exDurationMs)
s.Put(key, newObj, dstore.WithKeepTTL(params[types.KEEPTTL] != ""))
if params[types.GET] != "" {
// TODO: Optimize this because we have alread fetched the
// object in the existingObj variable. We can avoid executing
// the GET command again.
// If existingObj is nil then the key does not exist
// and we return nil
if existingObj == nil {
return cmdResNil, nil
}
return cmdResFromObject(existingObj)
}
return cmdResOK, nil
}
- Purpose: Evaluates the
SET
command, parsing parameters, validating them, and setting the key-value pair in the store. - Parameters:
c
(*Cmd): The command object.s
(*dstore.Store): The data store.
- Returns:
*CmdRes
: The command result.error
: An error if any.
executeSET(c *Cmd, sm *shardmanager.ShardManager) (*CmdRes, error)
func executeSET(c *Cmd, sm *shardmanager.ShardManager) (*CmdRes, error) {
if len(c.C.Args) <= 1 {
return cmdResNil, errors.ErrWrongArgumentCount("SET")
}
shard := sm.GetShardForKey(c.C.Args[0])
return evalSET(c, shard.Thread.Store())
}
- Purpose: Executes the
SET
command by getting the appropriate shard based on the key and then callingevalSET
. - Parameters:
c
(*Cmd): The command object.sm
(*shardmanager.ShardManager): The shard manager.
- Returns:
*CmdRes
: The command result.error
: An error if any.
CreateObjectFromValue(s *dstore.Store, value string, expiryMs int64) *object.Obj
func CreateObjectFromValue(s *dstore.Store, value string, expiryMs int64) *object.Obj {
intValue, err := strconv.ParseInt(value, 10, 64)
if err == nil {
return s.NewObj(intValue, expiryMs, object.ObjTypeInt)
}
floatValue, err := strconv.ParseFloat(value, 64)
if err == nil {
return s.NewObj(floatValue, expiryMs, object.ObjTypeFloat)
}
return s.NewObj(value, expiryMs, object.ObjTypeString)
}
- Purpose: Creates a new object based on the value provided, attempting to determine its type (integer, float, or string).
- Parameters:
s
(*dstore.Store): The data store.value
(string): The value to store.expiryMs
(int64): The expiration time in milliseconds.
- Returns:
*object.Obj
: The newly created object.