From 41c7e6c9016592dc65efb4800e8068179ddc43a0 Mon Sep 17 00:00:00 2001 From: Chen Tianjie Date: Tue, 16 Apr 2024 15:02:32 +0800 Subject: [PATCH 1/5] Add noscores option to zscan. Signed-off-by: Chen Tianjie --- src/commands.def | 3 ++- src/commands/zscan.json | 8 +++++++- src/db.c | 33 ++++++++++++++++++++++----------- tests/unit/scan.tcl | 11 +++++++++++ 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/commands.def b/src/commands.def index bd6ed38153..90af9195d7 100644 --- a/src/commands.def +++ b/src/commands.def @@ -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 ********************/ @@ -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}, diff --git a/src/commands/zscan.json b/src/commands/zscan.json index edadb8561b..9a532ed327 100644 --- a/src/commands/zscan.json +++ b/src/commands/zscan.json @@ -56,6 +56,12 @@ "name": "count", "type": "integer", "optional": true + }, + { + "token": "NOSCORES", + "name": "noscores", + "type": "pure-token", + "optional": true } ], "reply_schema": { @@ -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" diff --git a/src/db.c b/src/db.c index 17e74f5881..d49d09db22 100644 --- a/src/db.c +++ b/src/db.c @@ -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 */ @@ -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': @@ -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 @@ -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); @@ -1101,7 +1112,7 @@ 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, @@ -1109,7 +1120,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) { .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. */ @@ -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)); } diff --git a/tests/unit/scan.tcl b/tests/unit/scan.tcl index 49939288fb..2317a03e6a 100644 --- a/tests/unit/scan.tcl +++ b/tests/unit/scan.tcl @@ -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]] } } @@ -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} { From ae26fe4ccb4d65720da2123623ac65a67301c829 Mon Sep 17 00:00:00 2001 From: Chen Tianjie Date: Mon, 6 May 2024 11:36:36 +0800 Subject: [PATCH 2/5] Add history to zscan about noscore option. Signed-off-by: Chen Tianjie --- src/commands.def | 6 ++++-- src/commands/zscan.json | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/commands.def b/src/commands.def index 90af9195d7..4fd9ab9a9b 100644 --- a/src/commands.def +++ b/src/commands.def @@ -9057,7 +9057,9 @@ struct COMMAND_ARG ZREVRANK_Args[] = { #ifndef SKIP_CMD_HISTORY_TABLE /* ZSCAN history */ -#define ZSCAN_History NULL +commandHistory ZSCAN_History[] = { +{"7.2.5","Added noscores option."}, +}; #endif #ifndef SKIP_CMD_TIPS_TABLE @@ -10865,7 +10867,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,5),.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,1,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}, diff --git a/src/commands/zscan.json b/src/commands/zscan.json index 9a532ed327..3682469492 100644 --- a/src/commands/zscan.json +++ b/src/commands/zscan.json @@ -6,6 +6,12 @@ "since": "2.8.0", "arity": -3, "function": "zscanCommand", + "history": [ + [ + "7.2.5", + "Added noscores option." + ] + ], "command_flags": [ "READONLY" ], From 376c4be2ba3b39aba7c10700ad34bf396c4b7670 Mon Sep 17 00:00:00 2001 From: Chen Tianjie Date: Mon, 6 May 2024 11:41:23 +0800 Subject: [PATCH 3/5] Correct version. Signed-off-by: Chen Tianjie --- src/commands.def | 2 +- src/commands/zscan.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands.def b/src/commands.def index 4fd9ab9a9b..5ba37d2952 100644 --- a/src/commands.def +++ b/src/commands.def @@ -9058,7 +9058,7 @@ struct COMMAND_ARG ZREVRANK_Args[] = { #ifndef SKIP_CMD_HISTORY_TABLE /* ZSCAN history */ commandHistory ZSCAN_History[] = { -{"7.2.5","Added noscores option."}, +{"7.4","Added noscores option."}, }; #endif diff --git a/src/commands/zscan.json b/src/commands/zscan.json index 3682469492..828b000dd5 100644 --- a/src/commands/zscan.json +++ b/src/commands/zscan.json @@ -8,7 +8,7 @@ "function": "zscanCommand", "history": [ [ - "7.2.5", + "7.4", "Added noscores option." ] ], From 829430fd0069964e84b816715e7d2d14d8b7e76f Mon Sep 17 00:00:00 2001 From: Chen Tianjie Date: Mon, 6 May 2024 15:25:50 +0800 Subject: [PATCH 4/5] Update version history of zscan noscores. Signed-off-by: Chen Tianjie --- src/commands/zscan.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/zscan.json b/src/commands/zscan.json index 828b000dd5..7948e393a5 100644 --- a/src/commands/zscan.json +++ b/src/commands/zscan.json @@ -8,7 +8,7 @@ "function": "zscanCommand", "history": [ [ - "7.4", + "8.0.0", "Added noscores option." ] ], From 151d1889741f0be277c4123cbbe6ddd0d5b5cb60 Mon Sep 17 00:00:00 2001 From: Chen Tianjie Date: Mon, 6 May 2024 15:40:55 +0800 Subject: [PATCH 5/5] Update commands.def. Signed-off-by: Chen Tianjie --- src/commands.def | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.def b/src/commands.def index 5ba37d2952..f7d1d14449 100644 --- a/src/commands.def +++ b/src/commands.def @@ -9058,7 +9058,7 @@ struct COMMAND_ARG ZREVRANK_Args[] = { #ifndef SKIP_CMD_HISTORY_TABLE /* ZSCAN history */ commandHistory ZSCAN_History[] = { -{"7.4","Added noscores option."}, +{"8.0.0","Added noscores option."}, }; #endif