From 29a92b00dea2dbb0d5699863b17d09ce2e76a777 Mon Sep 17 00:00:00 2001 From: Evan Wies Date: Thu, 29 Dec 2016 15:45:48 -0500 Subject: [PATCH 1/4] Add CHOP command to rxstrings CHOP key count Removes characters from a value to a String key. If 'count' is positive or zero, it removes from the right of the string; if 'count' is negative, it removes from the left of the string. If |count| is greater than the string length, the value is set as an empty string. It is an error if the value is not a String. Integer Reply: the length of the string after the chop operation. --- src/rxstrings.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/rxstrings.c b/src/rxstrings.c index 1820a9d..fae6ffb 100644 --- a/src/rxstrings.c +++ b/src/rxstrings.c @@ -135,6 +135,59 @@ int CheckAndCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { return REDISMODULE_OK; } +/* +* CHOP key count +* Removes characters from a value to a String key. +* If 'count' is positive or zero, it removes from the right of the string; +* if 'count' is negative, it removes from the left of the string. +* If |count| is greater than the string length, the value is set as an empty string. +* It is an error if the value is not a String. +* Integer Reply: the length of the string after the chop operation. +*/ +int ChopCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 3) { + return RedisModule_WrongArity(ctx); + } + RedisModule_AutoMemory(ctx); + + /* Obtain key and extract offset argument. */ + RedisModuleKey *key = + RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE); + long long count; + if (RedisModule_StringToLongLong(argv[2], &count) != REDISMODULE_OK) { + RedisModule_ReplyWithError(ctx, "ERR invalid count"); + return REDISMODULE_ERR; + } + + /* Key must be a string. */ + if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_STRING) { + RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); + return REDISMODULE_OK; + } + + /* Calculate new length, protecting against overchop */ + size_t valLen = RedisModule_ValueLength(key); + size_t absCount = (count < 0) ? -count : count; + size_t newLen = (valLen < absCount) ? 0 : (valLen - absCount); + + /* If we chop from the left, we need to shift the characters, + * but only bother if we aren't zeroing the value */ + if (count < 0 && newLen != 0) { + size_t dmaLen; + char* valPtr = RedisModule_StringDMA(key, &dmaLen, REDISMODULE_READ|REDISMODULE_WRITE); + memmove(valPtr, valPtr + absCount, newLen); + } + + /* Truncate to new length */ + if (RedisModule_StringTruncate(key, newLen) != REDISMODULE_OK) { + RedisModule_ReplyWithError(ctx, "ERR RM_StringTruncate failed"); + return REDISMODULE_ERR; + } + + RedisModule_ReplyWithLongLong(ctx, newLen); + return REDISMODULE_OK; +} + /* * PREPEND key value * Prepends a value to a String key. @@ -409,6 +462,44 @@ int testCheckAnd(RedisModuleCtx *ctx) { return 0; } +int testChop(RedisModuleCtx *ctx) { + RedisModuleCallReply *r; + + r = RedisModule_Call(ctx, "set", "cc", "foo", "abcde"); + RMUtil_AssertReplyEquals(r,"OK"); + r = RedisModule_Call(ctx, "chop", "cc", "foo", "-2"); + RMUtil_Assert(RedisModule_CallReplyInteger(r) == 3); + r = RedisModule_Call(ctx, "get", "c", "foo"); + RMUtil_AssertReplyEquals(r,"cde"); + r = RedisModule_Call(ctx, "FLUSHALL", ""); + + r = RedisModule_Call(ctx, "set", "cc", "foo", "abcde"); + RMUtil_AssertReplyEquals(r,"OK"); + r = RedisModule_Call(ctx, "chop", "cc", "foo", "2"); + RMUtil_Assert(RedisModule_CallReplyInteger(r) == 3); + r = RedisModule_Call(ctx, "get", "c", "foo"); + RMUtil_AssertReplyEquals(r,"abc"); + r = RedisModule_Call(ctx, "FLUSHALL", ""); + + r = RedisModule_Call(ctx, "set", "cc", "foo", "abcde"); + RMUtil_AssertReplyEquals(r,"OK"); + r = RedisModule_Call(ctx, "chop", "cc", "foo", "-10"); + RMUtil_Assert(RedisModule_CallReplyInteger(r) == 0); + r = RedisModule_Call(ctx, "get", "c", "foo"); + RMUtil_AssertReplyEquals(r,""); + r = RedisModule_Call(ctx, "FLUSHALL", ""); + + r = RedisModule_Call(ctx, "set", "cc", "foo", "abcde"); + RMUtil_AssertReplyEquals(r,"OK"); + r = RedisModule_Call(ctx, "chop", "cc", "foo", "10"); + RMUtil_Assert(RedisModule_CallReplyInteger(r) == 0); + r = RedisModule_Call(ctx, "get", "c", "foo"); + RMUtil_AssertReplyEquals(r,""); + r = RedisModule_Call(ctx, "FLUSHALL", ""); + + return 0; +} + int testPrepend(RedisModuleCtx *ctx) { RedisModuleCallReply *r; @@ -445,6 +536,7 @@ int TestModule(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { } RMUtil_Test(testCheckAnd); + RMUtil_Test(testChop); RMUtil_Test(testPrepend); RMUtil_Test(testSetRangeRand); @@ -460,6 +552,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx) { if (RedisModule_CreateCommand(ctx, "checkand", CheckAndCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx, "chop", ChopCommand, + "write fast deny-oom", 1, 1, + 1) == REDISMODULE_ERR) + return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "prepend", PrependCommand, "write fast deny-oom", 1, 1, 1) == REDISMODULE_ERR) From 70fdc71f3d209d5c217330608eb6c9de1eea4ff6 Mon Sep 17 00:00:00 2001 From: Evan Wies Date: Fri, 6 Jan 2017 10:36:37 -0500 Subject: [PATCH 2/4] add CHOP to README.md and fast-path CHOP count == 0 --- README.md | 12 ++++++++++++ src/rxstrings.c | 17 +++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c08dd13..8817967 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,18 @@ Note: the key shouldn't be repeated for the executed command. **Reply:** Null if not equal or for non existing key when the `XX` flag is used. On success, the reply depends on the actual command executed. +## `CHOP key count` + +Removes characters from a value of a String key. + +* If 'count' is positive, it removes from the right of the string; +* if 'count' is negative, it removes from the left of the string. +* If |count| is greater than the string length, the value is set as an empty string. +* If 'count' is zero, then the value remains unchanged. +* It is an error if the value is not a String. + +**Reply:** Integer, the length of the string after the chop operation. + ## `PREPEND key value` > Time complexity: O(1). The amortized time complexity is O(1) assuming the prepended value is small and the already present value is of any size, since the dynamic string library used by Redis will double the free space available on every reallocation. diff --git a/src/rxstrings.c b/src/rxstrings.c index fae6ffb..7c0aa4a 100644 --- a/src/rxstrings.c +++ b/src/rxstrings.c @@ -137,10 +137,11 @@ int CheckAndCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { /* * CHOP key count -* Removes characters from a value to a String key. -* If 'count' is positive or zero, it removes from the right of the string; +* Removes characters from a value of a String key. +* If 'count' is positive, it removes from the right of the string; * if 'count' is negative, it removes from the left of the string. * If |count| is greater than the string length, the value is set as an empty string. +* If 'count' is zero, then the value remains unchanged. * It is an error if the value is not a String. * Integer Reply: the length of the string after the chop operation. */ @@ -167,6 +168,10 @@ int ChopCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { /* Calculate new length, protecting against overchop */ size_t valLen = RedisModule_ValueLength(key); + if (count == 0) { + RedisModule_ReplyWithLongLong(ctx, valLen); + return REDISMODULE_OK; + } size_t absCount = (count < 0) ? -count : count; size_t newLen = (valLen < absCount) ? 0 : (valLen - absCount); @@ -497,6 +502,14 @@ int testChop(RedisModuleCtx *ctx) { RMUtil_AssertReplyEquals(r,""); r = RedisModule_Call(ctx, "FLUSHALL", ""); + r = RedisModule_Call(ctx, "set", "cc", "foo", "abcde"); + RMUtil_AssertReplyEquals(r,"OK"); + r = RedisModule_Call(ctx, "chop", "cc", "foo", "0"); + RMUtil_Assert(RedisModule_CallReplyInteger(r) == 5); + r = RedisModule_Call(ctx, "get", "c", "foo"); + RMUtil_AssertReplyEquals(r,"abcde"); + r = RedisModule_Call(ctx, "FLUSHALL", ""); + return 0; } From 5b0785672ef93e1602fffecdf4e7f986395c1f55 Mon Sep 17 00:00:00 2001 From: Evan Wies Date: Mon, 17 Jul 2017 03:50:35 +0000 Subject: [PATCH 3/4] Fix CHOP when making the length 0 Since Redis 4 was released without https://github.com/antirez/redis/pull/3718 this change was required to make CHOP work properly with Redis 4.0.0. Now all tests pass on stock Redis 4.0.0. --- src/rxstrings.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/rxstrings.c b/src/rxstrings.c index 7c0aa4a..3adfae4 100644 --- a/src/rxstrings.c +++ b/src/rxstrings.c @@ -175,6 +175,18 @@ int ChopCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { size_t absCount = (count < 0) ? -count : count; size_t newLen = (valLen < absCount) ? 0 : (valLen - absCount); + /* Work around https://github.com/antirez/redis/issues/3717 + * We use RM_StringSet to an empty string instead */ + if (newLen == 0) { + RedisModuleString* emptyStr = RedisModule_CreateString(ctx, "", 0); + if (RedisModule_StringSet(key, emptyStr) != REDISMODULE_OK) { + RedisModule_ReplyWithError(ctx, "ERR RM_SetString empty failed"); + return REDISMODULE_ERR; + } + RedisModule_ReplyWithLongLong(ctx, 0); + return REDISMODULE_OK; + } + /* If we chop from the left, we need to shift the characters, * but only bother if we aren't zeroing the value */ if (count < 0 && newLen != 0) { @@ -582,4 +594,4 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx) { return REDISMODULE_ERR; return REDISMODULE_OK; -} \ No newline at end of file +} From a9c79cb726a4ce3a5714ee72951de96643dd3805 Mon Sep 17 00:00:00 2001 From: Evan Wies Date: Mon, 17 Jul 2017 05:33:01 +0000 Subject: [PATCH 4/4] Fix incorrect return values in PREPEND and CHOP. --- src/rxstrings.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rxstrings.c b/src/rxstrings.c index 3adfae4..ebf0b75 100644 --- a/src/rxstrings.c +++ b/src/rxstrings.c @@ -163,7 +163,7 @@ int ChopCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { /* Key must be a string. */ if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_STRING) { RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); - return REDISMODULE_OK; + return REDISMODULE_ERR; } /* Calculate new length, protecting against overchop */ @@ -237,7 +237,7 @@ int PrependCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { /* Otherwise key must be a string. */ if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_STRING) { RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); - return REDISMODULE_OK; + return REDISMODULE_ERR; } /* Prepend the string: 1) expand string, 2) shift oldVal via memmove, 3) prepend arg */