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("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("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("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 ********************/
|
/********** 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("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("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("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("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("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},
|
||||||
|
@ -56,6 +56,12 @@
|
|||||||
"name": "count",
|
"name": "count",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"optional": true
|
"optional": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "NOVALUES",
|
||||||
|
"name": "novalues",
|
||||||
|
"type": "pure-token",
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"reply_schema": {
|
"reply_schema": {
|
||||||
@ -69,7 +75,7 @@
|
|||||||
"type": "string"
|
"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",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"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 */
|
long long type; /* the particular type when scan the db */
|
||||||
sds pattern; /* pattern string, NULL means no pattern */
|
sds pattern; /* pattern string, NULL means no pattern */
|
||||||
long sampled; /* cumulative number of keys sampled */
|
long sampled; /* cumulative number of keys sampled */
|
||||||
|
int no_values; /* set to 1 means to return keys only */
|
||||||
} scanData;
|
} scanData;
|
||||||
|
|
||||||
/* Helper function to compare key type in scan commands */
|
/* Helper function to compare key type in scan commands */
|
||||||
@ -1114,7 +1115,7 @@ void scanCallback(void *privdata, const dictEntry *de) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listAddNodeTail(keys, key);
|
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':
|
/* 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 pat = NULL;
|
||||||
sds typename = NULL;
|
sds typename = NULL;
|
||||||
long long type = LLONG_MAX;
|
long long type = LLONG_MAX;
|
||||||
int patlen = 0, use_pattern = 0;
|
int patlen = 0, use_pattern = 0, no_values = 0;
|
||||||
dict *ht;
|
dict *ht;
|
||||||
|
|
||||||
/* Object must be NULL (to iterate keys names), or the type of the object
|
/* 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; */
|
return; */
|
||||||
}
|
}
|
||||||
i+= 2;
|
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 {
|
} else {
|
||||||
addReplyErrorObject(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
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;
|
* 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
|
* 3. data.type: the specified type scan in the db, LLONG_MAX means
|
||||||
* type matching is no needed;
|
* 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
|
* 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
|
* 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
|
* 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 = {
|
scanData data = {
|
||||||
.keys = keys,
|
.keys = keys,
|
||||||
.o = o,
|
.o = o,
|
||||||
.type = type,
|
.type = type,
|
||||||
.pattern = use_pattern ? pat : NULL,
|
.pattern = use_pattern ? pat : NULL,
|
||||||
.sampled = 0,
|
.sampled = 0,
|
||||||
|
.no_values = no_values,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* A pattern may restrict all matching keys to one cluster slot. */
|
/* 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 */
|
/* add key object */
|
||||||
listAddNodeTail(keys, sdsnewlen(str, len));
|
listAddNodeTail(keys, sdsnewlen(str, len));
|
||||||
/* add value object */
|
/* add value object */
|
||||||
str = lpGet(p, &len, intbuf);
|
if (!no_values) {
|
||||||
listAddNodeTail(keys, sdsnewlen(str, len));
|
str = lpGet(p, &len, intbuf);
|
||||||
|
listAddNodeTail(keys, sdsnewlen(str, len));
|
||||||
|
}
|
||||||
p = lpNext(o->ptr, p);
|
p = lpNext(o->ptr, p);
|
||||||
}
|
}
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
|
@ -272,6 +272,10 @@ proc test_scan {type} {
|
|||||||
|
|
||||||
set keys2 [lsort -unique $keys2]
|
set keys2 [lsort -unique $keys2]
|
||||||
assert_equal $count [llength $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]
|
lsort -unique [lindex $res 1]
|
||||||
} {1 10 foo foobar}
|
} {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" {
|
test "{$type} ZSCAN with PATTERN" {
|
||||||
r del mykey
|
r del mykey
|
||||||
r zadd mykey 1 foo 2 fab 3 fiz 10 foobar
|
r zadd mykey 1 foo 2 fab 3 fiz 10 foobar
|
||||||
|
Loading…
x
Reference in New Issue
Block a user