Repository: RediSearch/RediSearch
Pull request #6151: change config setters and getters - [MOD-9673]
Author: login: BenGoldberger
Labels: name: size:M
Created: 2025-05-18T12:31:18Z | Merged: 2025-05-28T01:00:36Z

this pr fixes the bug that was intrudoced by enable ft.config to be changed from redis by setter functions overiding other configs  


Diff:
diff --git a/src/config.c b/src/config.c
index b2e3171b211..7acbe0ed41a 100644
--- a/src/config.c
+++ b/src/config.c
@@ -99,7 +99,7 @@ static const char* FTConfigNameToConfigName(const char *name) {
   return NULL;
 }
 
-int set_numeric_config(const char *name, long long val, void *privdata,
+int set_long_numeric_config(const char *name, long long val, void *privdata,
                   RedisModuleString **err) {
   REDISMODULE_NOT_USED(name);
   REDISMODULE_NOT_USED(err);
@@ -107,9 +107,22 @@ int set_numeric_config(const char *name, long long val, void *privdata,
   return REDISMODULE_OK;
 }
 
-long long get_numeric_config(const char *name, void *privdata) {
+long long get_long_numeric_config(const char *name, void *privdata) {
   REDISMODULE_NOT_USED(name);
-  return (*(long long *)privdata);
+  return *(long long *)privdata;
+}
+
+int set_size_t_numeric_config(const char *name, long long val, void *privdata,
+                           RedisModuleString **err) {
+  REDISMODULE_NOT_USED(name);
+  REDISMODULE_NOT_USED(err);
+  *(size_t *)privdata = (size_t) val;
+  return REDISMODULE_OK;
+}
+
+long long get_size_t_numeric_config(const char *name, void *privdata) {
+  REDISMODULE_NOT_USED(name);
+  return (long long)(*(size_t *)privdata);
 }
 
 int set_uint_numeric_config(const char *name, long long val,
@@ -125,11 +138,24 @@ long long get_uint_numeric_config(const char *name, void *privdata) {
   return (long long)(*(unsigned int *)privdata);
 }
 
+int set_uint8_numeric_config(const char *name, long long val,
+                           void *privdata, RedisModuleString **err) {
+  REDISMODULE_NOT_USED(name);
+  REDISMODULE_NOT_USED(err);
+  *(uint8_t *)privdata = (uint8_t) val;
+  return REDISMODULE_OK;
+}
+
+long long get_uint8_numeric_config(const char *name, void *privdata) {
+  REDISMODULE_NOT_USED(name);
+  return (long long)(*(uint8_t *)privdata);
+}
+
 int set_bool_config(const char *name, int val, void *privdata,
                     RedisModuleString **err) {
   REDISMODULE_NOT_USED(name);
   REDISMODULE_NOT_USED(err);
-  *(int *)privdata = val;
+  *(bool *)privdata = val;
   return REDISMODULE_OK;
 }
 
@@ -137,18 +163,18 @@ int set_inverted_bool_config(const char *name, int val, void *privdata,
                              RedisModuleString **err) {
   REDISMODULE_NOT_USED(name);
   REDISMODULE_NOT_USED(err);
-  *(int *)privdata = (val == 0);
+  *(bool *)privdata = (val == 0);
   return REDISMODULE_OK;
 }
 
 int get_bool_config(const char *name, void *privdata) {
   REDISMODULE_NOT_USED(name);
-  return *(int *)privdata;
+  return *(bool *)privdata;
 }
 
 int get_inverted_bool_config(const char *name, void *privdata) {
   REDISMODULE_NOT_USED(name);
-  return !*(int *)privdata;
+  return !*(bool *)privdata;
 }
 
 int set_immutable_string_config(const char *name, RedisModuleString *val, void *privdata,
@@ -450,8 +476,8 @@ CONFIG_GETTER(getIndexingMemoryLimit) {
 
 // BM25STD_TANH_FACTOR
 CONFIG_SETTER(setBM25StdTanhFactor) {
-  uint64_t newFactor;
-  int acrc = AC_GetU64(ac, &newFactor, AC_F_GE1);
+  unsigned int newFactor;
+  int acrc = AC_GetUnsigned(ac, &newFactor, AC_F_GE1);
   CHECK_RETURN_PARSE_ERROR(acrc);
   if (newFactor > BM25STD_TANH_FACTOR_MAX) {
     QueryError_SetWithoutUserDataFmt(status, QUERY_ELIMIT,
@@ -465,7 +491,7 @@ CONFIG_SETTER(setBM25StdTanhFactor) {
 
 CONFIG_GETTER(getBM25StdTanhFactor) {
   sds ss = sdsempty();
-  return sdscatprintf(ss, "%lu", config->requestConfigParams.BM25STD_TanhFactor);
+  return sdscatprintf(ss, "%u", config->requestConfigParams.BM25STD_TanhFactor);
 }
 
 CONFIG_SETTER(setBgOOMpauseTimeForRetry) {
@@ -843,7 +869,9 @@ CONFIG_GETTER(getGcPolicy) {
 
 // PARTIAL_INDEXED_DOCS
 CONFIG_SETTER(setFilterCommand) {
-  int acrc = AC_GetInt(ac, &config->filterCommands, AC_F_GE0);
+  int filterCommands;
+  int acrc = AC_GetInt(ac, &filterCommands, AC_F_GE0);
+  config->filterCommands = (bool)filterCommands;
   RETURN_STATUS(acrc);
 }
 
@@ -1525,8 +1553,8 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-_numeric-ranges-parents", 0,
       REDISMODULE_CONFIG_UNPREFIXED, 0,
-      NR_MAX_DEPTH_BALANCE, get_numeric_config,
-      set_numeric_config, NULL,
+      NR_MAX_DEPTH_BALANCE, get_size_t_numeric_config,
+      set_size_t_numeric_config, NULL,
       (void *)&(RSGlobalConfig.numericTreeMaxDepthRange)
     )
   )
@@ -1545,7 +1573,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
       ctx, "search-default-dialect", DEFAULT_DIALECT_VERSION,
       REDISMODULE_CONFIG_UNPREFIXED,
       MIN_DIALECT_VERSION, MAX_DIALECT_VERSION,
-      get_numeric_config, set_numeric_config, NULL,
+      get_uint_numeric_config, set_uint_numeric_config, NULL,
       (void *)&(RSGlobalConfig.requestConfigParams.dialectVersion)
     )
   )
@@ -1554,7 +1582,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig (
       ctx, "search-fork-gc-clean-threshold", DEFAULT_FORK_GC_CLEAN_THRESHOLD,
       REDISMODULE_CONFIG_UNPREFIXED, 1,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_size_t_numeric_config, set_size_t_numeric_config, NULL,
       (void *)&(RSGlobalConfig.gcConfigParams.forkGc.forkGcCleanThreshold)
     )
   )
@@ -1563,7 +1591,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig (
       ctx, "search-fork-gc-retry-interval", DEFAULT_FORK_GC_RETRY_INTERVAL,
       REDISMODULE_CONFIG_UNPREFIXED, 1,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_size_t_numeric_config, set_size_t_numeric_config, NULL,
       (void *)&(RSGlobalConfig.gcConfigParams.forkGc.forkGcRetryInterval)
     )
   )
@@ -1572,7 +1600,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-fork-gc-run-interval", DEFAULT_FORK_GC_RUN_INTERVAL,
       REDISMODULE_CONFIG_UNPREFIXED, 1,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_size_t_numeric_config, set_size_t_numeric_config, NULL,
       (void *)&(RSGlobalConfig.gcConfigParams.forkGc.forkGcRunIntervalSec)
     )
   )
@@ -1581,7 +1609,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-fork-gc-sleep-before-exit", 0,
       REDISMODULE_CONFIG_UNPREFIXED, 0,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_size_t_numeric_config, set_size_t_numeric_config, NULL,
       (void *)&(RSGlobalConfig.gcConfigParams.forkGc.forkGcSleepBeforeExit)
     )
   )
@@ -1590,7 +1618,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-gc-scan-size", DEFAULT_GC_SCANSIZE,
       REDISMODULE_CONFIG_IMMUTABLE | REDISMODULE_CONFIG_UNPREFIXED, 1,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_size_t_numeric_config, set_size_t_numeric_config, NULL,
       (void *)&(RSGlobalConfig.gcConfigParams.gcScanSize)
     )
   )
@@ -1599,7 +1627,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-index-cursor-limit", DEFAULT_INDEX_CURSOR_LIMIT,
       REDISMODULE_CONFIG_UNPREFIXED, 0,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_long_numeric_config, set_long_numeric_config, NULL,
       (void *)&(RSGlobalConfig.indexCursorLimit)
     )
   )
@@ -1608,7 +1636,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-max-aggregate-results", DEFAULT_MAX_AGGREGATE_REQUEST_RESULTS,
       REDISMODULE_CONFIG_UNPREFIXED, 0,
-      MAX_AGGREGATE_REQUEST_RESULTS, get_numeric_config, set_numeric_config,
+      MAX_AGGREGATE_REQUEST_RESULTS, get_size_t_numeric_config, set_size_t_numeric_config,
       NULL, (void *)&(RSGlobalConfig.maxAggregateResults)
     )
   )
@@ -1616,8 +1644,8 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
   RM_TRY(
     RedisModule_RegisterNumericConfig(
       ctx, "search-max-prefix-expansions", DEFAULT_MAX_PREFIX_EXPANSIONS,
-      REDISMODULE_CONFIG_UNPREFIXED, 1,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      REDISMODULE_CONFIG_UNPREFIXED, 1, LLONG_MAX,
+      get_long_numeric_config, set_long_numeric_config, NULL,
       (void *)&(RSGlobalConfig.iteratorsConfigParams.maxPrefixExpansions)
     )
   )
@@ -1626,7 +1654,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-max-doctablesize", DEFAULT_DOC_TABLE_SIZE,
       REDISMODULE_CONFIG_IMMUTABLE | REDISMODULE_CONFIG_UNPREFIXED, 1,
-      MAX_DOC_TABLE_SIZE, get_numeric_config, set_numeric_config, NULL,
+      MAX_DOC_TABLE_SIZE, get_size_t_numeric_config, set_size_t_numeric_config, NULL,
       (void *)&(RSGlobalConfig.maxDocTableSize)
     )
   )
@@ -1635,7 +1663,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-cursor-max-idle", DEFAULT_MAX_CURSOR_IDLE,
       REDISMODULE_CONFIG_UNPREFIXED, 1,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_long_numeric_config, set_long_numeric_config, NULL,
       (void *)&(RSGlobalConfig.cursorMaxIdle)
     )
   )
@@ -1644,7 +1672,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-max-search-results", DEFAULT_MAX_SEARCH_REQUEST_RESULTS,
       REDISMODULE_CONFIG_UNPREFIXED, 0,
-      MAX_SEARCH_REQUEST_RESULTS, get_numeric_config, set_numeric_config, NULL,
+      MAX_SEARCH_REQUEST_RESULTS, get_size_t_numeric_config, set_size_t_numeric_config, NULL,
       (void *)&(RSGlobalConfig.maxSearchResults)
     )
   )
@@ -1663,7 +1691,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-min-phonetic-term-len", DEFAULT_MIN_PHONETIC_TERM_LEN,
       REDISMODULE_CONFIG_UNPREFIXED, 1,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_size_t_numeric_config, set_size_t_numeric_config, NULL,
       (void *)&(RSGlobalConfig.minPhoneticTermLen)
     )
   )
@@ -1672,7 +1700,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-min-prefix", DEFAULT_MIN_TERM_PREFIX,
       REDISMODULE_CONFIG_UNPREFIXED, 1,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_long_numeric_config, set_long_numeric_config, NULL,
       (void *)&(RSGlobalConfig.iteratorsConfigParams.minTermPrefix)
     )
   )
@@ -1681,7 +1709,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-min-stem-len", DEFAULT_MIN_STEM_LENGTH,
       REDISMODULE_CONFIG_UNPREFIXED, 2,
-      UINT32_MAX, get_numeric_config, set_numeric_config, NULL,
+      UINT32_MAX, get_uint_numeric_config, set_uint_numeric_config, NULL,
       (void *)&(RSGlobalConfig.iteratorsConfigParams.minStemLength)
     )
   )
@@ -1699,7 +1727,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-tiered-hnsw-buffer-limit", DEFAULT_BLOCK_SIZE,
       REDISMODULE_CONFIG_IMMUTABLE | REDISMODULE_CONFIG_UNPREFIXED, 0,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_size_t_numeric_config, set_size_t_numeric_config, NULL,
       (void *)&(RSGlobalConfig.tieredVecSimIndexBufferLimit)
     )
   )
@@ -1708,7 +1736,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-timeout", DEFAULT_QUERY_TIMEOUT_MS,
       REDISMODULE_CONFIG_UNPREFIXED, 1,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_long_numeric_config, set_long_numeric_config, NULL,
       (void *)&(RSGlobalConfig.requestConfigParams.queryTimeoutMS)
     )
   )
@@ -1717,7 +1745,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
     RedisModule_RegisterNumericConfig(
       ctx, "search-union-iterator-heap", DEFAULT_UNION_ITERATOR_HEAP,
       REDISMODULE_CONFIG_UNPREFIXED, 1,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_long_numeric_config, set_long_numeric_config, NULL,
       (void *)&(RSGlobalConfig.iteratorsConfigParams.minUnionIterHeap)
     )
   )
@@ -1745,7 +1773,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
       ctx, "search-workers-priority-bias-threshold",
       DEFAULT_HIGH_PRIORITY_BIAS_THRESHOLD,
       REDISMODULE_CONFIG_IMMUTABLE | REDISMODULE_CONFIG_UNPREFIXED, 0,
-      LLONG_MAX, get_numeric_config, set_numeric_config, NULL,
+      LLONG_MAX, get_size_t_numeric_config, set_size_t_numeric_config, NULL,
       (void *)&(RSGlobalConfig.highPriorityBiasNum)
     )
   )
@@ -1755,7 +1783,7 @@ int RegisterModuleConfig(RedisModuleCtx *ctx) {
       ctx, "search-_bg-index-mem-pct-thr",
       DEFAULT_INDEXING_MEMORY_LIMIT,
       REDISMODULE_CONFIG_DEFAULT | REDISMODULE_CONFIG_UNPREFIXED, 0,
-      100, get_uint_numeric_config, set_uint_numeric_config, NULL,
+      100, get_uint8_numeric_config, set_uint8_numeric_config, NULL,
       (void *)&(RSGlobalConfig.indexingMemoryLimit)
     )
   )
diff --git a/src/config.h b/src/config.h
index 5f6493b7fab..9bdb7887609 100644
--- a/src/config.h
+++ b/src/config.h
@@ -56,7 +56,7 @@ typedef struct {
 
 typedef struct {
   // If this is set, GC is enabled on all indexes (default: 1, disable with NOGC)
-  int enableGC;
+  bool enableGC;
   size_t gcScanSize;
   GCPolicy gcPolicy;
 
@@ -72,9 +72,9 @@ typedef struct {
   long long queryTimeoutMS;
   RSTimeoutPolicy timeoutPolicy;
   // reply with time on profile
-  int printProfileClock;
+  bool printProfileClock;
   // BM25STD.TANH factor
-  uint64_t BM25STD_TanhFactor;
+  unsigned int BM25STD_TanhFactor;
 } RequestConfig;
 
 // Configuration parameters related to the query execution.
@@ -126,18 +126,18 @@ typedef struct {
   // Chained configuration data
   void *chainedConfig;
 
-  int noMemPool;
+  bool noMemPool;
 
-  int filterCommands;
+  bool filterCommands;
 
   // free resource on shutdown
-  int freeResourcesThread;
+  bool freeResourcesThread;
   // compress double to float
-  int numericCompress;
+  bool numericCompress;
   // keep numeric ranges in parents of leafs
   size_t numericTreeMaxDepthRange;
   // disable compression for inverted index DocIdsOnly
-  int invertedIndexRawDocidEncoding;
+  bool invertedIndexRawDocidEncoding;
 
   // sets the memory limit for vector indexes to resize by (in bytes).
   // 0 indicates no limit. Default value is 0.
@@ -152,7 +152,7 @@ typedef struct {
   unsigned int numBGIndexingIterationsBeforeSleep;
   // If set, we use an optimization that sorts the children of an intersection iterator in a way
   // where union iterators are being factorize by the number of their own children.
-  int prioritizeIntersectUnionChildren;
+  bool prioritizeIntersectUnionChildren;
     // The number of indexing operations per field to perform before yielding to Redis during indexing while loading (so redis can be responsive)
   unsigned int indexerYieldEveryOpsWhileLoading;
   // Limit the number of cursors that can be created for a single index
diff --git a/src/coord/config.c b/src/coord/config.c
index 44b3101117e..131a2c2063f 100644
--- a/src/coord/config.c
+++ b/src/coord/config.c
@@ -42,7 +42,7 @@ CONFIG_GETTER(getNumPartitions) {
   return sdsnew("AUTO");
 }
 
-// TIMEOUT
+// CLUSTER_TIMEOUT
 CONFIG_SETTER(setClusterTimeout) {
   SearchClusterConfig *realConfig = getOrCreateRealConfig(config);
   int acrc = AC_GetInt(ac, &realConfig->timeoutMS, AC_F_GE1);
@@ -201,7 +201,7 @@ static RSConfigOptions clusterOptions_g = {
              .setValue = setNumPartitions,
              .getValue = getNumPartitions,
              .flags = RSCONFIGVAR_F_IMMUTABLE},
-            {.name = "TIMEOUT",
+            {.name = "CLUSTER_TIMEOUT",
              .helpText = "Cluster synchronization timeout",
              .setValue = setClusterTimeout,
              .getValue = getClusterTimeout},
diff --git a/tests/pytests/test_config.py b/tests/pytests/test_config.py
index 8bd714ba066..a7f3c6caecf 100644
--- a/tests/pytests/test_config.py
+++ b/tests/pytests/test_config.py
@@ -122,7 +122,6 @@ def testSetConfigOptions(env):
     env.expect(config_cmd(), 'set', '_BG_INDEX_MEM_PCT_THR', 1).equal('OK')
     env.expect(config_cmd(), 'set', 'BM25STD_TANH_FACTOR', 1).equal('OK')
     env.expect(config_cmd(), 'set', '_BG_INDEX_OOM_PAUSE_TIME', 1).equal('OK')
-
     env.expect(config_cmd(), 'set', 'INDEXER_YIELD_EVERY_OPS', 1).equal('OK')
 
 @skip(cluster=True)
@@ -1279,7 +1278,7 @@ def testStringArgDeprecationMessage():
     ('search-_print-profile-clock', '_PRINT_PROFILE_CLOCK', 'yes', False, False),
     ('search-no-gc', 'NOGC', 'no', True, True),
     ('search-no-mem-pools', 'NO_MEM_POOLS', 'no', True, True),
-    # ('search-partial-indexed-docs', 'PARTIAL_INDEXED_DOCS', 'no', True, False),
+    ('search-partial-indexed-docs', 'PARTIAL_INDEXED_DOCS', 'no', True, False),
     ('search-_prioritize-intersect-union-children', '_PRIORITIZE_INTERSECT_UNION_CHILDREN', 'no', False, False),
     ('search-raw-docid-encoding', 'RAW_DOCID_ENCODING', 'no', True, False),
     ('search-enable-unstable-features', 'ENABLE_UNSTABLE_FEATURES', 'no', False, False),
@@ -1510,6 +1509,10 @@ def testConfigFileAndArgsBooleanParams():
 
     moduleArgs = ''
     for configName, argName, defaultValue, immutable, isFlag in booleanConfigs:
+        # `search-partial-indexed-docs` has its own test because
+        # `PARTIAL_INDEXED_DOCS` is set using a number but returns a boolean
+        if configName == 'search-partial-indexed-docs':
+            continue
         # use non-default value as argument value
         ftDefaultValue = 'false' if defaultValue == 'yes' else 'true'
         if isFlag:
@@ -1519,10 +1522,7 @@ def testConfigFileAndArgsBooleanParams():
 
     env = Env(noDefaultModuleArgs=True, moduleArgs=moduleArgs, redisConfigFile=redisConfigFile)
     for configName, argName, defaultValue, immutable, isFlag in booleanConfigs:
-        # `search-partial-indexed-docs` has its own test because
-        # `PARTIAL_INDEXED_DOCS` is set using a number but returns a boolean
-        if configName == 'search-partial-indexed-docs':
-            continue
+
         # the expected value is the default value, taken from the config file
         ftExpectedValue = 'true' if defaultValue == 'yes' else 'false'
         res = env.cmd('CONFIG', 'GET', configName)
@@ -1538,6 +1538,10 @@ def testBooleanArgDeprecationMessage():
     # create module arguments
     moduleArgs = ''
     for configName, argName, defaultValue, immutable, isFlag in booleanConfigs:
+        # `search-partial-indexed-docs` has its own test because
+        # `PARTIAL_INDEXED_DOCS` is set using a number but returns a boolean
+        if configName == 'search-partial-indexed-docs':
+            continue
         # use non-default value as argument value
         ftDefaultValue = 'false' if defaultValue == 'yes' else 'true'
         if isFlag:
@@ -1550,6 +1554,10 @@ def testBooleanArgDeprecationMessage():
     logFileName = env.cmd('CONFIG', 'GET', 'logfile')[1]
     logFilePath = os.path.join(logDir, logFileName)
     for configName, argName, defaultValue, immutable, isFlag in booleanConfigs:
+        # `search-partial-indexed-docs` has its own test because
+        # `PARTIAL_INDEXED_DOCS` is set using a number but returns a boolean
+        if configName == 'search-partial-indexed-docs':
+            continue
         expectedMessage = f'`{argName}` was set, but module arguments are deprecated, consider using CONFIG parameter `{configName}` instead'
         matchCount = _grep_file_count(logFilePath, expectedMessage)
         env.assertEqual(matchCount, 1, message=f'argName: {argName}, configName: {configName}')
@@ -1639,3 +1647,196 @@ def testDeprecatedConfigParamMessage():
             expectedMatchCount = 2
         env.assertEqual(matchCount, expectedMatchCount,
                         message=f'configName: {ftConfigName}')
+
+
+def getConfigDict(env):
+    """Get all configuration values as a dictionary"""
+    return {d[0]: d[1:] for d in env.cmd(config_cmd(), 'GET', '*')}
+
+def checkConfigChange(env, configName, argName, newValue, baseConfigDict):
+    """Test changing a single configuration value and verify others remain unchanged
+
+    Args:
+        env: The test environment
+        configName: The Redis CONFIG name
+        argName: The FT.CONFIG name
+        newValue: The new value to set
+        baseConfigDict: Dictionary with baseline configuration values
+    """
+
+    # Change the configuration
+    env.expect('CONFIG', 'SET', configName, newValue).ok()
+
+    # Verify the change took effect via Redis CONFIG
+    env.expect('CONFIG', 'GET', configName).equal([configName, str(newValue)])
+
+    # Verify the change took effect via FT.CONFIG
+    if isinstance(newValue, bool) or newValue in ['yes', 'no']:
+        # Handle boolean values
+        if newValue in ['yes', True]:
+            expected_ft_value = 'true'
+        else:
+            expected_ft_value = 'false'
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, expected_ft_value]])
+    elif argName in ['MAXSEARCHRESULTS', 'MAXAGGREGATERESULTS'] and (newValue == MAX_SEARCH_REQUEST_RESULTS or newValue == MAX_AGGREGATE_REQUEST_RESULTS):
+        # Handle special case for unlimited values
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, 'unlimited']])
+    else:
+        # Handle numeric values
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, str(newValue)]])
+
+    # Get current configuration and verify only the target changed
+    currentConfigDict = getConfigDict(env)
+    for k, v in baseConfigDict.items():
+        if {k, argName} == {'MAXPREFIXEXPANSIONS', 'MAXEXPANSIONS'}:
+            env.assertEqual(currentConfigDict[k], [str(newValue)], message=f'changedConfig: {argName}')
+            continue
+        if k == argName:
+            if argName in ['MAXSEARCHRESULTS', 'MAXAGGREGATERESULTS'] and (newValue == MAX_SEARCH_REQUEST_RESULTS or newValue == MAX_AGGREGATE_REQUEST_RESULTS):
+                env.assertEqual(currentConfigDict[k], ['unlimited'], message=f'changedConfig: {argName}')
+            elif isinstance(newValue, bool) or newValue in ['yes', 'no']:
+                if newValue in ['yes', True]:
+                    expected_value = ['true']
+                else:
+                    expected_value = ['false']
+                env.assertEqual(currentConfigDict[k], expected_value, message=f'changedConfig: {argName}')
+            else:
+                env.assertEqual(currentConfigDict[k], [str(newValue)], message=f'changedConfig: {argName}')
+        else:
+            env.assertEqual(currentConfigDict[k], v, message=f'affectedConfig: {k}, changedConfig: {argName}')
+
+
+def testConfigIndependence_default():
+    """Test that changing one configuration value doesn't affect other configuration values"""
+    env = Env(noDefaultModuleArgs=True)
+
+    defaultConfigDict = getConfigDict(env)
+    for configName, argName, default, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+        if configName == 'search-conn-per-shard': # change search-conn-per-shard max value because it may open too many connections
+            maxValue = 20
+
+        # Test min value
+        checkConfigChange(env, configName, argName, minValue, defaultConfigDict)
+
+        # Test max value. Skip for search-conn-per-shard because it may open too many connections
+        checkConfigChange(env, configName, argName, maxValue, defaultConfigDict)
+
+        # Reset to default value
+        env.expect('CONFIG', 'SET', configName, default).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, defaultConfigDict)
+
+    for configName, argName, defaultValue, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+        # Test true (yes)
+        checkConfigChange(env, configName, argName, 'yes', defaultConfigDict)
+
+        # Test false (no)
+        checkConfigChange(env, configName, argName, 'no', defaultConfigDict)
+
+        # Reset to default value
+        env.expect('CONFIG', 'SET', configName, defaultValue).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, defaultConfigDict)
+
+def testConfigIndependence_min_values():
+    """Test that changing one configuration value doesn't affect other configuration values"""
+    env = Env(noDefaultModuleArgs=True)
+    # set all numeric configs to min value
+    for configName, argName, _, minValue, _, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+        env.expect('CONFIG', 'SET', configName, minValue).ok()
+    # set all boolean configs to false (no)
+    for configName, argName, _, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+        env.expect('CONFIG', 'SET', configName, 'no').ok()
+    minValueConfigDict = getConfigDict(env)
+
+    for configName, argName, _, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+        if configName == 'search-conn-per-shard': # change search-conn-per-shard max value because it may open too many connections
+            maxValue = 20
+
+        # Test max value.
+        checkConfigChange(env, configName, argName, maxValue, minValueConfigDict)
+
+        # Reset to min value
+        env.expect('CONFIG', 'SET', configName, minValue).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, minValueConfigDict)
+
+    for configName, argName, _, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+
+        # Test true (yes)
+        checkConfigChange(env, configName, argName, 'yes', minValueConfigDict)
+
+        # Reset to false (no)
+        env.expect('CONFIG', 'SET', configName, 'no').ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, minValueConfigDict)
+
+def testConfigIndependence_max_values():
+    """Test that changing one configuration value doesn't affect other configuration values"""
+    env = Env(noDefaultModuleArgs=True)
+    # set all numeric configs to max value
+    for configName, argName, _, _, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+        if configName == 'search-conn-per-shard': # change search-conn-per-shard max value because it may open too many connections
+            maxValue = 20
+        env.expect('CONFIG', 'SET', configName, maxValue).ok()
+    # set all boolean configs to true (yes)
+    for configName, argName, _, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+        env.expect('CONFIG', 'SET', configName, 'yes').ok()
+    maxValueConfigDict = getConfigDict(env)
+
+    for configName, argName, _, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+        if configName == 'search-conn-per-shard': # change search-conn-per-shard max value because it may open too many connections
+            maxValue = 20
+
+        # Test min value
+        checkConfigChange(env, configName, argName, minValue, maxValueConfigDict)
+
+        # Reset to max value
+        env.expect('CONFIG', 'SET', configName, maxValue).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, maxValueConfigDict)
+
+    for configName, argName, _, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+
+        # Test false (no)
+        checkConfigChange(env, configName, argName, 'no', maxValueConfigDict)
+
+        # Reset to true (yes)
+        env.expect('CONFIG', 'SET', configName, 'yes').ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, maxValueConfigDict)
diff --git a/tests/pytests/test_resp3.py b/tests/pytests/test_resp3.py
index 44845f8804d..0d692ade0e3 100644
--- a/tests/pytests/test_resp3.py
+++ b/tests/pytests/test_resp3.py
@@ -481,8 +481,8 @@ def test_config():
     res = env.cmd(config_cmd(), "SET", "TIMEOUT", 501)
 
     res = env.cmd(config_cmd(), "GET", "*")
-    env.assertEqual(res['TIMEOUT'], '0') # FIXME: should be '501'. This is a bug in the RESP3 implementation because we have 2 configurations with the same name
-
+    env.assertEqual(res['TIMEOUT'], '501') 
+    
     res = env.cmd(config_cmd(), "GET", "TIMEOUT")
     env.assertEqual(res, {'TIMEOUT': '501'})
 


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR refines the configuration subsystem by correcting a RESP3 test expectation, re-enabling and extending configuration independence tests, and refactoring the core config API to use type-specific getters/setters and more precise field types.

- Fix RESP3 `FT.CONFIG GET *` timeout mismatch in tests  
- Re-enable `search-partial-indexed-docs` tests and add generic config-independence test helpers  
- Rename cluster timeout key and update config struct fields from generic ints to `bool`, `size_t`, `unsigned int`, etc.  
- Implement new `set_*/get_*_numeric_config` variants and update module registrations accordingly

### Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

<details>
<summary>Show a summary per file</summary>

| File                               | Description                                                                          |
|------------------------------------|--------------------------------------------------------------------------------------|
| tests/pytests/test_resp3.py        | Update expected TIMEOUT value in RESP3 `GET *` test to reflect setter behavior       |
| tests/pytests/test_config.py       | Re-enable partial-indexed-docs entries, add skip logic, and introduce independence tests |
| src/coord/config.c                 | Rename `"TIMEOUT"` option to `"CLUSTER_TIMEOUT"` in cluster configuration            |
| src/config.h                       | Change various config struct fields from `int`/`uint64_t` to `bool`, `unsigned int`, etc. |
| src/config.c                       | Add type-specific `set_/get_` numeric config functions and update `RegisterModuleConfig` calls |
</details>



<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**tests/pytests/test_config.py:1663**
* `MAX_SEARCH_REQUEST_RESULTS` and `MAX_AGGREGATE_REQUEST_RESULTS` are not defined in this test file, causing a potential `NameError`. Import or define these constants, or derive the 'unlimited' sentinel from `numericConfigs` instead.
```
elif argName in ['MAXSEARCHRESULTS', 'MAXAGGREGATERESULTS'] and (newValue == MAX_SEARCH_REQUEST_RESULTS or newValue == MAX_AGGREGATE_REQUEST_RESULTS):
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T17:05:22Z
user: login: nafraf
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T18:06:04Z
user: login: BenGoldberger
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-21T11:04:23Z
user: login: BenGoldberger
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-21T11:07:00Z
user: login: nafraf
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-05-22T17:28:39Z
user: login: alonre24
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-25T11:30:27Z
user: login: BenGoldberger
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-25T11:34:28Z
user: login: BenGoldberger
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-25T11:41:06Z
user: login: alonre24
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-05-25T12:01:06Z
user: login: nafraf
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-26T14:28:41Z
user: login: alonre24
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-27T11:46:34Z
user: login: nafraf
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-27T12:31:18Z

Review comments:
diff_hunk: @@ -97,17 +97,30 @@ static const char* FTConfigNameToConfigName(const char *name) {
   return NULL;
 }
 
-int set_numeric_config(const char *name, long long val, void *privdata,
-                  RedisModuleString **err) {
+int set_long_numeric_config(const char *name, long long val, void *privdata,
+                           RedisModuleString **err) {
   REDISMODULE_NOT_USED(name);
   REDISMODULE_NOT_USED(err);
   *(long long *)privdata = val;
   return REDISMODULE_OK;
 }
 
-long long get_numeric_config(const char *name, void *privdata) {
+long long get_long_numeric_config(const char *name, void *privdata) {
   REDISMODULE_NOT_USED(name);
-  return (*(long long *)privdata);
+  return *(long long *)privdata;
+}
+
+int set_size_t_numeric_config(const char *name, long long val, void *privdata,
+                           RedisModuleString **err) {
+  REDISMODULE_NOT_USED(name);
+  REDISMODULE_NOT_USED(err);
+  *(size_t *)privdata = (size_t) val;
+  return REDISMODULE_OK;
+}
+
+long long get_size_t_numeric_config(const char *name, void *privdata) {
+  REDISMODULE_NOT_USED(name);
+  return *(size_t *)privdata;
path: src/config.c
user: login: nafraf
body: ```suggestion
  return (long long)(*(size_t *)privdata);
```
created_at: 2025-05-20T17:01:28Z
author_association: COLLABORATOR
diff_hunk: @@ -1624,3 +1629,188 @@ def testDeprecatedConfigParamMessage():
             expectedMatchCount = 2
         env.assertEqual(matchCount, expectedMatchCount,
                         message=f'configName: {ftConfigName}')
+
+
+def getConfigDict(env):
+    """Get all configuration values as a dictionary"""
+    return {d[0]: d[1:] for d in env.cmd(config_cmd() + ' get *')}
path: tests/pytests/test_config.py
user: login: Copilot
body: The invocation `env.cmd(config_cmd() + ' get *')` concatenates the command into one string, which may not be split into arguments correctly. Use `env.cmd(config_cmd(), 'GET', '*')` to pass the command and its arguments separately.
```suggestion
    return {d[0]: d[1:] for d in env.cmd(config_cmd(), 'GET', '*')}
```
created_at: 2025-05-20T17:05:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1495,6 +1495,10 @@ def testConfigFileAndArgsBooleanParams():
 
     moduleArgs = ''
     for configName, argName, defaultValue, immutable, isFlag in booleanConfigs:
+        # `search-partial-indexed-docs` has its own test because
+        # `PARTIAL_INDEXED_DOCS` is set using a number but returns a boolean
+        if configName == 'search-partial-indexed-docs':
path: tests/pytests/test_config.py
user: login: Copilot
body: [nitpick] The skip condition for `'search-partial-indexed-docs'` is duplicated across multiple loops. Consider filtering `booleanConfigs` once or extracting this into a helper to reduce repetition and improve clarity.
created_at: 2025-05-20T17:05:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -120,33 +133,46 @@ int set_uint_numeric_config(const char *name, long long val,
 
 long long get_uint_numeric_config(const char *name, void *privdata) {
   REDISMODULE_NOT_USED(name);
-  return (long long)(*(unsigned int *)privdata);
+  return *(unsigned int *)privdata;
path: src/config.c
user: login: nafraf
body: ```suggestion
  return (long long)(*(unsigned int *)privdata);
```
created_at: 2025-05-20T17:05:27Z
author_association: COLLABORATOR
diff_hunk: @@ -120,33 +133,46 @@ int set_uint_numeric_config(const char *name, long long val,
 
 long long get_uint_numeric_config(const char *name, void *privdata) {
   REDISMODULE_NOT_USED(name);
-  return (long long)(*(unsigned int *)privdata);
+  return *(unsigned int *)privdata;
+}
+
+int set_uint8_numeric_config(const char *name, long long val,
+                           void *privdata, RedisModuleString **err) {
+  REDISMODULE_NOT_USED(name);
+  REDISMODULE_NOT_USED(err);
+  *(uint8_t *)privdata = (uint8_t) val;
+  return REDISMODULE_OK;
+}
+
+long long get_uint8_numeric_config(const char *name, void *privdata) {
+  REDISMODULE_NOT_USED(name);
+  return *(uint8_t *)privdata;
path: src/config.c
user: login: nafraf
body: ```suggestion
  return (long long)(*(uint8_t *)privdata);
