Unverified Commit 432c92d8 authored by Jonah H. Harris's avatar Jonah H. Harris Committed by GitHub
Browse files

Add SINTERCARD/ZINTERCARD Commands (#8946)



Add SINTERCARD and ZINTERCARD commands that are similar to
ZINTER and SINTER but only return the cardinality with minimum
processing and memory overheads.
Co-authored-by: default avatarOran Agra <oran@redislabs.com>
parent bdbf5eed
...@@ -404,6 +404,10 @@ struct redisCommand redisCommandTable[] = { ...@@ -404,6 +404,10 @@ struct redisCommand redisCommandTable[] = {
"read-only to-sort @set", "read-only to-sort @set",
0,NULL,1,-1,1,0,0,0}, 0,NULL,1,-1,1,0,0,0},
{"sintercard",sinterCardCommand,-2,
"read-only @set",
0,NULL,1,-1,1,0,0,0},
{"sinterstore",sinterstoreCommand,-3, {"sinterstore",sinterstoreCommand,-3,
"write use-memory @set", "write use-memory @set",
0,NULL,1,-1,1,0,0,0}, 0,NULL,1,-1,1,0,0,0},
...@@ -476,6 +480,10 @@ struct redisCommand redisCommandTable[] = { ...@@ -476,6 +480,10 @@ struct redisCommand redisCommandTable[] = {
"read-only @sortedset", "read-only @sortedset",
0,zunionInterDiffGetKeys,0,0,0,0,0,0}, 0,zunionInterDiffGetKeys,0,0,0,0,0,0},
{"zintercard",zinterCardCommand,-3,
"read-only @sortedset",
0,zunionInterDiffGetKeys,0,0,0,0,0,0},
{"zdiff",zdiffCommand,-3, {"zdiff",zdiffCommand,-3,
"read-only @sortedset", "read-only @sortedset",
0,zunionInterDiffGetKeys,0,0,0,0,0,0}, 0,zunionInterDiffGetKeys,0,0,0,0,0,0},
......
...@@ -2583,6 +2583,7 @@ void scardCommand(client *c); ...@@ -2583,6 +2583,7 @@ void scardCommand(client *c);
void spopCommand(client *c); void spopCommand(client *c);
void srandmemberCommand(client *c); void srandmemberCommand(client *c);
void sinterCommand(client *c); void sinterCommand(client *c);
void sinterCardCommand(client *c);
void sinterstoreCommand(client *c); void sinterstoreCommand(client *c);
void sunionCommand(client *c); void sunionCommand(client *c);
void sunionstoreCommand(client *c); void sunionstoreCommand(client *c);
...@@ -2662,6 +2663,7 @@ void zinterstoreCommand(client *c); ...@@ -2662,6 +2663,7 @@ void zinterstoreCommand(client *c);
void zdiffstoreCommand(client *c); void zdiffstoreCommand(client *c);
void zunionCommand(client *c); void zunionCommand(client *c);
void zinterCommand(client *c); void zinterCommand(client *c);
void zinterCardCommand(client *c);
void zrangestoreCommand(client *c); void zrangestoreCommand(client *c);
void zdiffCommand(client *c); void zdiffCommand(client *c);
void zscanCommand(client *c); void zscanCommand(client *c);
......
...@@ -850,7 +850,7 @@ int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) { ...@@ -850,7 +850,7 @@ int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) {
} }
void sinterGenericCommand(client *c, robj **setkeys, void sinterGenericCommand(client *c, robj **setkeys,
unsigned long setnum, robj *dstkey) { unsigned long setnum, robj *dstkey, int cardinality_only) {
robj **sets = zmalloc(sizeof(robj*)*setnum); robj **sets = zmalloc(sizeof(robj*)*setnum);
setTypeIterator *si; setTypeIterator *si;
robj *dstset = NULL; robj *dstset = NULL;
...@@ -888,6 +888,8 @@ void sinterGenericCommand(client *c, robj **setkeys, ...@@ -888,6 +888,8 @@ void sinterGenericCommand(client *c, robj **setkeys,
server.dirty++; server.dirty++;
} }
addReply(c,shared.czero); addReply(c,shared.czero);
} else if (cardinality_only) {
addReplyLongLong(c,cardinality);
} else { } else {
addReply(c,shared.emptyset[c->resp]); addReply(c,shared.emptyset[c->resp]);
} }
...@@ -903,12 +905,12 @@ void sinterGenericCommand(client *c, robj **setkeys, ...@@ -903,12 +905,12 @@ void sinterGenericCommand(client *c, robj **setkeys,
* the intersection set size, so we use a trick, append an empty object * the intersection set size, so we use a trick, append an empty object
* to the output list and save the pointer to later modify it with the * to the output list and save the pointer to later modify it with the
* right length */ * right length */
if (!dstkey) { if (dstkey) {
replylen = addReplyDeferredLen(c);
} else {
/* If we have a target key where to store the resulting set /* If we have a target key where to store the resulting set
* create this key with an empty set inside */ * create this key with an empty set inside */
dstset = createIntsetObject(); dstset = createIntsetObject();
} else if (!cardinality_only) {
replylen = addReplyDeferredLen(c);
} }
/* Iterate all the elements of the first (smallest) set, and test /* Iterate all the elements of the first (smallest) set, and test
...@@ -944,7 +946,9 @@ void sinterGenericCommand(client *c, robj **setkeys, ...@@ -944,7 +946,9 @@ void sinterGenericCommand(client *c, robj **setkeys,
/* Only take action when all sets contain the member */ /* Only take action when all sets contain the member */
if (j == setnum) { if (j == setnum) {
if (!dstkey) { if (cardinality_only) {
cardinality++;
} else if (!dstkey) {
if (encoding == OBJ_ENCODING_HT) if (encoding == OBJ_ENCODING_HT)
addReplyBulkCBuffer(c,elesds,sdslen(elesds)); addReplyBulkCBuffer(c,elesds,sdslen(elesds));
else else
...@@ -963,7 +967,9 @@ void sinterGenericCommand(client *c, robj **setkeys, ...@@ -963,7 +967,9 @@ void sinterGenericCommand(client *c, robj **setkeys,
} }
setTypeReleaseIterator(si); setTypeReleaseIterator(si);
if (dstkey) { if (cardinality_only) {
addReplyLongLong(c,cardinality);
} else if (dstkey) {
/* Store the resulting set into the target, if the intersection /* Store the resulting set into the target, if the intersection
* is not an empty set. */ * is not an empty set. */
if (setTypeSize(dstset) > 0) { if (setTypeSize(dstset) > 0) {
...@@ -989,12 +995,16 @@ void sinterGenericCommand(client *c, robj **setkeys, ...@@ -989,12 +995,16 @@ void sinterGenericCommand(client *c, robj **setkeys,
/* SINTER key [key ...] */ /* SINTER key [key ...] */
void sinterCommand(client *c) { void sinterCommand(client *c) {
sinterGenericCommand(c,c->argv+1,c->argc-1,NULL); sinterGenericCommand(c,c->argv+1,c->argc-1,NULL,0);
}
void sinterCardCommand(client *c) {
sinterGenericCommand(c,c->argv+1,c->argc-1,NULL,1);
} }
/* SINTERSTORE destination key [key ...] */ /* SINTERSTORE destination key [key ...] */
void sinterstoreCommand(client *c) { void sinterstoreCommand(client *c) {
sinterGenericCommand(c,c->argv+2,c->argc-2,c->argv[1]); sinterGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],0);
} }
#define SET_OP_UNION 0 #define SET_OP_UNION 0
......
...@@ -2548,14 +2548,17 @@ dictType setAccumulatorDictType = { ...@@ -2548,14 +2548,17 @@ dictType setAccumulatorDictType = {
}; };
/* The zunionInterDiffGenericCommand() function is called in order to implement the /* The zunionInterDiffGenericCommand() function is called in order to implement the
* following commands: ZUNION, ZINTER, ZDIFF, ZUNIONSTORE, ZINTERSTORE, ZDIFFSTORE. * following commands: ZUNION, ZINTER, ZDIFF, ZUNIONSTORE, ZINTERSTORE, ZDIFFSTORE,
* ZINTERCARD.
* *
* 'numkeysIndex' parameter position of key number. for ZUNION/ZINTER/ZDIFF command, * 'numkeysIndex' parameter position of key number. for ZUNION/ZINTER/ZDIFF command,
* this value is 1, for ZUNIONSTORE/ZINTERSTORE/ZDIFFSTORE command, this value is 2. * this value is 1, for ZUNIONSTORE/ZINTERSTORE/ZDIFFSTORE command, this value is 2.
* *
* 'op' SET_OP_INTER, SET_OP_UNION or SET_OP_DIFF. * 'op' SET_OP_INTER, SET_OP_UNION or SET_OP_DIFF.
* 'cardinality_only' is currently only applicable when 'op' is SET_OP_INTER.
*/ */
void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, int op) { void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, int op,
int cardinality_only) {
int i, j; int i, j;
long setnum; long setnum;
int aggregate = REDIS_AGGR_SUM; int aggregate = REDIS_AGGR_SUM;
...@@ -2567,6 +2570,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in ...@@ -2567,6 +2570,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
zset *dstzset; zset *dstzset;
zskiplistNode *znode; zskiplistNode *znode;
int withscores = 0; int withscores = 0;
unsigned long cardinality = 0;
/* expect setnum input keys to be given */ /* expect setnum input keys to be given */
if ((getLongFromObjectOrReply(c, c->argv[numkeysIndex], &setnum, NULL) != C_OK)) if ((getLongFromObjectOrReply(c, c->argv[numkeysIndex], &setnum, NULL) != C_OK))
...@@ -2613,7 +2617,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in ...@@ -2613,7 +2617,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
int remaining = c->argc - j; int remaining = c->argc - j;
while (remaining) { while (remaining) {
if (op != SET_OP_DIFF && if (op != SET_OP_DIFF && !cardinality_only &&
remaining >= (setnum + 1) && remaining >= (setnum + 1) &&
!strcasecmp(c->argv[j]->ptr,"weights")) !strcasecmp(c->argv[j]->ptr,"weights"))
{ {
...@@ -2626,7 +2630,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in ...@@ -2626,7 +2630,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
return; return;
} }
} }
} else if (op != SET_OP_DIFF && } else if (op != SET_OP_DIFF && !cardinality_only &&
remaining >= 2 && remaining >= 2 &&
!strcasecmp(c->argv[j]->ptr,"aggregate")) !strcasecmp(c->argv[j]->ptr,"aggregate"))
{ {
...@@ -2644,7 +2648,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in ...@@ -2644,7 +2648,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
} }
j++; remaining--; j++; remaining--;
} else if (remaining >= 1 && } else if (remaining >= 1 &&
!dstkey && !dstkey && !cardinality_only &&
!strcasecmp(c->argv[j]->ptr,"withscores")) !strcasecmp(c->argv[j]->ptr,"withscores"))
{ {
j++; remaining--; j++; remaining--;
...@@ -2694,7 +2698,9 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in ...@@ -2694,7 +2698,9 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
} }
/* Only continue when present in every input. */ /* Only continue when present in every input. */
if (j == setnum) { if (j == setnum && cardinality_only) {
cardinality++;
} else if (j == setnum) {
tmp = zuiNewSdsFromValue(&zval); tmp = zuiNewSdsFromValue(&zval);
znode = zslInsert(dstzset->zsl,score,tmp); znode = zslInsert(dstzset->zsl,score,tmp);
dictAdd(dstzset->dict,tmp,&znode->score); dictAdd(dstzset->dict,tmp,&znode->score);
...@@ -2791,6 +2797,8 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in ...@@ -2791,6 +2797,8 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
server.dirty++; server.dirty++;
} }
} }
} else if (cardinality_only) {
addReplyLongLong(c, cardinality);
} else { } else {
unsigned long length = dstzset->zsl->length; unsigned long length = dstzset->zsl->length;
zskiplist *zsl = dstzset->zsl; zskiplist *zsl = dstzset->zsl;
...@@ -2815,27 +2823,31 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in ...@@ -2815,27 +2823,31 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
} }
void zunionstoreCommand(client *c) { void zunionstoreCommand(client *c) {
zunionInterDiffGenericCommand(c, c->argv[1], 2, SET_OP_UNION); zunionInterDiffGenericCommand(c, c->argv[1], 2, SET_OP_UNION, 0);
} }
void zinterstoreCommand(client *c) { void zinterstoreCommand(client *c) {
zunionInterDiffGenericCommand(c, c->argv[1], 2, SET_OP_INTER); zunionInterDiffGenericCommand(c, c->argv[1], 2, SET_OP_INTER, 0);
} }
void zdiffstoreCommand(client *c) { void zdiffstoreCommand(client *c) {
zunionInterDiffGenericCommand(c, c->argv[1], 2, SET_OP_DIFF); zunionInterDiffGenericCommand(c, c->argv[1], 2, SET_OP_DIFF, 0);
} }
void zunionCommand(client *c) { void zunionCommand(client *c) {
zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_UNION); zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_UNION, 0);
} }
void zinterCommand(client *c) { void zinterCommand(client *c) {
zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_INTER); zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_INTER, 0);
}
void zinterCardCommand(client *c) {
zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_INTER, 1);
} }
void zdiffCommand(client *c) { void zdiffCommand(client *c) {
zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_DIFF); zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_DIFF, 0);
} }
typedef enum { typedef enum {
......
...@@ -143,6 +143,10 @@ start_server { ...@@ -143,6 +143,10 @@ start_server {
r srem myset 1 2 3 4 5 6 7 8 r srem myset 1 2 3 4 5 6 7 8
} {3} } {3}
test "SINTERCARD against non-existing key" {
assert_equal 0 [r sintercard non-existing-key]
}
foreach {type} {hashtable intset} { foreach {type} {hashtable intset} {
for {set i 1} {$i <= 5} {incr i} { for {set i 1} {$i <= 5} {incr i} {
r del [format "set%d{t}" $i] r del [format "set%d{t}" $i]
...@@ -182,6 +186,10 @@ start_server { ...@@ -182,6 +186,10 @@ start_server {
assert_equal [list 195 196 197 198 199 $large] [lsort [r sinter set1{t} set2{t}]] assert_equal [list 195 196 197 198 199 $large] [lsort [r sinter set1{t} set2{t}]]
} }
test "SINTERCARD with two sets - $type" {
assert_equal 6 [r sintercard set1{t} set2{t}]
}
test "SINTERSTORE with two sets - $type" { test "SINTERSTORE with two sets - $type" {
r sinterstore setres{t} set1{t} set2{t} r sinterstore setres{t} set1{t} set2{t}
assert_encoding $type setres{t} assert_encoding $type setres{t}
...@@ -211,6 +219,10 @@ start_server { ...@@ -211,6 +219,10 @@ start_server {
assert_equal [list 195 199 $large] [lsort [r sinter set1{t} set2{t} set3{t}]] assert_equal [list 195 199 $large] [lsort [r sinter set1{t} set2{t} set3{t}]]
} }
test "SINTERCARD against three sets - $type" {
assert_equal 3 [r sintercard set1{t} set2{t} set3{t}]
}
test "SINTERSTORE with three sets - $type" { test "SINTERSTORE with three sets - $type" {
r sinterstore setres{t} set1{t} set2{t} set3{t} r sinterstore setres{t} set1{t} set2{t} set3{t}
assert_equal [list 195 199 $large] [lsort [r smembers setres{t}]] assert_equal [list 195 199 $large] [lsort [r smembers setres{t}]]
......
...@@ -647,10 +647,11 @@ start_server {tags {"zset"}} { ...@@ -647,10 +647,11 @@ start_server {tags {"zset"}} {
assert_equal 0 [r exists dst_key{t}] assert_equal 0 [r exists dst_key{t}]
} }
test "ZUNION/ZINTER/ZDIFF against non-existing key - $encoding" { test "ZUNION/ZINTER/ZINTERCARD/ZDIFF against non-existing key - $encoding" {
r del zseta r del zseta
assert_equal {} [r zunion 1 zseta] assert_equal {} [r zunion 1 zseta]
assert_equal {} [r zinter 1 zseta] assert_equal {} [r zinter 1 zseta]
assert_equal 0 [r zintercard 1 zseta]
assert_equal {} [r zdiff 1 zseta] assert_equal {} [r zdiff 1 zseta]
} }
...@@ -662,12 +663,13 @@ start_server {tags {"zset"}} { ...@@ -662,12 +663,13 @@ start_server {tags {"zset"}} {
r zrange zsetc{t} 0 -1 withscores r zrange zsetc{t} 0 -1 withscores
} {a 1 b 2} } {a 1 b 2}
test "ZUNION/ZINTER/ZDIFF with empty set - $encoding" { test "ZUNION/ZINTER/ZINTERCARD/ZDIFF with empty set - $encoding" {
r del zseta{t} zsetb{t} r del zseta{t} zsetb{t}
r zadd zseta{t} 1 a r zadd zseta{t} 1 a
r zadd zseta{t} 2 b r zadd zseta{t} 2 b
assert_equal {a 1 b 2} [r zunion 2 zseta{t} zsetb{t} withscores] assert_equal {a 1 b 2} [r zunion 2 zseta{t} zsetb{t} withscores]
assert_equal {} [r zinter 2 zseta{t} zsetb{t} withscores] assert_equal {} [r zinter 2 zseta{t} zsetb{t} withscores]
assert_equal 0 [r zintercard 2 zseta{t} zsetb{t}]
assert_equal {a 1 b 2} [r zdiff 2 zseta{t} zsetb{t} withscores] assert_equal {a 1 b 2} [r zdiff 2 zseta{t} zsetb{t} withscores]
} }
...@@ -684,7 +686,7 @@ start_server {tags {"zset"}} { ...@@ -684,7 +686,7 @@ start_server {tags {"zset"}} {
assert_equal {a 1 b 3 d 3 c 5} [r zrange zsetc{t} 0 -1 withscores] assert_equal {a 1 b 3 d 3 c 5} [r zrange zsetc{t} 0 -1 withscores]
} }
test "ZUNION/ZINTER/ZDIFF with integer members - $encoding" { test "ZUNION/ZINTER/ZINTERCARD/ZDIFF with integer members - $encoding" {
r del zsetd{t} zsetf{t} r del zsetd{t} zsetf{t}
r zadd zsetd{t} 1 1 r zadd zsetd{t} 1 1
r zadd zsetd{t} 2 2 r zadd zsetd{t} 2 2
...@@ -695,6 +697,7 @@ start_server {tags {"zset"}} { ...@@ -695,6 +697,7 @@ start_server {tags {"zset"}} {
assert_equal {1 2 2 2 4 4 3 6} [r zunion 2 zsetd{t} zsetf{t} withscores] assert_equal {1 2 2 2 4 4 3 6} [r zunion 2 zsetd{t} zsetf{t} withscores]
assert_equal {1 2 3 6} [r zinter 2 zsetd{t} zsetf{t} withscores] assert_equal {1 2 3 6} [r zinter 2 zsetd{t} zsetf{t} withscores]
assert_equal 2 [r zintercard 2 zsetd{t} zsetf{t}]
assert_equal {2 2} [r zdiff 2 zsetd{t} zsetf{t} withscores] assert_equal {2 2} [r zdiff 2 zsetd{t} zsetf{t} withscores]
} }
...@@ -747,6 +750,10 @@ start_server {tags {"zset"}} { ...@@ -747,6 +750,10 @@ start_server {tags {"zset"}} {
assert_equal {b 3 c 5} [r zinter 2 zseta{t} zsetb{t} withscores] assert_equal {b 3 c 5} [r zinter 2 zseta{t} zsetb{t} withscores]
} }
test "ZINTERCARD basics - $encoding" {
assert_equal 2 [r zintercard 2 zseta{t} zsetb{t}]
}
test "ZINTER RESP3 - $encoding" { test "ZINTER RESP3 - $encoding" {
r hello 3 r hello 3
assert_equal {{b 3.0} {c 5.0}} [r zinter 2 zseta{t} zsetb{t} withscores] assert_equal {{b 3.0} {c 5.0}} [r zinter 2 zseta{t} zsetb{t} withscores]
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment