diff --git a/src/db.c b/src/db.c index 5a6562a1e2..95f07c459f 100644 --- a/src/db.c +++ b/src/db.c @@ -792,6 +792,21 @@ void randomkeyCommand(client *c) { decrRefCount(key); } +/* Returns 1 if the pattern can be an exact match in KEYS context. */ +int patternExactMatch(const char *pattern, int length) { + for (int i = 0; i < length; i++) { + if (pattern[i] == '*' || pattern[i] == '?' || pattern[i] == '[') { + /* Wildcard or character class found. Keys can be in anywhere. */ + return 0; + } else if (pattern[i] == '\\') { + /* Escaped character. Computing the key name in this case is not + * implemented. We would need a temp buffer. */ + return 0; + } + } + return 1; +} + void keysCommand(client *c) { dictEntry *de; sds pattern = c->argv[1]->ptr; @@ -801,7 +816,27 @@ void keysCommand(client *c) { allkeys = (pattern[0] == '*' && plen == 1); if (server.cluster_enabled && !allkeys) { pslot = patternHashSlot(pattern, plen); + } else if (!server.cluster_enabled) { + pslot = 0; } + + /* Once the pattern can do an exact match, we can convert + * it to a kvstoreDictFind to avoid iterating over all data. */ + if (patternExactMatch(pattern, plen) && pslot != -1) { + de = kvstoreDictFind(c->db->keys, pslot, pattern); + if (de) { + robj keyobj; + sds key = dictGetKey(de); + initStaticStringObject(keyobj, key); + if (!keyIsExpired(c->db, &keyobj)) { + addReplyBulkCBuffer(c, key, sdslen(key)); + numkeys++; + } + } + setDeferredArrayLen(c, replylen, numkeys); + return; + } + kvstoreDictIterator *kvs_di = NULL; kvstoreIterator *kvs_it = NULL; if (pslot != -1) { diff --git a/tests/unit/keyspace.tcl b/tests/unit/keyspace.tcl index c8bdcac984..37b830e549 100644 --- a/tests/unit/keyspace.tcl +++ b/tests/unit/keyspace.tcl @@ -40,7 +40,20 @@ start_server {tags {"keyspace"}} { } assert_equal [lsort [r keys "{a}*"]] [list "{a}x" "{a}y" "{a}z"] assert_equal [lsort [r keys "*{b}*"]] [list "{b}a" "{b}b" "{b}c"] - } + } + + test {KEYS with no pattern} { + r debug populate 1000 + r set foo bar + r set foo{t} bar + r del non-exist + assert_equal [r keys foo] {foo} + assert_equal [r keys foo{t}] {foo{t}} + assert_equal [r keys non-exist] {} + + r set foo\\ bar + assert_equal [r keys foo\\] {foo\\} + } {} {needs:debug} test {DEL all keys} { foreach key [r keys *] {r del $key} @@ -548,3 +561,33 @@ foreach {type large} [array get largevalue] { r flushall } {OK} {singledb:skip} } + +start_cluster 1 0 {tags {"keyspace external:skip cluster"}} { + # SET keys for random slots, for random noise. + set num_keys 0 + while {$num_keys < 1000} { + set random_key [randomInt 163840] + r SET $random_key VALUE + incr num_keys 1 + } + + test {KEYS with hashtag in cluster mode} { + foreach key {"{a}x" "{a}y" "{a}z" "{b}a" "{b}b" "{b}c"} { + r set $key hello + } + assert_equal [lsort [r keys "{a}*"]] [list "{a}x" "{a}y" "{a}z"] + assert_equal [lsort [r keys "*{b}*"]] [list "{b}a" "{b}b" "{b}c"] + } + + test {KEYS with no pattern in cluster mode} { + r set foo bar + r set foo{t} bar + r del non-exist + assert_equal [r keys foo] {foo} + assert_equal [r keys foo{t}] {foo{t}} + assert_equal [r keys non-exist] {} + + r set foo\\ bar + assert_equal [r keys foo\\] {foo\\} + } +}