diff --git a/src/server.c b/src/server.c index 9138e4756a..37fa6a9968 100644 --- a/src/server.c +++ b/src/server.c @@ -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}, diff --git a/src/server.h b/src/server.h index cab2aaf125..72e8a0103e 100644 --- a/src/server.h +++ b/src/server.h @@ -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); diff --git a/src/t_set.c b/src/t_set.c index 88c5c79946..57a29a98f2 100644 --- a/src/t_set.c +++ b/src/t_set.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 diff --git a/src/t_zset.c b/src/t_zset.c index 679477a6ff..71344f135b 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -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 { diff --git a/tests/unit/type/set.tcl b/tests/unit/type/set.tcl index 463dc5859e..9c512726d4 100644 --- a/tests/unit/type/set.tcl +++ b/tests/unit/type/set.tcl @@ -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}]] diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl index 1f2f00576b..75d51bb2ed 100644 --- a/tests/unit/type/zset.tcl +++ b/tests/unit/type/zset.tcl @@ -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]