Merge pull request #6116 from AngusP/scan-types

SCAN: New Feature `SCAN cursor [TYPE type]` modifier suggested in issue #6107
This commit is contained in:
Salvatore Sanfilippo 2019-07-08 12:53:34 +02:00 committed by GitHub
commit 722446510f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 72 additions and 7 deletions

View File

@ -614,7 +614,7 @@ int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor) {
} }
/* This command implements SCAN, HSCAN and SSCAN commands. /* 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 * if 'o' is NULL the command will operate on the dictionary associated with
* the current database. * the current database.
* *
@ -630,6 +630,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
listNode *node, *nextnode; listNode *node, *nextnode;
long count = 10; long count = 10;
sds pat = NULL; sds pat = NULL;
sds typename = NULL;
int patlen = 0, use_pattern = 0; int patlen = 0, use_pattern = 0;
dict *ht; dict *ht;
@ -666,6 +667,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
use_pattern = !(pat[0] == '*' && patlen == 1); use_pattern = !(pat[0] == '*' && patlen == 1);
i += 2; 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 { } else {
addReply(c,shared.syntaxerr); addReply(c,shared.syntaxerr);
goto cleanup; 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. */ /* Filter element if it is an expired key. */
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1; if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
@ -816,11 +828,8 @@ void lastsaveCommand(client *c) {
addReplyLongLong(c,server.lastsave); addReplyLongLong(c,server.lastsave);
} }
void typeCommand(client *c) { char* getObjectTypeName(robj *o) {
robj *o; char* type;
char *type;
o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
if (o == NULL) { if (o == NULL) {
type = "none"; type = "none";
} else { } else {
@ -838,7 +847,13 @@ void typeCommand(client *c) {
default: type = "unknown"; break; 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) { void shutdownCommand(client *c) {

View File

@ -652,6 +652,11 @@ typedef struct redisObject {
void *ptr; void *ptr;
} robj; } 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. /* Macro used to initialize a Redis object allocated on the stack.
* Note that this macro is taken near the structure definition to make sure * 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 * we'll update it when the structure is changed, to avoid bugs like

View File

@ -53,6 +53,51 @@ start_server {tags {"scan"}} {
assert_equal 100 [llength $keys] 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} { foreach enc {intset hashtable} {
test "SSCAN with encoding $enc" { test "SSCAN with encoding $enc" {
# Create the Set # Create the Set