summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSalvatore Sanfilippo <antirez@gmail.com>2019-07-08 12:53:34 +0200
committerGitHub <noreply@github.com>2019-07-08 12:53:34 +0200
commit722446510faf0debf0d309708b2ed4fe4d939319 (patch)
tree45545f3157e91ae2b258292d39210cf985f29a94
parent4c4b7023dd335794ba31a4d0acea10ddf39df61e (diff)
parent6eb52e200ce3af68433b69e50e2a5044f7074b08 (diff)
downloadredis-722446510faf0debf0d309708b2ed4fe4d939319.tar.gz
Merge pull request #6116 from AngusP/scan-types
SCAN: New Feature `SCAN cursor [TYPE type]` modifier suggested in issue #6107
-rw-r--r--src/db.c29
-rw-r--r--src/server.h5
-rw-r--r--tests/unit/scan.tcl45
3 files changed, 72 insertions, 7 deletions
diff --git a/src/db.c b/src/db.c
index 4977873e9..07051dad4 100644
--- a/src/db.c
+++ b/src/db.c
@@ -614,7 +614,7 @@ int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor) {
}
/* This command implements SCAN, HSCAN and SSCAN commands.
- * If object 'o' is passed, then it must be a Hash or Set object, otherwise
+ * If object 'o' is passed, then it must be a Hash, Set or Zset object, otherwise
* if 'o' is NULL the command will operate on the dictionary associated with
* the current database.
*
@@ -630,6 +630,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
listNode *node, *nextnode;
long count = 10;
sds pat = NULL;
+ sds typename = NULL;
int patlen = 0, use_pattern = 0;
dict *ht;
@@ -666,6 +667,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
use_pattern = !(pat[0] == '*' && patlen == 1);
i += 2;
+ } else if (!strcasecmp(c->argv[i]->ptr, "type") && o == NULL && j >= 2) {
+ /* SCAN for a particular type only applies to the db dict */
+ typename = c->argv[i+1]->ptr;
+ i+= 2;
} else {
addReply(c,shared.syntaxerr);
goto cleanup;
@@ -760,6 +765,13 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
}
}
+ /* Filter an element if it isn't the type we want. */
+ if (!filter && o == NULL && typename){
+ robj* typecheck = lookupKeyReadWithFlags(c->db, kobj, LOOKUP_NOTOUCH);
+ char* type = getObjectTypeName(typecheck);
+ if (strcasecmp((char*) typename, type)) filter = 1;
+ }
+
/* Filter element if it is an expired key. */
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
@@ -816,11 +828,8 @@ void lastsaveCommand(client *c) {
addReplyLongLong(c,server.lastsave);
}
-void typeCommand(client *c) {
- robj *o;
- char *type;
-
- o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
+char* getObjectTypeName(robj *o) {
+ char* type;
if (o == NULL) {
type = "none";
} else {
@@ -838,7 +847,13 @@ void typeCommand(client *c) {
default: type = "unknown"; break;
}
}
- addReplyStatus(c,type);
+ return type;
+}
+
+void typeCommand(client *c) {
+ robj *o;
+ o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
+ addReplyStatus(c, getObjectTypeName(o));
}
void shutdownCommand(client *c) {
diff --git a/src/server.h b/src/server.h
index cb70b93ad..8686994f6 100644
--- a/src/server.h
+++ b/src/server.h
@@ -652,6 +652,11 @@ typedef struct redisObject {
void *ptr;
} robj;
+/* The a string name for an object's type as listed above
+ * Native types are checked against the OBJ_STRING, OBJ_LIST, OBJ_* defines,
+ * and Module types have their registered name returned. */
+char *getObjectTypeName(robj*);
+
/* Macro used to initialize a Redis object allocated on the stack.
* Note that this macro is taken near the structure definition to make sure
* we'll update it when the structure is changed, to avoid bugs like
diff --git a/tests/unit/scan.tcl b/tests/unit/scan.tcl
index c0f4349d2..9f9ff4df2 100644
--- a/tests/unit/scan.tcl
+++ b/tests/unit/scan.tcl
@@ -53,6 +53,51 @@ start_server {tags {"scan"}} {
assert_equal 100 [llength $keys]
}
+ test "SCAN TYPE" {
+ r flushdb
+ # populate only creates strings
+ r debug populate 1000
+
+ # Check non-strings are excluded
+ set cur 0
+ set keys {}
+ while 1 {
+ set res [r scan $cur type "list"]
+ set cur [lindex $res 0]
+ set k [lindex $res 1]
+ lappend keys {*}$k
+ if {$cur == 0} break
+ }
+
+ assert_equal 0 [llength $keys]
+
+ # Check strings are included
+ set cur 0
+ set keys {}
+ while 1 {
+ set res [r scan $cur type "string"]
+ set cur [lindex $res 0]
+ set k [lindex $res 1]
+ lappend keys {*}$k
+ if {$cur == 0} break
+ }
+
+ assert_equal 1000 [llength $keys]
+
+ # Check all three args work together
+ set cur 0
+ set keys {}
+ while 1 {
+ set res [r scan $cur type "string" match "key:*" count 10]
+ set cur [lindex $res 0]
+ set k [lindex $res 1]
+ lappend keys {*}$k
+ if {$cur == 0} break
+ }
+
+ assert_equal 1000 [llength $keys]
+ }
+
foreach enc {intset hashtable} {
test "SSCAN with encoding $enc" {
# Create the Set