mirror of https://github.com/redis/redis.git
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: Oran Agra <oran@redislabs.com>
This commit is contained in:
parent
bdbf5eedae
commit
432c92d8df
|
|
@ -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);
|
||||
|
|
|
|||
26
src/t_set.c
26
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
|
||||
|
|
|
|||
36
src/t_zset.c
36
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 {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue