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[] = {
"read-only to-sort @set",
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,
"write use-memory @set",
0,NULL,1,-1,1,0,0,0},
......@@ -476,6 +480,10 @@ struct redisCommand redisCommandTable[] = {
"read-only @sortedset",
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,
"read-only @sortedset",
0,zunionInterDiffGetKeys,0,0,0,0,0,0},
......
......@@ -2583,6 +2583,7 @@ void scardCommand(client *c);
void spopCommand(client *c);
void srandmemberCommand(client *c);
void sinterCommand(client *c);
void sinterCardCommand(client *c);
void sinterstoreCommand(client *c);
void sunionCommand(client *c);
void sunionstoreCommand(client *c);
......@@ -2662,6 +2663,7 @@ void zinterstoreCommand(client *c);
void zdiffstoreCommand(client *c);
void zunionCommand(client *c);
void zinterCommand(client *c);
void zinterCardCommand(client *c);
void zrangestoreCommand(client *c);
void zdiffCommand(client *c);
void zscanCommand(client *c);
......
......@@ -850,7 +850,7 @@ int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) {
}
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);
setTypeIterator *si;
robj *dstset = NULL;
......@@ -888,6 +888,8 @@ void sinterGenericCommand(client *c, robj **setkeys,
server.dirty++;
}
addReply(c,shared.czero);
} else if (cardinality_only) {
addReplyLongLong(c,cardinality);
} else {
addReply(c,shared.emptyset[c->resp]);
}
......@@ -903,12 +905,12 @@ void sinterGenericCommand(client *c, robj **setkeys,
* 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
* right length */
if (!dstkey) {
replylen = addReplyDeferredLen(c);
} else {
if (dstkey) {
/* If we have a target key where to store the resulting set
* create this key with an empty set inside */
dstset = createIntsetObject();
} else if (!cardinality_only) {
replylen = addReplyDeferredLen(c);
}
/* Iterate all the elements of the first (smallest) set, and test
......@@ -944,7 +946,9 @@ void sinterGenericCommand(client *c, robj **setkeys,
/* Only take action when all sets contain the member */
if (j == setnum) {
if (!dstkey) {
if (cardinality_only) {
cardinality++;
} else if (!dstkey) {
if (encoding == OBJ_ENCODING_HT)
addReplyBulkCBuffer(c,elesds,sdslen(elesds));
else
......@@ -963,7 +967,9 @@ void sinterGenericCommand(client *c, robj **setkeys,
}
setTypeReleaseIterator(si);
if (dstkey) {
if (cardinality_only) {
addReplyLongLong(c,cardinality);
} else if (dstkey) {
/* Store the resulting set into the target, if the intersection
* is not an empty set. */
if (setTypeSize(dstset) > 0) {
......@@ -989,12 +995,16 @@ void sinterGenericCommand(client *c, robj **setkeys,
/* SINTER key [key ...] */
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 ...] */
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
......
......@@ -2548,14 +2548,17 @@ dictType setAccumulatorDictType = {
};
/* 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,
* this value is 1, for ZUNIONSTORE/ZINTERSTORE/ZDIFFSTORE command, this value is 2.
*
* '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;
long setnum;
int aggregate = REDIS_AGGR_SUM;
......@@ -2567,6 +2570,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
zset *dstzset;
zskiplistNode *znode;
int withscores = 0;
unsigned long cardinality = 0;
/* expect setnum input keys to be given */
if ((getLongFromObjectOrReply(c, c->argv[numkeysIndex], &setnum, NULL) != C_OK))
......@@ -2613,7 +2617,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
int remaining = c->argc - j;
while (remaining) {
if (op != SET_OP_DIFF &&
if (op != SET_OP_DIFF && !cardinality_only &&
remaining >= (setnum + 1) &&
!strcasecmp(c->argv[j]->ptr,"weights"))
{
......@@ -2626,7 +2630,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
return;
}
}
} else if (op != SET_OP_DIFF &&
} else if (op != SET_OP_DIFF && !cardinality_only &&
remaining >= 2 &&
!strcasecmp(c->argv[j]->ptr,"aggregate"))
{
......@@ -2644,7 +2648,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
}
j++; remaining--;
} else if (remaining >= 1 &&
!dstkey &&
!dstkey && !cardinality_only &&
!strcasecmp(c->argv[j]->ptr,"withscores"))
{
j++; remaining--;
......@@ -2694,7 +2698,9 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
}
/* Only continue when present in every input. */
if (j == setnum) {
if (j == setnum && cardinality_only) {
cardinality++;
} else if (j == setnum) {
tmp = zuiNewSdsFromValue(&zval);
znode = zslInsert(dstzset->zsl,score,tmp);
dictAdd(dstzset->dict,tmp,&znode->score);
......@@ -2791,6 +2797,8 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
server.dirty++;
}
}
} else if (cardinality_only) {
addReplyLongLong(c, cardinality);
} else {
unsigned long length = dstzset->zsl->length;
zskiplist *zsl = dstzset->zsl;
......@@ -2815,27 +2823,31 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
}
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) {
zunionInterDiffGenericCommand(c, c->argv[1], 2, SET_OP_INTER);
zunionInterDiffGenericCommand(c, c->argv[1], 2, SET_OP_INTER, 0);
}
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) {
zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_UNION);
zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_UNION, 0);
}
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) {
zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_DIFF);
zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_DIFF, 0);
}
typedef enum {
......
......@@ -143,6 +143,10 @@ start_server {
r srem myset 1 2 3 4 5 6 7 8
} {3}
test "SINTERCARD against non-existing key" {
assert_equal 0 [r sintercard non-existing-key]
}
foreach {type} {hashtable intset} {
for {set i 1} {$i <= 5} {incr i} {
r del [format "set%d{t}" $i]
......@@ -182,6 +186,10 @@ start_server {
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" {
r sinterstore setres{t} set1{t} set2{t}
assert_encoding $type setres{t}
......@@ -211,6 +219,10 @@ start_server {
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" {
r sinterstore setres{t} set1{t} set2{t} set3{t}
assert_equal [list 195 199 $large] [lsort [r smembers setres{t}]]
......
......@@ -647,10 +647,11 @@ start_server {tags {"zset"}} {
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
assert_equal {} [r zunion 1 zseta]
assert_equal {} [r zinter 1 zseta]
assert_equal 0 [r zintercard 1 zseta]
assert_equal {} [r zdiff 1 zseta]
}
......@@ -662,12 +663,13 @@ start_server {tags {"zset"}} {
r zrange zsetc{t} 0 -1 withscores
} {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 zadd zseta{t} 1 a
r zadd zseta{t} 2 b
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 0 [r zintercard 2 zseta{t} zsetb{t}]
assert_equal {a 1 b 2} [r zdiff 2 zseta{t} zsetb{t} withscores]
}
......@@ -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]
}
test "ZUNION/ZINTER/ZDIFF with integer members - $encoding" {
test "ZUNION/ZINTER/ZINTERCARD/ZDIFF with integer members - $encoding" {
r del zsetd{t} zsetf{t}
r zadd zsetd{t} 1 1
r zadd zsetd{t} 2 2
......@@ -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 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]
}
......@@ -747,6 +750,10 @@ start_server {tags {"zset"}} {
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" {
r hello 3
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