mirror of
http://github.com/valkey-io/valkey
synced 2024-11-22 00:52:38 +00:00
Add novalues option to command HSCAN. (#12765)
Add a way to HSCAN a hash key, and get only the filed names. Command syntax is now: ``` HSCAN key cursor [MATCH pattern] [COUNT count] [NOVALUES] ``` when `NOVALUES` is on, the command will only return keys in the hash. --------- Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
This commit is contained in:
parent
24f6d08b3f
commit
f469dd8ca6
@ -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},
|
||||
|
@ -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"
|
||||
|
25
src/db.c
25
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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user