summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAngus Pearson <angus@toaster.cc>2019-05-22 16:39:04 +0100
committerAngus Pearson <angus@toaster.cc>2019-05-22 16:39:04 +0100
commitbf963253ecfd367b49081a26c1b5c410558aecfc (patch)
treeb2f5e6bf499aca568bd9c10cbd72c7a7c402ed41 /src
parentfd0ee469ab165d0e005e9fe1fca1c4f5c604cd56 (diff)
downloadredis-bf963253ecfd367b49081a26c1b5c410558aecfc.tar.gz
Implement `SCAN cursor [TYPE type]` modifier suggested in issue #6107.
Add tests to check basic functionality of this optional keyword, and also tested with a module (redisgraph). Checked quickly with valgrind, no issues. Copies name the type name canonicalisation code from `typeCommand`, perhaps this would be better factored out to prevent the two diverging and both needing to be edited to add new `OBJ_*` types, but this is a little fiddly with C strings. The [redis-doc](https://github.com/antirez/redis-doc/blob/master/commands.json) repo will need to be updated with this new arg if accepted. A quirk to be aware of here is that the GEO commands are backed by zsets not their own type, so they're not distinguishable from other zsets. Additionally, for sparse types this has the same behaviour as `MATCH` in that it may return many empty results before giving something, even for large `COUNT`s.
Diffstat (limited to 'src')
-rw-r--r--src/db.c32
1 files changed, 31 insertions, 1 deletions
diff --git a/src/db.c b/src/db.c
index b537a29a4..6623f7f2f 100644
--- a/src/db.c
+++ b/src/db.c
@@ -613,7 +613,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.
*
@@ -629,6 +629,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;
@@ -665,6 +666,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;
@@ -759,6 +764,31 @@ 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;
+ char *type;
+ typecheck = lookupKeyReadWithFlags(c->db, kobj, LOOKUP_NOTOUCH);
+ if (typecheck == NULL) {
+ type = "none";
+ } else {
+ switch(typecheck->type) {
+ case OBJ_STRING: type = "string"; break;
+ case OBJ_LIST: type = "list"; break;
+ case OBJ_SET: type = "set"; break;
+ case OBJ_ZSET: type = "zset"; break;
+ case OBJ_HASH: type = "hash"; break;
+ case OBJ_STREAM: type = "stream"; break;
+ case OBJ_MODULE: {
+ moduleValue *mv = typecheck->ptr;
+ type = mv->type->name;
+ }; break;
+ default: type = "unknown"; break;
+ }
+ }
+ 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;