```
created_at: 2025-05-20T17:06:57Z
author_association: COLLABORATOR
diff_hunk: @@ -120,33 +133,46 @@ int set_uint_numeric_config(const char *name, long long val,
 
 long long get_uint_numeric_config(const char *name, void *privdata) {
   REDISMODULE_NOT_USED(name);
-  return (long long)(*(unsigned int *)privdata);
+  return *(unsigned int *)privdata;
+}
+
+int set_uint8_numeric_config(const char *name, long long val,
+                           void *privdata, RedisModuleString **err) {
+  REDISMODULE_NOT_USED(name);
+  REDISMODULE_NOT_USED(err);
+  *(uint8_t *)privdata = (uint8_t) val;
path: src/config.c
user: login: nafraf
body: Should we validate if `val` is within the valid range 0-255? 
created_at: 2025-05-20T17:15:22Z
author_association: COLLABORATOR
diff_hunk: @@ -201,7 +201,7 @@ static RSConfigOptions clusterOptions_g = {
              .setValue = setNumPartitions,
              .getValue = getNumPartitions,
              .flags = RSCONFIGVAR_F_IMMUTABLE},
-            {.name = "TIMEOUT",
+            {.name = "CLUSTER_TIMEOUT",
path: src/coord/config.c
user: login: nafraf
body: Should this be part of a different PR?
I don't see the equivalent `search-cluster-timeout` config parameter, but I'm not sure if we should create it or if this parameter will be deprecated.
@GuyAv46 WDYT?
created_at: 2025-05-20T17:53:33Z
author_association: COLLABORATOR
diff_hunk: @@ -120,33 +133,46 @@ int set_uint_numeric_config(const char *name, long long val,
 
 long long get_uint_numeric_config(const char *name, void *privdata) {
   REDISMODULE_NOT_USED(name);
-  return (long long)(*(unsigned int *)privdata);
+  return *(unsigned int *)privdata;
+}
+
+int set_uint8_numeric_config(const char *name, long long val,
+                           void *privdata, RedisModuleString **err) {
+  REDISMODULE_NOT_USED(name);
+  REDISMODULE_NOT_USED(err);
+  *(uint8_t *)privdata = (uint8_t) val;
path: src/config.c
user: login: BenGoldberger
body: maybe, but it should be validated on redis side
created_at: 2025-05-21T11:04:21Z
author_association: COLLABORATOR
diff_hunk: @@ -201,7 +201,7 @@ static RSConfigOptions clusterOptions_g = {
              .setValue = setNumPartitions,
              .getValue = getNumPartitions,
              .flags = RSCONFIGVAR_F_IMMUTABLE},
-            {.name = "TIMEOUT",
+            {.name = "CLUSTER_TIMEOUT",
path: src/coord/config.c
user: login: BenGoldberger
body: this is a change to make the tests better. 
there will be another pr getting rid of this config
created_at: 2025-05-21T11:06:59Z
author_association: COLLABORATOR
diff_hunk: @@ -1538,6 +1538,8 @@ def testBooleanArgDeprecationMessage():
     # create module arguments
     moduleArgs = ''
     for configName, argName, defaultValue, immutable, isFlag in booleanConfigs:
+        if configName == 'search-partial-indexed-docs':
path: tests/pytests/test_config.py
user: login: alonre24
body: Why skipping it?
created_at: 2025-05-25T10:42:13Z
author_association: COLLABORATOR
diff_hunk: @@ -1639,3 +1643,188 @@ def testDeprecatedConfigParamMessage():
             expectedMatchCount = 2
         env.assertEqual(matchCount, expectedMatchCount,
                         message=f'configName: {ftConfigName}')
+
+
+def getConfigDict(env):
+    """Get all configuration values as a dictionary"""
+    return {d[0]: d[1:] for d in env.cmd(config_cmd(), 'GET', '*')}
+
+def checkConfigChange(env, configName, argName, newValue, baseConfigDict):
+    """Test changing a single configuration value and verify others remain unchanged
+
+    Args:
+        env: The test environment
+        configName: The Redis CONFIG name
+        argName: The FT.CONFIG name
+        newValue: The new value to set
+        baseConfigDict: Dictionary with baseline configuration values
+    """
+
+    # Change the configuration
+    env.expect('CONFIG', 'SET', configName, newValue).ok()
+
+    # Verify the change took effect via Redis CONFIG
+    env.expect('CONFIG', 'GET', configName).equal([configName, str(newValue)])
+
+    # Verify the change took effect via FT.CONFIG
+    if isinstance(newValue, bool) or newValue in ['yes', 'no']:
+        # Handle boolean values
+        if newValue in ['yes', True]:
+            expected_ft_value = 'true'
+        else:
+            expected_ft_value = 'false'
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, expected_ft_value]])
+    elif argName in ['MAXSEARCHRESULTS', 'MAXAGGREGATERESULTS'] and (newValue == MAX_SEARCH_REQUEST_RESULTS or newValue == MAX_AGGREGATE_REQUEST_RESULTS):
+        # Handle special case for unlimited values
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, 'unlimited']])
+    else:
+        # Handle numeric values
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, str(newValue)]])
+
+    # Get current configuration and verify only the target changed
+    currentConfigDict = getConfigDict(env)
+    for k, v in baseConfigDict.items():
+        if {k, argName} == {'MAXPREFIXEXPANSIONS', 'MAXEXPANSIONS'}:
+            env.assertEqual(currentConfigDict[k], [str(newValue)], message=f'changedConfig: {argName}')
+            continue
+        if k == argName:
+            if argName in ['MAXSEARCHRESULTS', 'MAXAGGREGATERESULTS'] and (newValue == MAX_SEARCH_REQUEST_RESULTS or newValue == MAX_AGGREGATE_REQUEST_RESULTS):
+                env.assertEqual(currentConfigDict[k], ['unlimited'], message=f'changedConfig: {argName}')
+            elif isinstance(newValue, bool) or newValue in ['yes', 'no']:
+                if newValue in ['yes', True]:
+                    expected_value = ['true']
+                else:
+                    expected_value = ['false']
+                env.assertEqual(currentConfigDict[k], expected_value, message=f'changedConfig: {argName}')
+            else:
+                env.assertEqual(currentConfigDict[k], [str(newValue)], message=f'changedConfig: {argName}')
+        else:
+            env.assertEqual(currentConfigDict[k], v, message=f'affectedConfig: {k}, changedConfig: {argName}')
+
+
+def testConfigIndependence_default():
+    """Test that changing one configuration value doesn't affect other configuration values"""
+    env = Env(noDefaultModuleArgs=True)
+
+    defaultConfigDict = getConfigDict(env)
+    for configName, argName, default, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+
+        # Test min value
+        checkConfigChange(env, configName, argName, minValue, defaultConfigDict)
+
+        # Test max value
+        checkConfigChange(env, configName, argName, maxValue, defaultConfigDict)
+
+        # Reset to default value
+        env.expect('CONFIG', 'SET', configName, default).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, defaultConfigDict)
+
+    for configName, argName, defaultValue, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+        # Test true (yes)
+        checkConfigChange(env, configName, argName, 'yes', defaultConfigDict)
+
+        # Test false (no)
+        checkConfigChange(env, configName, argName, 'no', defaultConfigDict)
+
+        # Reset to default value
+        env.expect('CONFIG', 'SET', configName, defaultValue).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, defaultConfigDict)
+
+def testConfigIndependence_min_values():
+    """Test that changing one configuration value doesn't affect other configuration values"""
+    env = Env(noDefaultModuleArgs=True)
+    # set all numeric configs to min value
+    for configName, argName, _, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+        env.expect('CONFIG', 'SET', configName, minValue).ok()
+    # set all boolean configs to false (no)
+    for configName, argName, _, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+        env.expect('CONFIG', 'SET', configName, 'no').ok()
+    minValueConfigDict = getConfigDict(env)
+
+    for configName, argName, _, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+
+        # Test max value
+        checkConfigChange(env, configName, argName, maxValue, minValueConfigDict)
+
+        # Reset to min value
+        env.expect('CONFIG', 'SET', configName, minValue).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, minValueConfigDict)
+
+    for configName, argName, _, immutable, _ in booleanConfigs:
path: tests/pytests/test_config.py
user: login: alonre24
body: How is the check on Boolean configs different than the one in the previous test? 
created_at: 2025-05-25T11:03:48Z
author_association: COLLABORATOR
diff_hunk: @@ -1538,6 +1538,8 @@ def testBooleanArgDeprecationMessage():
     # create module arguments
     moduleArgs = ''
     for configName, argName, defaultValue, immutable, isFlag in booleanConfigs:
+        if configName == 'search-partial-indexed-docs':
path: tests/pytests/test_config.py
user: login: BenGoldberger
body: I added the comment from the previous test.
This config is set with a number, but its a boolean config 
created_at: 2025-05-25T11:34:27Z
author_association: COLLABORATOR
diff_hunk: @@ -1639,3 +1643,188 @@ def testDeprecatedConfigParamMessage():
             expectedMatchCount = 2
         env.assertEqual(matchCount, expectedMatchCount,
                         message=f'configName: {ftConfigName}')
+
+
+def getConfigDict(env):
+    """Get all configuration values as a dictionary"""
+    return {d[0]: d[1:] for d in env.cmd(config_cmd(), 'GET', '*')}
+
+def checkConfigChange(env, configName, argName, newValue, baseConfigDict):
+    """Test changing a single configuration value and verify others remain unchanged
+
+    Args:
+        env: The test environment
+        configName: The Redis CONFIG name
+        argName: The FT.CONFIG name
+        newValue: The new value to set
+        baseConfigDict: Dictionary with baseline configuration values
+    """
+
+    # Change the configuration
+    env.expect('CONFIG', 'SET', configName, newValue).ok()
+
+    # Verify the change took effect via Redis CONFIG
+    env.expect('CONFIG', 'GET', configName).equal([configName, str(newValue)])
+
+    # Verify the change took effect via FT.CONFIG
+    if isinstance(newValue, bool) or newValue in ['yes', 'no']:
+        # Handle boolean values
+        if newValue in ['yes', True]:
+            expected_ft_value = 'true'
+        else:
+            expected_ft_value = 'false'
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, expected_ft_value]])
+    elif argName in ['MAXSEARCHRESULTS', 'MAXAGGREGATERESULTS'] and (newValue == MAX_SEARCH_REQUEST_RESULTS or newValue == MAX_AGGREGATE_REQUEST_RESULTS):
+        # Handle special case for unlimited values
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, 'unlimited']])
+    else:
+        # Handle numeric values
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, str(newValue)]])
+
+    # Get current configuration and verify only the target changed
+    currentConfigDict = getConfigDict(env)
+    for k, v in baseConfigDict.items():
+        if {k, argName} == {'MAXPREFIXEXPANSIONS', 'MAXEXPANSIONS'}:
+            env.assertEqual(currentConfigDict[k], [str(newValue)], message=f'changedConfig: {argName}')
+            continue
+        if k == argName:
+            if argName in ['MAXSEARCHRESULTS', 'MAXAGGREGATERESULTS'] and (newValue == MAX_SEARCH_REQUEST_RESULTS or newValue == MAX_AGGREGATE_REQUEST_RESULTS):
+                env.assertEqual(currentConfigDict[k], ['unlimited'], message=f'changedConfig: {argName}')
+            elif isinstance(newValue, bool) or newValue in ['yes', 'no']:
+                if newValue in ['yes', True]:
+                    expected_value = ['true']
+                else:
+                    expected_value = ['false']
+                env.assertEqual(currentConfigDict[k], expected_value, message=f'changedConfig: {argName}')
+            else:
+                env.assertEqual(currentConfigDict[k], [str(newValue)], message=f'changedConfig: {argName}')
+        else:
+            env.assertEqual(currentConfigDict[k], v, message=f'affectedConfig: {k}, changedConfig: {argName}')
+
+
+def testConfigIndependence_default():
+    """Test that changing one configuration value doesn't affect other configuration values"""
+    env = Env(noDefaultModuleArgs=True)
+
+    defaultConfigDict = getConfigDict(env)
+    for configName, argName, default, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+
+        # Test min value
+        checkConfigChange(env, configName, argName, minValue, defaultConfigDict)
+
+        # Test max value
+        checkConfigChange(env, configName, argName, maxValue, defaultConfigDict)
+
+        # Reset to default value
+        env.expect('CONFIG', 'SET', configName, default).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, defaultConfigDict)
+
+    for configName, argName, defaultValue, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+        # Test true (yes)
+        checkConfigChange(env, configName, argName, 'yes', defaultConfigDict)
+
+        # Test false (no)
+        checkConfigChange(env, configName, argName, 'no', defaultConfigDict)
+
+        # Reset to default value
+        env.expect('CONFIG', 'SET', configName, defaultValue).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, defaultConfigDict)
+
+def testConfigIndependence_min_values():
+    """Test that changing one configuration value doesn't affect other configuration values"""
+    env = Env(noDefaultModuleArgs=True)
+    # set all numeric configs to min value
+    for configName, argName, _, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+        env.expect('CONFIG', 'SET', configName, minValue).ok()
+    # set all boolean configs to false (no)
+    for configName, argName, _, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+        env.expect('CONFIG', 'SET', configName, 'no').ok()
+    minValueConfigDict = getConfigDict(env)
+
+    for configName, argName, _, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+
+        # Test max value
+        checkConfigChange(env, configName, argName, maxValue, minValueConfigDict)
+
+        # Reset to min value
+        env.expect('CONFIG', 'SET', configName, minValue).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, minValueConfigDict)
+
+    for configName, argName, _, immutable, _ in booleanConfigs:
path: tests/pytests/test_config.py
user: login: BenGoldberger
body: the difference is that all configs are set to minValue/false instead of the default config.

My assumption was that if some configs are set to 0/false by default changing other configs may not effect them.
so I tested that if all the configs are set to min/max they arent changing by others.
created_at: 2025-05-25T11:41:05Z
author_association: COLLABORATOR
diff_hunk: @@ -1639,3 +1647,194 @@ def testDeprecatedConfigParamMessage():
             expectedMatchCount = 2
         env.assertEqual(matchCount, expectedMatchCount,
                         message=f'configName: {ftConfigName}')
+
+
+def getConfigDict(env):
+    """Get all configuration values as a dictionary"""
+    return {d[0]: d[1:] for d in env.cmd(config_cmd(), 'GET', '*')}
+
+def checkConfigChange(env, configName, argName, newValue, baseConfigDict):
+    """Test changing a single configuration value and verify others remain unchanged
+
+    Args:
+        env: The test environment
+        configName: The Redis CONFIG name
+        argName: The FT.CONFIG name
+        newValue: The new value to set
+        baseConfigDict: Dictionary with baseline configuration values
+    """
+
+    # Change the configuration
+    env.expect('CONFIG', 'SET', configName, newValue).ok()
+
+    # Verify the change took effect via Redis CONFIG
+    env.expect('CONFIG', 'GET', configName).equal([configName, str(newValue)])
+
+    # Verify the change took effect via FT.CONFIG
+    if isinstance(newValue, bool) or newValue in ['yes', 'no']:
+        # Handle boolean values
+        if newValue in ['yes', True]:
+            expected_ft_value = 'true'
+        else:
+            expected_ft_value = 'false'
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, expected_ft_value]])
+    elif argName in ['MAXSEARCHRESULTS', 'MAXAGGREGATERESULTS'] and (newValue == MAX_SEARCH_REQUEST_RESULTS or newValue == MAX_AGGREGATE_REQUEST_RESULTS):
+        # Handle special case for unlimited values
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, 'unlimited']])
+    else:
+        # Handle numeric values
+        env.expect(config_cmd(), 'GET', argName).equal([[argName, str(newValue)]])
+
+    # Get current configuration and verify only the target changed
+    currentConfigDict = getConfigDict(env)
+    for k, v in baseConfigDict.items():
+        if {k, argName} == {'MAXPREFIXEXPANSIONS', 'MAXEXPANSIONS'}:
+            env.assertEqual(currentConfigDict[k], [str(newValue)], message=f'changedConfig: {argName}')
+            continue
+        if k == argName:
+            if argName in ['MAXSEARCHRESULTS', 'MAXAGGREGATERESULTS'] and (newValue == MAX_SEARCH_REQUEST_RESULTS or newValue == MAX_AGGREGATE_REQUEST_RESULTS):
+                env.assertEqual(currentConfigDict[k], ['unlimited'], message=f'changedConfig: {argName}')
+            elif isinstance(newValue, bool) or newValue in ['yes', 'no']:
+                if newValue in ['yes', True]:
+                    expected_value = ['true']
+                else:
+                    expected_value = ['false']
+                env.assertEqual(currentConfigDict[k], expected_value, message=f'changedConfig: {argName}')
+            else:
+                env.assertEqual(currentConfigDict[k], [str(newValue)], message=f'changedConfig: {argName}')
+        else:
+            env.assertEqual(currentConfigDict[k], v, message=f'affectedConfig: {k}, changedConfig: {argName}')
+
+
+def testConfigIndependence_default():
+    """Test that changing one configuration value doesn't affect other configuration values"""
+    env = Env(noDefaultModuleArgs=True)
+
+    defaultConfigDict = getConfigDict(env)
+    for configName, argName, default, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+
+        # Test min value
+        checkConfigChange(env, configName, argName, minValue, defaultConfigDict)
+
+        # Test max value. Skip for search-conn-per-shard because it may open too many connections
+        if configName != 'search-conn-per-shard':
+            checkConfigChange(env, configName, argName, maxValue, defaultConfigDict)
+
+        # Reset to default value
+        env.expect('CONFIG', 'SET', configName, default).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, defaultConfigDict)
+
+    for configName, argName, defaultValue, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+        # Test true (yes)
+        checkConfigChange(env, configName, argName, 'yes', defaultConfigDict)
+
+        # Test false (no)
+        checkConfigChange(env, configName, argName, 'no', defaultConfigDict)
+
+        # Reset to default value
+        env.expect('CONFIG', 'SET', configName, defaultValue).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, defaultConfigDict)
+
+def testConfigIndependence_min_values():
+    """Test that changing one configuration value doesn't affect other configuration values"""
+    env = Env(noDefaultModuleArgs=True)
+    # set all numeric configs to min value
+    for configName, argName, _, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+        env.expect('CONFIG', 'SET', configName, minValue).ok()
+    # set all boolean configs to false (no)
+    for configName, argName, _, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+        env.expect('CONFIG', 'SET', configName, 'no').ok()
+    minValueConfigDict = getConfigDict(env)
+
+    for configName, argName, _, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+
+        # Test max value. Skip for search-conn-per-shard because it may open too many connections
+        if configName != 'search-conn-per-shard':
+            checkConfigChange(env, configName, argName, maxValue, minValueConfigDict)
+
+        # Reset to min value
+        env.expect('CONFIG', 'SET', configName, minValue).ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, minValueConfigDict)
+
+    for configName, argName, _, immutable, _ in booleanConfigs:
+        if immutable:
+            continue
+
+        # Test true (yes)
+        checkConfigChange(env, configName, argName, 'yes', minValueConfigDict)
+
+        # Reset to false (no)
+        env.expect('CONFIG', 'SET', configName, 'no').ok()
+        currentConfigDict = getConfigDict(env)
+        env.assertEqual(currentConfigDict, minValueConfigDict)
+
+def testConfigIndependence_max_values():
+    """Test that changing one configuration value doesn't affect other configuration values"""
+    env = Env(noDefaultModuleArgs=True)
+    # set all numeric configs to max value
+    for configName, argName, _, minValue, maxValue, immutable, clusterConfig in numericConfigs:
+        if immutable:
+            continue
+        if clusterConfig:
+            if not env.isCluster():
+                continue
+        if configName == 'search-conn-per-shard': # Skip search-conn-per-shard because it may open too many connections
path: tests/pytests/test_config.py
user: login: nafraf
body: But to test config independence, shouldn't be better to don't skip it and test it with an smaller value?
created_at: 2025-05-26T14:28:41Z
author_association: COLLABORATOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-20T08:20:46Z
body: ## [Codecov](https://app.codecov.io/gh/RediSearch/RediSearch/pull/6151?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RediSearch) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 88.24%. Comparing base [(`3805001`)](https://app.codecov.io/gh/RediSearch/RediSearch/commit/380500167f2b11741167f1a7625e22259b038b31?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RediSearch) to head [(`e663062`)](https://app.codecov.io/gh/RediSearch/RediSearch/commit/e66306213bc3b445e7270464a52955cca1230e77?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RediSearch).
> Report is 8 commits behind head on master.

> :exclamation: **Current head e663062 differs from pull request most recent head 1898ce7**
> 
> Please [upload](https://docs.codecov.com/docs/codecov-uploader) reports for the commit 1898ce7 to get more accurate results.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #6151      +/-   ##
==========================================
- Coverage   88.31%   88.24%   -0.08%     
==========================================
  Files         227      227              
  Lines       39953    39895      -58     
  Branches     2592     2592              
==========================================
- Hits        35285    35205      -80     
- Misses       4653     4675      +22     
  Partials       15       15              
```

| [Flag](https://app.codecov.io/gh/RediSearch/RediSearch/pull/6151/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RediSearch) | Coverage Δ | |
|---|---|---|
| [flow](https://app.codecov.io/gh/RediSearch/RediSearch/pull/6151/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RediSearch) | `83.38% <100.00%> (-0.20%)` | :arrow_down: |
| [unit](https://app.codecov.io/gh/RediSearch/RediSearch/pull/6151/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RediSearch) | `45.46% <0.00%> (+0.06%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RediSearch#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/RediSearch/RediSearch/pull/6151?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RediSearch).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RediSearch).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: redisearch-backport-pull-request[bot]
created_at: 2025-05-28T01:00:52Z
body: Backport failed for `8.0`, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.
```bash
git fetch origin 8.0
git worktree add -d .worktree/backport-6151-to-8.0 origin/8.0
cd .worktree/backport-6151-to-8.0
git switch --create backport-6151-to-8.0
git cherry-pick -x fc03b3eddb0288a777d5c266e58b582d742fd02b
```
author_association: CONTRIBUTOR