From 6a55910c83d373c95c5911554464236a2bf0977d Mon Sep 17 00:00:00 2001 From: yihuang Date: Sun, 30 Jun 2019 01:13:36 +0800 Subject: [PATCH] add r.min/r.max command (#58) * add r.min/r.max command * add integration tests/performance tests/documentation on README * Small fix intergation tests --- README.md | 6 +++-- src/redis-roaring.c | 60 ++++++++++++++++++++++++++++++++++++++++++ tests/integration_1.sh | 50 +++++++++++++++++++++++++++++++++++ tests/performance.c | 40 +++++++++++++++++++++++++++- 4 files changed, 153 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7d3a5a5..8ede362 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ The performance tests can take a while, since they run on a real dataset of inte ## API -Currently the following operations are supported +The following operations are supported - `R.SETBIT` (same as [SETBIT](https://redis.io/commands/setbit)) - `R.GETBIT` (same as [GETBIT](https://redis.io/commands/getbit)) @@ -56,7 +56,7 @@ Currently the following operations are supported - `R.SETBITARRAY` (create a roaring bitmap from a bit array string) - `R.GETBITARRAY` (get a bit array string from a roaring bitmap) -new added API +Additional commands - `R.APPENDINTARRAY` (append integers to a roaring bitmap) - `R.RANGEINTARRAY` (get an integer array from a roaring bitmap with `start` and `end`, so can implements paging) @@ -64,6 +64,8 @@ Currently the following operations are supported - `R.SETFULL` (fill up a roaring bitmap in integer) - `R.STAT` (get statistical information of a roaring bitmap) - `R.OPTIMIZE` (optimize a roaring bitmap) +- `R.MIN` (get minimal integer from a roaring bitmap, if key is not exists or bitmap is empty, return -1) +- `R.MAX` (get maximal integer from a roaring bitmap, if key is not exists or bitmap is empty, return -1) Missing commands: diff --git a/src/redis-roaring.c b/src/redis-roaring.c index fe9df22..45edd01 100644 --- a/src/redis-roaring.c +++ b/src/redis-roaring.c @@ -607,6 +607,60 @@ int RBitPosCommand(RedisModuleCtx* ctx, RedisModuleString** argv, int argc) { return REDISMODULE_OK; } +int RMinCommand(RedisModuleCtx* ctx, RedisModuleString** argv, int argc) { + if (argc != 2) { + return RedisModule_WrongArity(ctx); + } + + RedisModuleKey* key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ); + int type = RedisModule_KeyType(key); + if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) != BitmapType) { + return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); + } + + long long result; + if (type == REDISMODULE_KEYTYPE_EMPTY) { + result = -1; + } else { + Bitmap* bitmap = RedisModule_ModuleTypeGetValue(key); + if (roaring_bitmap_is_empty(bitmap)) { + result = -1; + } else { + result = roaring_bitmap_minimum(bitmap); + } + } + + RedisModule_ReplyWithLongLong(ctx, result); + return REDISMODULE_OK; +} + +int RMaxCommand(RedisModuleCtx* ctx, RedisModuleString** argv, int argc) { + if (argc != 2) { + return RedisModule_WrongArity(ctx); + } + + RedisModuleKey* key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ); + int type = RedisModule_KeyType(key); + if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) != BitmapType) { + return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); + } + + long long result; + if (type == REDISMODULE_KEYTYPE_EMPTY) { + result = -1; + } else { + Bitmap* bitmap = RedisModule_ModuleTypeGetValue(key); + if (roaring_bitmap_is_empty(bitmap)) { + result = -1; + } else { + result = roaring_bitmap_maximum(bitmap); + } + } + + RedisModule_ReplyWithLongLong(ctx, result); + return REDISMODULE_OK; +} + int RedisModule_OnLoad(RedisModuleCtx* ctx) { // Register the module itself if (RedisModule_Init(ctx, "REDIS-ROARING", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) { @@ -673,6 +727,12 @@ int RedisModule_OnLoad(RedisModuleCtx* ctx) { if (RedisModule_CreateCommand(ctx, "R.BITPOS", RBitPosCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) { return REDISMODULE_ERR; } + if (RedisModule_CreateCommand(ctx, "R.MIN", RMinCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) { + return REDISMODULE_ERR; + } + if (RedisModule_CreateCommand(ctx, "R.MAX", RMaxCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) { + return REDISMODULE_ERR; + } return REDISMODULE_OK; } diff --git a/tests/integration_1.sh b/tests/integration_1.sh index 1c50844..d212525 100755 --- a/tests/integration_1.sh +++ b/tests/integration_1.sh @@ -195,6 +195,55 @@ function test_getbitarray_setbitarray() EXPECTED="" [ "$FOUND" == "$EXPECTED" ] } +function test_min_max() +{ + echo "test_min_max" + + FOUND=$(echo "R.MIN test_min_max" | redis-cli) + EXPECTED="-1" + [ "$FOUND" == "$EXPECTED" ] + + FOUND=$(echo "R.MAX test_min_max" | redis-cli) + EXPECTED="-1" + [ "$FOUND" == "$EXPECTED" ] + + echo "R.SETBIT test_min_max 100 1" | redis-cli + + FOUND=$(echo "R.MIN test_min_max" | redis-cli) + EXPECTED="100" + [ "$FOUND" == "$EXPECTED" ] + + FOUND=$(echo "R.MAX test_min_max" | redis-cli) + EXPECTED="100" + [ "$FOUND" == "$EXPECTED" ] + + echo "R.SETBIT test_min_max 0 1" | redis-cli + + FOUND=$(echo "R.MIN test_min_max" | redis-cli) + EXPECTED="0" + [ "$FOUND" == "$EXPECTED" ] + + FOUND=$(echo "R.MAX test_min_max" | redis-cli) + EXPECTED="100" + [ "$FOUND" == "$EXPECTED" ] + + FOUND=$(echo "R.SETBIT test_min_max 0 0" | redis-cli) + EXPECTED="1" + [ "$FOUND" == "$EXPECTED" ] + + FOUND=$(echo "R.SETBIT test_min_max 100 0" | redis-cli) + EXPECTED="1" + [ "$FOUND" == "$EXPECTED" ] + + FOUND=$(echo "R.MIN test_min_max" | redis-cli) + EXPECTED="-1" + [ "$FOUND" == "$EXPECTED" ] + + FOUND=$(echo "R.MAX test_min_max" | redis-cli) + EXPECTED="-1" + [ "$FOUND" == "$EXPECTED" ] + +} function test_del() { echo "test_del" @@ -221,5 +270,6 @@ test_bitcount test_bitpos test_getintarray_setintarray test_getbitarray_setbitarray +test_min_max test_del test_save diff --git a/tests/performance.c b/tests/performance.c index 1314445..8e48822 100644 --- a/tests/performance.c +++ b/tests/performance.c @@ -269,6 +269,44 @@ int main(int argc, char* argv[]) { } } + { + const char* ops[] = { + "R.MIN", + "MIN" + }; + + for (size_t op = 0; op < sizeof(ops) / sizeof(*ops); op++) { + Statistics statistics = statistics_init(ops[op]); + for (size_t i = 0; i < count; i++) { + timer(&statistics); + redisReply* reply = redisCommand(c, "%s %d-%d", ops[op], op, i); + timer(&statistics); + debug("reply %s %s %lld\n", ops[op], reply->str, reply->integer); + freeReplyObject(reply); + } + statistics_print(&statistics); + } + } + + { + const char* ops[] = { + "R.MAX", + "MAX" + }; + + for (size_t op = 0; op < sizeof(ops) / sizeof(*ops); op++) { + Statistics statistics = statistics_init(ops[op]); + for (size_t i = 0; i < count; i++) { + timer(&statistics); + redisReply* reply = redisCommand(c, "%s %d-%d", ops[op], op, i); + timer(&statistics); + debug("reply %s %s %lld\n", ops[op], reply->str, reply->integer); + freeReplyObject(reply); + } + statistics_print(&statistics); + } + } + printf("\n"); for (size_t i = 0; i < count; i++) { @@ -279,4 +317,4 @@ int main(int argc, char* argv[]) { redisFree(c); return 0; -} \ No newline at end of file +}