Implement SMISMEMBER key member [member ...] (#7615)

This is a rebased version of #3078 originally by shaharmor
with the following patches by TysonAndre made after rebasing
to work with the updated C API:

1. Add 2 more unit tests
   (wrong argument count error message, integer over 64 bits)
2. Use addReplyArrayLen instead of addReplyMultiBulkLen.
3. Undo changes to src/help.h - for the ZMSCORE PR,
   I heard those should instead be automatically
   generated from the redis-doc repo if it gets updated

Motivations:

- Example use case: Client code to efficiently check if each element of a set
  of 1000 items is a member of a set of 10 million items.
  (Similar to reasons for working on #7593)
- HMGET and ZMSCORE already exist. This may lead to developers deciding
  to implement functionality that's best suited to a regular set with a
  data type of sorted set or hash map instead, for the multi-get support.

Currently, multi commands or lua scripting to call sismember multiple times
would almost definitely be less efficient than a native smismember
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
  and received by the client for the repeated SISMEMBER KEY sections.
- Need to specially encode the data and decode it from the client
  for lua-based solutions.
- Proposed solutions using Lua or SADD/SDIFF could trigger writes to
  memory, which is undesirable on a redis replica server
  or when commands get replicated to replicas.

Co-Authored-By: Shahar Mor <shahar@peer5.com>
Co-Authored-By: Tyson Andre <tysonandre775@hotmail.com>
This commit is contained in:
Tyson Andre 2020-08-11 04:55:06 -04:00 committed by GitHub
parent b8f0c2de4a
commit 6e17afa80a
5 changed files with 57 additions and 3 deletions

View File

@ -354,6 +354,10 @@ struct redisCommand redisCommandTable[] = {
"read-only fast @set", "read-only fast @set",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,1,1,0,0,0},
{"smismember",smismemberCommand,-3,
"read-only fast @set",
0,NULL,1,1,1,0,0,0},
{"scard",scardCommand,2, {"scard",scardCommand,2,
"read-only fast @set", "read-only fast @set",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,1,1,0,0,0},

View File

@ -2278,6 +2278,7 @@ void saddCommand(client *c);
void sremCommand(client *c); void sremCommand(client *c);
void smoveCommand(client *c); void smoveCommand(client *c);
void sismemberCommand(client *c); void sismemberCommand(client *c);
void smismemberCommand(client *c);
void scardCommand(client *c); void scardCommand(client *c);
void spopCommand(client *c); void spopCommand(client *c);
void srandmemberCommand(client *c); void srandmemberCommand(client *c);

View File

@ -382,6 +382,25 @@ void sismemberCommand(client *c) {
addReply(c,shared.czero); addReply(c,shared.czero);
} }
void smismemberCommand(client *c) {
robj *set;
int j;
/* Don't abort when the key cannot be found. Non-existing keys are empty
* sets, where SMISMEMBER should respond with a series of zeros. */
set = lookupKeyRead(c->db,c->argv[1]);
if (set && checkType(c,set,OBJ_SET)) return;
addReplyArrayLen(c,c->argc - 2);
for (j = 2; j < c->argc; j++) {
if (set && setTypeIsMember(set,c->argv[j]->ptr))
addReply(c,shared.cone);
else
addReply(c,shared.czero);
}
}
void scardCommand(client *c) { void scardCommand(client *c) {
robj *o; robj *o;

View File

@ -24,7 +24,7 @@ set ::redis_cluster::plain_commands {
get set setnx setex psetex append strlen exists setbit getbit get set setnx setex psetex append strlen exists setbit getbit
setrange getrange substr incr decr rpush lpush rpushx lpushx setrange getrange substr incr decr rpush lpush rpushx lpushx
linsert rpop lpop brpop llen lindex lset lrange ltrim lrem linsert rpop lpop brpop llen lindex lset lrange ltrim lrem
sadd srem sismember scard spop srandmember smembers sscan zadd sadd srem sismember smismember scard spop srandmember smembers sscan zadd
zincrby zrem zremrangebyscore zremrangebyrank zremrangebylex zrange zincrby zrem zremrangebyscore zremrangebyrank zremrangebylex zrange
zrangebyscore zrevrangebyscore zrangebylex zrevrangebylex zcount zrangebyscore zrevrangebyscore zrangebylex zrevrangebylex zcount
zlexcount zrevrange zcard zscore zmscore zrank zrevrank zscan hset hsetnx zlexcount zrevrange zcard zscore zmscore zrank zrevrank zscan hset hsetnx

View File

@ -9,7 +9,7 @@ start_server {
foreach entry $entries { r sadd $key $entry } foreach entry $entries { r sadd $key $entry }
} }
test {SADD, SCARD, SISMEMBER, SMEMBERS basics - regular set} { test {SADD, SCARD, SISMEMBER, SMISMEMBER, SMEMBERS basics - regular set} {
create_set myset {foo} create_set myset {foo}
assert_encoding hashtable myset assert_encoding hashtable myset
assert_equal 1 [r sadd myset bar] assert_equal 1 [r sadd myset bar]
@ -18,10 +18,15 @@ start_server {
assert_equal 1 [r sismember myset foo] assert_equal 1 [r sismember myset foo]
assert_equal 1 [r sismember myset bar] assert_equal 1 [r sismember myset bar]
assert_equal 0 [r sismember myset bla] assert_equal 0 [r sismember myset bla]
assert_equal {1} [r smismember myset foo]
assert_equal {1 1} [r smismember myset foo bar]
assert_equal {1 0} [r smismember myset foo bla]
assert_equal {0 1} [r smismember myset bla foo]
assert_equal {0} [r smismember myset bla]
assert_equal {bar foo} [lsort [r smembers myset]] assert_equal {bar foo} [lsort [r smembers myset]]
} }
test {SADD, SCARD, SISMEMBER, SMEMBERS basics - intset} { test {SADD, SCARD, SISMEMBER, SMISMEMBER, SMEMBERS basics - intset} {
create_set myset {17} create_set myset {17}
assert_encoding intset myset assert_encoding intset myset
assert_equal 1 [r sadd myset 16] assert_equal 1 [r sadd myset 16]
@ -30,9 +35,33 @@ start_server {
assert_equal 1 [r sismember myset 16] assert_equal 1 [r sismember myset 16]
assert_equal 1 [r sismember myset 17] assert_equal 1 [r sismember myset 17]
assert_equal 0 [r sismember myset 18] assert_equal 0 [r sismember myset 18]
assert_equal {1} [r smismember myset 16]
assert_equal {1 1} [r smismember myset 16 17]
assert_equal {1 0} [r smismember myset 16 18]
assert_equal {0 1} [r smismember myset 18 16]
assert_equal {0} [r smismember myset 18]
assert_equal {16 17} [lsort [r smembers myset]] assert_equal {16 17} [lsort [r smembers myset]]
} }
test {SMISMEMBER against non set} {
r lpush mylist foo
assert_error WRONGTYPE* {r smismember mylist bar}
}
test {SMISMEMBER non existing key} {
assert_equal {0} [r smismember myset1 foo]
assert_equal {0 0} [r smismember myset1 foo bar]
}
test {SMISMEMBER requires one or more members} {
r del zmscoretest
r zadd zmscoretest 10 x
r zadd zmscoretest 20 y
catch {r smismember zmscoretest} e
assert_match {*ERR*wrong*number*arg*} $e
}
test {SADD against non set} { test {SADD against non set} {
r lpush mylist foo r lpush mylist foo
assert_error WRONGTYPE* {r sadd mylist bar} assert_error WRONGTYPE* {r sadd mylist bar}
@ -49,6 +78,7 @@ start_server {
create_set myset {213244124402402314402033402} create_set myset {213244124402402314402033402}
assert_encoding hashtable myset assert_encoding hashtable myset
assert_equal 1 [r sismember myset 213244124402402314402033402] assert_equal 1 [r sismember myset 213244124402402314402033402]
assert_equal {1} [r smismember myset 213244124402402314402033402]
} }
test "SADD overflows the maximum allowed integers in an intset" { test "SADD overflows the maximum allowed integers in an intset" {