From 3f7933826ddf511668dca9cbc303436eace717ab Mon Sep 17 00:00:00 2001 From: Arcadiy Ivanov Date: Thu, 19 Dec 2024 22:58:45 -0500 Subject: [PATCH] Add MGETPXT with the same semantics Remove the "overload" as non-precision timestamp isn't useful in the intended context --- src/commands.def | 27 ++++++++++++ src/commands/mgetpxt.json | 88 ++++++++++++++++++++++++++++++++++++++ src/server.h | 1 + src/t_string.c | 36 +++++++++++++--- tests/unit/type/string.tcl | 24 ++++++++++- 5 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 src/commands/mgetpxt.json diff --git a/src/commands.def b/src/commands.def index f09e5bd6ef..e9979386dc 100644 --- a/src/commands.def +++ b/src/commands.def @@ -10558,6 +10558,32 @@ struct COMMAND_ARG MGET_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; +/********** MGETPXT ********************/ + +#ifndef SKIP_CMD_HISTORY_TABLE +/* MGETPXT history */ +#define MGETPXT_History NULL +#endif + +#ifndef SKIP_CMD_TIPS_TABLE +/* MGETPXT tips */ +const char *MGETPXT_Tips[] = { +"request_policy:multi_shard", +}; +#endif + +#ifndef SKIP_CMD_KEY_SPECS_TABLE +/* MGETPXT key specs */ +keySpec MGETPXT_Keyspecs[1] = { +{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} +}; +#endif + +/* MGETPXT argument table */ +struct COMMAND_ARG MGETPXT_Args[] = { +{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, +}; + /********** MSET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE @@ -11163,6 +11189,7 @@ struct COMMAND_STRUCT serverCommandTable[] = { {MAKE_CMD("incrbyfloat","Increment the floating point value of a key by a number. Uses 0 as initial value if the key doesn't exist.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCRBYFLOAT_History,0,INCRBYFLOAT_Tips,0,incrbyfloatCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,INCRBYFLOAT_Keyspecs,1,NULL,2),.args=INCRBYFLOAT_Args}, {MAKE_CMD("lcs","Finds the longest common substring.","O(N*M) where N and M are the lengths of s1 and s2, respectively","7.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,LCS_History,0,LCS_Tips,0,lcsCommand,-3,CMD_READONLY,ACL_CATEGORY_STRING,LCS_Keyspecs,1,NULL,6),.args=LCS_Args}, {MAKE_CMD("mget","Atomically returns the string values of one or more keys.","O(N) where N is the number of keys to retrieve.","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MGET_History,0,MGET_Tips,1,mgetCommand,-2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING,MGET_Keyspecs,1,NULL,1),.args=MGET_Args}, +{MAKE_CMD("mgetpxt","Atomically returns the string values of one or more keys and their millisecond expiration, if available.","O(N) where N is the number of keys to retrieve.","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MGETPXT_History,0,MGETPXT_Tips,1,mgetpxtCommand,-2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING|ACL_CATEGORY_KEYSPACE,MGETPXT_Keyspecs,1,NULL,1),.args=MGETPXT_Args}, {MAKE_CMD("mset","Atomically creates or modifies the string values of one or more keys.","O(N) where N is the number of keys to set.","1.0.1",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MSET_History,0,MSET_Tips,2,msetCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,MSET_Keyspecs,1,NULL,1),.args=MSET_Args}, {MAKE_CMD("msetnx","Atomically modifies the string values of one or more keys only when all keys don't exist.","O(N) where N is the number of keys to set.","1.0.1",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MSETNX_History,0,MSETNX_Tips,0,msetnxCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,MSETNX_Keyspecs,1,NULL,1),.args=MSETNX_Args}, {MAKE_CMD("psetex","Sets both string value and expiration time in milliseconds of a key. The key is created if it doesn't exist.","O(1)","2.6.0",CMD_DOC_DEPRECATED,"`SET` with the `PX` argument","2.6.12","string",COMMAND_GROUP_STRING,PSETEX_History,0,PSETEX_Tips,0,psetexCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,PSETEX_Keyspecs,1,NULL,3),.args=PSETEX_Args}, diff --git a/src/commands/mgetpxt.json b/src/commands/mgetpxt.json new file mode 100644 index 0000000000..2c5ed43b98 --- /dev/null +++ b/src/commands/mgetpxt.json @@ -0,0 +1,88 @@ +{ + "MGETPXT": { + "summary": "Atomically returns the string values of one or more keys and their millisecond expiration, if available.", + "complexity": "O(N) where N is the number of keys to retrieve.", + "group": "string", + "since": "1.0.0", + "arity": -2, + "function": "mgetpxtCommand", + "command_flags": [ + "READONLY", + "FAST" + ], + "acl_categories": [ + "STRING", + "KEYSPACE" + ], + "command_tips": [ + "REQUEST_POLICY:MULTI_SHARD" + ], + "key_specs": [ + { + "flags": [ + "RO", + "ACCESS" + ], + "begin_search": { + "index": { + "pos": 1 + } + }, + "find_keys": { + "range": { + "lastkey": -1, + "step": 1, + "limit": 0 + } + } + } + ], + "reply_schema": { + "description": "List of values at the specified keys.", + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ + { + "description": "The value of the key.", + "type": "string" + }, + { + "oneOf": [ + { + "type": "integer", + "description": "Expiration Unix timestamp in milliseconds.", + "minimum": 0 + }, + { + "const": -1, + "description": "The key exists but has no associated expiration time." + } + ] + } + ] + } + }, + { + "type": "null" + } + ] + } + }, + "arguments": [ + { + "name": "key", + "type": "key", + "key_spec_index": 0, + "multiple": true + } + ] + } +} diff --git a/src/server.h b/src/server.h index c39feac3b9..0e0c51de12 100644 --- a/src/server.h +++ b/src/server.h @@ -3856,6 +3856,7 @@ void rpoplpushCommand(client *c); void lmoveCommand(client *c); void infoCommand(client *c); void mgetCommand(client *c); +void mgetpxtCommand(client *c); void monitorCommand(client *c); void expireCommand(client *c); void expireatCommand(client *c); diff --git a/src/t_string.c b/src/t_string.c index 525ed793f6..0623e175e3 100644 --- a/src/t_string.c +++ b/src/t_string.c @@ -393,7 +393,7 @@ void getCommand(client *c) { getGenericCommand(c); } -void getExpireGenericCommand(client *c, int output_ms) { +void getpxtCommand(client *c) { long long expire; robj *o; @@ -413,14 +413,10 @@ void getExpireGenericCommand(client *c, int output_ms) { if (expire == -1) { addReplyLongLong(c, -1); } else { - addReplyLongLong(c, output_ms ? expire : ((expire + 500) / 1000)); + addReplyLongLong(c, expire); } } -void getpxtCommand(client *c) { - getExpireGenericCommand(c, 1); -} - /* * GETEX [PERSIST][EX seconds][PX milliseconds][EXAT seconds-timestamp][PXAT milliseconds-timestamp] * @@ -636,6 +632,34 @@ void mgetCommand(client *c) { } } +void mgetpxtCommand(client *c) { + int j; + + addReplyArrayLen(c, c->argc - 1); + for (j = 1; j < c->argc; j++) { + robj *o = lookupKeyRead(c->db, c->argv[j]); + if (o == NULL) { + addReplyNull(c); + } else { + if (o->type != OBJ_STRING) { + addReplyNull(c); + } else { + addReplyArrayLen(c, 2); + addReplyBulk(c, o); + + /* The key exists. Return -1 if it has no expire, or the actual + * expire value otherwise. */ + long long expire = getExpire(c->db, c->argv[j]); + if (expire == -1) { + addReplyLongLong(c, -1); + } else { + addReplyLongLong(c, expire); + } + } + } + } +} + void msetGenericCommand(client *c, int nx) { int j; diff --git a/tests/unit/type/string.tcl b/tests/unit/type/string.tcl index a9bc22b4a8..4bf916f611 100644 --- a/tests/unit/type/string.tcl +++ b/tests/unit/type/string.tcl @@ -214,6 +214,28 @@ start_server {tags {"string"}} { r mget foo{t} baazz{t} bar{t} myset{t} } {BAR {} FOO {}} + test {MGETPXT} { + r flushdb + r set foo{t} BAR pxat 17344823940230 + r set bar{t} FOO pxat 17344823940231 + r mgetpxt foo{t} bar{t} + } {{BAR 17344823940230} {FOO 17344823940231}} + + test {MGETPXT against non existing key} { + r mgetpxt foo{t} baazz{t} bar{t} + } {{BAR 17344823940230} {} {FOO 17344823940231}} + + test {MGETPXT against non-string key} { + r sadd myset{t} ciao + r sadd myset{t} bau + r mgetpxt foo{t} baazz{t} bar{t} myset{t} + } {{BAR 17344823940230} {} {FOO 17344823940231} {}} + + test {MGETPXT against a key with no expiration} { + r set baz{t} BAZ + r mgetpxt foo{t} baz{t} bar{t} + } {{BAR 17344823940230} {BAZ -1} {FOO 17344823940231}} + test {GETSET (set new value)} { r del foo list [r getset foo xyz] [r get foo] @@ -664,7 +686,7 @@ if {[string match {*jemalloc*} [s mem_allocator]]} { r getpxt foo } {bar 17344823940230} - test "GETPXT after SET" { + test "GETPXT after SET with no expiration" { r del foo r set foo bar r getpxt foo