//===----------------------------------------------------------------------===//
//                         DuckDB
// This file will not be overwritten. To implement a custom function for
// a new setting, enable 'custom_implementation' in 'src/common/settings.json'
// for this setting. The 'update_settings_definitions.py' may include new
// setting methods' signatures that need to be implemented in this file. You
// can check the functions declaration in 'settings.hpp' and what is
// autogenerated in 'autogenerated_settings.cpp'.
//
//===----------------------------------------------------------------------===//

#include "duckdb/main/settings.hpp"

#include "duckdb/common/enums/access_mode.hpp"
#include "duckdb/common/enum_util.hpp"
#include "duckdb/catalog/catalog_search_path.hpp"
#include "duckdb/common/string_util.hpp"
#include "duckdb/common/file_system.hpp"
#include "duckdb/common/operator/double_cast_operator.hpp"
#include "duckdb/main/attached_database.hpp"
#include "duckdb/main/client_context.hpp"
#include "duckdb/main/client_data.hpp"
#include "duckdb/main/config.hpp"
#include "duckdb/main/database.hpp"
#include "duckdb/main/database_manager.hpp"
#include "duckdb/main/extension_helper.hpp"
#include "duckdb/main/query_profiler.hpp"
#include "duckdb/main/secret/secret_manager.hpp"
#include "duckdb/parallel/task_scheduler.hpp"
#include "duckdb/parser/parser.hpp"
#include "duckdb/planner/expression_binder.hpp"
#include "duckdb/storage/external_file_cache.hpp"
#include "duckdb/storage/buffer/buffer_pool.hpp"
#include "duckdb/storage/buffer_manager.hpp"
#include "duckdb/storage/storage_manager.hpp"
#include "duckdb/logging/logger.hpp"
#include "duckdb/logging/log_manager.hpp"
#include "duckdb/common/type_visitor.hpp"
#include "duckdb/function/variant/variant_shredding.hpp"
#include "duckdb/storage/block_allocator.hpp"

#include "mbedtls_wrapper.hpp"

namespace duckdb {

constexpr const char *LoggingMode::Name;
constexpr const char *LoggingLevel::Name;
constexpr const char *EnableLogging::Name;
constexpr const char *LoggingStorage::Name;
constexpr const char *EnabledLogTypes::Name;
constexpr const char *DisabledLogTypes::Name;
constexpr const char *DisabledFilesystemsSetting::Name;

namespace {

template <class T>
static DatabaseInstance &GetDB(DatabaseInstance *db) {
	if (!db) {
		throw InvalidInputException("Cannot change/set %s before the database is started", T::Name);
	}
	return *db;
}

} // namespace

//===----------------------------------------------------------------------===//
// Access Mode
//===----------------------------------------------------------------------===//
bool AccessModeSetting::OnGlobalSet(DatabaseInstance *db, DBConfig &config, const Value &input) {
	if (db) {
		throw InvalidInputException("Cannot change access_mode setting while database is running - it must be set when "
		                            "opening or attaching the database");
	}
	return true;
}

//===----------------------------------------------------------------------===//
// Allocator Background Threads
//===----------------------------------------------------------------------===//
void AllocatorBackgroundThreadsSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (info.db) {
		TaskScheduler::GetScheduler(*info.db).SetAllocatorBackgroundThreads(input.GetValue<bool>());
	}
}

//===----------------------------------------------------------------------===//
// Allocator Bulk Deallocation Flush Threshold
//===----------------------------------------------------------------------===//
void AllocatorBulkDeallocationFlushThresholdSetting::SetGlobal(DatabaseInstance *db, DBConfig &config,
                                                               const Value &input) {
	config.options.allocator_bulk_deallocation_flush_threshold = DBConfig::ParseMemoryLimit(input.ToString());
	if (db) {
		BufferManager::GetBufferManager(*db).GetBufferPool().SetAllocatorBulkDeallocationFlushThreshold(
		    config.options.allocator_bulk_deallocation_flush_threshold);
	}
}

void AllocatorBulkDeallocationFlushThresholdSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.options.allocator_bulk_deallocation_flush_threshold =
	    DBConfigOptions().allocator_bulk_deallocation_flush_threshold;
	if (db) {
		BufferManager::GetBufferManager(*db).GetBufferPool().SetAllocatorBulkDeallocationFlushThreshold(
		    config.options.allocator_bulk_deallocation_flush_threshold);
	}
}

Value AllocatorBulkDeallocationFlushThresholdSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return Value(StringUtil::BytesToHumanReadableString(config.options.allocator_bulk_deallocation_flush_threshold));
}

//===----------------------------------------------------------------------===//
// Delta Only Variant Legacy Encoding
//===----------------------------------------------------------------------===//
void DeltaOnlyVariantEncodingEnabledSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	throw InvalidInputException("This setting is not adjustable by a user");
}

void DeltaOnlyVariantEncodingEnabledSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	throw InvalidInputException("This setting is not adjustable by a user");
}

Value DeltaOnlyVariantEncodingEnabledSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return Value::BOOLEAN(config.options.variant_legacy_encoding);
}

//===----------------------------------------------------------------------===//
// Allocator Flush Threshold
//===----------------------------------------------------------------------===//
void AllocatorFlushThresholdSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	config.options.allocator_flush_threshold = DBConfig::ParseMemoryLimit(input.ToString());
	if (db) {
		TaskScheduler::GetScheduler(*db).SetAllocatorFlushTreshold(config.options.allocator_flush_threshold);
	}
}

void AllocatorFlushThresholdSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.options.allocator_flush_threshold = DBConfigOptions().allocator_flush_threshold;
	if (db) {
		TaskScheduler::GetScheduler(*db).SetAllocatorFlushTreshold(config.options.allocator_flush_threshold);
	}
}

Value AllocatorFlushThresholdSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return Value(StringUtil::BytesToHumanReadableString(config.options.allocator_flush_threshold));
}

//===----------------------------------------------------------------------===//
// Allow Community Extensions
//===----------------------------------------------------------------------===//
void AllowCommunityExtensionsSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (info.db && input.GetValue<bool>()) {
		throw InvalidInputException("Cannot change allow_community_extensions setting while database is running");
	}
}

//===----------------------------------------------------------------------===//
// Allow Persistent Secrets
//===----------------------------------------------------------------------===//
void AllowPersistentSecretsSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	auto value = input.DefaultCastAs(LogicalType::BOOLEAN);
	config.secret_manager->SetEnablePersistentSecrets(value.GetValue<bool>());
}

void AllowPersistentSecretsSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.secret_manager->ResetEnablePersistentSecrets();
}

Value AllowPersistentSecretsSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return Value::BOOLEAN(config.secret_manager->PersistentSecretsEnabled());
}

//===----------------------------------------------------------------------===//
// Allow Unredacted Secrets
//===----------------------------------------------------------------------===//
void AllowUnredactedSecretsSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if ((info.db || info.context) && input.GetValue<bool>()) {
		throw InvalidInputException("Cannot change allow_unredacted_secrets setting while database is running");
	}
}

//===----------------------------------------------------------------------===//
// Disable Database Invalidation
//===----------------------------------------------------------------------===//
void DisableDatabaseInvalidationSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (info.db || info.context) {
		throw InvalidInputException("Cannot change disable_database_invalidation setting while database is running");
	}
}

//===----------------------------------------------------------------------===//
// Allow Unsigned Extensions
//===----------------------------------------------------------------------===//
void AllowUnsignedExtensionsSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (info.db && input.GetValue<bool>()) {
		throw InvalidInputException("Cannot change allow_unsigned_extensions setting while database is running");
	}
}

//===----------------------------------------------------------------------===//
// Allowed Configs
//===----------------------------------------------------------------------===//
void AllowedConfigsSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	config.options.allowed_configs.clear();
	auto &list = ListValue::GetChildren(input);
	for (auto &val : list) {
		config.AddAllowedConfig(val.GetValue<string>());
	}
}

void AllowedConfigsSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.options.allowed_configs = DBConfigOptions().allowed_configs;
}

Value AllowedConfigsSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	vector<Value> configs;
	for (auto &cfg : config.options.allowed_configs) {
		configs.emplace_back(cfg);
	}
	return Value::LIST(LogicalType::VARCHAR, std::move(configs));
}

