From b752875a68de371a38756576f57f8e5929061ac6 Mon Sep 17 00:00:00 2001 From: 555aaditya <107977627+555aaditya@users.noreply.github.com> Date: Wed, 28 Oct 2026 23:53:34 +0530 Subject: [PATCH] fix(main): reject setting values that would wrap, and map threads=0 `CALL threads=0` stored 0 threads, after which the next parallel query never finished. Connection::setMaxNumThreadForExec already treats 0 as the database's thread count; the setting now does the same. checkpoint_threshold, sparse_frontier_threshold, var_length_extend_max_depth and recursive_pattern_factor take INT64 but are stored unsigned (the last two as 32 bits), so negative or oversized values wrapped: checkpoint_threshold=-1 read back as 18446744073709551615 (silently disabling auto-checkpoint) and var_length_extend_max_depth=-1 as 4294967295. Reject them with the same "must be >= 0" error physical_plan_pool_per_thread uses. --- src/main/settings.cpp | 34 ++++++++++++++--- .../function/call/setting_ranges.test | 38 +++++++++++++++++++ 2 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 test/test_files/function/call/setting_ranges.test diff --git a/src/main/settings.cpp b/src/main/settings.cpp index 3382027db0..f104c0a351 100644 --- a/src/main/settings.cpp +++ b/src/main/settings.cpp @@ -1,5 +1,7 @@ #include "main/settings.h" +#include + #include "common/assert.h" #include "common/exception/runtime.h" #include "common/string_utils.h" @@ -34,11 +36,30 @@ void setDefaultHashIndexEnabledOnStorageManagers(ClientContext* context, bool en } } +// The INT64 settings below are stored unsigned; a value that doesn't fit would wrap. +template +T toUnsignedSetting(const char* name, const common::Value& parameter) { + const auto value = parameter.getValue(); + if (value < 0) { + throw common::RuntimeException(std::format("{} must be >= 0, got {}.", name, value)); + } + if constexpr (sizeof(T) < sizeof(int64_t)) { + if (static_cast(value) > std::numeric_limits::max()) { + throw common::RuntimeException(std::format("{} must be <= {}, got {}.", name, + std::numeric_limits::max(), value)); + } + } + return static_cast(value); +} + } // namespace void ThreadsSetting::setContext(ClientContext* context, const common::Value& parameter) { parameter.validateType(inputType); - context->getClientConfigUnsafe()->numThreads = parameter.getValue(); + // 0 means the database's thread count, as in ClientContext::setMaxNumThreadForExec. + const auto numThreads = parameter.getValue(); + context->getClientConfigUnsafe()->numThreads = + numThreads == 0 ? context->getDBConfig()->maxNumThreads : numThreads; } common::Value ThreadsSetting::getSetting(const ClientContext* context) { @@ -76,7 +97,8 @@ common::Value ProgressBarSetting::getSetting(const ClientContext* context) { void VarLengthExtendMaxDepthSetting::setContext(ClientContext* context, const common::Value& parameter) { parameter.validateType(inputType); - context->getClientConfigUnsafe()->varLengthMaxDepth = parameter.getValue(); + context->getClientConfigUnsafe()->varLengthMaxDepth = + toUnsignedSetting(name, parameter); } common::Value VarLengthExtendMaxDepthSetting::getSetting(const ClientContext* context) { @@ -86,7 +108,8 @@ common::Value VarLengthExtendMaxDepthSetting::getSetting(const ClientContext* co void SparseFrontierThresholdSetting::setContext(ClientContext* context, const common::Value& parameter) { parameter.validateType(inputType); - context->getClientConfigUnsafe()->sparseFrontierThreshold = parameter.getValue(); + context->getClientConfigUnsafe()->sparseFrontierThreshold = + toUnsignedSetting(name, parameter); } common::Value SparseFrontierThresholdSetting::getSetting(const ClientContext* context) { @@ -156,7 +179,7 @@ void RecursivePatternFactorSetting::setContext(ClientContext* context, const common::Value& parameter) { parameter.validateType(inputType); context->getClientConfigUnsafe()->recursivePatternCardinalityScaleFactor = - parameter.getValue(); + toUnsignedSetting(name, parameter); } common::Value RecursivePatternFactorSetting::getSetting(const ClientContext* context) { @@ -177,7 +200,8 @@ common::Value EnableMVCCSetting::getSetting(const ClientContext* context) { void CheckpointThresholdSetting::setContext(ClientContext* context, const common::Value& parameter) { parameter.validateType(inputType); - context->getDBConfigUnsafe()->checkpointThreshold = parameter.getValue(); + context->getDBConfigUnsafe()->checkpointThreshold = + toUnsignedSetting(name, parameter); } common::Value CheckpointThresholdSetting::getSetting(const ClientContext* context) { diff --git a/test/test_files/function/call/setting_ranges.test b/test/test_files/function/call/setting_ranges.test new file mode 100644 index 0000000000..8c28da1bf9 --- /dev/null +++ b/test/test_files/function/call/setting_ranges.test @@ -0,0 +1,38 @@ +-DATASET CSV empty + +-- + +-CASE ThreadsZeroUsesDatabaseThreadCount +-LOG threads=0 means the database's thread count, as Connection::setMaxNumThreadForExec(0) does +-STATEMENT CALL threads=0 +---- ok +-STATEMENT UNWIND range(1, 100000) AS x RETURN sum(x) +---- 1 +5000050000 + +-CASE UnsignedSettingsRejectValuesThatWrap +-STATEMENT CALL checkpoint_threshold=-1 +---- error +Runtime exception: checkpoint_threshold must be >= 0, got -1. +-STATEMENT CALL sparse_frontier_threshold=-1 +---- error +Runtime exception: sparse_frontier_threshold must be >= 0, got -1. +-STATEMENT CALL var_length_extend_max_depth=-1 +---- error +Runtime exception: var_length_extend_max_depth must be >= 0, got -1. +-STATEMENT CALL var_length_extend_max_depth=4294967296 +---- error +Runtime exception: var_length_extend_max_depth must be <= 4294967295, got 4294967296. +-STATEMENT CALL recursive_pattern_factor=-5 +---- error +Runtime exception: recursive_pattern_factor must be >= 0, got -5. +-STATEMENT CALL var_length_extend_max_depth=7 +---- ok +-STATEMENT CALL current_setting('var_length_extend_max_depth') RETURN * +---- 1 +7 +-STATEMENT CALL checkpoint_threshold=0 +---- ok +-STATEMENT CALL current_setting('checkpoint_threshold') RETURN * +---- 1 +0