Add ZINTER/ZUNION command
Syntax: ZINTER/ZUNION numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] [WITHSCORES] see #7624
This commit is contained in:
parent
66a13ccbdf
commit
e08bf16637
27
src/db.c
27
src/db.c
@ -1388,7 +1388,7 @@ void getKeysFreeResult(int *result) {
|
||||
/* Helper function to extract keys from following commands:
|
||||
* ZUNIONSTORE <destkey> <num-keys> <key> <key> ... <key> <options>
|
||||
* ZINTERSTORE <destkey> <num-keys> <key> <key> ... <key> <options> */
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int *zunionInterStoreGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, num, *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
@ -1416,6 +1416,31 @@ int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *nu
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from following commands:
|
||||
* ZUNION <num-keys> <key> <key> ... <key> <options>
|
||||
* ZINTER <num-keys> <key> <key> ... <key> <options> */
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, num, *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
num = atoi(argv[1]->ptr);
|
||||
/* Sanity check. Don't return any key if the command is going to
|
||||
* reply with syntax error. */
|
||||
if (num < 1 || num > (argc-2)) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*(num));
|
||||
|
||||
/* Add all key positions for argv[2...n] to keys[] */
|
||||
for (i = 0; i < num; i++) keys[i] = 2+i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from the following commands:
|
||||
* EVAL <script> <num-keys> <key> <key> ... <key> [more stuff]
|
||||
* EVALSHA <script> <num-keys> <key> <key> ... <key> [more stuff] */
|
||||
|
10
src/server.c
10
src/server.c
@ -428,10 +428,18 @@ struct redisCommand redisCommandTable[] = {
|
||||
|
||||
{"zunionstore",zunionstoreCommand,-4,
|
||||
"write use-memory @sortedset",
|
||||
0,zunionInterGetKeys,0,0,0,0,0,0},
|
||||
0,zunionInterStoreGetKeys,0,0,0,0,0,0},
|
||||
|
||||
{"zinterstore",zinterstoreCommand,-4,
|
||||
"write use-memory @sortedset",
|
||||
0,zunionInterStoreGetKeys,0,0,0,0,0,0},
|
||||
|
||||
{"zunion",zunionCommand,-3,
|
||||
"read-only @sortedset",
|
||||
0,zunionInterGetKeys,0,0,0,0,0,0},
|
||||
|
||||
{"zinter",zinterCommand,-3,
|
||||
"read-only @sortedset",
|
||||
0,zunionInterGetKeys,0,0,0,0,0,0},
|
||||
|
||||
{"zrange",zrangeCommand,-4,
|
||||
|
@ -2175,6 +2175,7 @@ void freeObjAsync(robj *o);
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
void getKeysFreeResult(int *result);
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys);
|
||||
int *zunionInterStoreGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys);
|
||||
int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
@ -2391,6 +2392,8 @@ void hstrlenCommand(client *c);
|
||||
void zremrangebyrankCommand(client *c);
|
||||
void zunionstoreCommand(client *c);
|
||||
void zinterstoreCommand(client *c);
|
||||
void zunionCommand(client *c);
|
||||
void zinterCommand(client *c);
|
||||
void zscanCommand(client *c);
|
||||
void hkeysCommand(client *c);
|
||||
void hvalsCommand(client *c);
|
||||
|
77
src/t_zset.c
77
src/t_zset.c
@ -2196,7 +2196,15 @@ dictType setAccumulatorDictType = {
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
/* The zunionInterGenericCommand() function is called in order to implement the
|
||||
* following commands: ZUNION, ZINTER, ZUNIONSTORE, ZINTERSTORE.
|
||||
*
|
||||
* 'numkeysIndex' parameter position of key number. for ZUNION/ZINTER command, this
|
||||
* value is 1, for ZUNIONSTORE/ZINTERSTORE command, this value is 2.
|
||||
*
|
||||
* 'op' SET_OP_INTER or SET_OP_UNION.
|
||||
*/
|
||||
void zunionInterGenericCommand(client *c, robj *dstkey, int numkeysIndex, int op) {
|
||||
int i, j;
|
||||
long setnum;
|
||||
int aggregate = REDIS_AGGR_SUM;
|
||||
@ -2207,9 +2215,10 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
robj *dstobj;
|
||||
zset *dstzset;
|
||||
zskiplistNode *znode;
|
||||
int withscores = 0;
|
||||
|
||||
/* expect setnum input keys to be given */
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &setnum, NULL) != C_OK))
|
||||
if ((getLongFromObjectOrReply(c, c->argv[numkeysIndex], &setnum, NULL) != C_OK))
|
||||
return;
|
||||
|
||||
if (setnum < 1) {
|
||||
@ -2219,14 +2228,14 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
}
|
||||
|
||||
/* test if the expected number of keys would overflow */
|
||||
if (setnum > c->argc-3) {
|
||||
if (setnum > (c->argc-(numkeysIndex+1))) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* read keys to be used for input */
|
||||
src = zcalloc(sizeof(zsetopsrc) * setnum);
|
||||
for (i = 0, j = 3; i < setnum; i++, j++) {
|
||||
for (i = 0, j = numkeysIndex+1; i < setnum; i++, j++) {
|
||||
robj *obj = lookupKeyWrite(c->db,c->argv[j]);
|
||||
if (obj != NULL) {
|
||||
if (obj->type != OBJ_ZSET && obj->type != OBJ_SET) {
|
||||
@ -2279,6 +2288,11 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
return;
|
||||
}
|
||||
j++; remaining--;
|
||||
} else if (remaining >= 1 &&
|
||||
!strcasecmp(c->argv[j]->ptr,"withscores"))
|
||||
{
|
||||
j++; remaining--;
|
||||
withscores = 1;
|
||||
} else {
|
||||
zfree(src);
|
||||
addReply(c,shared.syntaxerr);
|
||||
@ -2399,20 +2413,37 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
serverPanic("Unknown operator");
|
||||
}
|
||||
|
||||
if (dstzset->zsl->length) {
|
||||
zsetConvertToZiplistIfNeeded(dstobj,maxelelen);
|
||||
setKey(c,c->db,dstkey,dstobj);
|
||||
addReplyLongLong(c,zsetLength(dstobj));
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,
|
||||
(op == SET_OP_UNION) ? "zunionstore" : "zinterstore",
|
||||
dstkey,c->db->id);
|
||||
server.dirty++;
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",dstkey,c->db->id);
|
||||
if (dstkey) {
|
||||
if (dstzset->zsl->length) {
|
||||
zsetConvertToZiplistIfNeeded(dstobj, maxelelen);
|
||||
setKey(c, c->db, dstkey, dstobj);
|
||||
addReplyLongLong(c, zsetLength(dstobj));
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,
|
||||
(op == SET_OP_UNION) ? "zunionstore" : "zinterstore",
|
||||
dstkey, c->db->id);
|
||||
server.dirty++;
|
||||
} else {
|
||||
addReply(c, shared.czero);
|
||||
if (dbDelete(c->db, dstkey)) {
|
||||
signalModifiedKey(c, c->db, dstkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", dstkey, c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unsigned long length = dstzset->zsl->length;
|
||||
zskiplist *zsl = dstzset->zsl;
|
||||
zskiplistNode *zn = zsl->header->level[0].forward;
|
||||
if (withscores && c->resp == 2)
|
||||
addReplyArrayLen(c, length*2);
|
||||
else
|
||||
addReplyArrayLen(c, length);
|
||||
|
||||
while (zn != NULL) {
|
||||
if (withscores && c->resp > 2) addReplyArrayLen(c,2);
|
||||
addReplyBulkCBuffer(c,zn->ele,sdslen(zn->ele));
|
||||
if (withscores) addReplyDouble(c,zn->score);
|
||||
zn = zn->level[0].forward;
|
||||
}
|
||||
}
|
||||
decrRefCount(dstobj);
|
||||
@ -2420,11 +2451,19 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
}
|
||||
|
||||
void zunionstoreCommand(client *c) {
|
||||
zunionInterGenericCommand(c,c->argv[1], SET_OP_UNION);
|
||||
zunionInterGenericCommand(c, c->argv[1], 2, SET_OP_UNION);
|
||||
}
|
||||
|
||||
void zinterstoreCommand(client *c) {
|
||||
zunionInterGenericCommand(c,c->argv[1], SET_OP_INTER);
|
||||
zunionInterGenericCommand(c, c->argv[1], 2, SET_OP_INTER);
|
||||
}
|
||||
|
||||
void zunionCommand(client *c) {
|
||||
zunionInterGenericCommand(c, NULL, 1, SET_OP_UNION);
|
||||
}
|
||||
|
||||
void zinterCommand(client *c) {
|
||||
zunionInterGenericCommand(c, NULL, 1, SET_OP_INTER);
|
||||
}
|
||||
|
||||
void zrangeGenericCommand(client *c, int reverse) {
|
||||
|
@ -612,6 +612,12 @@ start_server {tags {"zset"}} {
|
||||
assert_equal 0 [r exists dst_key]
|
||||
}
|
||||
|
||||
test "ZUNION/ZINTER against non-existing key - $encoding" {
|
||||
r del zseta
|
||||
assert_equal {} [r zunion 1 zseta]
|
||||
assert_equal {} [r zinter 1 zseta]
|
||||
}
|
||||
|
||||
test "ZUNIONSTORE with empty set - $encoding" {
|
||||
r del zseta zsetb
|
||||
r zadd zseta 1 a
|
||||
@ -620,6 +626,14 @@ start_server {tags {"zset"}} {
|
||||
r zrange zsetc 0 -1 withscores
|
||||
} {a 1 b 2}
|
||||
|
||||
test "ZUNION/ZINTER with empty set - $encoding" {
|
||||
r del zseta zsetb
|
||||
r zadd zseta 1 a
|
||||
r zadd zseta 2 b
|
||||
assert_equal {a 1 b 2} [r zunion 2 zseta zsetb withscores]
|
||||
assert_equal {} [r zinter 2 zseta zsetb withscores]
|
||||
}
|
||||
|
||||
test "ZUNIONSTORE basics - $encoding" {
|
||||
r del zseta zsetb zsetc
|
||||
r zadd zseta 1 a
|
||||
@ -633,11 +647,29 @@ start_server {tags {"zset"}} {
|
||||
assert_equal {a 1 b 3 d 3 c 5} [r zrange zsetc 0 -1 withscores]
|
||||
}
|
||||
|
||||
test "ZUNION/ZINTER with integer members - $encoding" {
|
||||
r del zsetd zsetf
|
||||
r zadd zsetd 1 1
|
||||
r zadd zsetd 2 2
|
||||
r zadd zsetd 3 3
|
||||
r zadd zsetf 1 1
|
||||
r zadd zsetf 3 3
|
||||
r zadd zsetf 4 4
|
||||
|
||||
assert_equal {1 2 2 2 4 4 3 6} [r zunion 2 zsetd zsetf withscores]
|
||||
assert_equal {1 2 3 6} [r zinter 2 zsetd zsetf withscores]
|
||||
}
|
||||
|
||||
test "ZUNIONSTORE with weights - $encoding" {
|
||||
assert_equal 4 [r zunionstore zsetc 2 zseta zsetb weights 2 3]
|
||||
assert_equal {a 2 b 7 d 9 c 12} [r zrange zsetc 0 -1 withscores]
|
||||
}
|
||||
|
||||
test "ZUNION with weights - $encoding" {
|
||||
assert_equal {a 2 b 7 d 9 c 12} [r zunion 2 zseta zsetb weights 2 3 withscores]
|
||||
assert_equal {b 7 c 12} [r zinter 2 zseta zsetb weights 2 3 withscores]
|
||||
}
|
||||
|
||||
test "ZUNIONSTORE with a regular set and weights - $encoding" {
|
||||
r del seta
|
||||
r sadd seta a
|
||||
@ -653,21 +685,39 @@ start_server {tags {"zset"}} {
|
||||
assert_equal {a 1 b 1 c 2 d 3} [r zrange zsetc 0 -1 withscores]
|
||||
}
|
||||
|
||||
test "ZUNION/ZINTER with AGGREGATE MIN - $encoding" {
|
||||
assert_equal {a 1 b 1 c 2 d 3} [r zunion 2 zseta zsetb aggregate min withscores]
|
||||
assert_equal {b 1 c 2} [r zinter 2 zseta zsetb aggregate min withscores]
|
||||
}
|
||||
|
||||
test "ZUNIONSTORE with AGGREGATE MAX - $encoding" {
|
||||
assert_equal 4 [r zunionstore zsetc 2 zseta zsetb aggregate max]
|
||||
assert_equal {a 1 b 2 c 3 d 3} [r zrange zsetc 0 -1 withscores]
|
||||
}
|
||||
|
||||
test "ZUNION/ZINTER with AGGREGATE MAX - $encoding" {
|
||||
assert_equal {a 1 b 2 c 3 d 3} [r zunion 2 zseta zsetb aggregate max withscores]
|
||||
assert_equal {b 2 c 3} [r zinter 2 zseta zsetb aggregate max withscores]
|
||||
}
|
||||
|
||||
test "ZINTERSTORE basics - $encoding" {
|
||||
assert_equal 2 [r zinterstore zsetc 2 zseta zsetb]
|
||||
assert_equal {b 3 c 5} [r zrange zsetc 0 -1 withscores]
|
||||
}
|
||||
|
||||
test "ZINTER basics - $encoding" {
|
||||
assert_equal {b 3 c 5} [r zinter 2 zseta zsetb withscores]
|
||||
}
|
||||
|
||||
test "ZINTERSTORE with weights - $encoding" {
|
||||
assert_equal 2 [r zinterstore zsetc 2 zseta zsetb weights 2 3]
|
||||
assert_equal {b 7 c 12} [r zrange zsetc 0 -1 withscores]
|
||||
}
|
||||
|
||||
test "ZINTER with weights - $encoding" {
|
||||
assert_equal {b 7 c 12} [r zinter 2 zseta zsetb weights 2 3 withscores]
|
||||
}
|
||||
|
||||
test "ZINTERSTORE with a regular set and weights - $encoding" {
|
||||
r del seta
|
||||
r sadd seta a
|
||||
|
Loading…
x
Reference in New Issue
Block a user