//===----------------------------------------------------------------------===//
// Allowed Directories
//===----------------------------------------------------------------------===//
void AllowedDirectoriesSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	if (!Settings::Get<EnableExternalAccessSetting>(config)) {
		throw InvalidInputException("Cannot change allowed_directories when enable_external_access is disabled");
	}
	if (!config.file_system) {
		throw InvalidInputException("Cannot change/set allowed_directories before the database is started");
	}
	config.options.allowed_directories.clear();
	auto &list = ListValue::GetChildren(input);
	for (auto &val : list) {
		config.AddAllowedDirectory(val.GetValue<string>());
	}
}

void AllowedDirectoriesSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	if (!Settings::Get<EnableExternalAccessSetting>(config)) {
		throw InvalidInputException("Cannot change allowed_directories when enable_external_access is disabled");
	}
	config.options.allowed_directories = DBConfigOptions().allowed_directories;
}

Value AllowedDirectoriesSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	vector<Value> allowed_directories;
	for (auto &dir : config.options.allowed_directories) {
		allowed_directories.emplace_back(dir);
	}
	return Value::LIST(LogicalType::VARCHAR, std::move(allowed_directories));
}

//===----------------------------------------------------------------------===//
// Allowed Paths
//===----------------------------------------------------------------------===//void
void AllowedPathsSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	if (!Settings::Get<EnableExternalAccessSetting>(config)) {
		throw InvalidInputException("Cannot change allowed_paths when enable_external_access is disabled");
	}
	if (!config.file_system) {
		throw InvalidInputException("Cannot change/set allowed_paths before the database is started");
	}

	config.options.allowed_paths.clear();
	auto &list = ListValue::GetChildren(input);
	for (auto &val : list) {
		config.AddAllowedPath(val.GetValue<string>());
	}
}

void AllowedPathsSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	if (!Settings::Get<EnableExternalAccessSetting>(config)) {
		throw InvalidInputException("Cannot change allowed_paths when enable_external_access is disabled");
	}
	config.options.allowed_paths = DBConfigOptions().allowed_paths;
}

Value AllowedPathsSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	vector<Value> allowed_paths;
	for (auto &dir : config.options.allowed_paths) {
		allowed_paths.emplace_back(dir);
	}
	return Value::LIST(LogicalType::VARCHAR, std::move(allowed_paths));
}

//===----------------------------------------------------------------------===//
// Block Allocator Memory
//===----------------------------------------------------------------------===//
void BlockAllocatorMemorySetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	const auto input_string = input.ToString();
	idx_t size;
	if (!input_string.empty() && input_string.back() == '%') {
		double percentage;
		if (!TryDoubleCast(input_string.c_str(), input_string.size() - 1, percentage, false) || percentage < 0 ||
		    percentage > 100) {
			throw InvalidInputException("Unable to parse valid percentage (input: %s)", input_string);
		}
		size = LossyNumericCast<idx_t>(percentage) * config.options.maximum_memory / 100;
	} else {
		size = DBConfig::ParseMemoryLimit(input_string);
	}
	if (db) {
		BlockAllocator::Get(*db).Resize(size);
	}
	config.options.block_allocator_size = size;
}

void BlockAllocatorMemorySetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	const auto size = DBConfigOptions().block_allocator_size;
	if (db) {
		BlockAllocator::Get(*db).Resize(size);
	}
	config.options.block_allocator_size = size;
}

Value BlockAllocatorMemorySetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return StringUtil::BytesToHumanReadableString(config.options.block_allocator_size);
}

//===----------------------------------------------------------------------===//
// Checkpoint Threshold
//===----------------------------------------------------------------------===//
void CheckpointThresholdSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	idx_t new_limit = DBConfig::ParseMemoryLimit(input.ToString());
	config.options.checkpoint_wal_size = new_limit;
}

Value CheckpointThresholdSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return Value(StringUtil::BytesToHumanReadableString(config.options.checkpoint_wal_size));
}

//===----------------------------------------------------------------------===//
// Custom Profiling Settings
//===----------------------------------------------------------------------===//
bool IsEnabledOptimizer(MetricType metric, const set<OptimizerType> &disabled_optimizers) {
	auto matching_optimizer_type = MetricsUtils::GetOptimizerTypeByMetric(metric);
	return matching_optimizer_type != OptimizerType::INVALID &&
	       disabled_optimizers.find(matching_optimizer_type) == disabled_optimizers.end();
}

template <typename ExtractFromType>
static profiler_settings_t ExtractSettings(ExtractFromType extract_from, const set<OptimizerType> &disabled_optimizers,
                                           vector<std::string> &invalid_settings) {
	profiler_settings_t enabled_metrics;

	auto insert_if_enabled = [&](MetricType m) {
		if (!MetricsUtils::IsOptimizerMetric(m) || IsEnabledOptimizer(m, disabled_optimizers)) {
			enabled_metrics.insert(m);
		}
	};

	extract_from([&](const std::string &metric) {
		const auto upper = StringUtil::Upper(metric);
		try {
			insert_if_enabled(EnumUtil::FromString<MetricType>(upper));
		} catch (std::exception &) {
			try {
				auto group = EnumUtil::FromString<MetricGroup>(upper);
				for (auto &converted_metric : MetricsUtils::GetMetricsByGroupType(group)) {
					insert_if_enabled(converted_metric);
				}
			} catch (std::exception &) {
				invalid_settings.push_back(metric);
			}
		}
	});
	return enabled_metrics;
}

void AddOptimizerMetrics(profiler_settings_t &settings, const set<OptimizerType> &disabled_optimizers) {
	if (settings.find(MetricType::ALL_OPTIMIZERS) != settings.end()) {
		auto optimizer_metrics = MetricsUtils::GetOptimizerMetrics();
		for (auto &metric : optimizer_metrics) {
			if (IsEnabledOptimizer(metric, disabled_optimizers)) {
				settings.insert(metric);
			}
		}
	}
}

void ExtractFromList(ClientConfig &config, profiler_settings_t &enabled_metrics, vector<string> &invalid_settings,
                     const Value &input, const set<OptimizerType> &disabled_optimizers) {
	config.profiler_settings_type = LogicalTypeId::LIST;

	enabled_metrics = ExtractSettings(
	    [&](const std::function<void(const std::string &)> &func) {
		    for (auto &val : ListValue::GetChildren(input)) {
			    func(val.GetValue<string>());
		    }
	    },
	    disabled_optimizers, invalid_settings);
}

void ExtractFromStruct(ClientConfig &config, profiler_settings_t &enabled_metrics, vector<string> &invalid_settings,
                       const Value &input, const set<OptimizerType> &disabled_optimizers) {
	config.profiler_settings_type = LogicalTypeId::STRUCT;

	enabled_metrics = ExtractSettings(
	    [&](const std::function<void(const std::string &)> &func) {
		    auto &children = StructValue::GetChildren(input);
		    for (idx_t i = 0; i < children.size(); i++) {
			    auto child_val = children[i];
			    if ((child_val.type() == LogicalType::BOOLEAN && child_val.GetValue<bool>() == true) ||
			        StringUtil::Lower(child_val.ToString()) == "true") {
				    func(StructType::GetChildName(input.type(), i));
			    }
		    }
	    },
	    disabled_optimizers, invalid_settings);
}

void ExtractFromJSON(ClientConfig &config, profiler_settings_t &enabled_metrics, vector<string> &invalid_settings,
                     const Value &input, const set<OptimizerType> &disabled_optimizers) {
	config.profiler_settings_type = LogicalTypeId::VARCHAR;

	// JSON string: parse, then accept entries with value == "true"
	std::unordered_map<std::string, std::string> json;
	try {
		json = StringUtil::ParseJSONMap(input.ToString())->Flatten();
	} catch (std::exception &ex) {
		throw IOException("Could not parse the custom profiler settings file due to incorrect JSON: \"%s\".  Make "
		                  "sure all the keys and values start with a quote. (error: %s)",
		                  input.ToString(), ex.what());
	}

	enabled_metrics = ExtractSettings(
	    [&](const std::function<void(const std::string &)> &func) {
		    for (auto &entry : json) {
			    if (StringUtil::Lower(entry.second) == "true") {
				    func(entry.first);
			    }
		    }
	    },
	    disabled_optimizers, invalid_settings);
}

void ConstructInvalidSettingsAndThrow(const vector<string> &invalid_settings) {
	string invalid_settings_str;
	for (auto &invalid_setting : invalid_settings) {
		if (!invalid_settings_str.empty()) {
			invalid_settings_str += ", ";
		}
		invalid_settings_str += invalid_setting;
	}
	throw IOException("Invalid custom profiler settings: \"%s\"", invalid_settings_str);
}

void CustomProfilingSettingsSetting::SetLocal(ClientContext &context, const Value &input) {
	auto &config = ClientConfig::GetConfig(context);

	auto &db_config = DBConfig::GetConfig(context);
	auto &disabled_optimizers = db_config.options.disabled_optimizers;

	vector<string> invalid_settings;
	profiler_settings_t enabled_metrics;
	if (input.type() == LogicalType::LIST(LogicalType::VARCHAR)) {
		ExtractFromList(config, enabled_metrics, invalid_settings, input, disabled_optimizers);
	} else if (input.type().id() == LogicalTypeId::STRUCT) {
		ExtractFromStruct(config, enabled_metrics, invalid_settings, input, disabled_optimizers);
	} else if (input.type() == LogicalType::VARCHAR) {
		ExtractFromJSON(config, enabled_metrics, invalid_settings, input, disabled_optimizers);
	} else {
		throw ParserException("Invalid custom profiler settings type \"%s\", expected LIST(VARCHAR) or JSON",
		                      input.type().ToString());
	}

	if (!invalid_settings.empty()) {
		ConstructInvalidSettingsAndThrow(invalid_settings);
	}

	AddOptimizerMetrics(enabled_metrics, disabled_optimizers);
	config.enable_profiler = true;
	config.profiler_settings = enabled_metrics;
}

void CustomProfilingSettingsSetting::ResetLocal(ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	config.enable_profiler = ClientConfig().enable_profiler;
	config.profiler_settings = MetricsUtils::GetDefaultMetrics();
	config.profiler_settings_type = LogicalTypeId::VARCHAR;
}

Value CustomProfilingSettingsSetting::GetSetting(const ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);

	set<string> enabled_settings;
	for (auto &entry : config.profiler_settings) {
		enabled_settings.insert(EnumUtil::ToString(entry));
	}

	switch (config.profiler_settings_type) {
	case LogicalTypeId::VARCHAR: {
		// i.e. JSON
		string profiling_settings_str;
		for (auto &entry : enabled_settings) {
			if (!profiling_settings_str.empty()) {
				profiling_settings_str += ", ";
			}
			profiling_settings_str += "\"" + entry + "\": \"true\"";
		}

		return Value(StringUtil::Format("{%s}", profiling_settings_str));
	}
	case LogicalTypeId::STRUCT: {
		child_list_t<Value> children;
		for (auto &entry : enabled_settings) {
			children.emplace_back(entry, Value::BOOLEAN(true));
		}
		return Value::STRUCT(std::move(children));
	}
	case LogicalTypeId::LIST: {
		vector<Value> children;
		for (auto &entry : enabled_settings) {
			children.emplace_back(entry);
		}
		return Value::LIST(std::move(children));
	}
	default:
		throw InternalException("Invalid custom profiler settings type");
	}
}

//===----------------------------------------------------------------------===//
// Custom User Agent
//===----------------------------------------------------------------------===//
void CustomUserAgentSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	auto new_value = input.GetValue<string>();
	if (db) {
		throw InvalidInputException("Cannot change custom_user_agent setting while database is running");
	}
	config.options.custom_user_agent =
	    config.options.custom_user_agent.empty() ? new_value : config.options.custom_user_agent + " " + new_value;
}

void CustomUserAgentSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	if (db) {
		throw InvalidInputException("Cannot change custom_user_agent setting while database is running");
	}
	config.options.custom_user_agent = DBConfigOptions().custom_user_agent;
}

//===----------------------------------------------------------------------===//
// Default Block Size
//===----------------------------------------------------------------------===//
void DefaultBlockSizeSetting::OnSet(SettingCallbackInfo &, Value &input) {
	auto block_alloc_size = input.GetValue<uint64_t>();
	Storage::VerifyBlockAllocSize(block_alloc_size);
}

//===----------------------------------------------------------------------===//
// Default Collation
//===----------------------------------------------------------------------===//
void DefaultCollationSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (info.context) {
		ExpressionBinder::TestCollation(*info.context, input.ToString());
	}
}

//===----------------------------------------------------------------------===//
// Default Null Order
//===----------------------------------------------------------------------===//
void DefaultNullOrderSetting::OnSet(SettingCallbackInfo &, Value &input) {
	auto parameter = StringUtil::Lower(input.ToString());

	if (parameter == "nulls_first" || parameter == "nulls first" || parameter == "null first" || parameter == "first") {
		input = Value("NULLS_FIRST");
	} else if (parameter == "nulls_last" || parameter == "nulls last" || parameter == "null last" ||
	           parameter == "last") {
		input = Value("NULLS_LAST");
	} else if (parameter == "nulls_first_on_asc_last_on_desc" || parameter == "sqlite" || parameter == "mysql") {
		input = Value("NULLS_FIRST_ON_ASC_LAST_ON_DESC");
	} else if (parameter == "nulls_last_on_asc_first_on_desc" || parameter == "postgres") {
		input = Value("NULLS_LAST_ON_ASC_FIRST_ON_DESC");
	} else {
		throw ParserException("Unrecognized parameter for option NULL_ORDER \"%s\", expected either NULLS FIRST, NULLS "
		                      "LAST, SQLite, MySQL or Postgres",
		                      parameter);
	}
}

//===----------------------------------------------------------------------===//
// Default Order
//===----------------------------------------------------------------------===//
void DefaultOrderSetting::OnSet(SettingCallbackInfo &, Value &input) {
	auto parameter = StringUtil::Lower(input.ToString());
	if (parameter == "ascending" || parameter == "asc") {
		input = Value("ASC");
	} else if (parameter == "descending" || parameter == "desc") {
		input = Value("DESC");
	} else {
		throw InvalidInputException("Unrecognized parameter for option DEFAULT_ORDER \"%s\". Expected ASC or DESC.",
		                            parameter);
	}
}

//===----------------------------------------------------------------------===//
// Default Secret Storage
//===----------------------------------------------------------------------===//
void DefaultSecretStorageSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	config.secret_manager->SetDefaultStorage(input.ToString());
}

void DefaultSecretStorageSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.secret_manager->ResetDefaultStorage();
}

Value DefaultSecretStorageSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return config.secret_manager->DefaultStorage();
}

//===----------------------------------------------------------------------===//
// Disabled Compression Methods
//===----------------------------------------------------------------------===//
void DisabledCompressionMethodsSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	auto list = StringUtil::Split(input.ToString(), ",");
	vector<CompressionType> disabled_compression_methods;
	for (auto &entry : list) {
		auto param = StringUtil::Lower(entry);
		StringUtil::Trim(param);
		if (param.empty()) {
			continue;
		}
		if (param == "none") {
			disabled_compression_methods.clear();
			break;
		}
		auto compression_type = EnumUtil::FromString<CompressionType>(param);
		switch (compression_type) {
		case CompressionType::COMPRESSION_AUTO:
		case CompressionType::COMPRESSION_CONSTANT:
		case CompressionType::COMPRESSION_EMPTY:
		case CompressionType::COMPRESSION_UNCOMPRESSED:
			throw InvalidInputException("Compression method %s cannot be disabled", param);
		default:
			break;
		}
		disabled_compression_methods.push_back(compression_type);
	}
	config.SetDisabledCompressionMethods(disabled_compression_methods);
}

void DisabledCompressionMethodsSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	vector<CompressionType> disabled_compression_methods;
	config.SetDisabledCompressionMethods(disabled_compression_methods);
}

Value DisabledCompressionMethodsSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	string result;
	for (auto &optimizer : config.GetDisabledCompressionMethods()) {
		if (!result.empty()) {
			result += ",";
		}
		result += CompressionTypeToString(optimizer);
	}
	return Value(result);
}

//===----------------------------------------------------------------------===//
// Disabled Filesystems
//===----------------------------------------------------------------------===//
void DisabledFilesystemsSetting::SetGlobal(DatabaseInstance *db_p, DBConfig &config, const Value &input) {
	auto &db = GetDB<DisabledFilesystemsSetting>(db_p);
	auto &fs = FileSystem::GetFileSystem(db);
	auto list = StringUtil::Split(input.ToString(), ",");
	fs.SetDisabledFileSystems(list);
}

void DisabledFilesystemsSetting::ResetGlobal(DatabaseInstance *db_p, DBConfig &config) {
	auto &db = GetDB<DisabledFilesystemsSetting>(db_p);
	auto &fs = FileSystem::GetFileSystem(db);
	fs.SetDisabledFileSystems(vector<string>());
}

Value DisabledFilesystemsSetting::GetSetting(const ClientContext &context) {
	return Value("");
}

//===----------------------------------------------------------------------===//
// Disabled Optimizers
//===----------------------------------------------------------------------===//
void DisabledOptimizersSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	auto list = StringUtil::Split(input.ToString(), ",");
	set<OptimizerType> disabled_optimizers;
	for (auto &entry : list) {
		auto param = StringUtil::Lower(entry);
		StringUtil::Trim(param);
		if (param.empty()) {
			continue;
		}
		disabled_optimizers.insert(OptimizerTypeFromString(param));
	}
	config.options.disabled_optimizers = std::move(disabled_optimizers);
}

void DisabledOptimizersSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.options.disabled_optimizers = DBConfigOptions().disabled_optimizers;
}

Value DisabledOptimizersSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	string result;
	for (auto &optimizer : config.options.disabled_optimizers) {
		if (!result.empty()) {
			result += ",";
		}
		result += OptimizerTypeToString(optimizer);
	}
	return Value(result);
}

//===----------------------------------------------------------------------===//
// Duckdb Api
//===----------------------------------------------------------------------===//
void DuckDBAPISetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (info.db) {
		throw InvalidInputException("Cannot change duckdb_api setting while database is running");
	}
}

//===----------------------------------------------------------------------===//
// Vacuum Rebuild Indexes
//===----------------------------------------------------------------------===//
void VacuumRebuildIndexesSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (info.db || info.context) {
		throw InvalidInputException("Cannot change vacuum_rebuild_indexes setting while database is running");
	}
}

//===----------------------------------------------------------------------===//
// Enable External Access
//===----------------------------------------------------------------------===//
void EnableExternalAccessSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (!info.db) {
		return;
	}
	if (input.GetValue<bool>()) {
		throw InvalidInputException("Cannot enable external access while database is running");
	}
	auto &config = info.config;
	if (info.db && Settings::Get<EnableExternalAccessSetting>(*info.db)) {
		// we are turning off external access - add any already attached databases to the list of accepted paths
		auto &db_manager = DatabaseManager::Get(*info.db);
		auto attached_paths = db_manager.GetAttachedDatabasePaths();
		for (auto &path : attached_paths) {
			config.AddAllowedPath(path);
			config.AddAllowedPath(path + ".wal");
			config.AddAllowedPath(path + ".wal.checkpoint");
			config.AddAllowedPath(path + ".wal.recovery");
		}
	}
	if (config.options.use_temporary_directory && !config.options.temporary_directory.empty()) {
		// if temp directory is enabled we can also write there
		config.AddAllowedDirectory(config.options.temporary_directory);
	}
}

//===----------------------------------------------------------------------===//
// Enable External File Cache
//===----------------------------------------------------------------------===//
void EnableExternalFileCacheSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (info.db) {
		ExternalFileCache::Get(*info.db).SetEnabled(input.GetValue<bool>());
	}
}

//===----------------------------------------------------------------------===//
// Enable Logging
//===----------------------------------------------------------------------===//
Value EnableLogging::GetSetting(const ClientContext &context) {
	return context.db->GetLogManager().GetConfig().enabled;
}
void EnableLogging::SetGlobal(DatabaseInstance *db_p, DBConfig &config, const Value &parameter) {
	auto &db = GetDB<EnableLogging>(db_p);
	db.GetLogManager().SetEnableLogging(parameter.GetValue<bool>());
}

void EnableLogging::ResetGlobal(DatabaseInstance *db_p, DBConfig &config) {
	auto &db = GetDB<EnableLogging>(db_p);
	db.GetLogManager().SetEnableLogging(false);
}

//===----------------------------------------------------------------------===//
// Force VARIANT Shredding
//===----------------------------------------------------------------------===//

void ForceVariantShredding::SetGlobal(DatabaseInstance *_, DBConfig &config, const Value &value) {
	auto &force_variant_shredding = config.options.force_variant_shredding;

	if (value.type().id() != LogicalTypeId::VARCHAR) {
		throw InvalidInputException("The argument to 'force_variant_shredding' should be of type VARCHAR, not %s",
		                            value.type().ToString());
	}

	auto logical_type = UnboundType::TryParseAndDefaultBind(value.GetValue<string>());
	if (logical_type.id() == LogicalTypeId::INVALID) {
		throw InvalidInputException("Could not parse the argument '%s' to 'force_variant_shredding' as a built in type",
		                            value.GetValue<string>());
	}

	TypeVisitor::Contains(logical_type, [](const LogicalType &type) {
		if (type.IsNested()) {
			if (type.id() != LogicalTypeId::STRUCT && type.id() != LogicalTypeId::LIST) {
				throw InvalidInputException("Shredding can consist of the nested types LIST (for ARRAY Variant values) "
				                            "or STRUCT (for OBJECT Variant values), not %s",
				                            type.ToString());
			}
			if (type.id() == LogicalTypeId::STRUCT && StructType::IsUnnamed(type)) {
				throw InvalidInputException("STRUCT types in the shredding can not be empty");
			}
			return false;
		}
		switch (type.id()) {
		case LogicalTypeId::BOOLEAN:
		case LogicalTypeId::TINYINT:
		case LogicalTypeId::SMALLINT:
		case LogicalTypeId::INTEGER:
		case LogicalTypeId::BIGINT:
		case LogicalTypeId::HUGEINT:
		case LogicalTypeId::UTINYINT:
		case LogicalTypeId::USMALLINT:
		case LogicalTypeId::UINTEGER:
		case LogicalTypeId::UBIGINT:
		case LogicalTypeId::UHUGEINT:
		case LogicalTypeId::FLOAT:
		case LogicalTypeId::DOUBLE:
		case LogicalTypeId::DECIMAL:
		case LogicalTypeId::DATE:
		case LogicalTypeId::TIME:
		case LogicalTypeId::TIME_TZ:
		case LogicalTypeId::TIMESTAMP_TZ:
		case LogicalTypeId::TIMESTAMP:
		case LogicalTypeId::TIMESTAMP_SEC:
		case LogicalTypeId::TIMESTAMP_MS:
		case LogicalTypeId::TIMESTAMP_NS:
		case LogicalTypeId::BLOB:
		case LogicalTypeId::VARCHAR:
		case LogicalTypeId::UUID:
		case LogicalTypeId::BIGNUM:
		case LogicalTypeId::TIME_NS:
		case LogicalTypeId::INTERVAL:
		case LogicalTypeId::BIT:
		case LogicalTypeId::GEOMETRY:
			break;
		default:
			throw InvalidInputException("Variants can not be shredded on type: %s", type.ToString());
		}
		return false;
	});

	auto shredding_type = TypeVisitor::VisitReplace(logical_type, [](const LogicalType &type) {
		return LogicalType::STRUCT({{"typed_value", type}, {"untyped_value_index", LogicalType::UINTEGER}});
	});
	force_variant_shredding =
	    LogicalType::STRUCT({{"unshredded", VariantShredding::GetUnshreddedType()}, {"shredded", shredding_type}});
}

void ForceVariantShredding::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.options.force_variant_shredding = LogicalType::INVALID;
}

Value ForceVariantShredding::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return Value(config.options.force_variant_shredding.ToString());
}

//===----------------------------------------------------------------------===//
// Extension Directory
//===----------------------------------------------------------------------===//
void ExtensionDirectoriesSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	config.options.extension_directories.clear();

	auto &list = ListValue::GetChildren(input);
	for (auto &val : list) {
		config.options.extension_directories.emplace_back(val.GetValue<string>());
	}
}

void ExtensionDirectoriesSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.options.extension_directories = DBConfigOptions().extension_directories;
}

Value ExtensionDirectoriesSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	vector<Value> extension_directories;
	for (auto &dir : config.options.extension_directories) {
		extension_directories.emplace_back(dir);
	}
	return Value::LIST(LogicalType::VARCHAR, std::move(extension_directories));
}

//===----------------------------------------------------------------------===//
// Logging Mode
//===----------------------------------------------------------------------===//
Value LoggingMode::GetSetting(const ClientContext &context) {
	return EnumUtil::ToString(context.db->GetLogManager().GetConfig().mode);
}
void LoggingMode::SetGlobal(DatabaseInstance *db_p, DBConfig &config, const Value &parameter) {
	auto &db = GetDB<LoggingMode>(db_p);
	db.GetLogManager().SetLogMode(EnumUtil::FromString<LogMode>(parameter.GetValue<string>()));
}

void LoggingMode::ResetGlobal(DatabaseInstance *db_p, DBConfig &config) {
	auto &db = GetDB<LoggingMode>(db_p);
	db.GetLogManager().SetLogMode(LogMode::LEVEL_ONLY);
}

//===----------------------------------------------------------------------===//
// Logging Level
//===----------------------------------------------------------------------===//
Value LoggingLevel::GetSetting(const ClientContext &context) {
	return EnumUtil::ToString(context.db->GetLogManager().GetConfig().level);
}
void LoggingLevel::SetGlobal(DatabaseInstance *db_p, DBConfig &config, const Value &parameter) {
	auto &db = GetDB<LoggingLevel>(db_p);
	db.GetLogManager().SetLogLevel(EnumUtil::FromString<LogLevel>(parameter.GetValue<string>()));
}

void LoggingLevel::ResetGlobal(DatabaseInstance *db_p, DBConfig &config) {
	auto &db = GetDB<LoggingLevel>(db_p);
	db.GetLogManager().SetLogLevel(LogConfig::DEFAULT_LOG_LEVEL);
}

//===----------------------------------------------------------------------===//
// Logging Storage
//===----------------------------------------------------------------------===//
Value LoggingStorage::GetSetting(const ClientContext &context) {
	return context.db->GetLogManager().GetConfig().storage;
}
void LoggingStorage::SetGlobal(DatabaseInstance *db_p, DBConfig &config, const Value &parameter) {
	auto &db = GetDB<LoggingStorage>(db_p);
	db.GetLogManager().SetLogStorage(db, parameter.GetValue<string>());
}

void LoggingStorage::ResetGlobal(DatabaseInstance *db_p, DBConfig &config) {
	auto &db = GetDB<LoggingStorage>(db_p);
	db.GetLogManager().SetLogStorage(db, LogConfig::DEFAULT_LOG_STORAGE);
}

//===----------------------------------------------------------------------===//
// Enabled Loggers
//===----------------------------------------------------------------------===//
Value EnabledLogTypes::GetSetting(const ClientContext &context) {
	vector<string> loggers;
	for (const auto &item : context.db->GetLogManager().GetConfig().enabled_log_types) {
		loggers.push_back(item);
	}
	return StringUtil::Join(loggers, ",");
}
void EnabledLogTypes::SetGlobal(DatabaseInstance *db_p, DBConfig &config, const Value &parameter) {
	auto &db = GetDB<EnabledLogTypes>(db_p);

	auto values = StringUtil::Split(parameter.GetValue<string>(), ",");
	unordered_set<string> set;
	for (const auto &value : values) {
		set.insert(value);
	}
	db.GetLogManager().SetEnabledLogTypes(set);
}

void EnabledLogTypes::ResetGlobal(DatabaseInstance *db_p, DBConfig &config) {
	auto &db = GetDB<EnabledLogTypes>(db_p);
	unordered_set<string> set;
	db.GetLogManager().SetEnabledLogTypes(set);
}

//===----------------------------------------------------------------------===//
// Disabled Loggers
//===----------------------------------------------------------------------===//
Value DisabledLogTypes::GetSetting(const ClientContext &context) {
	vector<string> loggers;
	for (const auto &item : context.db->GetLogManager().GetConfig().disabled_log_types) {
		loggers.push_back(item);
	}
	return StringUtil::Join(loggers, ",");
}
void DisabledLogTypes::SetGlobal(DatabaseInstance *db_p, DBConfig &config, const Value &parameter) {
	auto &db = GetDB<DisabledLogTypes>(db_p);
	auto values = StringUtil::Split(parameter.GetValue<string>(), ",");
	unordered_set<string> set;
	for (const auto &value : values) {
		set.insert(value);
	}
	db.GetLogManager().SetDisabledLogTypes(set);
}

void DisabledLogTypes::ResetGlobal(DatabaseInstance *db_p, DBConfig &config) {
	auto &db = GetDB<DisabledLogTypes>(db_p);
	unordered_set<string> set;
	db.GetLogManager().SetDisabledLogTypes(set);
}

//===----------------------------------------------------------------------===//
// Enable Profiling
//===----------------------------------------------------------------------===//
void EnableProfilingSetting::SetLocal(ClientContext &context, const Value &input) {
	auto parameter = StringUtil::Lower(input.ToString());

	auto &config = ClientConfig::GetConfig(context);
	config.enable_profiler = true;
	config.emit_profiler_output = true;

	if (parameter != "no_output" && !config.profiler_save_location.empty()) {
		auto &file_system = FileSystem::GetFileSystem(context);
		const auto file_type = file_system.ExtractExtension(config.profiler_save_location);
		if (file_type != parameter && file_type != "txt") {
			throw ParserException(
			    "Profiler file type (%s) must either have the same file extension as the profiling output type (%s), "
			    "or be a '.txt' file. Set 'profiling_output' to a '%s' file or run \"RESET profiling_output\" first.",
			    config.profiler_save_location, parameter, parameter);
		}
	}

	if (parameter == "json") {
		config.profiler_print_format = ProfilerPrintFormat::JSON;
	} else if (parameter == "query_tree") {
		config.profiler_print_format = ProfilerPrintFormat::QUERY_TREE;
	} else if (parameter == "query_tree_optimizer") {
		config.profiler_print_format = ProfilerPrintFormat::QUERY_TREE_OPTIMIZER;

		// add optimizer settings to the profiler settings
		auto optimizer_settings = MetricsUtils::GetOptimizerMetrics();
		for (auto &setting : optimizer_settings) {
			config.profiler_settings.insert(setting);
		}

		// add the phase timing settings to the profiler settings
		auto phase_timing_settings = MetricsUtils::GetPhaseTimingMetrics();
		for (auto &setting : phase_timing_settings) {
			config.profiler_settings.insert(setting);
		}
	} else if (parameter == "no_output") {
		config.profiler_print_format = ProfilerPrintFormat::NO_OUTPUT;
		config.emit_profiler_output = false;
	} else if (parameter == "html") {
		config.profiler_print_format = ProfilerPrintFormat::HTML;
	} else if (parameter == "graphviz") {
		config.profiler_print_format = ProfilerPrintFormat::GRAPHVIZ;
	} else {
		throw ParserException("Unrecognized print format %s, supported formats: [json, query_tree, "
		                      "query_tree_optimizer, no_output, html, graphviz]",
		                      parameter);
	}
}

void EnableProfilingSetting::ResetLocal(ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	config.profiler_print_format = ClientConfig().profiler_print_format;
	config.enable_profiler = ClientConfig().enable_profiler;
	config.emit_profiler_output = ClientConfig().emit_profiler_output;
	config.profiler_settings = ClientConfig().profiler_settings;
}

Value EnableProfilingSetting::GetSetting(const ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	if (!config.enable_profiler) {
		return Value();
	}
	switch (config.profiler_print_format) {
	case ProfilerPrintFormat::JSON:
		return Value("json");
	case ProfilerPrintFormat::QUERY_TREE:
		return Value("query_tree");
	case ProfilerPrintFormat::QUERY_TREE_OPTIMIZER:
		return Value("query_tree_optimizer");
	case ProfilerPrintFormat::NO_OUTPUT:
		return Value("no_output");
	case ProfilerPrintFormat::HTML:
		return Value("html");
	case ProfilerPrintFormat::GRAPHVIZ:
		return Value("graphviz");
	default:
		throw InternalException("Unsupported profiler print format");
	}
}

//===----------------------------------------------------------------------===//
// Enable Progress Bar Print
//===----------------------------------------------------------------------===//
void EnableProgressBarPrintSetting::SetLocal(ClientContext &context, const Value &input) {
	auto &config = ClientConfig::GetConfig(context);
	ProgressBar::SystemOverrideCheck(config);
	config.print_progress_bar = input.GetValue<bool>();
}

void EnableProgressBarPrintSetting::ResetLocal(ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	ProgressBar::SystemOverrideCheck(config);
	config.print_progress_bar = ClientConfig().print_progress_bar;
}

Value EnableProgressBarPrintSetting::GetSetting(const ClientContext &context) {
	return Value::BOOLEAN(ClientConfig::GetConfig(context).print_progress_bar);
}

//===----------------------------------------------------------------------===//
// Enable Progress Bar
//===----------------------------------------------------------------------===//
bool EnableProgressBarSetting::OnLocalSet(ClientContext &context, const Value &input) {
	auto &config = ClientConfig::GetConfig(context);
	ProgressBar::SystemOverrideCheck(config);
	return true;
}

bool EnableProgressBarSetting::OnLocalReset(ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	ProgressBar::SystemOverrideCheck(config);
	return true;
}

//===----------------------------------------------------------------------===//
// External Threads
//===----------------------------------------------------------------------===//
void ExternalThreadsSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	auto new_external_threads = input.GetValue<uint64_t>();
	if (info.db) {
		TaskScheduler::GetScheduler(*info.db).SetThreads(info.config.options.maximum_threads, new_external_threads);
	}
}

//===----------------------------------------------------------------------===//
// Force Compression
//===----------------------------------------------------------------------===//
void ForceCompressionSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	auto compression = StringUtil::Lower(input.ToString());
	if (compression == "none" || compression == "auto") {
		input = "auto";
	} else {
		auto compression_type = EnumUtil::FromString<CompressionType>(compression);
		if (compression_type == CompressionType::COMPRESSION_CONSTANT ||
		    compression_type == CompressionType::COMPRESSION_EMPTY) {
			throw ParserException("auto / constant cannot be used for force_compression");
		}
		auto compression_availability_result = CompressionTypeIsAvailable(compression_type);
		if (!compression_availability_result.IsAvailable()) {
			if (compression_availability_result.IsDeprecated()) {
				throw ParserException("Attempted to force a deprecated compression type (%s)",
				                      CompressionTypeToString(compression_type));
			} else {
				throw ParserException("Attempted to force a compression type that isn't available yet (%s)",
				                      CompressionTypeToString(compression_type));
			}
		}
	}
}

//===----------------------------------------------------------------------===//
// Home Directory
//===----------------------------------------------------------------------===//
void HomeDirectorySetting::OnSet(SettingCallbackInfo &info, Value &input) {
	optional_ptr<FileSystem> fs;
	if (info.context) {
		fs = FileSystem::GetFileSystem(*info.context);
	} else if (info.db) {
		fs = FileSystem::GetFileSystem(*info.db);
	} else {
		fs = info.config.file_system.get();
	}
	if (fs && !input.IsNull()) {
		auto new_home_directory = input.ToString();
		if (fs->IsRemoteFile(new_home_directory)) {
			throw InvalidInputException("Cannot set the home directory to a remote path");
		}
	}
}

//===----------------------------------------------------------------------===//
// Enable H T T P Logging
//===----------------------------------------------------------------------===//
void EnableHTTPLoggingSetting::SetLocal(ClientContext &context, const Value &input) {
	auto &config = ClientConfig::GetConfig(context);
	config.enable_http_logging = input.GetValue<bool>();

	// NOTE: this is a deprecated setting: we mimick the old behaviour by setting the log storage output to STDOUT and
	// enabling logging for http only. Note that this behaviour is slightly wonky in that it sets all sorts of logging
	// config
	auto &log_manager = LogManager::Get(context);
	if (config.enable_http_logging) {
		log_manager.SetEnableLogging(true);
		log_manager.SetLogLevel(HTTPLogType::LEVEL);
		unordered_set<string> enabled_log_types = {HTTPLogType::NAME};
		log_manager.SetEnabledLogTypes(enabled_log_types);
		log_manager.SetLogStorage(*context.db, LogConfig::STDOUT_STORAGE_NAME);
	} else {
		log_manager.SetEnableLogging(false);
	}
}

void EnableHTTPLoggingSetting::ResetLocal(ClientContext &context) {
	ClientConfig::GetConfig(context).enable_http_logging = ClientConfig().enable_http_logging;
}

Value EnableHTTPLoggingSetting::GetSetting(const ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	return Value::BOOLEAN(config.enable_http_logging);
}

//===----------------------------------------------------------------------===//
// Enable Mbedtls
//===----------------------------------------------------------------------===//

void ForceMbedtlsUnsafeSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	config.options.force_mbedtls = input.GetValue<bool>();

	if (!config.options.force_mbedtls) {
		// check if there are attached databases encrypted that are not read only
		bool encrypted_db_attached = false;
		for (auto &database : db->GetDatabaseManager().GetDatabases()) {
			if (database->HasStorageManager() && database->GetStorageManager().IsEncrypted() &&
			    !database->IsReadOnly()) {
				encrypted_db_attached = true;
				break;
			};
		};

		if (encrypted_db_attached) {
			// autoload httpfs if any attached db uses encryption
			if (!ExtensionHelper::TryAutoLoadExtension(*db, "httpfs")) {
				throw InvalidConfigurationException("Failed to autoload HTTPFS. Cannot disable MbedTLS, HTTPFS "
				                                    "extension is required to write encrypted databases.");
			};
		}
	}
}

void ForceMbedtlsUnsafeSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	// If encryption is initialized, httpfs will be attempted to autoload again
	SetGlobal(db, config, false);
}

Value ForceMbedtlsUnsafeSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return Value::BOOLEAN(config.options.force_mbedtls);
}

//===----------------------------------------------------------------------===//
// H T T P Logging Output
//===----------------------------------------------------------------------===//
void HTTPLoggingOutputSetting::SetLocal(ClientContext &context, const Value &input) {
	throw NotImplementedException("This setting is deprecated and can no longer be used. Check out the DuckDB docs on "
	                              "logging for more information");
}

void HTTPLoggingOutputSetting::ResetLocal(ClientContext &context) {
	throw NotImplementedException("This setting is deprecated and can no longer be used. Check out the DuckDB docs on "
	                              "logging for more information");
}

Value HTTPLoggingOutputSetting::GetSetting(const ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	return Value(config.http_logging_output);
}

//===----------------------------------------------------------------------===//
// HTTP Proxy
//===----------------------------------------------------------------------===//
void HTTPProxySetting::SetGlobal(DatabaseInstance *, DBConfig &config, const Value &input) {
	config.options.http_proxy = input.GetValue<string>();
}

void HTTPProxySetting::ResetGlobal(DatabaseInstance *, DBConfig &config) {
	config.options.http_proxy = FileSystem::GetEnvVariable("HTTP_PROXY");
}

//===----------------------------------------------------------------------===//
// Index Scan Percentage
//===----------------------------------------------------------------------===//
void IndexScanPercentageSetting::OnSet(SettingCallbackInfo &, Value &input) {
	auto index_scan_percentage = input.GetValue<double>();
	if (index_scan_percentage < 0 || index_scan_percentage > 1.0) {
		throw InvalidInputException("the index scan percentage must be within [0, 1]");
	}
}

//===----------------------------------------------------------------------===//
// Log Query Path
//===----------------------------------------------------------------------===//
void LogQueryPathSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (!info.context) {
		throw InvalidInputException("log_query_path can only be set when a context is present");
	}
	auto &client_data = ClientData::Get(*info.context);
	auto path = input.ToString();
	if (path.empty()) {
		// empty path: clean up query writer
		client_data.log_query_writer = nullptr;
	} else {
		client_data.log_query_writer = make_uniq<BufferedFileWriter>(FileSystem::GetFileSystem(*info.context), path,
		                                                             BufferedFileWriter::DEFAULT_OPEN_FLAGS);
	}
}

//===----------------------------------------------------------------------===//
// Max Memory
//===----------------------------------------------------------------------===//
void MaxMemorySetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	config.options.maximum_memory = DBConfig::ParseMemoryLimit(input.ToString());
	if (db) {
		BufferManager::GetBufferManager(*db).SetMemoryLimit(config.options.maximum_memory);
	}
}

void MaxMemorySetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.SetDefaultMaxMemory();
}

Value MaxMemorySetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return Value(StringUtil::BytesToHumanReadableString(config.options.maximum_memory));
}

//===----------------------------------------------------------------------===//
// Max Temp Directory Size
//===----------------------------------------------------------------------===//
void MaxTempDirectorySizeSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	if (input == "90% of available disk space") {
		ResetGlobal(db, config);
		return;
	}
	auto maximum_swap_space = DBConfig::ParseMemoryLimit(input.ToString());
	if (maximum_swap_space == DConstants::INVALID_INDEX) {
		// We use INVALID_INDEX to indicate that the value is not set by the user
		// use one lower to indicate 'unlimited'
		maximum_swap_space--;
	}
	if (!db) {
		config.options.maximum_swap_space = maximum_swap_space;
		return;
	}
	auto &buffer_manager = BufferManager::GetBufferManager(*db);
	buffer_manager.SetSwapLimit(maximum_swap_space);
	config.options.maximum_swap_space = maximum_swap_space;
}

void MaxTempDirectorySizeSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.options.maximum_swap_space = DConstants::INVALID_INDEX;
	if (!db) {
		return;
	}
	auto &buffer_manager = BufferManager::GetBufferManager(*db);
	buffer_manager.SetSwapLimit();
}

Value MaxTempDirectorySizeSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	if (config.options.maximum_swap_space != DConstants::INVALID_INDEX) {
		// Explicitly set by the user
		return Value(StringUtil::BytesToHumanReadableString(config.options.maximum_swap_space));
	}
	auto &buffer_manager = BufferManager::GetBufferManager(context);
	// Database is initialized, use the setting from the temporary directory
	auto max_swap = buffer_manager.GetMaxSwap();
	if (max_swap.IsValid()) {
		return Value(StringUtil::BytesToHumanReadableString(max_swap.GetIndex()));
	} else {
		// The temp directory has not been used yet
		return Value("90% of available disk space");
	}
}

//===----------------------------------------------------------------------===//
// Ordered Aggregate Threshold
//===----------------------------------------------------------------------===//
void OrderedAggregateThresholdSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	const auto param = input.GetValue<uint64_t>();
	if (param <= 0) {
		throw ParserException("Invalid option for PRAGMA ordered_aggregate_threshold, value must be positive");
	}
}

//===----------------------------------------------------------------------===//
// Perfect Ht Threshold
//===----------------------------------------------------------------------===//
void PerfectHtThresholdSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	auto bits = input.GetValue<int64_t>();
	if (bits < 0 || bits > 32) {
		throw ParserException("Perfect HT threshold out of range: should be within range 0 - 32");
	}
}

//===----------------------------------------------------------------------===//
// Profile Output
//===----------------------------------------------------------------------===//
void ProfileOutputSetting::SetLocal(ClientContext &context, const Value &input) {
	auto &config = ClientConfig::GetConfig(context);
	auto parameter = input.ToString();

	if (!parameter.empty() && config.profiler_print_format != ProfilerPrintFormat::NO_OUTPUT) {
		auto &file_system = FileSystem::GetFileSystem(context);
		const auto file_type = file_system.ExtractExtension(parameter);
		if (file_type != "txt") {
			try {
				EnumUtil::FromString<ProfilerPrintFormat>(file_type);
			} catch (std::exception &e) {
				throw ParserException("Invalid output file type: %s", file_type);
			}
		}

		const auto printer_format = StringUtil::Lower(EnumUtil::ToString(config.profiler_print_format));
		if (file_type != printer_format && file_type != "txt") {
			throw ParserException("Profiler file type (%s) must either have the same file extension as the profiling "
			                      "output type (%s), or be a '.txt' file. Set \"enable_profiling = \'%s\'\" first.",
			                      parameter, printer_format, file_type);
		}
	}

	config.profiler_save_location = parameter;
}

void ProfileOutputSetting::ResetLocal(ClientContext &context) {
	ClientConfig::GetConfig(context).profiler_save_location = ClientConfig().profiler_save_location;
}

Value ProfileOutputSetting::GetSetting(const ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	return Value(config.profiler_save_location);
}

//===----------------------------------------------------------------------===//
// Profiling Mode
//===----------------------------------------------------------------------===//
void ProfilingModeSetting::SetLocal(ClientContext &context, const Value &input) {
	auto parameter = StringUtil::Lower(input.ToString());
	auto &config = ClientConfig::GetConfig(context);
	if (parameter == "standard") {
		config.enable_profiler = true;
		config.enable_detailed_profiling = false;
	} else if (parameter == "detailed") {
		config.enable_profiler = true;
		config.enable_detailed_profiling = true;

		// add optimizer settings to the profiler settings
		auto optimizer_settings = MetricsUtils::GetOptimizerMetrics();
		for (auto &setting : optimizer_settings) {
			config.profiler_settings.insert(setting);
		}

		// add the phase timing settings to the profiler settings
		auto phase_timing_settings = MetricsUtils::GetPhaseTimingMetrics();
		for (auto &setting : phase_timing_settings) {
			config.profiler_settings.insert(setting);
		}
	} else if (parameter == "all") {
		config.enable_profiler = true;
		auto all_metrics = MetricsUtils::GetAllMetrics();
		for (auto &metric : all_metrics) {
			config.profiler_settings.insert(metric);
		}
	} else {
		throw ParserException("Unrecognized profiling mode \"%s\", supported formats: [standard, detailed]", parameter);
	}
}

void ProfilingModeSetting::ResetLocal(ClientContext &context) {
	ClientConfig::GetConfig(context).enable_profiler = ClientConfig().enable_profiler;
	ClientConfig::GetConfig(context).enable_detailed_profiling = ClientConfig().enable_detailed_profiling;
	ClientConfig::GetConfig(context).emit_profiler_output = ClientConfig().emit_profiler_output;
	ClientConfig::GetConfig(context).profiler_settings = ClientConfig().profiler_settings;
}

Value ProfilingModeSetting::GetSetting(const ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	if (!config.enable_profiler) {
		return Value();
	}
	return Value(config.enable_detailed_profiling ? "detailed" : "standard");
}

//===----------------------------------------------------------------------===//
// Profiling Coverage Setting
//===----------------------------------------------------------------------===//
void ProfilingCoverageSetting::SetLocal(ClientContext &context, const Value &input) {
	auto setting_type = EnumUtil::FromString<ProfilingCoverage>(input.ToString());
	auto &config = ClientConfig::GetConfig(context);
	config.profiling_coverage = setting_type;
}

void ProfilingCoverageSetting::ResetLocal(ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	config.profiling_coverage = ProfilingCoverage::SELECT;
}

Value ProfilingCoverageSetting::GetSetting(const ClientContext &context) {
	const auto &config = ClientConfig::GetConfig(context);
	return Value(EnumUtil::ToString(config.profiling_coverage));
}

//===----------------------------------------------------------------------===//
// Progress Bar Time
//===----------------------------------------------------------------------===//
void ProgressBarTimeSetting::SetLocal(ClientContext &context, const Value &input) {
	auto &config = ClientConfig::GetConfig(context);
	ProgressBar::SystemOverrideCheck(config);
	config.wait_time = input.GetValue<int32_t>();
	config.enable_progress_bar = true;
}

void ProgressBarTimeSetting::ResetLocal(ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	ProgressBar::SystemOverrideCheck(config);
	config.wait_time = ClientConfig().wait_time;
	config.enable_progress_bar = ClientConfig().enable_progress_bar;
}

Value ProgressBarTimeSetting::GetSetting(const ClientContext &context) {
	return Value::BIGINT(ClientConfig::GetConfig(context).wait_time);
}

//===----------------------------------------------------------------------===//
// Schema
//===----------------------------------------------------------------------===//
void SchemaSetting::SetLocal(ClientContext &context, const Value &input) {
	auto parameter = input.ToString();
	auto &client_data = ClientData::Get(context);
	client_data.catalog_search_path->Set(CatalogSearchEntry::Parse(parameter), CatalogSetPathType::SET_SCHEMA);
}

void SchemaSetting::ResetLocal(ClientContext &context) {
	// FIXME: catalog_search_path is controlled by both SchemaSetting and SearchPathSetting
	auto &client_data = ClientData::Get(context);
	client_data.catalog_search_path->Reset();
}

Value SchemaSetting::GetSetting(const ClientContext &context) {
	auto &client_data = ClientData::Get(context);
	return client_data.catalog_search_path->GetDefault().schema;
}

//===----------------------------------------------------------------------===//
// Search Path
//===----------------------------------------------------------------------===//
void SearchPathSetting::SetLocal(ClientContext &context, const Value &input) {
	auto parameter = input.ToString();
	auto &client_data = ClientData::Get(context);
	client_data.catalog_search_path->Set(CatalogSearchEntry::ParseList(parameter), CatalogSetPathType::SET_SCHEMAS);
}

void SearchPathSetting::ResetLocal(ClientContext &context) {
	// FIXME: catalog_search_path is controlled by both SchemaSetting and SearchPathSetting
	auto &client_data = ClientData::Get(context);
	client_data.catalog_search_path->Reset();
}

Value SearchPathSetting::GetSetting(const ClientContext &context) {
	auto &client_data = ClientData::Get(context);
	auto &set_paths = client_data.catalog_search_path->GetSetPaths();
	return Value(CatalogSearchEntry::ListToString(set_paths));
}

//===----------------------------------------------------------------------===//
// Secret Directory
//===----------------------------------------------------------------------===//
void SecretDirectorySetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	config.secret_manager->SetPersistentSecretPath(input.ToString());
}

void SecretDirectorySetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.secret_manager->ResetPersistentSecretPath();
}

Value SecretDirectorySetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return config.secret_manager->PersistentSecretPath();
}

//===----------------------------------------------------------------------===//
// Storage Compatibility Version
//===----------------------------------------------------------------------===//
void StorageCompatibilityVersionSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	auto version_string = input.GetValue<string>();
	auto serialization_compatibility = SerializationCompatibility::FromString(version_string);
	config.options.serialization_compatibility = serialization_compatibility;
}

void StorageCompatibilityVersionSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.options.serialization_compatibility = DBConfigOptions().serialization_compatibility;
}

Value StorageCompatibilityVersionSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);

	auto &version_name = config.options.serialization_compatibility.duckdb_version;
	return Value(version_name);
}

//===----------------------------------------------------------------------===//
// Streaming Buffer Size
//===----------------------------------------------------------------------===//
void StreamingBufferSizeSetting::SetLocal(ClientContext &context, const Value &input) {
	auto &config = ClientConfig::GetConfig(context);
	config.streaming_buffer_size = DBConfig::ParseMemoryLimit(input.ToString());
}

void StreamingBufferSizeSetting::ResetLocal(ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	config.SetDefaultStreamingBufferSize();
}

Value StreamingBufferSizeSetting::GetSetting(const ClientContext &context) {
	auto &config = ClientConfig::GetConfig(context);
	return Value(StringUtil::BytesToHumanReadableString(config.streaming_buffer_size));
}

//===----------------------------------------------------------------------===//
// Temp Directory
//===----------------------------------------------------------------------===//
void TempDirectorySetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	if (!Settings::Get<EnableExternalAccessSetting>(config)) {
		throw PermissionException("Modifying the temp_directory has been disabled by configuration");
	}
	config.options.temporary_directory = input.IsNull() ? "" : input.ToString();
	config.options.use_temporary_directory = !config.options.temporary_directory.empty();
	if (db) {
		auto &buffer_manager = BufferManager::GetBufferManager(*db);
		buffer_manager.SetTemporaryDirectory(config.options.temporary_directory);
	}
}

void TempDirectorySetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	if (!Settings::Get<EnableExternalAccessSetting>(config)) {
		throw PermissionException("Modifying the temp_directory has been disabled by configuration");
	}
	config.SetDefaultTempDirectory();
	config.options.use_temporary_directory = DBConfigOptions().use_temporary_directory;
	if (db) {
		auto &buffer_manager = BufferManager::GetBufferManager(*db);
		buffer_manager.SetTemporaryDirectory(config.options.temporary_directory);
	}
}

Value TempDirectorySetting::GetSetting(const ClientContext &context) {
	auto &buffer_manager = BufferManager::GetBufferManager(context);
	return Value(buffer_manager.GetTemporaryDirectory());
}

//===----------------------------------------------------------------------===//
// Temporary File Encryption
//===----------------------------------------------------------------------===//
void TempFileEncryptionSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (!info.db) {
		return;
	}
	auto &buffer_manager = BufferManager::GetBufferManager(*info.db);
	if (buffer_manager.HasFilesInTemporaryDirectory()) {
		throw PermissionException("Existing temporary files found: Modifying the temp_file_encryption setting "
		                          "while there are existing temporary files is disabled.");
	}
}

//===----------------------------------------------------------------------===//
// Threads
//===----------------------------------------------------------------------===//
void ThreadsSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	auto new_val = input.GetValue<int64_t>();
	if (new_val < 1) {
		throw SyntaxException("Must have at least 1 thread!");
	}
	auto new_maximum_threads = NumericCast<idx_t>(new_val);
	if (db) {
		TaskScheduler::GetScheduler(*db).SetThreads(new_maximum_threads, Settings::Get<ExternalThreadsSetting>(config));
	}
	config.options.maximum_threads = new_maximum_threads;
}

void ThreadsSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	idx_t new_maximum_threads = config.GetSystemMaxThreads(*config.file_system);
	if (db) {
		TaskScheduler::GetScheduler(*db).SetThreads(new_maximum_threads, Settings::Get<ExternalThreadsSetting>(config));
	}
	config.options.maximum_threads = new_maximum_threads;
}

Value ThreadsSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	return Value::BIGINT(NumericCast<int64_t>(config.options.maximum_threads));
}

//===----------------------------------------------------------------------===//
// Warnings As Errors
//===----------------------------------------------------------------------===//

void WarningsAsErrorsSetting::OnSet(SettingCallbackInfo &info, Value &input) {
	auto &log_manager = LogManager::Get(*info.context);
	if (input == Value(true) && !log_manager.GetConfig().enabled) {
		throw Exception(
		    ExceptionType::SETTINGS,
		    "Can not set 'warnings_as_errors=true'; no logger is available. To solve, run: 'SET enable_logging=true;'");
	}
}

//===----------------------------------------------------------------------===//
// Streaming Buffer Size
//===----------------------------------------------------------------------===//
void WriteBufferRowGroupMemoryLimitSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) {
	if (input.IsNull() || input.ToString().empty()) {
		config.options.write_buffer_row_group_memory_limit = optional_idx();
	} else {
		config.options.write_buffer_row_group_memory_limit = DBConfig::ParseMemoryLimit(input.ToString());
	}
}

void WriteBufferRowGroupMemoryLimitSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) {
	config.options.write_buffer_row_group_memory_limit = optional_idx();
}

Value WriteBufferRowGroupMemoryLimitSetting::GetSetting(const ClientContext &context) {
	auto &config = DBConfig::GetConfig(context);
	idx_t bytes = 0;
	if (config.options.write_buffer_row_group_memory_limit.IsValid()) {
		bytes = config.options.write_buffer_row_group_memory_limit.GetIndex();
	} else {
		bytes = config.options.maximum_memory / 5 / (config.options.maximum_threads + 1);
	}
	return Value(StringUtil::BytesToHumanReadableString(bytes));
}

void CurrentTransactionInvalidationPolicySetting::OnSet(SettingCallbackInfo &info, Value &input) {
	if (!info.context) {
		throw InvalidInputException(
		    "current_transaction_invalidaton_policy can only be set when there is an active client context");
	}
	info.context->transaction.SetInvalidationPolicy(
	    EnumUtil::FromString<TransactionInvalidationPolicy>(input.GetValue<string>()));
}
} // namespace duckdb
