summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOran Agra <oran@redislabs.com>2022-06-12 16:00:17 +0300
committerGitHub <noreply@github.com>2022-06-12 16:00:17 +0300
commit05833959e3875ea10f9b2934dc68daca549c9531 (patch)
treedff5b7e2aa9e891e5b324aa89b20857eb1338fe0
parent2667c41235069baae1d87541c7839a8fa5cfbb97 (diff)
parent1973558b63dd7354841c8a12fb9d4107ac7eaa4e (diff)
downloadredis-05833959e3875ea10f9b2934dc68daca549c9531.tar.gz
Merge pull request #10851 from oranagra/release7027.0.2
Release 7.0.2
-rw-r--r--00-RELEASENOTES21
-rw-r--r--src/module.c10
-rw-r--r--src/server.c87
-rw-r--r--src/server.h4
-rw-r--r--src/syscheck.c6
-rw-r--r--src/t_stream.c15
-rw-r--r--src/version.h4
-rw-r--r--tests/integration/logging.tcl9
-rw-r--r--tests/modules/keyspecs.c42
-rw-r--r--tests/unit/introspection-2.tcl15
-rw-r--r--tests/unit/moduleapi/keyspecs.tcl30
-rwxr-xr-xutils/generate-module-api-doc.rb2
12 files changed, 173 insertions, 72 deletions
diff --git a/00-RELEASENOTES b/00-RELEASENOTES
index 78dbc5ad6..b6ebeb5ed 100644
--- a/00-RELEASENOTES
+++ b/00-RELEASENOTES
@@ -11,6 +11,23 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
SECURITY: There are security fixes in the release.
--------------------------------------------------------------------------------
+
+================================================================================
+Redis 7.0.2 Released Sunday Jun 12 12:00:00 IST 2022
+================================================================================
+
+Upgrade urgency: MODERATE, specifically if you're using a previous release of
+Redis 7.0, contains fixes for bugs in previous 7.0 releases.
+
+Bug Fixes
+=========
+
+* Fixed SET and BITFIELD commands being wrongly marked movablekeys (#10837)
+ Regression in 7.0 possibly resulting in excessive roundtrip from cluster clients.
+* Fix crash when /proc/sys/vm/overcommit_memory is inaccessible (#10848)
+ Regression in 7.0.1 resulting in crash on startup on some configurations.
+
+
================================================================================
Redis 7.0.1 Released Wed Jun 8 12:00:00 IST 2022
================================================================================
@@ -79,8 +96,8 @@ Bug Fixes
* Replica fail and retry the PSYNC if the master is unresponsive (#10726)
* Fix ZRANGESTORE crash when zset_max_listpack_entries is 0 (#10767)
-Fixes for issues in previous release candidates of Redis 7.0
-------------------------------------------------------------
+Fixes for issues in previous releases of Redis 7.0
+--------------------------------------------------
* CONFIG REWRITE could cause a config change to be dropped for aliased configs (#10811)
* CONFIG REWRITE would omit rename-command and include lines (#10761)
diff --git a/src/module.c b/src/module.c
index 64ec8f38c..a1f6a66f5 100644
--- a/src/module.c
+++ b/src/module.c
@@ -1154,15 +1154,10 @@ RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds dec
cp->rediscmd->key_specs[0].fk.range.lastkey = lastkey < 0 ? lastkey : (lastkey-firstkey);
cp->rediscmd->key_specs[0].fk.range.keystep = keystep;
cp->rediscmd->key_specs[0].fk.range.limit = 0;
-
- /* Copy the default range to legacy_range_key_spec */
- cp->rediscmd->legacy_range_key_spec = cp->rediscmd->key_specs[0];
} else {
cp->rediscmd->key_specs_num = 0;
- cp->rediscmd->legacy_range_key_spec.begin_search_type = KSPEC_BS_INVALID;
- cp->rediscmd->legacy_range_key_spec.find_keys_type = KSPEC_FK_INVALID;
}
- populateCommandMovableKeys(cp->rediscmd);
+ populateCommandLegacyRangeSpec(cp->rediscmd);
cp->rediscmd->microseconds = 0;
cp->rediscmd->calls = 0;
cp->rediscmd->rejected_calls = 0;
@@ -1697,10 +1692,9 @@ int RM_SetCommandInfo(RedisModuleCommand *command, const RedisModuleCommandInfo
}
}
- /* Update the legacy (first,last,step) spec used by the COMMAND command,
+ /* Update the legacy (first,last,step) spec and "movablekeys" flag used by the COMMAND command,
* by trying to "glue" consecutive range key specs. */
populateCommandLegacyRangeSpec(cmd);
- populateCommandMovableKeys(cmd);
}
if (info->args) {
diff --git a/src/server.c b/src/server.c
index dfb627694..96ceb0122 100644
--- a/src/server.c
+++ b/src/server.c
@@ -372,7 +372,7 @@ int dictExpandAllowed(size_t moreMem, double usedRatio) {
* belonging to the same cluster slot. See the Slot to Key API in cluster.c. */
size_t dictEntryMetadataSize(dict *d) {
UNUSED(d);
- /* NOTICE: this also affect overhead_ht_slot_to_keys in getMemoryOverheadData.
+ /* NOTICE: this also affects overhead_ht_slot_to_keys in getMemoryOverheadData.
* If we ever add non-cluster related data here, that code must be modified too. */
return server.cluster_enabled ? sizeof(clusterDictEntryMetadata) : 0;
}
@@ -774,7 +774,7 @@ int clientsCronResizeOutputBuffer(client *c, mstime_t now_ms) {
*
* This is how it works. We have an array of CLIENTS_PEAK_MEM_USAGE_SLOTS slots
* where we track, for each, the biggest client output and input buffers we
- * saw in that slot. Every slot correspond to one of the latest seconds, since
+ * saw in that slot. Every slot corresponds to one of the latest seconds, since
* the array is indexed by doing UNIXTIME % CLIENTS_PEAK_MEM_USAGE_SLOTS.
*
* When we want to know what was recently the peak memory usage, we just scan
@@ -2643,9 +2643,12 @@ void InitServerLast() {
* By far the most common case is just one range spec (e.g. SET)
* but some commands' ranges were split into two or more ranges
* in order to have different flags for different keys (e.g. SMOVE,
- * first key is "read write", second key is "write").
+ * first key is "RW ACCESS DELETE", second key is "RW INSERT").
*
- * This functions uses very basic heuristics and is "best effort":
+ * Additionally set the CMD_MOVABLE_KEYS flag for commands that may have key
+ * names in their arguments, but the legacy range spec doesn't cover all of them.
+ *
+ * This function uses very basic heuristics and is "best effort":
* 1. Only commands which have only "range" specs are considered.
* 2. Only range specs with keystep of 1 are considered.
* 3. The order of the range specs must be ascending (i.e.
@@ -2667,15 +2670,26 @@ void InitServerLast() {
void populateCommandLegacyRangeSpec(struct redisCommand *c) {
memset(&c->legacy_range_key_spec, 0, sizeof(c->legacy_range_key_spec));
- if (c->key_specs_num == 0)
+ /* Set the movablekeys flag if we have a GETKEYS flag for modules.
+ * Note that for native redis commands, we always have keyspecs,
+ * with enough information to rely on for movablekeys. */
+ if (c->flags & CMD_MODULE_GETKEYS)
+ c->flags |= CMD_MOVABLE_KEYS;
+
+ /* no key-specs, no keys, exit. */
+ if (c->key_specs_num == 0) {
return;
+ }
if (c->key_specs_num == 1 &&
c->key_specs[0].begin_search_type == KSPEC_BS_INDEX &&
c->key_specs[0].find_keys_type == KSPEC_FK_RANGE)
{
- /* Quick win */
+ /* Quick win, exactly one range spec. */
c->legacy_range_key_spec = c->key_specs[0];
+ /* If it has the incomplete flag, set the movablekeys flag on the command. */
+ if (c->key_specs[0].flags & CMD_KEY_INCOMPLETE)
+ c->flags |= CMD_MOVABLE_KEYS;
return;
}
@@ -2684,11 +2698,23 @@ void populateCommandLegacyRangeSpec(struct redisCommand *c) {
for (int i = 0; i < c->key_specs_num; i++) {
if (c->key_specs[i].begin_search_type != KSPEC_BS_INDEX ||
c->key_specs[i].find_keys_type != KSPEC_FK_RANGE)
+ {
+ /* Found an incompatible (non range) spec, skip it, and set the movablekeys flag. */
+ c->flags |= CMD_MOVABLE_KEYS;
continue;
- if (c->key_specs[i].fk.range.keystep != 1)
- return;
- if (prev_lastkey && prev_lastkey != c->key_specs[i].bs.index.pos-1)
- return;
+ }
+ if (c->key_specs[i].fk.range.keystep != 1 ||
+ (prev_lastkey && prev_lastkey != c->key_specs[i].bs.index.pos-1))
+ {
+ /* Found a range spec that's not plain (step of 1) or not consecutive to the previous one.
+ * Skip it, and we set the movablekeys flag. */
+ c->flags |= CMD_MOVABLE_KEYS;
+ continue;
+ }
+ if (c->key_specs[i].flags & CMD_KEY_INCOMPLETE) {
+ /* The spec we're using is incomplete, we can use it, but we also have to set the movablekeys flag. */
+ c->flags |= CMD_MOVABLE_KEYS;
+ }
firstkey = min(firstkey, c->key_specs[i].bs.index.pos);
/* Get the absolute index for lastkey (in the "range" spec, lastkey is relative to firstkey) */
int lastkey_abs_index = c->key_specs[i].fk.range.lastkey;
@@ -2696,10 +2722,14 @@ void populateCommandLegacyRangeSpec(struct redisCommand *c) {
lastkey_abs_index += c->key_specs[i].bs.index.pos;
/* For lastkey we use unsigned comparison to handle negative values correctly */
lastkey = max((unsigned)lastkey, (unsigned)lastkey_abs_index);
+ prev_lastkey = lastkey;
}
- if (firstkey == INT_MAX)
+ if (firstkey == INT_MAX) {
+ /* Couldn't find range specs, the legacy range spec will remain empty, and we set the movablekeys flag. */
+ c->flags |= CMD_MOVABLE_KEYS;
return;
+ }
serverAssert(firstkey != 0);
serverAssert(lastkey != 0);
@@ -2787,11 +2817,9 @@ void populateCommandStructure(struct redisCommand *c) {
c->num_tips++;
c->num_args = populateArgsStructure(c->args);
+ /* Handle the legacy range spec and the "movablekeys" flag (must be done after populating all key specs). */
populateCommandLegacyRangeSpec(c);
- /* Handle the "movablekeys" flag (must be done after populating all key specs). */
- populateCommandMovableKeys(c);
-
/* Assign the ID used for ACL. */
c->id = ACLGetCommandID(c->fullname);
@@ -2812,7 +2840,7 @@ void populateCommandStructure(struct redisCommand *c) {
extern struct redisCommand redisCommandTable[];
-/* Populates the Redis Command Table dict from from the static table in commands.c
+/* Populates the Redis Command Table dict from the static table in commands.c
* which is auto generated from the json files in the commands folder. */
void populateCommandTable(void) {
int j;
@@ -3498,31 +3526,6 @@ void afterCommand(client *c) {
}
}
-/* Returns 1 for commands that may have key names in their arguments, but the legacy range
- * spec doesn't cover all of them. */
-void populateCommandMovableKeys(struct redisCommand *cmd) {
- int movablekeys = 0;
- if (cmd->getkeys_proc || (cmd->flags & CMD_MODULE_GETKEYS)) {
- /* Command with getkeys proc */
- movablekeys = 1;
- } else {
- /* Redis command without getkeys proc, but possibly has
- * movable keys because of a keys spec. */
- for (int i = 0; i < cmd->key_specs_num; i++) {
- if (cmd->key_specs[i].begin_search_type != KSPEC_BS_INDEX ||
- cmd->key_specs[i].find_keys_type != KSPEC_FK_RANGE)
- {
- /* If we have a non-range spec it means we have movable keys */
- movablekeys = 1;
- break;
- }
- }
- }
-
- if (movablekeys)
- cmd->flags |= CMD_MOVABLE_KEYS;
-}
-
/* Check if c->cmd exists, fills `err` with details in case it doesn't.
* Return 1 if exists. */
int commandCheckExistence(client *c, sds *err) {
@@ -6028,7 +6031,7 @@ static int THPDisable(void) {
}
void linuxMemoryWarnings(void) {
- sds err_msg;
+ sds err_msg = NULL;
if (checkOvercommit(&err_msg) < 0) {
serverLog(LL_WARNING,"WARNING %s", err_msg);
sdsfree(err_msg);
@@ -6939,7 +6942,7 @@ int main(int argc, char **argv) {
serverLog(LL_WARNING,"Server initialized");
#ifdef __linux__
linuxMemoryWarnings();
- sds err_msg;
+ sds err_msg = NULL;
if (checkXenClocksource(&err_msg) < 0) {
serverLog(LL_WARNING, "WARNING %s", err_msg);
sdsfree(err_msg);
diff --git a/src/server.h b/src/server.h
index 6dfa8a067..abaa5f046 100644
--- a/src/server.h
+++ b/src/server.h
@@ -215,7 +215,8 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
#define CMD_MODULE_NO_CLUSTER (1ULL<<22) /* Deny on Redis Cluster. */
#define CMD_NO_ASYNC_LOADING (1ULL<<23)
#define CMD_NO_MULTI (1ULL<<24)
-#define CMD_MOVABLE_KEYS (1ULL<<25) /* populated by populateCommandMovableKeys */
+#define CMD_MOVABLE_KEYS (1ULL<<25) /* The legacy range spec doesn't cover all keys.
+ * Populated by populateCommandLegacyRangeSpec. */
#define CMD_ALLOW_BUSY ((1ULL<<26))
#define CMD_MODULE_GETCHANNELS (1ULL<<27) /* Use the modules getchannels interface. */
@@ -3550,7 +3551,6 @@ void mixDigest(unsigned char *digest, const void *ptr, size_t len);
void xorDigest(unsigned char *digest, const void *ptr, size_t len);
sds catSubCommandFullname(const char *parent_name, const char *sub_name);
void commandAddSubcommand(struct redisCommand *parent, struct redisCommand *subcommand, const char *declared_name);
-void populateCommandMovableKeys(struct redisCommand *cmd);
void debugDelay(int usec);
void killIOThreads(void);
void killThreads(void);
diff --git a/src/syscheck.c b/src/syscheck.c
index 9f338b118..58dc78f1b 100644
--- a/src/syscheck.c
+++ b/src/syscheck.c
@@ -143,7 +143,7 @@ int checkOvercommit(sds *error_msg) {
FILE *fp = fopen("/proc/sys/vm/overcommit_memory","r");
char buf[64];
- if (!fp) return -1;
+ if (!fp) return 0;
if (fgets(buf,64,fp) == NULL) {
fclose(fp);
return 0;
@@ -152,7 +152,7 @@ int checkOvercommit(sds *error_msg) {
if (atoi(buf)) {
*error_msg = sdsnew(
- "WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. "
+ "overcommit_memory is set to 0! Background save may fail under low memory condition. "
"To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the "
"command 'sysctl vm.overcommit_memory=1' for this to take effect.");
return -1;
@@ -351,7 +351,7 @@ check checks[] = {
int syscheck(void) {
check *cur_check = checks;
int ret = 1;
- sds err_msg;
+ sds err_msg = NULL;
while (cur_check->check_fn) {
int res = cur_check->check_fn(&err_msg);
printf("[%s]...", cur_check->name);
diff --git a/src/t_stream.c b/src/t_stream.c
index 4383dcd5a..617976c9c 100644
--- a/src/t_stream.c
+++ b/src/t_stream.c
@@ -2144,7 +2144,7 @@ void xrevrangeCommand(client *c) {
xrangeGenericCommand(c,1);
}
-/* XLEN */
+/* XLEN key*/
void xlenCommand(client *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL
@@ -2156,10 +2156,10 @@ void xlenCommand(client *c) {
/* XREAD [BLOCK <milliseconds>] [COUNT <count>] STREAMS key_1 key_2 ... key_N
* ID_1 ID_2 ... ID_N
*
- * This function also implements the XREAD-GROUP command, which is like XREAD
+ * This function also implements the XREADGROUP command, which is like XREAD
* but accepting the [GROUP group-name consumer-name] additional option.
* This is useful because while XREAD is a read command and can be called
- * on slaves, XREAD-GROUP is not. */
+ * on slaves, XREADGROUP is not. */
#define XREAD_BLOCKED_DEFAULT_COUNT 1000
void xreadCommand(client *c) {
long long timeout = -1; /* -1 means, no BLOCK argument given. */
@@ -2566,8 +2566,8 @@ void streamDelConsumer(streamCG *cg, streamConsumer *consumer) {
* Consumer groups commands
* ----------------------------------------------------------------------- */
-/* XGROUP CREATE <key> <groupname> <id or $> [MKSTREAM] [ENTRIESADDED count]
- * XGROUP SETID <key> <groupname> <id or $> [ENTRIESADDED count]
+/* XGROUP CREATE <key> <groupname> <id or $> [MKSTREAM] [ENTRIESREAD entries_read]
+ * XGROUP SETID <key> <groupname> <id or $> [ENTRIESREAD entries_read]
* XGROUP DESTROY <key> <groupname>
* XGROUP CREATECONSUMER <key> <groupname> <consumer>
* XGROUP DELCONSUMER <key> <groupname> <consumername> */
@@ -2805,7 +2805,6 @@ void xsetidCommand(client *c) {
}
/* XACK <key> <group> <id> <id> ... <id>
- *
* Acknowledge a message as processed. In practical terms we just check the
* pending entries list (PEL) of the group, and delete the PEL entry both from
* the group and the consumer (pending messages are referenced in both places).
@@ -3050,7 +3049,7 @@ void xpendingCommand(client *c) {
* [IDLE <milliseconds>] [TIME <mstime>] [RETRYCOUNT <count>]
* [FORCE] [JUSTID]
*
- * Gets ownership of one or multiple messages in the Pending Entries List
+ * Changes ownership of one or multiple messages in the Pending Entries List
* of a given stream consumer group.
*
* If the message ID (among the specified ones) exists, and its idle
@@ -3316,7 +3315,7 @@ cleanup:
/* XAUTOCLAIM <key> <group> <consumer> <min-idle-time> <start> [COUNT <count>] [JUSTID]
*
- * Gets ownership of one or multiple messages in the Pending Entries List
+ * Changes ownership of one or multiple messages in the Pending Entries List
* of a given stream consumer group.
*
* For each PEL entry, if its idle time greater or equal to <min-idle-time>,
diff --git a/src/version.h b/src/version.h
index 84d0dd25c..eff0a6015 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,2 +1,2 @@
-#define REDIS_VERSION "7.0.1"
-#define REDIS_VERSION_NUM 0x00070001
+#define REDIS_VERSION "7.0.2"
+#define REDIS_VERSION_NUM 0x00070002
diff --git a/tests/integration/logging.tcl b/tests/integration/logging.tcl
index ef74ef498..8617ed2fc 100644
--- a/tests/integration/logging.tcl
+++ b/tests/integration/logging.tcl
@@ -8,9 +8,12 @@ if {$system_name eq {darwin}} {
set backtrace_supported 1
} elseif {$system_name eq {linux}} {
# Avoid the test on libmusl, which does not support backtrace
- set ldd [exec ldd src/redis-server]
- if {![string match {*libc.*musl*} $ldd]} {
- set backtrace_supported 1
+ # and on static binaries (ldd exit code 1) where we can't detect libmusl
+ catch {
+ set ldd [exec ldd src/redis-server]
+ if {![string match {*libc.*musl*} $ldd]} {
+ set backtrace_supported 1
+ }
}
}
diff --git a/tests/modules/keyspecs.c b/tests/modules/keyspecs.c
index 32a6bebaa..d2ae9fd6c 100644
--- a/tests/modules/keyspecs.c
+++ b/tests/modules/keyspecs.c
@@ -18,6 +18,13 @@ int createKspecNone(RedisModuleCtx *ctx) {
return REDISMODULE_OK;
}
+int createKspecNoneWithGetkeys(RedisModuleCtx *ctx) {
+ /* A command without keyspecs; only the legacy (first,last,step) triple (MSET like spec), but also has a getkeys callback */
+ if (RedisModule_CreateCommand(ctx,"kspec.nonewithgetkeys",kspec_impl,"getkeys-api",1,-1,2) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+ return REDISMODULE_OK;
+}
+
int createKspecTwoRanges(RedisModuleCtx *ctx) {
/* Test that two position/range-based key specs are combined to produce the
* legacy (first,last,step) values representing both keys. */
@@ -51,6 +58,39 @@ int createKspecTwoRanges(RedisModuleCtx *ctx) {
return REDISMODULE_OK;
}
+int createKspecTwoRangesWithGap(RedisModuleCtx *ctx) {
+ /* Test that two position/range-based key specs are combined to produce the
+ * legacy (first,last,step) values representing just one key. */
+ if (RedisModule_CreateCommand(ctx,"kspec.tworangeswithgap",kspec_impl,"",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.tworangeswithgap");
+ RedisModuleCommandInfo info = {
+ .version = REDISMODULE_COMMAND_INFO_VERSION,
+ .arity = -2,
+ .key_specs = (RedisModuleCommandKeySpec[]){
+ {
+ .flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
+ .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
+ .bs.index.pos = 1,
+ .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
+ .fk.range = {0,1,0}
+ },
+ {
+ .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
+ .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
+ .bs.index.pos = 3,
+ /* Omitted find_keys_type is shorthand for RANGE {0,1,0} */
+ },
+ {0}
+ }
+ };
+ if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ return REDISMODULE_OK;
+}
+
int createKspecKeyword(RedisModuleCtx *ctx) {
/* Only keyword-based specs. The legacy triple is wiped and set to (0,0,0). */
if (RedisModule_CreateCommand(ctx,"kspec.keyword",kspec_impl,"",3,-1,1) == REDISMODULE_ERR)
@@ -177,7 +217,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_ERR;
if (createKspecNone(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
+ if (createKspecNoneWithGetkeys(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecTwoRanges(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
+ if (createKspecTwoRangesWithGap(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecKeyword(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecComplex1(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecComplex2(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
diff --git a/tests/unit/introspection-2.tcl b/tests/unit/introspection-2.tcl
index 46dac50b7..dab8008e8 100644
--- a/tests/unit/introspection-2.tcl
+++ b/tests/unit/introspection-2.tcl
@@ -176,4 +176,19 @@ start_server {tags {"introspection"}} {
assert_equal {{}} [r command info get|key]
assert_equal {{}} [r command info config|get|key]
}
+
+ foreach cmd {SET GET MSET BITFIELD LMOVE LPOP BLPOP PING MEMORY MEMORY|USAGE RENAME GEORADIUS_RO} {
+ test "$cmd command will not be marked with movablekeys" {
+ set info [lindex [r command info $cmd] 0]
+ assert_no_match {*movablekeys*} [lindex $info 2]
+ }
+ }
+
+ foreach cmd {ZUNIONSTORE XREAD EVAL SORT SORT_RO MIGRATE GEORADIUS} {
+ test "$cmd command is marked with movablekeys" {
+ set info [lindex [r command info $cmd] 0]
+ assert_match {*movablekeys*} [lindex $info 2]
+ }
+ }
+
}
diff --git a/tests/unit/moduleapi/keyspecs.tcl b/tests/unit/moduleapi/keyspecs.tcl
index 60d3fe5d3..ef5b92334 100644
--- a/tests/unit/moduleapi/keyspecs.tcl
+++ b/tests/unit/moduleapi/keyspecs.tcl
@@ -31,6 +31,20 @@ start_server {tags {"modules"}} {
assert_equal [r command getkeys kspec.tworanges foo bar baz quux] {foo bar}
}
+ test "Module key specs: Two ranges with gap" {
+ set reply [lindex [r command info kspec.tworangeswithgap] 0]
+ # Verify (first, last, step) and movablekeys
+ assert_equal [lindex $reply 2] {module movablekeys}
+ assert_equal [lindex $reply 3] 1
+ assert_equal [lindex $reply 4] 1
+ assert_equal [lindex $reply 5] 1
+ # Verify key-specs
+ set keyspecs [lindex $reply 8]
+ assert_equal [lindex $keyspecs 0] {flags {RO access} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
+ assert_equal [lindex $keyspecs 1] {flags {RW update} begin_search {type index spec {index 3}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
+ assert_equal [r command getkeys kspec.tworangeswithgap foo bar baz quux] {foo baz}
+ }
+
test "Module key specs: Keyword-only spec clears the legacy triple" {
set reply [lindex [r command info kspec.keyword] 0]
# Verify (first, last, step) and movablekeys
@@ -79,7 +93,7 @@ start_server {tags {"modules"}} {
test "Module command list filtering" {
;# Note: we piggyback this tcl file to test the general functionality of command list filtering
set reply [r command list filterby module keyspecs]
- assert_equal [lsort $reply] {kspec.complex1 kspec.complex2 kspec.keyword kspec.none kspec.tworanges}
+ assert_equal [lsort $reply] {kspec.complex1 kspec.complex2 kspec.keyword kspec.none kspec.nonewithgetkeys kspec.tworanges kspec.tworangeswithgap}
assert_equal [r command getkeys kspec.complex2 foo bar 2 baz quux banana STORE dst dummy MOREKEYS hey ho] {dst foo bar baz quux hey ho}
}
@@ -108,6 +122,20 @@ start_server {tags {"modules"}} {
assert_equal "This user has no permissions to access the 'write' key" [r ACL DRYRUN testuser kspec.tworanges write rw]
}
+ foreach cmd {kspec.none kspec.tworanges} {
+ test "$cmd command will not be marked with movablekeys" {
+ set info [lindex [r command info $cmd] 0]
+ assert_no_match {*movablekeys*} [lindex $info 2]
+ }
+ }
+
+ foreach cmd {kspec.keyword kspec.complex1 kspec.complex2 kspec.nonewithgetkeys} {
+ test "$cmd command is marked with movablekeys" {
+ set info [lindex [r command info $cmd] 0]
+ assert_match {*movablekeys*} [lindex $info 2]
+ }
+ }
+
test "Unload the module - keyspecs" {
assert_equal {OK} [r module unload keyspecs]
}
diff --git a/utils/generate-module-api-doc.rb b/utils/generate-module-api-doc.rb
index 1f7b7c2e5..d4282cbfa 100755
--- a/utils/generate-module-api-doc.rb
+++ b/utils/generate-module-api-doc.rb
@@ -20,7 +20,7 @@ def markdown(s)
# Add backquotes around RedisModule functions and type where missing.
l = l.gsub(/(?<!`)RedisModule[A-z]+(?:\*?\(\))?/){|x| "`#{x}`"}
# Add backquotes around c functions like malloc() where missing.
- l = l.gsub(/(?<![`A-z])[a-z_]+\(\)/, '`\0`')
+ l = l.gsub(/(?<![`A-z.])[a-z_]+\(\)/, '`\0`')
# Add backquotes around macro and var names containing underscores.
l = l.gsub(/(?<![`A-z\*])[A-Za-z]+_[A-Za-z0-9_]+/){|x| "`#{x}`"}
# Link URLs preceded by space or newline (not already linked)