mirror of https://mirror.osredm.com/root/redis.git
Delete hsetf and hgetf (#13291)
Changes: - Delete hsetf and hgetf commands - Hfe commands will return empty array instead of nil. --------- Co-authored-by: Moti Cohen <moticless@gmail.com>
This commit is contained in:
parent
60e1582ddb
commit
2f34f6f0b9
115
src/commands.def
115
src/commands.def
|
@ -3455,52 +3455,6 @@ struct COMMAND_ARG HGETALL_Args[] = {
|
|||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/********** HGETF ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* HGETF history */
|
||||
#define HGETF_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HGETF tips */
|
||||
#define HGETF_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* HGETF key specs */
|
||||
keySpec HGETF_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HGETF condition argument table */
|
||||
struct COMMAND_ARG HGETF_condition_Subargs[] = {
|
||||
{MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HGETF expiration argument table */
|
||||
struct COMMAND_ARG HGETF_expiration_Subargs[] = {
|
||||
{MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"EX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,"PX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,"EXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,"PXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("persist",ARG_TYPE_PURE_TOKEN,-1,"PERSIST",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HGETF argument table */
|
||||
struct COMMAND_ARG HGETF_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HGETF_condition_Subargs},
|
||||
{MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=HGETF_expiration_Subargs},
|
||||
{MAKE_ARG("fields",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/********** HINCRBY ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
|
@ -3910,73 +3864,6 @@ struct COMMAND_ARG HSET_Args[] = {
|
|||
{MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=HSET_data_Subargs},
|
||||
};
|
||||
|
||||
/********** HSETF ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* HSETF history */
|
||||
#define HSETF_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HSETF tips */
|
||||
#define HSETF_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* HSETF key specs */
|
||||
keySpec HSETF_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HSETF create option argument table */
|
||||
struct COMMAND_ARG HSETF_create_option_Subargs[] = {
|
||||
{MAKE_ARG("dcf",ARG_TYPE_PURE_TOKEN,-1,"DCF",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("dof",ARG_TYPE_PURE_TOKEN,-1,"DOF",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HSETF return option argument table */
|
||||
struct COMMAND_ARG HSETF_return_option_Subargs[] = {
|
||||
{MAKE_ARG("getnew",ARG_TYPE_PURE_TOKEN,-1,"GETNEW",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("getold",ARG_TYPE_PURE_TOKEN,-1,"GETOLD",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HSETF condition argument table */
|
||||
struct COMMAND_ARG HSETF_condition_Subargs[] = {
|
||||
{MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HSETF expiration argument table */
|
||||
struct COMMAND_ARG HSETF_expiration_Subargs[] = {
|
||||
{MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"EX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,"PX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,"EXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,"PXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("keepttl",ARG_TYPE_PURE_TOKEN,-1,"KEEPTTL",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HSETF data argument table */
|
||||
struct COMMAND_ARG HSETF_data_Subargs[] = {
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HSETF argument table */
|
||||
struct COMMAND_ARG HSETF_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("create key option",ARG_TYPE_PURE_TOKEN,-1,"DC",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
{MAKE_ARG("create option",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=HSETF_create_option_Subargs},
|
||||
{MAKE_ARG("return option",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=HSETF_return_option_Subargs},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HSETF_condition_Subargs},
|
||||
{MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=HSETF_expiration_Subargs},
|
||||
{MAKE_ARG("fvs",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=HSETF_data_Subargs},
|
||||
};
|
||||
|
||||
/********** HSETNX ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
|
@ -11111,7 +10998,6 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
|||
{MAKE_CMD("hexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in seconds.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRETIME_History,0,HEXPIRETIME_Tips,0,hexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HEXPIRETIME_Keyspecs,1,NULL,4),.args=HEXPIRETIME_Args},
|
||||
{MAKE_CMD("hget","Returns the value of a field in a hash.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGET_History,0,HGET_Tips,0,hgetCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HGET_Keyspecs,1,NULL,2),.args=HGET_Args},
|
||||
{MAKE_CMD("hgetall","Returns all fields and values in a hash.","O(N) where N is the size of the hash.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGETALL_History,0,HGETALL_Tips,1,hgetallCommand,2,CMD_READONLY,ACL_CATEGORY_HASH,HGETALL_Keyspecs,1,NULL,1),.args=HGETALL_Args},
|
||||
{MAKE_CMD("hgetf","For each specified field: get its value and optionally set the field's remaining time to live / UNIX expiration timestamp in seconds / milliseconds","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGETF_History,0,HGETF_Tips,0,hgetfCommand,-5,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HGETF_Keyspecs,1,NULL,6),.args=HGETF_Args},
|
||||
{MAKE_CMD("hincrby","Increments the integer value of a field in a hash by a number. Uses 0 as initial value if the field doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HINCRBY_History,0,HINCRBY_Tips,0,hincrbyCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HINCRBY_Keyspecs,1,NULL,3),.args=HINCRBY_Args},
|
||||
{MAKE_CMD("hincrbyfloat","Increments the floating point value of a field by a number. Uses 0 as initial value if the field doesn't exist.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HINCRBYFLOAT_History,0,HINCRBYFLOAT_Tips,0,hincrbyfloatCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HINCRBYFLOAT_Keyspecs,1,NULL,3),.args=HINCRBYFLOAT_Args},
|
||||
{MAKE_CMD("hkeys","Returns all fields in a hash.","O(N) where N is the size of the hash.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HKEYS_History,0,HKEYS_Tips,1,hkeysCommand,2,CMD_READONLY,ACL_CATEGORY_HASH,HKEYS_Keyspecs,1,NULL,1),.args=HKEYS_Args},
|
||||
|
@ -11126,7 +11012,6 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
|||
{MAKE_CMD("hrandfield","Returns one or more random fields from a hash.","O(N) where N is the number of fields returned","6.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HRANDFIELD_History,0,HRANDFIELD_Tips,1,hrandfieldCommand,-2,CMD_READONLY,ACL_CATEGORY_HASH,HRANDFIELD_Keyspecs,1,NULL,2),.args=HRANDFIELD_Args},
|
||||
{MAKE_CMD("hscan","Iterates over fields and values of a hash.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSCAN_History,0,HSCAN_Tips,1,hscanCommand,-3,CMD_READONLY,ACL_CATEGORY_HASH,HSCAN_Keyspecs,1,NULL,5),.args=HSCAN_Args},
|
||||
{MAKE_CMD("hset","Creates or modifies the value of a field in a hash.","O(1) for each field/value pair added, so O(N) to add N field/value pairs when the command is called with multiple field/value pairs.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSET_History,1,HSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSET_Keyspecs,1,NULL,2),.args=HSET_Args},
|
||||
{MAKE_CMD("hsetf","For each specified field value pair: set field to value and optionally set the field's remaining time to live / UNIX expiration timestamp in seconds / milliseconds","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSETF_History,0,HSETF_Tips,0,hsetfCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSETF_Keyspecs,1,NULL,9),.args=HSETF_Args},
|
||||
{MAKE_CMD("hsetnx","Sets the value of a field in a hash only when the field doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSETNX_History,0,HSETNX_Tips,0,hsetnxCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSETNX_Keyspecs,1,NULL,3),.args=HSETNX_Args},
|
||||
{MAKE_CMD("hstrlen","Returns the length of the value of a field.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSTRLEN_History,0,HSTRLEN_Tips,0,hstrlenCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HSTRLEN_Keyspecs,1,NULL,2),.args=HSTRLEN_Args},
|
||||
{MAKE_CMD("httl","Returns the TTL in seconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,0,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HTTL_Keyspecs,1,NULL,4),.args=HTTL_Args},
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
{
|
||||
"HGETF": {
|
||||
"summary": "For each specified field: get its value and optionally set the field's remaining time to live / UNIX expiration timestamp in seconds / milliseconds",
|
||||
"complexity": "O(N) where N is the number of specified fields",
|
||||
"group": "hash",
|
||||
"since": "7.4.0",
|
||||
"arity": -5,
|
||||
"function": "hgetfCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Key does not exist.",
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"description": "Array of results",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"description": "Field value",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "condition",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "nx",
|
||||
"type": "pure-token",
|
||||
"token": "NX"
|
||||
},
|
||||
{
|
||||
"name": "xx",
|
||||
"type": "pure-token",
|
||||
"token": "XX"
|
||||
},
|
||||
{
|
||||
"name": "gt",
|
||||
"type": "pure-token",
|
||||
"token": "GT"
|
||||
},
|
||||
{
|
||||
"name": "lt",
|
||||
"type": "pure-token",
|
||||
"token": "LT"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "expiration",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "seconds",
|
||||
"type": "integer",
|
||||
"token": "EX"
|
||||
},
|
||||
{
|
||||
"name": "milliseconds",
|
||||
"type": "integer",
|
||||
"token": "PX"
|
||||
},
|
||||
{
|
||||
"name": "unix-time-seconds",
|
||||
"type": "unix-time",
|
||||
"token": "EXAT"
|
||||
},
|
||||
{
|
||||
"name": "unix-time-milliseconds",
|
||||
"type": "unix-time",
|
||||
"token": "PXAT"
|
||||
},
|
||||
{
|
||||
"name": "persist",
|
||||
"type": "pure-token",
|
||||
"token": "PERSIST"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FIELDS",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
{
|
||||
"HSETF": {
|
||||
"summary": "For each specified field value pair: set field to value and optionally set the field's remaining time to live / UNIX expiration timestamp in seconds / milliseconds",
|
||||
"complexity": "O(N) where N is the number of specified fields",
|
||||
"group": "hash",
|
||||
"since": "7.4.0",
|
||||
"arity": -6,
|
||||
"function": "hsetfCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Key does not exist and DC condition was given",
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"description": "Array of field values when GETNEW/GETOLD is used",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Field value",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Field does not exist and couldn't create it (DCF not met)",
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Array of results",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Cannot set field value. DOC/DOF condition not met.",
|
||||
"const": 0
|
||||
},
|
||||
{
|
||||
"description": "Set field value without updating TTL",
|
||||
"const": 1
|
||||
},
|
||||
{
|
||||
"description": "Set field value and updated TTL",
|
||||
"const": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "create key option",
|
||||
"optional": true,
|
||||
"type": "pure-token",
|
||||
"token": "DC"
|
||||
},
|
||||
{
|
||||
"name": "create option",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "dcf",
|
||||
"type": "pure-token",
|
||||
"token": "DCF"
|
||||
},
|
||||
{
|
||||
"name": "dof",
|
||||
"type": "pure-token",
|
||||
"token": "DOF"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "return option",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "getnew",
|
||||
"type": "pure-token",
|
||||
"token": "GETNEW"
|
||||
},
|
||||
{
|
||||
"name": "getold",
|
||||
"type": "pure-token",
|
||||
"token": "GETOLD"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "condition",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "nx",
|
||||
"type": "pure-token",
|
||||
"token": "NX"
|
||||
},
|
||||
{
|
||||
"name": "xx",
|
||||
"type": "pure-token",
|
||||
"token": "XX"
|
||||
},
|
||||
{
|
||||
"name": "gt",
|
||||
"type": "pure-token",
|
||||
"token": "GT"
|
||||
},
|
||||
{
|
||||
"name": "lt",
|
||||
"type": "pure-token",
|
||||
"token": "LT"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "expiration",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "seconds",
|
||||
"type": "integer",
|
||||
"token": "EX"
|
||||
},
|
||||
{
|
||||
"name": "milliseconds",
|
||||
"type": "integer",
|
||||
"token": "PX"
|
||||
},
|
||||
{
|
||||
"name": "unix-time-seconds",
|
||||
"type": "unix-time",
|
||||
"token": "EXAT"
|
||||
},
|
||||
{
|
||||
"name": "unix-time-milliseconds",
|
||||
"type": "unix-time",
|
||||
"token": "PXAT"
|
||||
},
|
||||
{
|
||||
"name": "keepttl",
|
||||
"type": "pure-token",
|
||||
"token": "KEEPTTL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FVS",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "data",
|
||||
"type": "block",
|
||||
"multiple": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -3669,7 +3669,6 @@ void strlenCommand(client *c);
|
|||
void zrankCommand(client *c);
|
||||
void zrevrankCommand(client *c);
|
||||
void hsetCommand(client *c);
|
||||
void hsetfCommand(client *c);
|
||||
void hpexpireCommand(client *c);
|
||||
void hexpireCommand(client *c);
|
||||
void hpexpireatCommand(client *c);
|
||||
|
@ -3681,7 +3680,6 @@ void hpexpiretimeCommand(client *c);
|
|||
void hpersistCommand(client *c);
|
||||
void hsetnxCommand(client *c);
|
||||
void hgetCommand(client *c);
|
||||
void hgetfCommand(client *c);
|
||||
void hmgetCommand(client *c);
|
||||
void hdelCommand(client *c);
|
||||
void hlenCommand(client *c);
|
||||
|
|
921
src/t_hash.c
921
src/t_hash.c
|
@ -21,7 +21,6 @@ static ExpireMeta *hashGetExpireMeta(const eItem hash);
|
|||
static void hexpireGenericCommand(client *c, const char *cmd, long long basetime, int unit);
|
||||
static ExpireAction hashTypeActiveExpire(eItem hashObj, void *ctx);
|
||||
static void hfieldPersist(robj *hashObj, hfield field);
|
||||
static void updateGlobalHfeDs(redisDb *db, robj *o, uint64_t minExpire, uint64_t minExpireFields);
|
||||
|
||||
/* hash dictType funcs */
|
||||
static int dictHfieldKeyCompare(dict *d, const void *key1, const void *key2);
|
||||
|
@ -1076,7 +1075,31 @@ void hashTypeSetExDone(HashTypeSetEx *ex) {
|
|||
dbDelete(ex->db,ex->key);
|
||||
if (ex->c) notifyKeyspaceEvent(NOTIFY_GENERIC,"del",ex->key, ex->db->id);
|
||||
} else {
|
||||
updateGlobalHfeDs(ex->db, ex->hashObj, ex->minExpire, ex->minExpireFields);
|
||||
/* If minimum HFE of the hash is smaller than expiration time of the
|
||||
* specified fields in the command as well as it is smaller or equal
|
||||
* than expiration time provided in the command, then the minimum
|
||||
* HFE of the hash won't change following this command. */
|
||||
if ((ex->minExpire < ex->minExpireFields))
|
||||
return;
|
||||
|
||||
/* retrieve new expired time. It might have changed. */
|
||||
uint64_t newMinExpire = hashTypeGetNextTimeToExpire(ex->hashObj);
|
||||
|
||||
/* Calculate the diff between old minExpire and newMinExpire. If it is
|
||||
* only few seconds, then don't have to update global HFE DS. At the worst
|
||||
* case fields of hash will be active-expired up to few seconds later.
|
||||
*
|
||||
* In any case, active-expire operation will know to update global
|
||||
* HFE DS more efficiently than here for a single item.
|
||||
*/
|
||||
uint64_t diff = (ex->minExpire > newMinExpire) ?
|
||||
(ex->minExpire - newMinExpire) : (newMinExpire - ex->minExpire);
|
||||
if (diff < HASH_NEW_EXPIRE_DIFF_THRESHOLD) return;
|
||||
|
||||
if (ex->minExpire != EB_EXPIRE_TIME_INVALID)
|
||||
ebRemove(&ex->db->hexpires, &hashExpireBucketsType, ex->hashObj);
|
||||
if (newMinExpire != EB_EXPIRE_TIME_INVALID)
|
||||
ebAdd(&ex->db->hexpires, &hashExpireBucketsType, ex->hashObj, newMinExpire);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2679,7 +2702,7 @@ static void httlGenericCommand(client *c, const char *cmd, long long basetime, i
|
|||
long numFields = 0, numFieldsAt = 3;
|
||||
|
||||
/* Read the hash object */
|
||||
if ((hashObj = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp])) == NULL ||
|
||||
if ((hashObj = lookupKeyReadOrReply(c, c->argv[1], shared.emptyarray)) == NULL ||
|
||||
checkType(c, hashObj, OBJ_HASH)) return;
|
||||
|
||||
if (strcasecmp(c->argv[numFieldsAt-1]->ptr, "FIELDS")) {
|
||||
|
@ -2802,7 +2825,7 @@ static void hexpireGenericCommand(client *c, const char *cmd, long long basetime
|
|||
robj *hashObj, *keyArg = c->argv[1], *expireArg = c->argv[2];
|
||||
|
||||
/* Read the hash object */
|
||||
if ((hashObj = lookupKeyWriteOrReply(c, keyArg, shared.null[c->resp])) == NULL ||
|
||||
if ((hashObj = lookupKeyWriteOrReply(c, keyArg, shared.emptyarray)) == NULL ||
|
||||
checkType(c, hashObj, OBJ_HASH)) return;
|
||||
|
||||
/* Read the expiry time from command */
|
||||
|
@ -2926,7 +2949,7 @@ void hpersistCommand(client *c) {
|
|||
int changed = 0; /* Used to determine whether to send a notification. */
|
||||
|
||||
/* Read the hash object */
|
||||
if ((hashObj = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp])) == NULL ||
|
||||
if ((hashObj = lookupKeyReadOrReply(c, c->argv[1], shared.emptyarray)) == NULL ||
|
||||
checkType(c, hashObj, OBJ_HASH)) return;
|
||||
|
||||
if (strcasecmp(c->argv[numFieldsAt-1]->ptr, "FIELDS")) {
|
||||
|
@ -3035,891 +3058,3 @@ void hpersistCommand(client *c) {
|
|||
* has been successfully deleted. */
|
||||
if (changed) notifyKeyspaceEvent(NOTIFY_HASH,"hpersist",c->argv[1],c->db->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Move top of the file
|
||||
* HGETF - HSETF command arguments
|
||||
*/
|
||||
#define HFE_CMD_NX (1<<0) /* If not exist */
|
||||
#define HFE_CMD_XX (1<<1) /* If exists */
|
||||
#define HFE_CMD_GT (1<<2) /* Greater than */
|
||||
#define HFE_CMD_LT (1<<3) /* Less than */
|
||||
#define HFE_CMD_COND_MASK (0x0F)
|
||||
|
||||
#define HFE_CMD_PX (1<<4) /* Milliseconds */
|
||||
#define HFE_CMD_EX (1<<5) /* Seconds */
|
||||
#define HFE_CMD_PXAT (1<<6) /* Unix timestamp milliseconds */
|
||||
#define HFE_CMD_EXAT (1<<7) /* Unix timestamp seconds */
|
||||
#define HFE_CMD_PERSIST (1<<8) /* Delete TTL */
|
||||
#define HFE_CMD_KEEPTTL (1<<9) /* Keep TTL */
|
||||
#define HFE_CMD_EXPIRY_MASK (0x3F0)
|
||||
|
||||
#define HFE_CMD_DC (1<<10) /* Don't create key */
|
||||
#define HFE_CMD_DCF (1<<11) /* Don't create field */
|
||||
#define HFE_CMD_DOF (1<<12) /* Don't overwrite field */
|
||||
#define HFE_CMD_GETNEW (1<<13) /* Get new value */
|
||||
#define HFE_CMD_GETOLD (1<<14) /* Get old value */
|
||||
|
||||
#define HSETF_FAIL 0 /* Failed to set value (DCF/DOF not met) */
|
||||
#define HSETF_FIELD 1 /* Field value is set without TTL */
|
||||
#define HSETF_FIELD_AND_TTL 3 /* Both field value and TTL is set */
|
||||
|
||||
/* Validate expire time is not more than EB_EXPIRE_TIME_MAX,
|
||||
* or it does not overflow */
|
||||
static int validateExpire(client *c, int unit, robj *o, long long basetime,
|
||||
uint64_t *expire)
|
||||
{
|
||||
long long val;
|
||||
/* Read the expiry time from command */
|
||||
if (getLongLongFromObjectOrReply(c, o, &val, NULL) != C_OK)
|
||||
return C_ERR;
|
||||
|
||||
if (val < 0 || val > (long long) EB_EXPIRE_TIME_MAX) {
|
||||
addReplyErrorExpireTime(c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (unit == UNIT_SECONDS) {
|
||||
if (val > (long long) EB_EXPIRE_TIME_MAX / 1000) {
|
||||
addReplyErrorExpireTime(c);
|
||||
return C_ERR;
|
||||
}
|
||||
val *= 1000;
|
||||
} else {
|
||||
if (val > (long long) EB_EXPIRE_TIME_MAX) {
|
||||
addReplyErrorExpireTime(c);
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
if (val > (long long) EB_EXPIRE_TIME_MAX - basetime) {
|
||||
addReplyErrorExpireTime(c);
|
||||
return C_ERR;
|
||||
}
|
||||
val += basetime;
|
||||
*expire = val;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Convert listpack to listpackEx encoding or attach hfe meta to dict */
|
||||
static void attachHfeMeta(redisDb *db, robj *o, robj *keyArg) {
|
||||
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
hashTypeConvert(o, OBJ_ENCODING_LISTPACK_EX, &db->hexpires);
|
||||
|
||||
listpackEx *lpt = o->ptr;
|
||||
dictEntry *de = dbFind(db, keyArg->ptr);
|
||||
serverAssert(de != NULL);
|
||||
lpt->key = dictGetKey(de);
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
dictExpireMetadata *dictExpireMeta;
|
||||
dict *d = o->ptr;
|
||||
|
||||
/* If dict doesn't have metadata attached */
|
||||
if (!isDictWithMetaHFE(d)) {
|
||||
/* Realloc (only header of dict) with metadata for hash-field expiration */
|
||||
dictTypeAddMeta(&d, &mstrHashDictTypeWithHFE);
|
||||
dictExpireMeta = (dictExpireMetadata *) dictMetadata(d);
|
||||
o->ptr = d;
|
||||
|
||||
/* Find the key in the keyspace. Need to keep reference to the key for
|
||||
* notifications or even removal of the hash */
|
||||
dictEntry *de = dbFind(db, keyArg->ptr);
|
||||
serverAssert(de != NULL);
|
||||
sds key = dictGetKey(de);
|
||||
|
||||
/* Fillup dict HFE metadata */
|
||||
dictExpireMeta->key = key; /* reference key in keyspace */
|
||||
dictExpireMeta->hfe = ebCreate(); /* Allocate HFE DS */
|
||||
dictExpireMeta->expireMeta.trash = 1; /* mark as trash (as long it wasn't ebAdd()) */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called after modifying fields to update global hfe DS if necessary
|
||||
*
|
||||
* minExpire: minimum expiry time of the key before modification
|
||||
* minExpireFields: minimum expiry time of the modified fields
|
||||
*/
|
||||
static void updateGlobalHfeDs(redisDb *db, robj *o,uint64_t minExpire, uint64_t minExpireFields)
|
||||
{
|
||||
/* If minimum HFE of the hash is smaller than expiration time of the
|
||||
* specified fields in the command as well as it is smaller or equal
|
||||
* than expiration time provided in the command, then the minimum
|
||||
* HFE of the hash won't change following this command. */
|
||||
if ((minExpire < minExpireFields))
|
||||
return;
|
||||
|
||||
/* retrieve new expired time. It might have changed. */
|
||||
uint64_t newMinExpire = hashTypeGetNextTimeToExpire(o);
|
||||
|
||||
/* Calculate the diff between old minExpire and newMinExpire. If it is
|
||||
* only few seconds, then don't have to update global HFE DS. At the worst
|
||||
* case fields of hash will be active-expired up to few seconds later.
|
||||
*
|
||||
* In any case, active-expire operation will know to update global
|
||||
* HFE DS more efficiently than here for a single item.
|
||||
*/
|
||||
uint64_t diff = (minExpire > newMinExpire) ?
|
||||
(minExpire - newMinExpire) : (newMinExpire - minExpire);
|
||||
if (diff < HASH_NEW_EXPIRE_DIFF_THRESHOLD) return;
|
||||
|
||||
if (minExpire != EB_EXPIRE_TIME_INVALID)
|
||||
ebRemove(&db->hexpires, &hashExpireBucketsType, o);
|
||||
if (newMinExpire != EB_EXPIRE_TIME_INVALID)
|
||||
ebAdd(&db->hexpires, &hashExpireBucketsType, o, newMinExpire);
|
||||
}
|
||||
|
||||
/* Parse hgetf command arguments. */
|
||||
static int hgetfParseArgs(client *c, int *flags, uint64_t *expireAt,
|
||||
int *firstFieldPos, int *fieldCount)
|
||||
{
|
||||
*flags = 0;
|
||||
*firstFieldPos = -1;
|
||||
*fieldCount = -1;
|
||||
|
||||
for (int i = 2; i < c->argc; i++) {
|
||||
if (!strcasecmp(c->argv[i]->ptr, "fields")) {
|
||||
long val;
|
||||
|
||||
if (*firstFieldPos != -1) {
|
||||
addReplyErrorFormat(c, "multiple FIELDS argument");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (i >= c->argc - 2) {
|
||||
addReplyErrorArity(c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (getRangeLongFromObjectOrReply(c, c->argv[i + 1], 1, INT_MAX, &val,
|
||||
"invalid number of fields") != C_OK)
|
||||
return C_ERR;
|
||||
|
||||
if (val > c->argc - i - 2) {
|
||||
addReplyErrorArity(c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
*firstFieldPos = i + 2;
|
||||
*fieldCount = (int) val;
|
||||
i = *firstFieldPos + *fieldCount - 1;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "NX")) {
|
||||
if (*flags & (HFE_CMD_XX | HFE_CMD_GT | HFE_CMD_LT))
|
||||
goto err_condition;
|
||||
*flags |= HFE_CMD_NX;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "XX")) {
|
||||
if (*flags & (HFE_CMD_NX | HFE_CMD_GT | HFE_CMD_LT))
|
||||
goto err_condition;
|
||||
*flags |= HFE_CMD_XX;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "GT")) {
|
||||
if (*flags & (HFE_CMD_NX | HFE_CMD_XX | HFE_CMD_LT))
|
||||
goto err_condition;
|
||||
*flags |= HFE_CMD_GT;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "LT")) {
|
||||
if (*flags & (HFE_CMD_NX | HFE_CMD_XX | HFE_CMD_GT))
|
||||
goto err_condition;
|
||||
*flags |= HFE_CMD_LT;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "EX")) {
|
||||
if (*flags & (HFE_CMD_EXAT | HFE_CMD_PX | HFE_CMD_PXAT | HFE_CMD_PERSIST))
|
||||
goto err_expiration;
|
||||
|
||||
if (i >= c->argc - 1)
|
||||
goto err_missing_expire;
|
||||
|
||||
*flags |= HFE_CMD_EX;
|
||||
i++;
|
||||
if (validateExpire(c, UNIT_SECONDS, c->argv[i],
|
||||
commandTimeSnapshot(), expireAt) != C_OK)
|
||||
return C_ERR;
|
||||
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "PX")) {
|
||||
if (*flags & (HFE_CMD_EX | HFE_CMD_EXAT | HFE_CMD_PXAT | HFE_CMD_PERSIST))
|
||||
goto err_expiration;
|
||||
|
||||
if (i >= c->argc - 1)
|
||||
goto err_missing_expire;
|
||||
|
||||
*flags |= HFE_CMD_PX;
|
||||
i++;
|
||||
if (validateExpire(c, UNIT_MILLISECONDS, c->argv[i],
|
||||
commandTimeSnapshot(), expireAt) != C_OK)
|
||||
return C_ERR;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "EXAT")) {
|
||||
if (*flags & (HFE_CMD_EX | HFE_CMD_PX | HFE_CMD_PXAT | HFE_CMD_PERSIST))
|
||||
goto err_expiration;
|
||||
|
||||
if (i >= c->argc - 1)
|
||||
goto err_missing_expire;
|
||||
|
||||
*flags |= HFE_CMD_EXAT;
|
||||
i++;
|
||||
if (validateExpire(c, UNIT_SECONDS, c->argv[i], 0, expireAt) != C_OK)
|
||||
return C_ERR;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "PXAT")) {
|
||||
if (*flags & (HFE_CMD_EX | HFE_CMD_EXAT | HFE_CMD_PX | HFE_CMD_PERSIST))
|
||||
goto err_expiration;
|
||||
|
||||
if (i >= c->argc - 1)
|
||||
goto err_missing_expire;
|
||||
|
||||
*flags |= HFE_CMD_PXAT;
|
||||
i++;
|
||||
if (validateExpire(c, UNIT_MILLISECONDS, c->argv[i], 0, expireAt) != C_OK)
|
||||
return C_ERR;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "PERSIST")) {
|
||||
if (*flags & (HFE_CMD_EX | HFE_CMD_EXAT | HFE_CMD_PX | HFE_CMD_PXAT))
|
||||
goto err_expiration;
|
||||
*flags |= HFE_CMD_PERSIST;
|
||||
} else {
|
||||
addReplyErrorFormat(c, "unknown argument: %s", (char*) c->argv[i]->ptr);
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* FIELDS argument is mandatory. */
|
||||
if (*firstFieldPos < 0) {
|
||||
addReplyError(c, "missing FIELDS argument");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (*flags & HFE_CMD_COND_MASK &&
|
||||
(!(*flags & (HFE_CMD_EX | HFE_CMD_EXAT | HFE_CMD_PX | HFE_CMD_PXAT))))
|
||||
{
|
||||
addReplyError(c, "NX, XX, GT, and LT can be specified only when EX, PX, EXAT, or PXAT is specified");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
return C_OK;
|
||||
|
||||
err_missing_expire:
|
||||
addReplyError(c, "missing expire time");
|
||||
return C_ERR;
|
||||
err_condition:
|
||||
addReplyError(c, "Only one of NX, XX, GT, and LT arguments can be specified");
|
||||
return C_ERR;
|
||||
err_expiration:
|
||||
addReplyError(c, "Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Reply with field value and optionally set expire time according to 'flag'.
|
||||
* Return 1 if expire time is updated. */
|
||||
static int hgetfReplyValueAndSetExpiry(client *c, robj *o, sds field, int flag,
|
||||
uint64_t expireAt, uint64_t *minPrevExp)
|
||||
{
|
||||
unsigned char *fptr = NULL, *vptr = NULL, *tptr;
|
||||
hfield hf = NULL;
|
||||
dict *d = NULL;
|
||||
dictEntry *de = NULL;
|
||||
uint64_t prevExpire = EB_EXPIRE_TIME_INVALID;
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_HT) {
|
||||
d = o->ptr;
|
||||
/* First retrieve the field to check if it exists */
|
||||
de = dictFind(d, field);
|
||||
if (de == NULL) {
|
||||
addReplyNull(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
hf = dictGetKey(de);
|
||||
if (hfieldIsExpired(hf)) {
|
||||
addReplyNull(c);
|
||||
return 0;
|
||||
}
|
||||
prevExpire = hfieldGetExpireTime(hf);
|
||||
|
||||
/* Reply with value */
|
||||
sds val = dictGetVal(de);
|
||||
addReplyBulkCBuffer(c, val, sdslen(val));
|
||||
} else if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {
|
||||
long long expire;
|
||||
unsigned char *vstr = NULL;
|
||||
unsigned int vlen = UINT_MAX;
|
||||
long long vll = LLONG_MAX;
|
||||
listpackEx *lpt = o->ptr;
|
||||
|
||||
fptr = lpFirst(lpt->lp);
|
||||
if (fptr != NULL) {
|
||||
fptr = lpFind(lpt->lp, fptr, (unsigned char *) field, sdslen(field), 2);
|
||||
if (fptr != NULL) {
|
||||
vptr = lpNext(lpt->lp, fptr);
|
||||
serverAssert(vptr != NULL);
|
||||
|
||||
tptr = lpNext(lpt->lp, vptr);
|
||||
serverAssert(tptr && lpGetIntegerValue(tptr, &expire));
|
||||
|
||||
if (expire != HASH_LP_NO_TTL)
|
||||
prevExpire = expire;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return null if field does not exist */
|
||||
if (fptr == NULL || hashTypeIsExpired(o, expire)) {
|
||||
addReplyNull(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Reply with value */
|
||||
vstr = lpGetValue(vptr, &vlen, &vll);
|
||||
if (vstr)
|
||||
addReplyBulkCBuffer(c, vstr, vlen);
|
||||
else
|
||||
addReplyBulkLongLong(c, vll);
|
||||
} else {
|
||||
serverPanic("Unknown encoding: %d", o->encoding);
|
||||
}
|
||||
|
||||
if (!(flag & HFE_CMD_EXPIRY_MASK) || /* Check if any of EX, EXAT, PX, PXAT, PERSIST flags is set */
|
||||
((flag & HFE_CMD_GT) && (expireAt <= prevExpire)) ||
|
||||
((flag & HFE_CMD_LT) && (expireAt >= prevExpire)) ||
|
||||
((flag & HFE_CMD_XX) && (prevExpire == EB_EXPIRE_TIME_INVALID)) ||
|
||||
((flag & HFE_CMD_NX) && (prevExpire != EB_EXPIRE_TIME_INVALID)) ||
|
||||
((flag & HFE_CMD_PERSIST) && (prevExpire == EB_EXPIRE_TIME_INVALID))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (*minPrevExp > prevExpire)
|
||||
*minPrevExp = prevExpire;
|
||||
|
||||
/* if expiration time is in the past */
|
||||
if (checkAlreadyExpired(expireAt)) {
|
||||
hashTypeDelete(o, field);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_HT) {
|
||||
if (flag & HFE_CMD_PERSIST) {
|
||||
hfieldPersist(o, hf);
|
||||
} else {
|
||||
if (!hfieldIsExpireAttached(hf)) {
|
||||
/* allocate new field with expire metadata */
|
||||
hfield hfNew = hfieldNew(hf, hfieldlen(hf), 1 /*withExpireMeta*/);
|
||||
/* Replace the old field with the new one with metadata */
|
||||
dictSetKey(d, de, hfNew);
|
||||
hfieldFree(hf);
|
||||
hf = hfNew;
|
||||
}
|
||||
|
||||
dictExpireMetadata *meta = (dictExpireMetadata *) dictMetadata(d);
|
||||
if (prevExpire != EB_EXPIRE_TIME_INVALID)
|
||||
ebRemove(&meta->hfe, &hashFieldExpireBucketsType, hf);
|
||||
|
||||
ebAdd(&meta->hfe, &hashFieldExpireBucketsType, hf, expireAt);
|
||||
}
|
||||
} else {
|
||||
uint64_t exp = flag & HFE_CMD_PERSIST ? HASH_LP_NO_TTL : expireAt;
|
||||
listpackExUpdateExpiry(o, field, fptr, vptr, exp);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* For each specified field: get its value and optionally set the field's
|
||||
* remaining time to live.
|
||||
*
|
||||
* HGETF key
|
||||
* [NX | XX | GT | LT]
|
||||
* [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | PERSIST]
|
||||
* <FIELDS count field [field ...]>
|
||||
**/
|
||||
void hgetfCommand(client *c) {
|
||||
int flags = 0;
|
||||
robj *hashObj, *keyArg = c->argv[1];
|
||||
|
||||
int firstFieldPos = 0;
|
||||
int numFields = 0;
|
||||
uint64_t expireAt = EB_EXPIRE_TIME_INVALID;
|
||||
|
||||
if (hgetfParseArgs(c, &flags, &expireAt, &firstFieldPos, &numFields) != C_OK)
|
||||
return;
|
||||
|
||||
/* Read the hash object */
|
||||
if ((hashObj = lookupKeyWriteOrReply(c, c->argv[1], shared.null[c->resp])) == NULL ||
|
||||
checkType(c, hashObj, OBJ_HASH)) return;
|
||||
|
||||
attachHfeMeta(c->db, hashObj, keyArg);
|
||||
uint64_t minExpire = hashTypeGetMinExpire(hashObj);
|
||||
|
||||
/* Figure out from provided set of fields in command, which one has the minimum
|
||||
* expiration time, before the modification (Will be used for optimization below) */
|
||||
uint64_t minExpireFields = EB_EXPIRE_TIME_INVALID;
|
||||
|
||||
int updated = 0;
|
||||
addReplyArrayLen(c, numFields);
|
||||
for (int i = 0; i < numFields ; i++) {
|
||||
sds field = c->argv[firstFieldPos + i]->ptr;
|
||||
updated += hgetfReplyValueAndSetExpiry(c, hashObj, field, flags,
|
||||
expireAt, &minExpireFields);
|
||||
}
|
||||
|
||||
/* Notify keyspace event, update dirty count and update global HFE DS */
|
||||
if (updated > 0) {
|
||||
server.dirty += updated;
|
||||
signalModifiedKey(c,c->db,keyArg);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hgetf",keyArg,c->db->id);
|
||||
if (hashTypeLength(hashObj, 0) == 0) {
|
||||
dbDelete(c->db,keyArg);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",keyArg, c->db->id);
|
||||
} else {
|
||||
updateGlobalHfeDs(c->db, hashObj, minExpire, minExpireFields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check hsetf command args and return 1 if TTL will be updated/discarded. */
|
||||
static int hsetfCheckTTLCondition(int flag, uint64_t prevExpire, uint64_t expireAt) {
|
||||
/* When none of EX, PX, EXAT, PXAT, KEEPTTL are specified:
|
||||
* any previous expiration time associated with field is discarded. */
|
||||
if (!(flag & HFE_CMD_EXPIRY_MASK) && prevExpire != EB_EXPIRE_TIME_INVALID)
|
||||
return 1;
|
||||
|
||||
if ((flag & (HFE_CMD_PX | HFE_CMD_PXAT | HFE_CMD_EX | HFE_CMD_EXAT))) {
|
||||
if (((flag & HFE_CMD_COND_MASK) == 0) || /* None of NX, PX, GT, LT is set */
|
||||
((flag & HFE_CMD_GT) && (expireAt > prevExpire)) ||
|
||||
((flag & HFE_CMD_LT) && (expireAt < prevExpire)) ||
|
||||
(flag & HFE_CMD_XX && prevExpire != EB_EXPIRE_TIME_INVALID) ||
|
||||
(flag & HFE_CMD_NX && prevExpire == EB_EXPIRE_TIME_INVALID)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* For hsetf command, add reply from listpack item */
|
||||
static void hsetfReplyFromListpack(client *c, unsigned char *vptr) {
|
||||
unsigned int vlen = UINT_MAX;
|
||||
long long vll = LLONG_MAX;
|
||||
unsigned char *vstr = NULL;
|
||||
|
||||
if (!vptr) {
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
vstr = lpGetValue(vptr, &vlen, &vll);
|
||||
if (vstr)
|
||||
addReplyBulkCBuffer(c, vstr, vlen);
|
||||
else
|
||||
addReplyBulkLongLong(c, vll);
|
||||
}
|
||||
}
|
||||
|
||||
/* For hsetf command, add reply to client according to flag argument. */
|
||||
static void hsetfAddReply(client *c, int flag, sds prevval, sds newval, int ret) {
|
||||
if (flag & HFE_CMD_GETOLD) {
|
||||
if (!prevval) {
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
addReplyBulkCBuffer(c, prevval, sdslen(prevval));
|
||||
}
|
||||
} else if (flag & HFE_CMD_GETNEW) {
|
||||
if (!newval) {
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
addReplyBulkCBuffer(c, newval, sdslen(newval));
|
||||
}
|
||||
} else {
|
||||
addReplyLongLong(c, ret);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set field and expire time according to 'flag'.
|
||||
* Return 1 if field and/or expire time is updated. */
|
||||
static int hsetfSetFieldAndReply(client *c, robj *o, sds field, sds value,
|
||||
int flag, uint64_t expireAt, uint64_t *minPrevExp)
|
||||
{
|
||||
int ret = HSETF_FAIL;
|
||||
uint64_t prevExpire = EB_EXPIRE_TIME_INVALID;
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {
|
||||
long long expire;
|
||||
unsigned char *fptr, *vptr = NULL, *tptr;
|
||||
listpackEx *lpt = o->ptr;
|
||||
|
||||
fptr = lpFirst(lpt->lp);
|
||||
if (fptr != NULL) {
|
||||
fptr = lpFind(lpt->lp, fptr, (unsigned char *) field, sdslen(field), 2);
|
||||
if (fptr != NULL) {
|
||||
vptr = lpNext(lpt->lp, fptr);
|
||||
tptr = lpNext(lpt->lp, vptr);
|
||||
serverAssert(tptr && lpGetIntegerValue(tptr, &expire));
|
||||
|
||||
if (expire != HASH_LP_NO_TTL)
|
||||
prevExpire = expire;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check DCF (don't create fields) and DOF (don't override fields) arg. */
|
||||
if ((!fptr && (flag & HFE_CMD_DCF)) || (fptr && (flag & HFE_CMD_DOF))) {
|
||||
/* When GETNEW or GETOLD is specified, regardless if a set operation
|
||||
* was actually performed, we return value / old value of field or
|
||||
* nil if there is no field. One corner case, if GETNEW and DOF
|
||||
* (don't override fields) arguments are given and field exists, we
|
||||
* won't override the field and return the existing value.
|
||||
*/
|
||||
if (flag & (HFE_CMD_GETNEW | HFE_CMD_GETOLD))
|
||||
hsetfReplyFromListpack(c, vptr);
|
||||
else
|
||||
addReplyLongLong(c, ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Field value will be updated. */
|
||||
ret = HSETF_FIELD;
|
||||
|
||||
/* Decide if we are going to set TTL */
|
||||
if (hsetfCheckTTLCondition(flag, prevExpire, expireAt))
|
||||
ret = HSETF_FIELD_AND_TTL;
|
||||
|
||||
if (flag & HFE_CMD_GETOLD)
|
||||
hsetfReplyFromListpack(c, vptr);
|
||||
else if (flag & HFE_CMD_GETNEW)
|
||||
addReplyBulkCBuffer(c, (char*)value, sdslen(value));
|
||||
else
|
||||
addReplyLongLong(c, ret);
|
||||
|
||||
if (!fptr) {
|
||||
if (ret != HSETF_FIELD_AND_TTL) {
|
||||
listpackExAddNew(o, field, value, HASH_LP_NO_TTL);
|
||||
} else {
|
||||
/* If expiration time is in the past, no need to create the field */
|
||||
if (!checkAlreadyExpired(expireAt)) {
|
||||
if (*minPrevExp > expireAt)
|
||||
*minPrevExp = expireAt;
|
||||
|
||||
listpackExAddNew(o, field, value, expireAt);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (ret != HSETF_FIELD_AND_TTL) {
|
||||
/* We just set the field value without updating the TTL */
|
||||
lpt->lp = lpReplace(lpt->lp, &vptr, (unsigned char *) value, sdslen(value));
|
||||
} else {
|
||||
/* We are going to update TTL. Delete the field first and then
|
||||
* insert again according to new TTL if necessary. */
|
||||
lpt->lp = lpDeleteRangeWithEntry(lpt->lp, &fptr, 3);
|
||||
|
||||
if (*minPrevExp > prevExpire)
|
||||
*minPrevExp = prevExpire;
|
||||
|
||||
if (!(flag & HFE_CMD_EXPIRY_MASK)) {
|
||||
/* If none of EX,EXAT,PX,PXAT,KEEPTTL is specified, TTL is
|
||||
* discarded. */
|
||||
listpackExAddNew(o, field, value, HASH_LP_NO_TTL);
|
||||
} else if (!checkAlreadyExpired(expireAt)){
|
||||
if (*minPrevExp > expireAt)
|
||||
*minPrevExp = expireAt;
|
||||
|
||||
listpackExAddNew(o, field, value, expireAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
hfield hf = NULL;
|
||||
dictEntry *de = NULL;
|
||||
dict *d = o->ptr;
|
||||
dictExpireMetadata *meta = (dictExpireMetadata *) dictMetadata(d);
|
||||
sds prevVal = NULL;
|
||||
|
||||
/* First retrieve the field to check if it exists */
|
||||
de = dictFind(d, field);
|
||||
if (de) {
|
||||
hf = dictGetKey(de);
|
||||
prevExpire = hfieldGetExpireTime(hf);
|
||||
prevVal = dictGetVal(de);
|
||||
}
|
||||
|
||||
/* Check DCF (don't create fields) and DOF (don't override fields) arg. */
|
||||
if ((!de && (flag & HFE_CMD_DCF)) || (de && (flag & HFE_CMD_DOF))) {
|
||||
hsetfAddReply(c, flag, prevVal, prevVal, ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Field value will be updated. */
|
||||
ret = HSETF_FIELD;
|
||||
|
||||
/* Decide if we are going to set/discard TTL */
|
||||
if (hsetfCheckTTLCondition(flag, prevExpire, expireAt))
|
||||
ret = HSETF_FIELD_AND_TTL;
|
||||
|
||||
hsetfAddReply(c, flag, prevVal, value, ret);
|
||||
|
||||
if (!hf || !hfieldIsExpireAttached(hf)) {
|
||||
hfieldFree(hf);
|
||||
|
||||
int withExpireMeta = (ret == HSETF_FIELD_AND_TTL) ? 1 : 0;
|
||||
hf = hfieldNew(field, sdslen(field), withExpireMeta);
|
||||
|
||||
if (!de) {
|
||||
dictUseStoredKeyApi(d, 1);
|
||||
de = dictAddRaw(d, hf, NULL);
|
||||
dictUseStoredKeyApi(d, 0);
|
||||
}
|
||||
dictSetKey(d, de, hf);
|
||||
}
|
||||
|
||||
dictSetVal(d, de, sdsdup(value));
|
||||
sdsfree(prevVal);
|
||||
|
||||
if (ret == HSETF_FIELD_AND_TTL) {
|
||||
if (*minPrevExp > prevExpire)
|
||||
*minPrevExp = prevExpire;
|
||||
|
||||
if (!(flag & HFE_CMD_EXPIRY_MASK)) {
|
||||
/* If none of EX,EXAT,PX,PXAT,KEEPTTL is specified, TTL is
|
||||
* discarded. */
|
||||
hfieldPersist(o, hf);
|
||||
} else if (checkAlreadyExpired(expireAt)) {
|
||||
/* if expiration time is in the past */
|
||||
hashTypeDelete(o, field);
|
||||
} else {
|
||||
if (*minPrevExp > expireAt)
|
||||
*minPrevExp = expireAt;
|
||||
|
||||
if (prevExpire != EB_EXPIRE_TIME_INVALID)
|
||||
ebRemove(&meta->hfe, &hashFieldExpireBucketsType, hf);
|
||||
|
||||
ebAdd(&meta->hfe, &hashFieldExpireBucketsType, hf, expireAt);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
serverPanic("Unknown encoding: %d", o->encoding);
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse hsetf command arguments. */
|
||||
static int hsetfParseArgs(client *c, int *flags, uint64_t *expireAt,
|
||||
int *firstFieldPos, int *fieldCount)
|
||||
{
|
||||
long val;
|
||||
|
||||
*flags = 0;
|
||||
*firstFieldPos = -1;
|
||||
*fieldCount = -1;
|
||||
|
||||
for (int i = 2; i < c->argc; i++) {
|
||||
if (!strcasecmp(c->argv[i]->ptr, "fvs")) {
|
||||
if (*firstFieldPos != -1) {
|
||||
addReplyErrorFormat(c, "multiple FVS argument");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (i >= c->argc - 3) {
|
||||
addReplyErrorArity(c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (getRangeLongFromObjectOrReply(c, c->argv[i + 1], 1, INT_MAX, &val,
|
||||
"invalid number of fvs count") != C_OK)
|
||||
return C_ERR;
|
||||
|
||||
if (val > ((c->argc - i - 2) / 2)) {
|
||||
addReplyErrorArity(c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
*firstFieldPos = i + 2;
|
||||
*fieldCount = (int) val;
|
||||
i = *firstFieldPos + (*fieldCount) * 2 - 1;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "NX")) {
|
||||
if (*flags & (HFE_CMD_XX | HFE_CMD_GT | HFE_CMD_LT))
|
||||
goto err_condition;
|
||||
*flags |= HFE_CMD_NX;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "XX")) {
|
||||
if (*flags & (HFE_CMD_NX | HFE_CMD_GT | HFE_CMD_LT))
|
||||
goto err_condition;
|
||||
*flags |= HFE_CMD_XX;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "GT")) {
|
||||
if (*flags & (HFE_CMD_NX | HFE_CMD_XX | HFE_CMD_LT))
|
||||
goto err_condition;
|
||||
*flags |= HFE_CMD_GT;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "LT")) {
|
||||
if (*flags & (HFE_CMD_NX | HFE_CMD_XX | HFE_CMD_GT))
|
||||
goto err_condition;
|
||||
*flags |= HFE_CMD_LT;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "EX")) {
|
||||
if (*flags & (HFE_CMD_EXAT | HFE_CMD_PX | HFE_CMD_PXAT | HFE_CMD_KEEPTTL))
|
||||
goto err_expiration;
|
||||
|
||||
if (i >= c->argc - 1)
|
||||
goto err_missing_expire;
|
||||
|
||||
*flags |= HFE_CMD_EX;
|
||||
i++;
|
||||
if (validateExpire(c, UNIT_SECONDS, c->argv[i],
|
||||
commandTimeSnapshot(), expireAt) != C_OK)
|
||||
return C_ERR;
|
||||
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "PX")) {
|
||||
if (*flags & (HFE_CMD_EX | HFE_CMD_EXAT | HFE_CMD_PXAT | HFE_CMD_KEEPTTL))
|
||||
goto err_expiration;
|
||||
|
||||
if (i >= c->argc - 1)
|
||||
goto err_missing_expire;
|
||||
|
||||
*flags |= HFE_CMD_PX;
|
||||
i++;
|
||||
if (validateExpire(c, UNIT_MILLISECONDS, c->argv[i],
|
||||
commandTimeSnapshot(), expireAt) != C_OK)
|
||||
return C_ERR;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "EXAT")) {
|
||||
if (*flags & (HFE_CMD_EX | HFE_CMD_PX | HFE_CMD_PXAT | HFE_CMD_KEEPTTL))
|
||||
goto err_expiration;
|
||||
|
||||
if (i >= c->argc - 1)
|
||||
goto err_missing_expire;
|
||||
|
||||
*flags |= HFE_CMD_EXAT;
|
||||
i++;
|
||||
if (validateExpire(c, UNIT_SECONDS, c->argv[i], 0, expireAt) != C_OK)
|
||||
return C_ERR;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "PXAT")) {
|
||||
if (*flags & (HFE_CMD_EX | HFE_CMD_EXAT | HFE_CMD_PX | HFE_CMD_KEEPTTL))
|
||||
goto err_expiration;
|
||||
|
||||
if (i >= c->argc - 1)
|
||||
goto err_missing_expire;
|
||||
|
||||
*flags |= HFE_CMD_PXAT;
|
||||
i++;
|
||||
if (validateExpire(c, UNIT_MILLISECONDS, c->argv[i], 0, expireAt) != C_OK)
|
||||
return C_ERR;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "KEEPTTL")) {
|
||||
if (*flags & (HFE_CMD_EX | HFE_CMD_EXAT | HFE_CMD_PX | HFE_CMD_PXAT))
|
||||
goto err_expiration;
|
||||
*flags |= HFE_CMD_KEEPTTL;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "DC")) {
|
||||
*flags |= HFE_CMD_DC;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "DCF")) {
|
||||
if (*flags & HFE_CMD_DOF)
|
||||
goto err_field_condition;
|
||||
*flags |= HFE_CMD_DCF;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "DOF")) {
|
||||
if (*flags & HFE_CMD_DCF)
|
||||
goto err_field_condition;
|
||||
*flags |= HFE_CMD_DOF;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "GETNEW")) {
|
||||
if (*flags & HFE_CMD_GETOLD)
|
||||
goto err_return_condition;
|
||||
*flags |= HFE_CMD_GETNEW;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "GETOLD")) {
|
||||
if (*flags & HFE_CMD_GETNEW)
|
||||
goto err_return_condition;
|
||||
*flags |= HFE_CMD_GETOLD;
|
||||
} else {
|
||||
addReplyErrorFormat(c, "unknown argument: %s", (char*) c->argv[i]->ptr);
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* FVS argument is mandatory. */
|
||||
if (*firstFieldPos <= 0) {
|
||||
addReplyError(c, "missing FVS argument");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (*flags & HFE_CMD_COND_MASK &&
|
||||
(!(*flags & (HFE_CMD_EX | HFE_CMD_EXAT | HFE_CMD_PX | HFE_CMD_PXAT))))
|
||||
{
|
||||
addReplyError(c, "NX, XX, GT, and LT can be specified only when EX, PX, EXAT, or PXAT is specified");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
return C_OK;
|
||||
|
||||
err_missing_expire:
|
||||
addReplyError(c, "missing expire time");
|
||||
return C_ERR;
|
||||
err_condition:
|
||||
addReplyError(c, "Only one of NX, XX, GT, and LT arguments can be specified");
|
||||
return C_ERR;
|
||||
err_expiration:
|
||||
addReplyError(c, "Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified");
|
||||
return C_ERR;
|
||||
err_field_condition:
|
||||
addReplyError(c, "Only one of DCF or DOF arguments can be specified");
|
||||
return C_ERR;
|
||||
err_return_condition:
|
||||
addReplyError(c, "Only one of GETOLD or GETNEW arguments can be specified");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set field value and optionally set the field's remaining time to live.
|
||||
* Optionally it creates the key/fields.
|
||||
*
|
||||
* HSETF key
|
||||
* [DC] [DCF | DOF]
|
||||
* [NX | XX | GT | LT]
|
||||
* [GETNEW | GETOLD]
|
||||
* [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]
|
||||
* <FVS count field value [field value …]>
|
||||
*/
|
||||
void hsetfCommand(client *c) {
|
||||
int flags = 0;
|
||||
robj *hashObj, *keyArg = c->argv[1];
|
||||
|
||||
int firstFieldPos = 0;
|
||||
int numFields = 0;
|
||||
uint64_t expireAt = EB_EXPIRE_TIME_INVALID;
|
||||
|
||||
if (hsetfParseArgs(c, &flags, &expireAt, &firstFieldPos, &numFields) != C_OK)
|
||||
return;
|
||||
|
||||
hashObj = lookupKeyWrite(c->db, c->argv[1]);
|
||||
if (!hashObj) {
|
||||
/* Don't create the object if command has DC or DCF arguments */
|
||||
if (flags & HFE_CMD_DC || flags & HFE_CMD_DCF) {
|
||||
addReplyOrErrorObject(c, shared.null[c->resp]);
|
||||
return;
|
||||
}
|
||||
|
||||
hashObj = createHashObject();
|
||||
dbAdd(c->db,c->argv[1],hashObj);
|
||||
}
|
||||
|
||||
hashTypeTryConversion(c->db,hashObj,c->argv,
|
||||
firstFieldPos,
|
||||
firstFieldPos + (numFields * 2) - 1);
|
||||
|
||||
attachHfeMeta(c->db, hashObj, keyArg);
|
||||
uint64_t minExpire = hashTypeGetMinExpire(hashObj);
|
||||
|
||||
/* Figure out from provided set of fields in command, which one has the minimum
|
||||
* expiration time, before the modification (Will be used for optimization below) */
|
||||
uint64_t minExpireFields = EB_EXPIRE_TIME_INVALID;
|
||||
|
||||
int updated = 0;
|
||||
addReplyArrayLen(c, numFields);
|
||||
for (int i = 0; i < numFields ; i++) {
|
||||
sds field = c->argv[firstFieldPos + (i * 2)]->ptr;
|
||||
sds value = c->argv[firstFieldPos + (i * 2) + 1]->ptr;
|
||||
updated += hsetfSetFieldAndReply(c, hashObj, field, value, flags,
|
||||
expireAt, &minExpireFields);
|
||||
}
|
||||
|
||||
if (updated == 0) {
|
||||
/* If we didn't update anything and object is empty, it means we just
|
||||
* created the object above and leaving it empty. If this is the case,
|
||||
* we should avoid creating the object in the first place.
|
||||
* See above DC / DCF flags check when object does not exist. */
|
||||
serverAssert(hashTypeLength(hashObj, 0) != 0);
|
||||
} else {
|
||||
/* Notify keyspace event, update dirty count and update global HFE DS */
|
||||
server.dirty += updated;
|
||||
signalModifiedKey(c,c->db,keyArg);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hsetf",keyArg,c->db->id);
|
||||
if (hashTypeLength(hashObj, 0) == 0) {
|
||||
dbDelete(c->db,keyArg);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",keyArg, c->db->id);
|
||||
} else {
|
||||
updateGlobalHfeDs(c->db, hashObj, minExpire, minExpireFields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,6 @@ set P_NO_FIELD -2
|
|||
set P_NO_EXPIRY -1
|
||||
set P_OK 1
|
||||
|
||||
######## HSETF
|
||||
set S_FAIL 0
|
||||
set S_FIELD 1
|
||||
set S_FIELD_AND_TTL 3
|
||||
|
||||
############################### AUX FUNCS ######################################
|
||||
|
||||
proc get_hashes_with_expiry_fields {r} {
|
||||
|
@ -109,6 +104,17 @@ start_server {tags {"external:skip needs:debug"}} {
|
|||
r config set hash-max-listpack-entries 512
|
||||
}
|
||||
|
||||
test "HEXPIRE/HEXPIREAT/HPEXPIRE/HPEXPIREAT - Returns empty array if key does not exist" {
|
||||
r del myhash
|
||||
# Make sure we can distinguish between an empty array and a null response
|
||||
r readraw 1
|
||||
assert_equal {*0} [r HEXPIRE myhash 1000 FIELDS 1 a]
|
||||
assert_equal {*0} [r HEXPIREAT myhash 1000 FIELDS 1 a]
|
||||
assert_equal {*0} [r HPEXPIRE myhash 1000 FIELDS 1 a]
|
||||
assert_equal {*0} [r HPEXPIREAT myhash 1000 FIELDS 1 a]
|
||||
r readraw 0
|
||||
}
|
||||
|
||||
test "HPEXPIRE(AT) - Test 'NX' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash field1 value1 field2 value2 field3 value3
|
||||
|
@ -278,6 +284,15 @@ start_server {tags {"external:skip needs:debug"}} {
|
|||
r flushall async
|
||||
}
|
||||
|
||||
test "HTTL/HPTTL - Returns empty array if key does not exist" {
|
||||
r del myhash
|
||||
# Make sure we can distinguish between an empty array and a null response
|
||||
r readraw 1
|
||||
assert_equal {*0} [r HTTL myhash FIELDS 1 a]
|
||||
assert_equal {*0} [r HPTTL myhash FIELDS 1 a]
|
||||
r readraw 0
|
||||
}
|
||||
|
||||
test "HTTL/HPTTL - Input validation gets failed on nonexists field or field without expire ($type)" {
|
||||
r del myhash
|
||||
r HSET myhash field1 value1 field2 value2
|
||||
|
@ -301,6 +316,15 @@ start_server {tags {"external:skip needs:debug"}} {
|
|||
assert_range $ttl 1000 2000
|
||||
}
|
||||
|
||||
test "HEXPIRETIME/HPEXPIRETIME - Returns empty array if key does not exist" {
|
||||
r del myhash
|
||||
# Make sure we can distinguish between an empty array and a null response
|
||||
r readraw 1
|
||||
assert_equal {*0} [r HEXPIRETIME myhash FIELDS 1 a]
|
||||
assert_equal {*0} [r HPEXPIRETIME myhash FIELDS 1 a]
|
||||
r readraw 0
|
||||
}
|
||||
|
||||
test "HEXPIRETIME - returns TTL in Unix timestamp ($type)" {
|
||||
r del myhash
|
||||
r HSET myhash field1 value1
|
||||
|
@ -652,6 +676,14 @@ start_server {tags {"external:skip needs:debug"}} {
|
|||
wait_for_condition 20 10 { [r exists myhash] == 0 } else { fail "'myhash' should be expired" }
|
||||
} {} {singledb:skip}
|
||||
|
||||
test "HPERSIST - Returns empty array if key does not exist" {
|
||||
r del myhash
|
||||
# Make sure we can distinguish between an empty array and a null response
|
||||
r readraw 1
|
||||
assert_equal {*0} [r HPERSIST myhash FIELDS 1 a]
|
||||
r readraw 0
|
||||
}
|
||||
|
||||
test "HPERSIST - input validation ($type)" {
|
||||
# HPERSIST key <num-fields> <field [field ...]>
|
||||
r del myhash
|
||||
|
@ -685,462 +717,9 @@ start_server {tags {"external:skip needs:debug"}} {
|
|||
assert_equal [r hpersist myhash FIELDS 1 fieldnonexist] $P_NO_FIELD
|
||||
}
|
||||
|
||||
test "HGETF - input validation ($type)" {
|
||||
assert_error {*wrong number of arguments*} {r hgetf myhash}
|
||||
assert_error {*wrong number of arguments*} {r hgetf myhash fields}
|
||||
assert_error {*wrong number of arguments*} {r hgetf myhash fields 1}
|
||||
assert_error {*wrong number of arguments*} {r hgetf myhash fields 2 a}
|
||||
assert_error {*wrong number of arguments*} {r hgetf myhash fields 3 a b}
|
||||
assert_error {*wrong number of arguments*} {r hgetf myhash fields 3 a b}
|
||||
assert_error {*unknown argument*} {r hgetf myhash fields 1 a unknown}
|
||||
assert_error {*missing FIELDS argument*} {r hgetf myhash nx ex 100}
|
||||
assert_error {*multiple FIELDS argument*} {r hgetf myhash fields 1 a fields 1 b}
|
||||
|
||||
r hset myhash f1 v1 f2 v2 f3 v3
|
||||
# NX, XX, GT, and LT can be specified only when EX, PX, EXAT, or PXAT is specified
|
||||
assert_error {*only when EX, PX, EXAT, or PXAT is specified*} {r hgetf myhash nx fields 1 a}
|
||||
assert_error {*only when EX, PX, EXAT, or PXAT is specified*} {r hgetf myhash xx fields 1 a}
|
||||
assert_error {*only when EX, PX, EXAT, or PXAT is specified*} {r hgetf myhash gt fields 1 a}
|
||||
assert_error {*only when EX, PX, EXAT, or PXAT is specified*} {r hgetf myhash lt fields 1 a}
|
||||
|
||||
# Only one of NX, XX, GT, and LT can be specified
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hgetf myhash nx xx EX 100 fields 1 a}
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hgetf myhash xx nx EX 100 fields 1 a}
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hgetf myhash gt nx EX 100 fields 1 a}
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hgetf myhash gt lt EX 100 fields 1 a}
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hgetf myhash xx gt EX 100 fields 1 a}
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hgetf myhash lt gt EX 100 fields 1 a}
|
||||
|
||||
# Only one of EX, PX, EXAT, PXAT or PERSIST can be specified
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or PERSIST arguments*} {r hgetf myhash EX 100 PX 1000 fields 1 a}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or PERSIST arguments*} {r hgetf myhash EX 100 EXAT 100 fields 1 a}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or PERSIST arguments*} {r hgetf myhash EXAT 100 EX 1000 fields 1 a}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or PERSIST arguments*} {r hgetf myhash EXAT 100 PX 1000 fields 1 a}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or PERSIST arguments*} {r hgetf myhash PX 100 EXAT 100 fields 1 a}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or PERSIST arguments*} {r hgetf myhash PX 100 PXAT 100 fields 1 a}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or PERSIST arguments*} {r hgetf myhash PXAT 100 EX 100 fields 1 a}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or PERSIST arguments*} {r hgetf myhash PXAT 100 EXAT 100 fields 1 a}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or PERSIST arguments*} {r hgetf myhash EX 100 PERSIST fields 1 a}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or PERSIST arguments*} {r hgetf myhash PERSIST EX 100 fields 1 a}
|
||||
|
||||
# missing expire time
|
||||
assert_error {*not an integer or out of range*} {r hgetf myhash ex fields 1 a}
|
||||
assert_error {*not an integer or out of range*} {r hgetf myhash px fields 1 a}
|
||||
assert_error {*not an integer or out of range*} {r hgetf myhash exat fields 1 a}
|
||||
assert_error {*not an integer or out of range*} {r hgetf myhash pxat fields 1 a}
|
||||
|
||||
# expire time more than 2 ^ 48
|
||||
assert_error {*invalid expire time*} {r hgetf myhash EXAT [expr (1<<48)] 1 f1}
|
||||
assert_error {*invalid expire time*} {r hgetf myhash PXAT [expr (1<<48)] 1 f1}
|
||||
assert_error {*invalid expire time*} {r hgetf myhash EX [expr (1<<48) - [clock seconds] + 1000 ] 1 f1}
|
||||
assert_error {*invalid expire time*} {r hgetf myhash PX [expr (1<<48) - [clock milliseconds] + 1000 ] 1 f1}
|
||||
|
||||
# negative expire time
|
||||
assert_error {*invalid expire time*} {r hgetf myhash EXAT -10 1 f1}
|
||||
|
||||
# negative field value count
|
||||
assert_error {*invalid number of fields*} {r hgetf myhash fields -1 a}
|
||||
}
|
||||
|
||||
test "HGETF - Verify field value reply type is string ($type)" {
|
||||
r del myhash
|
||||
r hsetf myhash FVS 1 f1 1
|
||||
|
||||
r readraw 1
|
||||
assert_equal [r hgetf myhash FIELDS 1 f1] {*1}
|
||||
assert_equal [r read] {$1}
|
||||
assert_equal [r read] {1}
|
||||
r readraw 0
|
||||
}
|
||||
|
||||
test "HGETF - Test 'NX' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash field1 value1 field2 value2 field3 value3
|
||||
assert_equal [r hgetf myhash EX 1000 NX FIELDS 1 field1] [list "value1"]
|
||||
assert_equal [r hgetf myhash EX 10000 NX FIELDS 2 field1 field2] [list "value1" "value2"]
|
||||
assert_range [r httl myhash FIELDS 1 field1] 1 1000
|
||||
assert_range [r httl myhash FIELDS 1 field2] 5000 10000
|
||||
|
||||
# A field with no expiration is treated as an infinite expiration.
|
||||
# LT should set the expire time if field has no TTL.
|
||||
r del myhash
|
||||
r hset myhash field1 value1
|
||||
assert_equal [r hgetf myhash EX 1500 LT FIELDS 1 field1] [list "value1"]
|
||||
assert_not_equal [r httl myhash FIELDS 1 field1] "$T_NO_EXPIRY"
|
||||
}
|
||||
|
||||
test "HGETF - Test 'XX' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash field1 value1 field2 value2 field3 value3
|
||||
assert_equal [r hgetf myhash EX 1000 NX FIELDS 1 field1] [list "value1"]
|
||||
assert_equal [r hgetf myhash EX 10000 XX FIELDS 2 field1 field2] [list "value1" "value2"]
|
||||
assert_range [r httl myhash FIELDS 1 field1] 9900 10000
|
||||
assert_equal [r httl myhash FIELDS 1 field2] "$T_NO_EXPIRY"
|
||||
}
|
||||
|
||||
test "HGETF - Test 'GT' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash field1 value1 field2 value2 field3 value3
|
||||
assert_equal [r hgetf myhash EX 1000 NX FIELDS 1 field1] [list "value1"]
|
||||
assert_equal [r hgetf myhash EX 2000 NX FIELDS 1 field2] [list "value2"]
|
||||
assert_equal [r hgetf myhash EX 1500 GT FIELDS 2 field1 field2] [list "value1" "value2"]
|
||||
assert_range [r httl myhash FIELDS 1 field1] 1400 1500
|
||||
assert_range [r httl myhash FIELDS 1 field2] 1900 2000
|
||||
}
|
||||
|
||||
test "HGETF - Test 'LT' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash field1 value1 field2 value2 field3 value3
|
||||
assert_equal [r hgetf myhash EX 1000 NX FIELDS 1 field1] [list "value1"]
|
||||
assert_equal [r hgetf myhash EX 2000 NX FIELDS 1 field2] [list "value2"]
|
||||
assert_equal [r hgetf myhash EX 1500 LT FIELDS 2 field1 field2] [list "value1" "value2"]
|
||||
assert_range [r httl myhash FIELDS 1 field1] 1 1000
|
||||
assert_range [r httl myhash FIELDS 1 field2] 1000 1500
|
||||
}
|
||||
|
||||
test "HGETF - Test 'EX' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash field1 value1 field2 value2 field3 value3
|
||||
assert_equal [r hgetf myhash EX 1000 FIELDS 1 field3] [list "value3"]
|
||||
assert_range [r httl myhash FIELDS 1 field3] 1 1000
|
||||
}
|
||||
|
||||
test "HGETF - Test 'EXAT' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash field1 value1 field2 value2 field3 value3
|
||||
assert_equal [r hgetf myhash EXAT 4000000000 FIELDS 1 field3] [list "value3"]
|
||||
assert_range [expr [r httl myhash FIELDS 1 field3] + [clock seconds]] 3900000000 4000000000
|
||||
}
|
||||
|
||||
test "HGETF - Test 'PX' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash field1 value1 field2 value2 field3 value3
|
||||
assert_equal [r hgetf myhash PX 1000000 FIELDS 1 field3] [list "value3"]
|
||||
assert_range [r httl myhash FIELDS 1 field3] 900 1000
|
||||
}
|
||||
|
||||
test "HGETF - Test 'PXAT' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash field1 value1 field2 value2 field3 value3
|
||||
assert_equal [r hgetf myhash PXAT 4000000000000 FIELDS 1 field3] [list "value3"]
|
||||
assert_range [expr [r httl myhash FIELDS 1 field3] + [clock seconds]] 3900000000 4000000000
|
||||
}
|
||||
|
||||
test "HGETF - Test 'PERSIST' flag ($type)" {
|
||||
r del myhash
|
||||
r debug set-active-expire 0
|
||||
|
||||
r hset myhash f1 v1 f2 v2 f3 v3
|
||||
r hgetf myhash PX 5000 FIELDS 3 f1 f2 f3
|
||||
assert_not_equal [r httl myhash FIELDS 1 f1] "$T_NO_EXPIRY"
|
||||
assert_not_equal [r httl myhash FIELDS 1 f2] "$T_NO_EXPIRY"
|
||||
assert_not_equal [r httl myhash FIELDS 1 f3] "$T_NO_EXPIRY"
|
||||
|
||||
assert_equal [r hgetf myhash PERSIST FIELDS 1 f1] "v1"
|
||||
assert_equal [r httl myhash FIELDS 1 f1] "$T_NO_EXPIRY"
|
||||
|
||||
assert_equal [r hgetf myhash PERSIST FIELDS 2 f2 f3] "v2 v3"
|
||||
assert_equal [r httl myhash FIELDS 2 f2 f3] "$T_NO_EXPIRY $T_NO_EXPIRY"
|
||||
}
|
||||
|
||||
test "HGETF - Test setting expired ttl deletes key ($type)" {
|
||||
r del myhash
|
||||
r hset myhash f1 v1 f2 v2 f3 v3
|
||||
|
||||
# hgetf without setting ttl
|
||||
assert_equal [lsort [r hgetf myhash fields 3 f1 f2 f3]] [lsort "v1 v2 v3"]
|
||||
assert_equal [r httl myhash FIELDS 3 f1 f2 f3] "$T_NO_EXPIRY $T_NO_EXPIRY $T_NO_EXPIRY"
|
||||
|
||||
# set expired ttl and verify key is deleted
|
||||
r hgetf myhash PXAT 1 fields 3 f1 f2 f3
|
||||
assert_equal [r exists myhash] 0
|
||||
}
|
||||
|
||||
test "HGETF - Test active expiry ($type)" {
|
||||
r del myhash
|
||||
r debug set-active-expire 0
|
||||
|
||||
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
|
||||
r hgetf myhash PXAT 1 FIELDS 5 f1 f2 f3 f4 f5
|
||||
|
||||
r debug set-active-expire 1
|
||||
wait_for_condition 50 20 { [r EXISTS myhash] == 0 } else { fail "'myhash' should be expired" }
|
||||
}
|
||||
|
||||
test "HGETF - A field with TTL overridden with another value (TTL discarded) ($type)" {
|
||||
r del myhash
|
||||
r hset myhash field1 value1 field2 value2 field3 value3
|
||||
r hgetf myhash PX 10000 NX FIELDS 1 field1
|
||||
r hgetf myhash EX 100 NX FIELDS 1 field2
|
||||
|
||||
# field2 TTL will be discarded
|
||||
r hset myhash field2 value4
|
||||
|
||||
# Expected TTL will be discarded
|
||||
assert_equal [r hget myhash field2] "value4"
|
||||
assert_equal [r httl myhash FIELDS 2 field2 field3] "$T_NO_EXPIRY $T_NO_EXPIRY"
|
||||
|
||||
# Other field is not affected.
|
||||
assert_not_equal [r httl myhash FIELDS 1 field1] "$T_NO_EXPIRY"
|
||||
}
|
||||
|
||||
test "HSETF - input validation ($type)" {
|
||||
assert_error {*wrong number of arguments*} {r hsetf myhash}
|
||||
assert_error {*wrong number of arguments*} {r hsetf myhash fvs}
|
||||
assert_error {*wrong number of arguments*} {r hsetf myhash fvs 1}
|
||||
assert_error {*wrong number of arguments*} {r hsetf myhash fvs 2 a b}
|
||||
assert_error {*wrong number of arguments*} {r hsetf myhash fvs 3 a b c d}
|
||||
assert_error {*wrong number of arguments*} {r hsetf myhash fvs 3 a b}
|
||||
assert_error {*unknown argument*} {r hsetf myhash fvs 1 a b unknown}
|
||||
assert_error {*missing FVS argument*} {r hsetf myhash nx nx ex 100}
|
||||
assert_error {*multiple FVS argument*} {r hsetf myhash DC fvs 1 a b fvs 1 a b}
|
||||
|
||||
r hset myhash f1 v1 f2 v2 f3 v3
|
||||
# NX, XX, GT, and LT can be specified only when EX, PX, EXAT, or PXAT is specified
|
||||
assert_error {*only when EX, PX, EXAT, or PXAT is specified*} {r hsetf myhash nx fvs 1 a b}
|
||||
assert_error {*only when EX, PX, EXAT, or PXAT is specified*} {r hsetf myhash xx fvs 1 a b}
|
||||
assert_error {*only when EX, PX, EXAT, or PXAT is specified*} {r hsetf myhash gt fvs 1 a b}
|
||||
assert_error {*only when EX, PX, EXAT, or PXAT is specified*} {r hsetf myhash lt fvs 1 a b}
|
||||
|
||||
# Only one of NX, XX, GT, and LT can be specified
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hsetf myhash nx xx EX 100 fvs 1 a b}
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hsetf myhash xx nx EX 100 fvs 1 a b}
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hsetf myhash gt nx EX 100 fvs 1 a b}
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hsetf myhash gt lt EX 100 fvs 1 a b}
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hsetf myhash xx gt EX 100 fvs 1 a b}
|
||||
assert_error {*Only one of NX, XX, GT, and LT arguments*} {r hsetf myhash lt gt EX 100 fvs 1 a b}
|
||||
|
||||
# Only one of EX, PX, EXAT, PXAT or KEEPTTL can be specified
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetf myhash EX 100 PX 1000 fvs 1 a b}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetf myhash EX 100 EXAT 100 fvs 1 a b}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetf myhash EXAT 100 EX 1000 fvs 1 a b}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetf myhash EXAT 100 PX 1000 fvs 1 a b}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetf myhash PX 100 EXAT 100 fvs 1 a b}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetf myhash PX 100 PXAT 100 fvs 1 a b}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetf myhash PXAT 100 EX 100 fvs 1 a b}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetf myhash PXAT 100 EXAT 100 fvs 1 a b}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetf myhash EX 100 KEEPTTL fvs 1 a b}
|
||||
assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetf myhash KEEPTTL EX 100 fvs 1 a b}
|
||||
|
||||
# Only one of DCF, DOF can be specified
|
||||
assert_error {*Only one of DCF or DOF arguments can be specified*} {r hsetf myhash DCF DOF fvs 1 a b}
|
||||
assert_error {*Only one of DCF or DOF arguments can be specified*} {r hsetf myhash DOF DCF fvs 1 a b}
|
||||
|
||||
# Only one of GETNEW, GETOLD can be specified
|
||||
assert_error {*Only one of GETOLD or GETNEW arguments can be specified*} {r hsetf myhash GETNEW GETOLD fvs 1 a b}
|
||||
assert_error {*Only one of GETOLD or GETNEW arguments can be specified*} {r hsetf myhash GETOLD GETNEW fvs 1 a b}
|
||||
|
||||
# missing expire time
|
||||
assert_error {*not an integer or out of range*} {r hsetf myhash ex fvs 1 a b}
|
||||
assert_error {*not an integer or out of range*} {r hsetf myhash px fvs 1 a b}
|
||||
assert_error {*not an integer or out of range*} {r hsetf myhash exat fvs 1 a b}
|
||||
assert_error {*not an integer or out of range*} {r hsetf myhash pxat fvs 1 a b}
|
||||
|
||||
# expire time more than 2 ^ 48
|
||||
assert_error {*invalid expire time*} {r hsetf myhash EXAT [expr (1<<48)] 1 a b}
|
||||
assert_error {*invalid expire time*} {r hsetf myhash PXAT [expr (1<<48)] 1 a b}
|
||||
assert_error {*invalid expire time*} {r hsetf myhash EX [expr (1<<48) - [clock seconds] + 1000 ] 1 a b}
|
||||
assert_error {*invalid expire time*} {r hsetf myhash PX [expr (1<<48) - [clock milliseconds] + 1000 ] 1 a b}
|
||||
|
||||
# negative ttl
|
||||
assert_error {*invalid expire time*} {r hsetf myhash EXAT -1 1 a b}
|
||||
|
||||
# negative field value count
|
||||
assert_error {*invalid number of fvs count*} {r hsetf myhash fvs -1 a b}
|
||||
}
|
||||
|
||||
test "HSETF - Verify field value reply type is string ($type)" {
|
||||
r del myhash
|
||||
r hsetf myhash FVS 1 field 1
|
||||
r readraw 1
|
||||
|
||||
# Test with GETOLD
|
||||
assert_equal [r hsetf myhash GETOLD FVS 1 field 200] {*1}
|
||||
assert_equal [r read] {$1}
|
||||
assert_equal [r read] {1}
|
||||
|
||||
# Test with GETNEW.
|
||||
assert_equal [r hsetf myhash DOF GETNEW FVS 1 field 300] {*1}
|
||||
assert_equal [r read] {$3}
|
||||
assert_equal [r read] {200}
|
||||
|
||||
r readraw 0
|
||||
}
|
||||
|
||||
test "HSETF - Test DC flag ($type)" {
|
||||
r del myhash
|
||||
# don't create key
|
||||
assert_equal "" [r hsetf myhash DC fvs 1 a b]
|
||||
}
|
||||
|
||||
test "HSETF - Test DCF/DOF flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash f1 v1 f2 v2 f3 v3
|
||||
|
||||
# Don't overwrite fields
|
||||
assert_equal [r hsetf myhash DOF fvs 2 f1 n1 f2 n2] "$S_FAIL $S_FAIL"
|
||||
assert_equal [r hsetf myhash DOF fvs 3 f1 n1 f2 b2 f4 v4] "$S_FAIL $S_FAIL $S_FIELD"
|
||||
assert_equal [lsort [r hgetall myhash]] [lsort "f1 v1 f2 v2 f3 v3 f4 v4"]
|
||||
|
||||
# Don't create fields
|
||||
assert_equal [r hsetf myhash DCF fvs 3 f1 n1 f2 b2 f5 v5] "$S_FIELD $S_FIELD $S_FAIL"
|
||||
assert_equal [lsort [r hgetall myhash]] [lsort "f1 n1 f2 b2 f3 v3 f4 v4"]
|
||||
}
|
||||
|
||||
test "HSETF - Test 'NX' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash f1 v1 f2 v2 f3 v3
|
||||
assert_equal [r hsetf myhash EX 1000 NX FVS 1 f1 n1] "$S_FIELD_AND_TTL"
|
||||
assert_equal [r hsetf myhash EX 10000 NX FVS 2 f1 n1 f2 n2] "$S_FIELD $S_FIELD_AND_TTL"
|
||||
assert_range [r httl myhash FIELDS 1 f1] 990 1000
|
||||
assert_range [r httl myhash FIELDS 1 f2] 9990 10000
|
||||
}
|
||||
|
||||
test "HSETF - Test 'XX' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash f1 v1 f2 v2 f3 v3
|
||||
assert_equal [r hsetf myhash EX 1000 NX FVS 1 f1 n1] "$S_FIELD_AND_TTL"
|
||||
assert_equal [r hsetf myhash EX 10000 XX FVS 2 f1 n1 f2 n2] "$S_FIELD_AND_TTL $S_FIELD"
|
||||
assert_range [r httl myhash FIELDS 1 f1] 9900 10000
|
||||
assert_equal [r httl myhash FIELDS 1 f2] "$T_NO_EXPIRY"
|
||||
}
|
||||
|
||||
test "HSETF - Test 'GT' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash f1 v1 f2 v2 f3 v3
|
||||
assert_equal [r hsetf myhash EX 1000 NX FVS 1 f1 n1] "$S_FIELD_AND_TTL"
|
||||
assert_equal [r hsetf myhash EX 2000 NX FVS 1 f2 n2] "$S_FIELD_AND_TTL"
|
||||
assert_equal [r hsetf myhash EX 1500 GT FVS 2 f1 n1 f2 n2] "$S_FIELD_AND_TTL $S_FIELD"
|
||||
assert_range [r httl myhash FIELDS 1 f1] 1400 1500
|
||||
assert_range [r httl myhash FIELDS 1 f2] 1600 2000
|
||||
}
|
||||
|
||||
test "HSETF - Test 'LT' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash f1 v1 f2 v2 f3 v3
|
||||
assert_equal [r hsetf myhash EX 1000 NX FVS 1 f1 v1] "$S_FIELD_AND_TTL"
|
||||
assert_equal [r hsetf myhash EX 2000 NX FVS 1 f2 v2] "$S_FIELD_AND_TTL"
|
||||
assert_equal [r hsetf myhash EX 1500 LT FVS 2 f1 v1 f2 v2] "$S_FIELD $S_FIELD_AND_TTL"
|
||||
assert_range [r httl myhash FIELDS 1 f1] 900 1000
|
||||
assert_range [r httl myhash FIELDS 1 f2] 1400 1500
|
||||
|
||||
# A field with no expiration is treated as an infinite expiration.
|
||||
# LT should set the expire time if field has no TTL.
|
||||
r del myhash
|
||||
r hset myhash f1 v1
|
||||
assert_equal [r hsetf myhash EX 1500 LT FVS 1 f1 v1] "$S_FIELD_AND_TTL"
|
||||
}
|
||||
|
||||
test "HSETF - Test 'EX' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash f1 v1 f2 v2
|
||||
assert_equal [r hsetf myhash EX 1000 FVS 1 f3 v3 ] "$S_FIELD_AND_TTL"
|
||||
assert_range [r httl myhash FIELDS 1 f3] 900 1000
|
||||
}
|
||||
|
||||
test "HSETF - Test 'EXAT' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash f1 v1 f2 v2
|
||||
assert_equal [r hsetf myhash EXAT 4000000000 FVS 1 f3 v3] "$S_FIELD_AND_TTL"
|
||||
assert_range [expr [r httl myhash FIELDS 1 f3] + [clock seconds]] 3900000000 4000000000
|
||||
}
|
||||
|
||||
test "HSETF - Test 'PX' flag ($type)" {
|
||||
r del myhash
|
||||
assert_equal [r hsetf myhash PX 1000000 FVS 1 f3 v3] "$S_FIELD_AND_TTL"
|
||||
assert_range [r httl myhash FIELDS 1 f3] 990 1000
|
||||
}
|
||||
|
||||
test "HSETF - Test 'PXAT' flag ($type)" {
|
||||
r del myhash
|
||||
r hset myhash f1 v2 f2 v2 f3 v3
|
||||
assert_equal [r hsetf myhash PXAT 4000000000000 FVS 1 f2 v2] "$S_FIELD_AND_TTL"
|
||||
assert_range [expr [r httl myhash FIELDS 1 f2] + [clock seconds]] 3900000000 4000000000
|
||||
}
|
||||
|
||||
test "HSETF - Test 'KEEPTTL' flag ($type)" {
|
||||
r del myhash
|
||||
|
||||
r hsetf myhash FVS 2 f1 v1 f2 v2
|
||||
r hsetf myhash PX 5000 FVS 1 f2 v2
|
||||
|
||||
# f1 does not have ttl
|
||||
assert_equal [r httl myhash FIELDS 1 f1] "$T_NO_EXPIRY"
|
||||
|
||||
# f2 has ttl
|
||||
assert_not_equal [r httl myhash FIELDS 1 f2] "$T_NO_EXPIRY"
|
||||
|
||||
# Validate KEEPTTL preserve TTL
|
||||
assert_equal [r hsetf myhash KEEPTTL FVS 1 f2 n2] "$S_FIELD"
|
||||
assert_not_equal [r httl myhash FIELDS 1 f2] "$T_NO_EXPIRY"
|
||||
assert_equal [r hget myhash f2] "n2"
|
||||
}
|
||||
|
||||
test "HSETF - Test no expiry flag discards TTL ($type)" {
|
||||
r del myhash
|
||||
|
||||
r hsetf myhash FVS 1 f1 v1
|
||||
r hsetf myhash PX 5000 FVS 1 f2 v2
|
||||
|
||||
assert_equal [r hsetf myhash FVS 2 f1 v1 f2 v2] "$S_FIELD $S_FIELD_AND_TTL"
|
||||
assert_not_equal [r httl myhash FIELDS 1 f1 f2] "$T_NO_EXPIRY $T_NO_EXPIRY"
|
||||
}
|
||||
|
||||
test "HSETF - Test 'GETNEW/GETOLD' flag ($type)" {
|
||||
r del myhash
|
||||
|
||||
assert_equal [r hsetf myhash GETOLD fvs 2 f1 v1 f2 v2] "{} {}"
|
||||
assert_equal [r hsetf myhash GETNEW fvs 2 f1 v1 f2 v2] "v1 v2"
|
||||
assert_equal [r hsetf myhash GETOLD fvs 2 f1 n1 f2 n2] "v1 v2"
|
||||
assert_equal [r hsetf myhash GETOLD DOF fvs 2 f1 n1 f2 n2] "n1 n2"
|
||||
assert_equal [r hsetf myhash GETNEW DOF fvs 2 f1 n1 f2 n2] "n1 n2"
|
||||
assert_equal [r hsetf myhash GETNEW DCF fvs 2 f1 x1 f2 x2] "x1 x2"
|
||||
assert_equal [r hsetf myhash GETNEW DCF fvs 2 f4 x4 f5 x5] "{} {}"
|
||||
|
||||
r del myhash
|
||||
assert_equal [r hsetf myhash GETOLD fvs 2 f1 v1 f2 v2] "{} {}"
|
||||
|
||||
# DOF check will prevent override and GETNEW should return old value
|
||||
assert_equal [r hsetf myhash DOF GETNEW fvs 2 f1 v12 f2 v22] "v1 v2"
|
||||
}
|
||||
|
||||
test "HSETF - Test with active expiry" {
|
||||
r del myhash
|
||||
r debug set-active-expire 0
|
||||
|
||||
r hsetf myhash PX 10 FVS 5 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
|
||||
r debug set-active-expire 1
|
||||
wait_for_condition 50 20 { [r EXISTS myhash] == 0 } else { fail "'myhash' should be expired" }
|
||||
}
|
||||
|
||||
test "HSETF - Set time in the past ($type)" {
|
||||
r del myhash
|
||||
assert_equal [r hsetf myhash EXAT [expr {[clock seconds] - 1}] FVS 2 f1 v1 f2 v2] "$S_FIELD_AND_TTL $S_FIELD_AND_TTL"
|
||||
assert_equal [r hexists myhash field1] 0
|
||||
|
||||
# Try with override
|
||||
r hset myhash fvs 2 f1 v1 f2 v2
|
||||
assert_equal [r hsetf myhash EXAT [expr {[clock seconds] - 1}] FVS 2 f1 v1 f2 v2] "$S_FIELD_AND_TTL $S_FIELD_AND_TTL"
|
||||
assert_equal [r hexists myhash field1] 0
|
||||
}
|
||||
|
||||
test "HSETF - Test failed hsetf call should not leave empty key ($type)" {
|
||||
r del myhash
|
||||
# This should not create the field as DCF flag is given
|
||||
assert_equal [r hsetf myhash DCF FVS 1 a b] ""
|
||||
|
||||
# Key should not exist
|
||||
assert_equal [r exists myhash] 0
|
||||
|
||||
# Try with GETNEW/GETOLD
|
||||
assert_equal [r hsetf myhash GETNEW DCF FVS 1 a b] ""
|
||||
assert_equal [r exists myhash] 0
|
||||
assert_equal [r hsetf myhash GETOLD DCF FVS 1 a b] ""
|
||||
assert_equal [r exists myhash] 0
|
||||
}
|
||||
|
||||
test {DUMP / RESTORE are able to serialize / unserialize a hash} {
|
||||
r config set sanitize-dump-payload yes
|
||||
r del myhash
|
||||
r hmset myhash a 1 b 2 c 3
|
||||
r hexpireat myhash 2524600800 fields 1 a
|
||||
r hexpireat myhash 2524600801 fields 1 b
|
||||
|
@ -1153,6 +732,7 @@ start_server {tags {"external:skip needs:debug"}} {
|
|||
|
||||
test {DUMP / RESTORE are able to serialize / unserialize a hash with TTL 0 for all fields} {
|
||||
r config set sanitize-dump-payload yes
|
||||
r del myhash
|
||||
r hmset myhash a 1 b 2 c 3
|
||||
r hexpire myhash 9999999 fields 1 a ;# make all TTLs of fields to 0
|
||||
r hpersist myhash fields 1 a
|
||||
|
@ -1231,15 +811,19 @@ start_server {tags {"external:skip needs:debug"}} {
|
|||
wait_for_condition 50 20 { [r EXISTS myhash] == 0 } else { fail "'myhash' should be expired" }
|
||||
}
|
||||
|
||||
test "HSETF - Test listpack converts to ht" {
|
||||
test "Test listpack converts to ht and active expiry works" {
|
||||
r del myhash
|
||||
r debug set-active-expire 0
|
||||
|
||||
# Check expiry works after listpack converts ht by using hsetf
|
||||
# Check expiry works after listpack converts to ht
|
||||
for {set i 0} {$i < 1024} {incr i} {
|
||||
r hsetf myhash PX 10 FVS 3 a$i b$i c$i d$i e$i f$i
|
||||
r hset myhash f1_$i v1_$i f2_$i v2_$i f3_$i v3_$i f4_$i v4_$i
|
||||
r hpexpire myhash 10 FIELDS 4 f1_$i f2_$i f3_$i f4_$i
|
||||
}
|
||||
|
||||
assert_encoding hashtable myhash
|
||||
assert_equal [r hlen myhash] 4096
|
||||
|
||||
r debug set-active-expire 1
|
||||
wait_for_condition 50 20 { [r EXISTS myhash] == 0 } else { fail "'myhash' should be expired" }
|
||||
}
|
||||
|
@ -1258,7 +842,8 @@ start_server {tags {"external:skip needs:debug"}} {
|
|||
|
||||
# Test with single item list
|
||||
r hset myhash f1 $payload1
|
||||
assert_equal [r hgetf myhash EX 2000 FIELDS 1 f1] $payload1
|
||||
r hexpire myhash 2000 FIELDS 1 f1
|
||||
assert_equal [r hget myhash f1] $payload1
|
||||
r del myhash
|
||||
|
||||
# Test with multiple items
|
||||
|
|
Loading…
Reference in New Issue