| 1 | #include "duckdb/main/config.hpp" |
| 2 | |
| 3 | #include "duckdb/common/operator/cast_operators.hpp" |
| 4 | #include "duckdb/common/string_util.hpp" |
| 5 | #include "duckdb/main/settings.hpp" |
| 6 | #include "duckdb/storage/storage_extension.hpp" |
| 7 | |
| 8 | #ifndef DUCKDB_NO_THREADS |
| 9 | #include "duckdb/common/thread.hpp" |
| 10 | #endif |
| 11 | |
| 12 | #include <cstdio> |
| 13 | #include <cinttypes> |
| 14 | |
| 15 | namespace duckdb { |
| 16 | |
| 17 | #ifdef DEBUG |
| 18 | bool DBConfigOptions::debug_print_bindings = false; |
| 19 | #endif |
| 20 | |
| 21 | #define DUCKDB_GLOBAL(_PARAM) \ |
| 22 | { \ |
| 23 | _PARAM::Name, _PARAM::Description, _PARAM::InputType, _PARAM::SetGlobal, nullptr, _PARAM::ResetGlobal, \ |
| 24 | nullptr, _PARAM::GetSetting \ |
| 25 | } |
| 26 | #define DUCKDB_GLOBAL_ALIAS(_ALIAS, _PARAM) \ |
| 27 | { \ |
| 28 | _ALIAS, _PARAM::Description, _PARAM::InputType, _PARAM::SetGlobal, nullptr, _PARAM::ResetGlobal, nullptr, \ |
| 29 | _PARAM::GetSetting \ |
| 30 | } |
| 31 | |
| 32 | #define DUCKDB_LOCAL(_PARAM) \ |
| 33 | { \ |
| 34 | _PARAM::Name, _PARAM::Description, _PARAM::InputType, nullptr, _PARAM::SetLocal, nullptr, _PARAM::ResetLocal, \ |
| 35 | _PARAM::GetSetting \ |
| 36 | } |
| 37 | #define DUCKDB_LOCAL_ALIAS(_ALIAS, _PARAM) \ |
| 38 | { \ |
| 39 | _ALIAS, _PARAM::Description, _PARAM::InputType, nullptr, _PARAM::SetLocal, nullptr, _PARAM::ResetLocal, \ |
| 40 | _PARAM::GetSetting \ |
| 41 | } |
| 42 | |
| 43 | #define DUCKDB_GLOBAL_LOCAL(_PARAM) \ |
| 44 | { \ |
| 45 | _PARAM::Name, _PARAM::Description, _PARAM::InputType, _PARAM::SetGlobal, _PARAM::SetLocal, \ |
| 46 | _PARAM::ResetGlobal, _PARAM::ResetLocal, _PARAM::GetSetting \ |
| 47 | } |
| 48 | #define DUCKDB_GLOBAL_LOCAL_ALIAS(_ALIAS, _PARAM) \ |
| 49 | { \ |
| 50 | _ALIAS, _PARAM::Description, _PARAM::InputType, _PARAM::SetGlobal, _PARAM::SetLocal, _PARAM::ResetGlobal, \ |
| 51 | _PARAM::ResetLocal, _PARAM::GetSetting \ |
| 52 | } |
| 53 | #define FINAL_SETTING \ |
| 54 | { nullptr, nullptr, LogicalTypeId::INVALID, nullptr, nullptr, nullptr, nullptr, nullptr } |
| 55 | |
| 56 | static ConfigurationOption internal_options[] = {DUCKDB_GLOBAL(AccessModeSetting), |
| 57 | DUCKDB_GLOBAL(CheckpointThresholdSetting), |
| 58 | DUCKDB_GLOBAL(DebugCheckpointAbort), |
| 59 | DUCKDB_LOCAL(DebugForceExternal), |
| 60 | DUCKDB_LOCAL(DebugForceNoCrossProduct), |
| 61 | DUCKDB_LOCAL(DebugAsOfIEJoin), |
| 62 | DUCKDB_GLOBAL(DebugWindowMode), |
| 63 | DUCKDB_GLOBAL_LOCAL(DefaultCollationSetting), |
| 64 | DUCKDB_GLOBAL(DefaultOrderSetting), |
| 65 | DUCKDB_GLOBAL(DefaultNullOrderSetting), |
| 66 | DUCKDB_GLOBAL(DisabledOptimizersSetting), |
| 67 | DUCKDB_GLOBAL(EnableExternalAccessSetting), |
| 68 | DUCKDB_GLOBAL(EnableFSSTVectors), |
| 69 | DUCKDB_GLOBAL(AllowUnsignedExtensionsSetting), |
| 70 | DUCKDB_LOCAL(CustomExtensionRepository), |
| 71 | DUCKDB_GLOBAL(EnableObjectCacheSetting), |
| 72 | DUCKDB_GLOBAL(EnableHTTPMetadataCacheSetting), |
| 73 | DUCKDB_LOCAL(EnableProfilingSetting), |
| 74 | DUCKDB_LOCAL(EnableProgressBarSetting), |
| 75 | DUCKDB_LOCAL(EnableProgressBarPrintSetting), |
| 76 | DUCKDB_GLOBAL(ExperimentalParallelCSVSetting), |
| 77 | DUCKDB_LOCAL(ExplainOutputSetting), |
| 78 | DUCKDB_GLOBAL(ExtensionDirectorySetting), |
| 79 | DUCKDB_GLOBAL(ExternalThreadsSetting), |
| 80 | DUCKDB_LOCAL(FileSearchPathSetting), |
| 81 | DUCKDB_GLOBAL(ForceCompressionSetting), |
| 82 | DUCKDB_GLOBAL(ForceBitpackingModeSetting), |
| 83 | DUCKDB_LOCAL(HomeDirectorySetting), |
| 84 | DUCKDB_LOCAL(LogQueryPathSetting), |
| 85 | DUCKDB_GLOBAL(LockConfigurationSetting), |
| 86 | DUCKDB_GLOBAL(ImmediateTransactionModeSetting), |
| 87 | DUCKDB_LOCAL(IntegerDivisionSetting), |
| 88 | DUCKDB_LOCAL(MaximumExpressionDepthSetting), |
| 89 | DUCKDB_GLOBAL(MaximumMemorySetting), |
| 90 | DUCKDB_GLOBAL_ALIAS("memory_limit" , MaximumMemorySetting), |
| 91 | DUCKDB_GLOBAL_ALIAS("null_order" , DefaultNullOrderSetting), |
| 92 | DUCKDB_LOCAL(OrderedAggregateThreshold), |
| 93 | DUCKDB_GLOBAL(PasswordSetting), |
| 94 | DUCKDB_LOCAL(PerfectHashThresholdSetting), |
| 95 | DUCKDB_LOCAL(PivotLimitSetting), |
| 96 | DUCKDB_LOCAL(PreserveIdentifierCase), |
| 97 | DUCKDB_GLOBAL(PreserveInsertionOrder), |
| 98 | DUCKDB_LOCAL(ProfilerHistorySize), |
| 99 | DUCKDB_LOCAL(ProfileOutputSetting), |
| 100 | DUCKDB_LOCAL(ProfilingModeSetting), |
| 101 | DUCKDB_LOCAL_ALIAS("profiling_output" , ProfileOutputSetting), |
| 102 | DUCKDB_LOCAL(ProgressBarTimeSetting), |
| 103 | DUCKDB_LOCAL(SchemaSetting), |
| 104 | DUCKDB_LOCAL(SearchPathSetting), |
| 105 | DUCKDB_GLOBAL(TempDirectorySetting), |
| 106 | DUCKDB_GLOBAL(ThreadsSetting), |
| 107 | DUCKDB_GLOBAL(UsernameSetting), |
| 108 | DUCKDB_GLOBAL(ExportLargeBufferArrow), |
| 109 | DUCKDB_GLOBAL_ALIAS("user" , UsernameSetting), |
| 110 | DUCKDB_GLOBAL_ALIAS("wal_autocheckpoint" , CheckpointThresholdSetting), |
| 111 | DUCKDB_GLOBAL_ALIAS("worker_threads" , ThreadsSetting), |
| 112 | FINAL_SETTING}; |
| 113 | |
| 114 | vector<ConfigurationOption> DBConfig::GetOptions() { |
| 115 | vector<ConfigurationOption> options; |
| 116 | for (idx_t index = 0; internal_options[index].name; index++) { |
| 117 | options.push_back(x: internal_options[index]); |
| 118 | } |
| 119 | return options; |
| 120 | } |
| 121 | |
| 122 | idx_t DBConfig::GetOptionCount() { |
| 123 | idx_t count = 0; |
| 124 | for (idx_t index = 0; internal_options[index].name; index++) { |
| 125 | count++; |
| 126 | } |
| 127 | return count; |
| 128 | } |
| 129 | |
| 130 | vector<std::string> DBConfig::GetOptionNames() { |
| 131 | vector<string> names; |
| 132 | for (idx_t i = 0, option_count = DBConfig::GetOptionCount(); i < option_count; i++) { |
| 133 | names.emplace_back(args&: DBConfig::GetOptionByIndex(index: i)->name); |
| 134 | } |
| 135 | return names; |
| 136 | } |
| 137 | |
| 138 | ConfigurationOption *DBConfig::GetOptionByIndex(idx_t target_index) { |
| 139 | for (idx_t index = 0; internal_options[index].name; index++) { |
| 140 | if (index == target_index) { |
| 141 | return internal_options + index; |
| 142 | } |
| 143 | } |
| 144 | return nullptr; |
| 145 | } |
| 146 | |
| 147 | ConfigurationOption *DBConfig::GetOptionByName(const string &name) { |
| 148 | auto lname = StringUtil::Lower(str: name); |
| 149 | for (idx_t index = 0; internal_options[index].name; index++) { |
| 150 | D_ASSERT(StringUtil::Lower(internal_options[index].name) == string(internal_options[index].name)); |
| 151 | if (internal_options[index].name == lname) { |
| 152 | return internal_options + index; |
| 153 | } |
| 154 | } |
| 155 | return nullptr; |
| 156 | } |
| 157 | |
| 158 | void DBConfig::SetOption(const ConfigurationOption &option, const Value &value) { |
| 159 | SetOption(db: nullptr, option, value); |
| 160 | } |
| 161 | |
| 162 | void DBConfig::SetOptionByName(const string &name, const Value &value) { |
| 163 | auto option = DBConfig::GetOptionByName(name); |
| 164 | if (option) { |
| 165 | SetOption(option: *option, value); |
| 166 | } else { |
| 167 | options.unrecognized_options[name] = value; |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | void DBConfig::SetOption(DatabaseInstance *db, const ConfigurationOption &option, const Value &value) { |
| 172 | lock_guard<mutex> l(config_lock); |
| 173 | if (!option.set_global) { |
| 174 | throw InternalException("Could not set option \"%s\" as a global option" , option.name); |
| 175 | } |
| 176 | D_ASSERT(option.reset_global); |
| 177 | Value input = value.DefaultCastAs(target_type: option.parameter_type); |
| 178 | option.set_global(db, *this, input); |
| 179 | } |
| 180 | |
| 181 | void DBConfig::ResetOption(DatabaseInstance *db, const ConfigurationOption &option) { |
| 182 | lock_guard<mutex> l(config_lock); |
| 183 | if (!option.reset_global) { |
| 184 | throw InternalException("Could not reset option \"%s\" as a global option" , option.name); |
| 185 | } |
| 186 | D_ASSERT(option.set_global); |
| 187 | option.reset_global(db, *this); |
| 188 | } |
| 189 | |
| 190 | void DBConfig::SetOption(const string &name, Value value) { |
| 191 | lock_guard<mutex> l(config_lock); |
| 192 | options.set_variables[name] = std::move(value); |
| 193 | } |
| 194 | |
| 195 | void DBConfig::ResetOption(const string &name) { |
| 196 | lock_guard<mutex> l(config_lock); |
| 197 | auto extension_option = extension_parameters.find(x: name); |
| 198 | D_ASSERT(extension_option != extension_parameters.end()); |
| 199 | auto &default_value = extension_option->second.default_value; |
| 200 | if (!default_value.IsNull()) { |
| 201 | // Default is not NULL, override the setting |
| 202 | options.set_variables[name] = default_value; |
| 203 | } else { |
| 204 | // Otherwise just remove it from the 'set_variables' map |
| 205 | options.set_variables.erase(x: name); |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | void DBConfig::AddExtensionOption(const string &name, string description, LogicalType parameter, |
| 210 | const Value &default_value, set_option_callback_t function) { |
| 211 | extension_parameters.insert( |
| 212 | x: make_pair(x: name, y: ExtensionOption(std::move(description), std::move(parameter), function, default_value))); |
| 213 | if (!default_value.IsNull()) { |
| 214 | // Default value is set, insert it into the 'set_variables' list |
| 215 | options.set_variables[name] = default_value; |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | CastFunctionSet &DBConfig::GetCastFunctions() { |
| 220 | return *cast_functions; |
| 221 | } |
| 222 | |
| 223 | void DBConfig::SetDefaultMaxMemory() { |
| 224 | auto memory = FileSystem::GetAvailableMemory(); |
| 225 | if (memory != DConstants::INVALID_INDEX) { |
| 226 | options.maximum_memory = memory * 8 / 10; |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | idx_t CGroupBandwidthQuota(idx_t physical_cores, FileSystem &fs) { |
| 231 | static constexpr const char *CPU_MAX = "/sys/fs/cgroup/cpu.max" ; |
| 232 | static constexpr const char *CFS_QUOTA = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us" ; |
| 233 | static constexpr const char *CFS_PERIOD = "/sys/fs/cgroup/cpu/cpu.cfs_period_us" ; |
| 234 | |
| 235 | int64_t quota, period; |
| 236 | char byte_buffer[1000]; |
| 237 | unique_ptr<FileHandle> handle; |
| 238 | int64_t read_bytes; |
| 239 | |
| 240 | if (fs.FileExists(filename: CPU_MAX)) { |
| 241 | // cgroup v2 |
| 242 | // https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html |
| 243 | handle = |
| 244 | fs.OpenFile(path: CPU_MAX, flags: FileFlags::FILE_FLAGS_READ, lock: FileSystem::DEFAULT_LOCK, compression: FileSystem::DEFAULT_COMPRESSION); |
| 245 | read_bytes = fs.Read(handle&: *handle, buffer: (void *)byte_buffer, nr_bytes: 999); |
| 246 | byte_buffer[read_bytes] = '\0'; |
| 247 | if (std::sscanf(s: byte_buffer, format: "%" SCNd64 " %" SCNd64 "" , "a, &period) != 2) { |
| 248 | return physical_cores; |
| 249 | } |
| 250 | } else if (fs.FileExists(filename: CFS_QUOTA) && fs.FileExists(filename: CFS_PERIOD)) { |
| 251 | // cgroup v1 |
| 252 | // https://www.kernel.org/doc/html/latest/scheduler/sched-bwc.html#management |
| 253 | |
| 254 | // Read the quota, this indicates how many microseconds the CPU can be utilized by this cgroup per period |
| 255 | handle = fs.OpenFile(path: CFS_QUOTA, flags: FileFlags::FILE_FLAGS_READ, lock: FileSystem::DEFAULT_LOCK, |
| 256 | compression: FileSystem::DEFAULT_COMPRESSION); |
| 257 | read_bytes = fs.Read(handle&: *handle, buffer: (void *)byte_buffer, nr_bytes: 999); |
| 258 | byte_buffer[read_bytes] = '\0'; |
| 259 | if (std::sscanf(s: byte_buffer, format: "%" SCNd64 "" , "a) != 1) { |
| 260 | return physical_cores; |
| 261 | } |
| 262 | |
| 263 | // Read the time period, a cgroup can utilize the CPU up to quota microseconds every period |
| 264 | handle = fs.OpenFile(path: CFS_PERIOD, flags: FileFlags::FILE_FLAGS_READ, lock: FileSystem::DEFAULT_LOCK, |
| 265 | compression: FileSystem::DEFAULT_COMPRESSION); |
| 266 | read_bytes = fs.Read(handle&: *handle, buffer: (void *)byte_buffer, nr_bytes: 999); |
| 267 | byte_buffer[read_bytes] = '\0'; |
| 268 | if (std::sscanf(s: byte_buffer, format: "%" SCNd64 "" , &period) != 1) { |
| 269 | return physical_cores; |
| 270 | } |
| 271 | } else { |
| 272 | // No cgroup quota |
| 273 | return physical_cores; |
| 274 | } |
| 275 | if (quota > 0 && period > 0) { |
| 276 | return idx_t(std::ceil(x: (double)quota / (double)period)); |
| 277 | } else { |
| 278 | return physical_cores; |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | idx_t GetSystemMaxThreadsInternal(FileSystem &fs) { |
| 283 | #ifndef DUCKDB_NO_THREADS |
| 284 | idx_t physical_cores = std::thread::hardware_concurrency(); |
| 285 | #ifdef __linux__ |
| 286 | auto cores_available_per_period = CGroupBandwidthQuota(physical_cores, fs); |
| 287 | return MaxValue<idx_t>(a: cores_available_per_period, b: 1); |
| 288 | #else |
| 289 | return physical_cores; |
| 290 | #endif |
| 291 | #else |
| 292 | return 1; |
| 293 | #endif |
| 294 | } |
| 295 | |
| 296 | void DBConfig::SetDefaultMaxThreads() { |
| 297 | #ifndef DUCKDB_NO_THREADS |
| 298 | options.maximum_threads = GetSystemMaxThreadsInternal(fs&: *file_system); |
| 299 | #else |
| 300 | options.maximum_threads = 1; |
| 301 | #endif |
| 302 | } |
| 303 | |
| 304 | idx_t DBConfig::ParseMemoryLimit(const string &arg) { |
| 305 | if (arg[0] == '-' || arg == "null" || arg == "none" ) { |
| 306 | return DConstants::INVALID_INDEX; |
| 307 | } |
| 308 | // split based on the number/non-number |
| 309 | idx_t idx = 0; |
| 310 | while (StringUtil::CharacterIsSpace(c: arg[idx])) { |
| 311 | idx++; |
| 312 | } |
| 313 | idx_t num_start = idx; |
| 314 | while ((arg[idx] >= '0' && arg[idx] <= '9') || arg[idx] == '.' || arg[idx] == 'e' || arg[idx] == 'E' || |
| 315 | arg[idx] == '-') { |
| 316 | idx++; |
| 317 | } |
| 318 | if (idx == num_start) { |
| 319 | throw ParserException("Memory limit must have a number (e.g. SET memory_limit=1GB" ); |
| 320 | } |
| 321 | string number = arg.substr(pos: num_start, n: idx - num_start); |
| 322 | |
| 323 | // try to parse the number |
| 324 | double limit = Cast::Operation<string_t, double>(input: string_t(number)); |
| 325 | |
| 326 | // now parse the memory limit unit (e.g. bytes, gb, etc) |
| 327 | while (StringUtil::CharacterIsSpace(c: arg[idx])) { |
| 328 | idx++; |
| 329 | } |
| 330 | idx_t start = idx; |
| 331 | while (idx < arg.size() && !StringUtil::CharacterIsSpace(c: arg[idx])) { |
| 332 | idx++; |
| 333 | } |
| 334 | if (limit < 0) { |
| 335 | // limit < 0, set limit to infinite |
| 336 | return (idx_t)-1; |
| 337 | } |
| 338 | string unit = StringUtil::Lower(str: arg.substr(pos: start, n: idx - start)); |
| 339 | idx_t multiplier; |
| 340 | if (unit == "byte" || unit == "bytes" || unit == "b" ) { |
| 341 | multiplier = 1; |
| 342 | } else if (unit == "kilobyte" || unit == "kilobytes" || unit == "kb" || unit == "k" ) { |
| 343 | multiplier = 1000LL; |
| 344 | } else if (unit == "megabyte" || unit == "megabytes" || unit == "mb" || unit == "m" ) { |
| 345 | multiplier = 1000LL * 1000LL; |
| 346 | } else if (unit == "gigabyte" || unit == "gigabytes" || unit == "gb" || unit == "g" ) { |
| 347 | multiplier = 1000LL * 1000LL * 1000LL; |
| 348 | } else if (unit == "terabyte" || unit == "terabytes" || unit == "tb" || unit == "t" ) { |
| 349 | multiplier = 1000LL * 1000LL * 1000LL * 1000LL; |
| 350 | } else { |
| 351 | throw ParserException("Unknown unit for memory_limit: %s (expected: b, mb, gb or tb)" , unit); |
| 352 | } |
| 353 | return (idx_t)multiplier * limit; |
| 354 | } |
| 355 | |
| 356 | // Right now we only really care about access mode when comparing DBConfigs |
| 357 | bool DBConfigOptions::operator==(const DBConfigOptions &other) const { |
| 358 | return other.access_mode == access_mode; |
| 359 | } |
| 360 | |
| 361 | bool DBConfig::operator==(const DBConfig &other) { |
| 362 | return other.options == options; |
| 363 | } |
| 364 | |
| 365 | bool DBConfig::operator!=(const DBConfig &other) { |
| 366 | return !(other.options == options); |
| 367 | } |
| 368 | |
| 369 | OrderType DBConfig::ResolveOrder(OrderType order_type) const { |
| 370 | if (order_type != OrderType::ORDER_DEFAULT) { |
| 371 | return order_type; |
| 372 | } |
| 373 | return options.default_order_type; |
| 374 | } |
| 375 | |
| 376 | OrderByNullType DBConfig::ResolveNullOrder(OrderType order_type, OrderByNullType null_type) const { |
| 377 | if (null_type != OrderByNullType::ORDER_DEFAULT) { |
| 378 | return null_type; |
| 379 | } |
| 380 | switch (options.default_null_order) { |
| 381 | case DefaultOrderByNullType::NULLS_FIRST: |
| 382 | return OrderByNullType::NULLS_FIRST; |
| 383 | case DefaultOrderByNullType::NULLS_LAST: |
| 384 | return OrderByNullType::NULLS_LAST; |
| 385 | case DefaultOrderByNullType::NULLS_FIRST_ON_ASC_LAST_ON_DESC: |
| 386 | return order_type == OrderType::ASCENDING ? OrderByNullType::NULLS_FIRST : OrderByNullType::NULLS_LAST; |
| 387 | case DefaultOrderByNullType::NULLS_LAST_ON_ASC_FIRST_ON_DESC: |
| 388 | return order_type == OrderType::ASCENDING ? OrderByNullType::NULLS_LAST : OrderByNullType::NULLS_FIRST; |
| 389 | default: |
| 390 | throw InternalException("Unknown null order setting" ); |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | } // namespace duckdb |
| 395 | |