Add a ZMSCORE command returning an array of scores. (#7593)

Syntax: `ZMSCORE KEY MEMBER [MEMBER ...]`

This is an extension of #2359
amended by Tyson Andre to work with the changed unstable API,
add more tests, and consistently return an array.

- It seemed as if it would be more likely to get reviewed
  after updating the implementation.

Currently, multi commands or lua scripting to call zscore multiple times
would almost definitely be less efficient than a native ZMSCORE
for the following reasons:

- Need to fetch the set from the string every time instead of reusing the C
  pointer.
- Using pipelining or multi-commands would result in more bytes sent by
  the client for the repeated `ZMSCORE KEY` sections.
- Need to specially encode the data and decode it from the client
  for lua-based solutions.
- The fastest solution I've seen for large sets(thousands or millions)
  involves lua and a variadic ZADD, then a ZINTERSECT, then a ZRANGE 0 -1,
  then UNLINK of a temporary set (or lua). This is still inefficient.

Co-authored-by: Tyson Andre <tysonandre775@hotmail.com>
This commit is contained in:
Tyson Andre 2020-08-04 10:49:33 -04:00 committed by GitHub
parent 824bd2ac11
commit f11f26cc53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 77 additions and 1 deletions

View File

@ -470,6 +470,10 @@ struct redisCommand redisCommandTable[] = {
"read-only fast @sortedset",
0,NULL,1,1,1,0,0,0},
{"zmscore",zmscoreCommand,-3,
"read-only fast @sortedset",
0,NULL,1,1,1,0,0,0},
{"zrank",zrankCommand,3,
"read-only fast @sortedset",
0,NULL,1,1,1,0,0,0},

View File

@ -2321,6 +2321,7 @@ void zrevrangeCommand(client *c);
void zcardCommand(client *c);
void zremCommand(client *c);
void zscoreCommand(client *c);
void zmscoreCommand(client *c);
void zremrangebyscoreCommand(client *c);
void zremrangebylexCommand(client *c);
void zpopminCommand(client *c);

View File

@ -3082,6 +3082,24 @@ void zscoreCommand(client *c) {
}
}
void zmscoreCommand(client *c) {
robj *key = c->argv[1];
robj *zobj;
double score;
zobj = lookupKeyRead(c->db,key);
if (zobj != NULL && checkType(c,zobj,OBJ_ZSET)) return;
addReplyArrayLen(c,c->argc - 2);
for (int j = 2; j < c->argc; j++) {
/* Treat a missing set the same way as an empty set */
if (zobj == NULL || zsetScore(zobj,c->argv[j]->ptr,&score) == C_ERR) {
addReplyNull(c);
} else {
addReplyDouble(c,score);
}
}
}
void zrankGenericCommand(client *c, int reverse) {
robj *key = c->argv[1];
robj *ele = c->argv[2];

View File

@ -27,7 +27,7 @@ set ::redis_cluster::plain_commands {
sadd srem sismember scard spop srandmember smembers sscan zadd
zincrby zrem zremrangebyscore zremrangebyrank zremrangebylex zrange
zrangebyscore zrevrangebyscore zrangebylex zrevrangebylex zcount
zlexcount zrevrange zcard zscore zrank zrevrank zscan hset hsetnx
zlexcount zrevrange zcard zscore zmscore zrank zrevrank zscan hset hsetnx
hget hmset hmget hincrby hincrbyfloat hdel hlen hkeys hvals
hgetall hexists hscan incrby decrby incrbyfloat getset move
expire expireat pexpire pexpireat type ttl pttl persist restore

View File

@ -781,6 +781,44 @@ start_server {tags {"zset"}} {
}
}
test {ZMSCORE retrieve} {
r del zmscoretest
r zadd zmscoretest 10 x
r zadd zmscoretest 20 y
r zmscore zmscoretest x y
} {10 20}
test {ZMSCORE retrieve from empty set} {
r del zmscoretest
r zmscore zmscoretest x y
} {{} {}}
test {ZMSCORE retrieve with missing member} {
r del zmscoretest
r zadd zmscoretest 10 x
r zmscore zmscoretest x y
} {10 {}}
test {ZMSCORE retrieve single member} {
r del zmscoretest
r zadd zmscoretest 10 x
r zadd zmscoretest 20 y
r zmscore zmscoretest x
} {10}
test {ZMSCORE retrieve requires one or more members} {
r del zmscoretest
r zadd zmscoretest 10 x
r zadd zmscoretest 20 y
catch {r zmscore zmscoretest} e
assert_match {*ERR*wrong*number*arg*} $e
}
test "ZSET commands don't accept the empty strings as valid score" {
assert_error "*not*float*" {r zadd myzset "" abc}
}
@ -815,6 +853,21 @@ start_server {tags {"zset"}} {
}
}
test "ZMSCORE - $encoding" {
r del zscoretest
set aux {}
for {set i 0} {$i < $elements} {incr i} {
set score [expr rand()]
lappend aux $score
r zadd zscoretest $score $i
}
assert_encoding $encoding zscoretest
for {set i 0} {$i < $elements} {incr i} {
assert_equal [lindex $aux $i] [r zmscore zscoretest $i]
}
}
test "ZSCORE after a DEBUG RELOAD - $encoding" {
r del zscoretest
set aux {}