Skip to content

Commit

Permalink
Add noscore option to zscan.
Browse files Browse the repository at this point in the history
  • Loading branch information
CharlesChen888 committed Apr 16, 2024
1 parent 2ec8f63 commit f3b952b
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 13 deletions.
3 changes: 2 additions & 1 deletion src/commands.def
Expand Up @@ -9080,6 +9080,7 @@ struct COMMAND_ARG ZSCAN_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("noscores",ARG_TYPE_PURE_TOKEN,-1,"NOSCORES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};

/********** ZSCORE ********************/
Expand Down Expand Up @@ -10864,7 +10865,7 @@ struct COMMAND_STRUCT serverCommandTable[] = {
{MAKE_CMD("zrevrangebylex","Returns members in a sorted set within a lexicographical range in reverse order.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).","2.8.9",CMD_DOC_DEPRECATED,"`ZRANGE` with the `REV` and `BYLEX` arguments","6.2.0","sorted_set",COMMAND_GROUP_SORTED_SET,ZREVRANGEBYLEX_History,0,ZREVRANGEBYLEX_Tips,0,zrevrangebylexCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZREVRANGEBYLEX_Keyspecs,1,NULL,4),.args=ZREVRANGEBYLEX_Args},
{MAKE_CMD("zrevrangebyscore","Returns members in a sorted set within a range of scores in reverse order.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).","2.2.0",CMD_DOC_DEPRECATED,"`ZRANGE` with the `REV` and `BYSCORE` arguments","6.2.0","sorted_set",COMMAND_GROUP_SORTED_SET,ZREVRANGEBYSCORE_History,1,ZREVRANGEBYSCORE_Tips,0,zrevrangebyscoreCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZREVRANGEBYSCORE_Keyspecs,1,NULL,5),.args=ZREVRANGEBYSCORE_Args},
{MAKE_CMD("zrevrank","Returns the index of a member in a sorted set ordered by descending scores.","O(log(N))","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZREVRANK_History,1,ZREVRANK_Tips,0,zrevrankCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZREVRANK_Keyspecs,1,NULL,3),.args=ZREVRANK_Args},
{MAKE_CMD("zscan","Iterates over members and scores of a sorted set.","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,"sorted_set",COMMAND_GROUP_SORTED_SET,ZSCAN_History,0,ZSCAN_Tips,1,zscanCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZSCAN_Keyspecs,1,NULL,4),.args=ZSCAN_Args},
{MAKE_CMD("zscan","Iterates over members and scores of a sorted set.","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,"sorted_set",COMMAND_GROUP_SORTED_SET,ZSCAN_History,0,ZSCAN_Tips,1,zscanCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZSCAN_Keyspecs,1,NULL,5),.args=ZSCAN_Args},
{MAKE_CMD("zscore","Returns the score of a member in a sorted set.","O(1)","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZSCORE_History,0,ZSCORE_Tips,0,zscoreCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZSCORE_Keyspecs,1,NULL,2),.args=ZSCORE_Args},
{MAKE_CMD("zunion","Returns the union of multiple sorted sets.","O(N)+O(M*log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZUNION_History,0,ZUNION_Tips,0,zunionCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZUNION_Keyspecs,1,zunionInterDiffGetKeys,5),.args=ZUNION_Args},
{MAKE_CMD("zunionstore","Stores the union of multiple sorted sets in a key.","O(N)+O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZUNIONSTORE_History,0,ZUNIONSTORE_Tips,0,zunionstoreCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,ZUNIONSTORE_Keyspecs,2,zunionInterDiffStoreGetKeys,5),.args=ZUNIONSTORE_Args},
Expand Down
8 changes: 7 additions & 1 deletion src/commands/zscan.json
Expand Up @@ -56,6 +56,12 @@
"name": "count",
"type": "integer",
"optional": true
},
{
"token": "NOSCORES",
"name": "noscores",
"type": "pure-token",
"optional": true
}
],
"reply_schema": {
Expand All @@ -69,7 +75,7 @@
"type": "string"
},
{
"description": "List of elements of the sorted set, where each even element is the member, and each odd value is its associated score.",
"description": "List of elements of the sorted set, where each even element is the member, and each odd value is its associated score, or when noscores option is on, a list of members from the sorted set.",
"type": "array",
"items": {
"type": "string"
Expand Down
33 changes: 22 additions & 11 deletions src/db.c
Expand Up @@ -853,7 +853,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 */
int only_keys; /* set to 1 means to return keys only */
} scanData;

/* Helper function to compare key type in scan commands */
Expand Down Expand Up @@ -905,18 +905,22 @@ void scanCallback(void *privdata, const dictEntry *de) {
key = keysds;
} else if (o->type == OBJ_HASH) {
key = keysds;
val = dictGetVal(de);
if (!data->only_keys) {
val = dictGetVal(de);
}
} else if (o->type == OBJ_ZSET) {
char buf[MAX_LONG_DOUBLE_CHARS];
int len = ld2string(buf, sizeof(buf), *(double *)dictGetVal(de), LD_STR_AUTO);
key = sdsdup(keysds);
val = sdsnewlen(buf, len);
if (!data->only_keys) {
char buf[MAX_LONG_DOUBLE_CHARS];
int len = ld2string(buf, sizeof(buf), *(double *)dictGetVal(de), LD_STR_AUTO);
val = sdsnewlen(buf, len);
}
} else {
serverPanic("Type not handled in SCAN callback.");
}

listAddNodeTail(keys, key);
if (val && !data->no_values) listAddNodeTail(keys, val);
if (val) listAddNodeTail(keys, val);
}

/* Try to parse a SCAN cursor stored at object 'o':
Expand Down Expand Up @@ -989,7 +993,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, no_values = 0;
int patlen = 0, use_pattern = 0, only_keys = 0;
dict *ht;

/* Object must be NULL (to iterate keys names), or the type of the object
Expand Down Expand Up @@ -1040,7 +1044,14 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
addReplyError(c, "NOVALUES option can only be used in HSCAN");
return;
}
no_values = 1;
only_keys = 1;
i++;
} else if (!strcasecmp(c->argv[i]->ptr, "noscores")) {
if (!o || o->type != OBJ_ZSET) {
addReplyError(c, "NOSCORES option can only be used in ZSCAN");
return;
}
only_keys = 1;
i++;
} else {
addReplyErrorObject(c,shared.syntaxerr);
Expand Down Expand Up @@ -1101,15 +1112,15 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
* 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;
* 6. data.no_values: to control whether values will be returned or
* 6. data.only_keys: 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,
.only_keys = only_keys,
};

/* A pattern may restrict all matching keys to one cluster slot. */
Expand Down Expand Up @@ -1164,7 +1175,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
/* add key object */
listAddNodeTail(keys, sdsnewlen(str, len));
/* add value object */
if (!no_values) {
if (!only_keys) {
str = lpGet(p, &len, intbuf);
listAddNodeTail(keys, sdsnewlen(str, len));
}
Expand Down
11 changes: 11 additions & 0 deletions tests/unit/scan.tcl
Expand Up @@ -316,6 +316,10 @@ proc test_scan {type} {

set keys2 [lsort -unique $keys2]
assert_equal $count [llength $keys2]

# Test NOSCORES
set res [r zscan zset 0 count 1000 noscores]
assert_equal [lsort $keys2] [lsort [lindex $res 1]]
}
}

Expand Down Expand Up @@ -386,6 +390,13 @@ proc test_scan {type} {
lsort -unique [lindex $res 1]
}

test "{$type} ZSCAN with NOSCORES" {
r del mykey
r zadd mykey 1 foo 2 fab 3 fiz 10 foobar
set res [r zscan mykey 0 NOSCORES]
lsort -unique [lindex $res 1]
} {fab fiz foo foobar}

test "{$type} ZSCAN scores: regression test for issue #2175" {
r del mykey
for {set j 0} {$j < 500} {incr j} {
Expand Down

0 comments on commit f3b952b

Please sign in to comment.