Unverified Commit 2f34f6f0 authored by Ozan Tezcan's avatar Ozan Tezcan Committed by GitHub
Browse files

Delete hsetf and hgetf (#13291)



Changes:
- Delete hsetf and hgetf commands
- Hfe commands will return empty array instead of nil.
---------
Co-authored-by: default avatarMoti Cohen <moticless@gmail.com>
parent 60e1582d
...@@ -3455,52 +3455,6 @@ struct COMMAND_ARG HGETALL_Args[] = { ...@@ -3455,52 +3455,6 @@ struct COMMAND_ARG HGETALL_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {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 ********************/ /********** HINCRBY ********************/
#ifndef SKIP_CMD_HISTORY_TABLE #ifndef SKIP_CMD_HISTORY_TABLE
...@@ -3910,73 +3864,6 @@ struct COMMAND_ARG HSET_Args[] = { ...@@ -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}, {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 ********************/ /********** HSETNX ********************/
#ifndef SKIP_CMD_HISTORY_TABLE #ifndef SKIP_CMD_HISTORY_TABLE
...@@ -11111,7 +10998,6 @@ struct COMMAND_STRUCT redisCommandTable[] = { ...@@ -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("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("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("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("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("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}, {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[] = { ...@@ -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("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("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("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("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("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}, {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},
......
{
"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
}
]
}
}
{
"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); ...@@ -3669,7 +3669,6 @@ void strlenCommand(client *c);
void zrankCommand(client *c); void zrankCommand(client *c);
void zrevrankCommand(client *c); void zrevrankCommand(client *c);
void hsetCommand(client *c); void hsetCommand(client *c);
void hsetfCommand(client *c);
void hpexpireCommand(client *c); void hpexpireCommand(client *c);
void hexpireCommand(client *c); void hexpireCommand(client *c);
void hpexpireatCommand(client *c); void hpexpireatCommand(client *c);
...@@ -3681,7 +3680,6 @@ void hpexpiretimeCommand(client *c); ...@@ -3681,7 +3680,6 @@ void hpexpiretimeCommand(client *c);
void hpersistCommand(client *c); void hpersistCommand(client *c);
void hsetnxCommand(client *c); void hsetnxCommand(client *c);
void hgetCommand(client *c); void hgetCommand(client *c);
void hgetfCommand(client *c);
void hmgetCommand(client *c); void hmgetCommand(client *c);
void hdelCommand(client *c); void hdelCommand(client *c);
void hlenCommand(client *c); void hlenCommand(client *c);
......
...@@ -21,7 +21,6 @@ static ExpireMeta *hashGetExpireMeta(const eItem hash); ...@@ -21,7 +21,6 @@ static ExpireMeta *hashGetExpireMeta(const eItem hash);
static void hexpireGenericCommand(client *c, const char *cmd, long long basetime, int unit); static void hexpireGenericCommand(client *c, const char *cmd, long long basetime, int unit);
static ExpireAction hashTypeActiveExpire(eItem hashObj, void *ctx); static ExpireAction hashTypeActiveExpire(eItem hashObj, void *ctx);
static void hfieldPersist(robj *hashObj, hfield field); static void hfieldPersist(robj *hashObj, hfield field);
static void updateGlobalHfeDs(redisDb *db, robj *o, uint64_t minExpire, uint64_t minExpireFields);
/* hash dictType funcs */ /* hash dictType funcs */
static int dictHfieldKeyCompare(dict *d, const void *key1, const void *key2); static int dictHfieldKeyCompare(dict *d, const void *key1, const void *key2);
...@@ -1076,7 +1075,31 @@ void hashTypeSetExDone(HashTypeSetEx *ex) { ...@@ -1076,7 +1075,31 @@ void hashTypeSetExDone(HashTypeSetEx *ex) {
dbDelete(ex->db,ex->key); dbDelete(ex->db,ex->key);
if (ex->c) notifyKeyspaceEvent(NOTIFY_GENERIC,"del",ex->key, ex->db->id); if (ex->c) notifyKeyspaceEvent(NOTIFY_GENERIC,"del",ex->key, ex->db->id);
} else { } 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 ...@@ -2679,7 +2702,7 @@ static void httlGenericCommand(client *c, const char *cmd, long long basetime, i
long numFields = 0, numFieldsAt = 3; long numFields = 0, numFieldsAt = 3;
/* Read the hash object */ /* 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; checkType(c, hashObj, OBJ_HASH)) return;
if (strcasecmp(c->argv[numFieldsAt-1]->ptr, "FIELDS")) { if (strcasecmp(c->argv[numFieldsAt-1]->ptr, "FIELDS")) {
...@@ -2802,7 +2825,7 @@ static void hexpireGenericCommand(client *c, const char *cmd, long long basetime ...@@ -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]; robj *hashObj, *keyArg = c->argv[1], *expireArg = c->argv[2];
/* Read the hash object */ /* 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; checkType(c, hashObj, OBJ_HASH)) return;
/* Read the expiry time from command */ /* Read the expiry time from command */
...@@ -2926,7 +2949,7 @@ void hpersistCommand(client *c) { ...@@ -2926,7 +2949,7 @@ void hpersistCommand(client *c) {
int changed = 0; /* Used to determine whether to send a notification. */ int changed = 0; /* Used to determine whether to send a notification. */
/* Read the hash object */ /* 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; checkType(c, hashObj, OBJ_HASH)) return;
if (strcasecmp(c->argv[numFieldsAt-1]->ptr, "FIELDS")) { if (strcasecmp(c->argv[numFieldsAt-1]->ptr, "FIELDS")) {
...@@ -3035,891 +3058,3 @@ void hpersistCommand(client *c) { ...@@ -3035,891 +3058,3 @@ void hpersistCommand(client *c) {
* has been successfully deleted. */ * has been successfully deleted. */
if (changed) notifyKeyspaceEvent(NOTIFY_HASH,"hpersist",c->argv[1],c->db->id); 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 ...@@ -17,11 +17,6 @@ set P_NO_FIELD -2
set P_NO_EXPIRY -1 set P_NO_EXPIRY -1
set P_OK 1 set P_OK 1
######## HSETF
set S_FAIL 0
set S_FIELD 1
set S_FIELD_AND_TTL 3
############################### AUX FUNCS ###################################### ############################### AUX FUNCS ######################################
proc get_hashes_with_expiry_fields {r} { proc get_hashes_with_expiry_fields {r} {
...@@ -109,6 +104,17 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -109,6 +104,17 @@ start_server {tags {"external:skip needs:debug"}} {
r config set hash-max-listpack-entries 512 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)" { test "HPEXPIRE(AT) - Test 'NX' flag ($type)" {
r del myhash r del myhash
r hset myhash field1 value1 field2 value2 field3 value3 r hset myhash field1 value1 field2 value2 field3 value3
...@@ -278,6 +284,15 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -278,6 +284,15 @@ start_server {tags {"external:skip needs:debug"}} {
r flushall async 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)" { test "HTTL/HPTTL - Input validation gets failed on nonexists field or field without expire ($type)" {
r del myhash r del myhash
r HSET myhash field1 value1 field2 value2 r HSET myhash field1 value1 field2 value2
...@@ -301,6 +316,15 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -301,6 +316,15 @@ start_server {tags {"external:skip needs:debug"}} {
assert_range $ttl 1000 2000 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)" { test "HEXPIRETIME - returns TTL in Unix timestamp ($type)" {
r del myhash r del myhash
r HSET myhash field1 value1 r HSET myhash field1 value1
...@@ -652,6 +676,14 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -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" } wait_for_condition 20 10 { [r exists myhash] == 0 } else { fail "'myhash' should be expired" }
} {} {singledb:skip} } {} {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)" { test "HPERSIST - input validation ($type)" {
# HPERSIST key <num-fields> <field [field ...]> # HPERSIST key <num-fields> <field [field ...]>
r del myhash r del myhash
...@@ -685,462 +717,9 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -685,462 +717,9 @@ start_server {tags {"external:skip needs:debug"}} {
assert_equal [r hpersist myhash FIELDS 1 fieldnonexist] $P_NO_FIELD 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} { test {DUMP / RESTORE are able to serialize / unserialize a hash} {
r config set sanitize-dump-payload yes r config set sanitize-dump-payload yes
r del myhash
r hmset myhash a 1 b 2 c 3 r hmset myhash a 1 b 2 c 3
r hexpireat myhash 2524600800 fields 1 a r hexpireat myhash 2524600800 fields 1 a
r hexpireat myhash 2524600801 fields 1 b r hexpireat myhash 2524600801 fields 1 b
...@@ -1153,6 +732,7 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -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} { test {DUMP / RESTORE are able to serialize / unserialize a hash with TTL 0 for all fields} {
r config set sanitize-dump-payload yes r config set sanitize-dump-payload yes
r del myhash
r hmset myhash a 1 b 2 c 3 r hmset myhash a 1 b 2 c 3
r hexpire myhash 9999999 fields 1 a ;# make all TTLs of fields to 0 r hexpire myhash 9999999 fields 1 a ;# make all TTLs of fields to 0
r hpersist myhash fields 1 a r hpersist myhash fields 1 a
...@@ -1231,15 +811,19 @@ start_server {tags {"external:skip needs:debug"}} { ...@@ -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" } 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 del myhash
r debug set-active-expire 0 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} { 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 r debug set-active-expire 1
wait_for_condition 50 20 { [r EXISTS myhash] == 0 } else { fail "'myhash' should be expired" } 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"}} { ...@@ -1258,7 +842,8 @@ start_server {tags {"external:skip needs:debug"}} {
# Test with single item list # Test with single item list
r hset myhash f1 $payload1 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 r del myhash
# Test with multiple items # Test with multiple items
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment