diff --git a/src/commands.def b/src/commands.def index aede27a02..b8c949209 100644 --- a/src/commands.def +++ b/src/commands.def @@ -3567,6 +3567,7 @@ struct COMMAND_ARG HSCAN_Args[] = { {MAKE_ARG("cursor",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,"MATCH",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("novalues",ARG_TYPE_PURE_TOKEN,-1,"NOVALUES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** HSET ********************/ @@ -10713,7 +10714,7 @@ struct COMMAND_STRUCT redisCommandTable[] = { {MAKE_CMD("hmget","Returns the values of all fields in a hash.","O(N) where N is the number of fields being requested.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HMGET_History,0,HMGET_Tips,0,hmgetCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HMGET_Keyspecs,1,NULL,2),.args=HMGET_Args}, {MAKE_CMD("hmset","Sets the values of multiple fields.","O(N) where N is the number of fields being set.","2.0.0",CMD_DOC_DEPRECATED,"`HSET` with multiple field-value pairs","4.0.0","hash",COMMAND_GROUP_HASH,HMSET_History,0,HMSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HMSET_Keyspecs,1,NULL,2),.args=HMSET_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,4),.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("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}, diff --git a/src/commands/hscan.json b/src/commands/hscan.json index 0888eec9f..99e916574 100644 --- a/src/commands/hscan.json +++ b/src/commands/hscan.json @@ -56,6 +56,12 @@ "name": "count", "type": "integer", "optional": true + }, + { + "token": "NOVALUES", + "name": "novalues", + "type": "pure-token", + "optional": true } ], "reply_schema": { @@ -69,7 +75,7 @@ "type": "string" }, { - "description": "list of key/value pairs from the hash where each even element is the key, and each odd element is the value", + "description": "list of key/value pairs from the hash where each even element is the key, and each odd element is the value, or when novalues option is on, a list of keys from the hash", "type": "array", "items": { "type": "string" diff --git a/src/db.c b/src/db.c index 03e5cc5f1..e5b7387d0 100644 --- a/src/db.c +++ b/src/db.c @@ -1052,6 +1052,7 @@ typedef struct { long long type; /* the particular type when scan the db */ sds pattern; /* pattern string, NULL means no pattern */ long sampled; /* cumulative number of keys sampled */ + int no_values; /* set to 1 means to return keys only */ } scanData; /* Helper function to compare key type in scan commands */ @@ -1114,7 +1115,7 @@ void scanCallback(void *privdata, const dictEntry *de) { } listAddNodeTail(keys, key); - if (val) listAddNodeTail(keys, val); + if (val && !data->no_values) listAddNodeTail(keys, val); } /* Try to parse a SCAN cursor stored at object 'o': @@ -1187,7 +1188,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) { sds pat = NULL; sds typename = NULL; long long type = LLONG_MAX; - int patlen = 0, use_pattern = 0; + int patlen = 0, use_pattern = 0, no_values = 0; dict *ht; /* Object must be NULL (to iterate keys names), or the type of the object @@ -1233,6 +1234,13 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) { return; */ } i+= 2; + } else if (!strcasecmp(c->argv[i]->ptr, "novalues")) { + if (!o || o->type != OBJ_HASH) { + addReplyError(c, "NOVALUES option can only be used in HSCAN"); + return; + } + no_values = 1; + i++; } else { addReplyErrorObject(c,shared.syntaxerr); return; @@ -1287,17 +1295,20 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) { * it is possible to fetch more data in a type-dependent way; * 3. data.type: the specified type scan in the db, LLONG_MAX means * type matching is no needed; - * 4. data.pattern: the pattern string + * 4. data.pattern: the pattern string; * 5. data.sampled: the maxiteration limit is there in case we're * working on an empty dict, one with a lot of empty buckets, and * for the buckets are not empty, we need to limit the spampled number - * to prevent a long hang time caused by filtering too many keys*/ + * to prevent a long hang time caused by filtering too many keys; + * 6. data.no_values: to control whether values will be returned or + * only keys are returned. */ scanData data = { .keys = keys, .o = o, .type = type, .pattern = use_pattern ? pat : NULL, .sampled = 0, + .no_values = no_values, }; /* A pattern may restrict all matching keys to one cluster slot. */ @@ -1352,8 +1363,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) { /* add key object */ listAddNodeTail(keys, sdsnewlen(str, len)); /* add value object */ - str = lpGet(p, &len, intbuf); - listAddNodeTail(keys, sdsnewlen(str, len)); + if (!no_values) { + str = lpGet(p, &len, intbuf); + listAddNodeTail(keys, sdsnewlen(str, len)); + } p = lpNext(o->ptr, p); } cursor = 0; diff --git a/tests/unit/scan.tcl b/tests/unit/scan.tcl index 5e093d90d..d980a52ad 100644 --- a/tests/unit/scan.tcl +++ b/tests/unit/scan.tcl @@ -272,6 +272,10 @@ proc test_scan {type} { set keys2 [lsort -unique $keys2] assert_equal $count [llength $keys2] + + # Test NOVALUES + set res [r hscan hash 0 count 1000 novalues] + assert_equal [lsort $keys2] [lsort [lindex $res 1]] } } @@ -368,6 +372,13 @@ proc test_scan {type} { lsort -unique [lindex $res 1] } {1 10 foo foobar} + test "{$type} HSCAN with NOVALUES" { + r del mykey + r hmset mykey foo 1 fab 2 fiz 3 foobar 10 1 a 2 b 3 c 4 d + set res [r hscan mykey 0 NOVALUES] + lsort -unique [lindex $res 1] + } {1 2 3 4 fab fiz foo foobar} + test "{$type} ZSCAN with PATTERN" { r del mykey r zadd mykey 1 foo 2 fab 3 fiz 10 foobar