diff --git a/docs/en/operations/settings/constraints-on-settings.md b/docs/en/operations/settings/constraints-on-settings.md index 08966db0ad6..651b6465f7e 100644 --- a/docs/en/operations/settings/constraints-on-settings.md +++ b/docs/en/operations/settings/constraints-on-settings.md @@ -26,15 +26,33 @@ The constraints are defined as the following: + + lower_boundary + upper_boundary + + ``` If the user tries to violate the constraints an exception is thrown and the setting isn’t changed. -There are supported three types of constraints: `min`, `max`, `readonly`. The `min` and `max` constraints specify upper and lower boundaries for a numeric setting and can be used in combination. The `readonly` constraint specifies that the user cannot change the corresponding setting at all. +There are supported few types of constraints: `min`, `max`, `readonly` (with alias `const`) and `changeable_in_readonly`. The `min` and `max` constraints specify upper and lower boundaries for a numeric setting and can be used in combination. The `readonly` or `const` constraint specifies that the user cannot change the corresponding setting at all. The `changeable_in_readonly` constraint type allows user to change the setting within `min`/`max` range even if `readonly` setting is set to 1, otherwise settings are not allow to be changed in `readonly=1` mode. Note that `changeable_in_readonly` is supported only if `settings_constraints_replace_previous` is enabled: +``` xml + + true + +``` + +If there are multiple profiles active for a user, then constraints are merged. Merge process depends on `settings_constraints_replace_previous`: +- **true** (recommended): constraints for the same setting are replaced during merge, such that the last constraint is used and all previous are ignored including fields that are not set in new constraint. +- **false** (default): constraints for the same setting are merged in a way that every not set type of constraint is taken from previous profile and every set type of constraint is replaced by value from new profile. + +Read-only mode is enabled by `readonly` setting (not to confuse with `readonly` constraint type): +- `readonly=0`: No read-only restrictions. +- `readonly=1`: Only read queries are allowed and settings cannot be changes unless `changeable_in_readonly` is set. +- `readonly=2`: Only read queries are allowed, but settings can be changed, except for `readonly` setting itself. -If there are multiple profiles active for a user, then constraints are merged. Constraints for the same setting are replaced during merge, such that the last constraint is used and all previous are ignored. **Example:** Let `users.xml` includes lines: @@ -71,38 +89,6 @@ Code: 452, e.displayText() = DB::Exception: Setting max_memory_usage should not Code: 452, e.displayText() = DB::Exception: Setting force_index_by_date should not be changed. ``` -## Allowances in read-only mode {#settings-readonly-allowance} -Read-only mode is enabled by `readonly` setting: -- `readonly=0`: No read-only restrictions. -- `readonly=1`: Only read queries are allowed and settings cannot be changes unless explicitly allowed. -- `readonly=2`: Only read queries are allowed, but settings can be changed, except for `readonly` setting itself. - -In `readonly=0` and `readonly=2` modes settings constraints are applied as usual. But in `readonly=1` by default all settings changes are forbidden and constraints are ignored. But special allowances can be set in the following way: -``` xml - - - - - lower_boundary - - - upper_boundary - - - lower_boundary - upper_boundary - - - - - - - - -``` - -Constraints and allowances are merged from multiple profiles independently. Previous constraint is not canceled by new allowance, as well as previous allowance is not canceled by new constraint. Instead profile can have one allowance and one constraint for every setting. Allowances are used in `readonly=1` mode, otherwise constraints are used. - **Note:** the `default` profile has special handling: all the constraints defined for the `default` profile become the default constraints, so they restrict all the users until they’re overridden explicitly for these users. [Original article](https://clickhouse.com/docs/en/operations/settings/constraints_on_settings/) diff --git a/programs/server/config.xml b/programs/server/config.xml index 00a0608f607..0c58c9777d1 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -644,7 +644,8 @@ + actions of previous constraint (defined in other profiles) for the same specific setting, including fields that are not set by new constraint. + Also it enables 'changeable_in_readonly' constraint type --> false diff --git a/src/Access/SettingsConstraints.cpp b/src/Access/SettingsConstraints.cpp index f49847ad701..51065cef062 100644 --- a/src/Access/SettingsConstraints.cpp +++ b/src/Access/SettingsConstraints.cpp @@ -66,7 +66,7 @@ void SettingsConstraints::get(const Settings & current_settings, std::string_vie void SettingsConstraints::merge(const SettingsConstraints & other) { - if (access_control.doesSettingsConstraintsReplacePrevious()) + if (access_control->doesSettingsConstraintsReplacePrevious()) { for (const auto & [other_name, other_constraint] : other.constraints) constraints[other_name] = other_constraint; diff --git a/src/Access/SettingsConstraints.h b/src/Access/SettingsConstraints.h index f9dedb49b68..5a90d7e698c 100644 --- a/src/Access/SettingsConstraints.h +++ b/src/Access/SettingsConstraints.h @@ -140,7 +140,7 @@ private: using RangeMap = std::unordered_map>; RangeMap constraints; - const AccessControl * access_control = nullptr; + const AccessControl * access_control; }; } diff --git a/src/Access/SettingsProfileElement.cpp b/src/Access/SettingsProfileElement.cpp index 7cd5d127815..0eeb0712161 100644 --- a/src/Access/SettingsProfileElement.cpp +++ b/src/Access/SettingsProfileElement.cpp @@ -69,6 +69,8 @@ void SettingsProfileElement::init(const ASTSettingsProfileElement & ast, const A is_const.emplace(true); break; case ASTSettingsProfileElement::ConstraintType::CHANGEABLE_IN_READONLY: + if (!access_control->doesSettingsConstraintsReplacePrevious()) + throw Exception("CHANGEABLE_IN_READONLY for " + setting_name + " is not allowed unless settings_constraints_replace_previous is enabled", ErrorCodes::NOT_IMPLEMENTED); is_const.reset(); changeable_in_readonly = true; break; @@ -96,7 +98,12 @@ std::shared_ptr SettingsProfileElement::toAST() const ast->value = value; ast->min_value = min_value; ast->max_value = max_value; - ast->readonly = readonly; + if (changeable_in_readonly) + ast->type = ASTSettingsProfileElement::ConstraintType::CHANGEABLE_IN_READONLY; + if (is_const.has_value()) + ast->type = *is_const ? ASTSettingsProfileElement::ConstraintType::CONST : ASTSettingsProfileElement::ConstraintType::WRITABLE; + else + ast->type = ASTSettingsProfileElement::ConstraintType::NONE; return ast; } @@ -117,7 +124,12 @@ std::shared_ptr SettingsProfileElement::toASTWithName ast->value = value; ast->min_value = min_value; ast->max_value = max_value; - ast->readonly = readonly; + if (changeable_in_readonly) + ast->type = ASTSettingsProfileElement::ConstraintType::CHANGEABLE_IN_READONLY; + if (is_const.has_value()) + ast->type = *is_const ? ASTSettingsProfileElement::ConstraintType::CONST : ASTSettingsProfileElement::ConstraintType::WRITABLE; + else + ast->type = ASTSettingsProfileElement::ConstraintType::NONE; return ast; } diff --git a/src/Access/UsersConfigAccessStorage.cpp b/src/Access/UsersConfigAccessStorage.cpp index 6ffd9bcb681..b3280ac1fd3 100644 --- a/src/Access/UsersConfigAccessStorage.cpp +++ b/src/Access/UsersConfigAccessStorage.cpp @@ -425,8 +425,7 @@ namespace SettingsProfileElements parseSettingsConstraints(const Poco::Util::AbstractConfiguration & config, const String & path_to_constraints, - const AccessControl & access_control, - SettingsProfileElement::RangeKind kind) + const AccessControl & access_control) { SettingsProfileElements profile_elements; Poco::Util::AbstractConfiguration::Keys keys; @@ -438,7 +437,6 @@ namespace SettingsProfileElement profile_element; profile_element.setting_name = setting_name; - profile_element.kind = kind; Poco::Util::AbstractConfiguration::Keys constraint_types; String path_to_name = path_to_constraints + "." + setting_name; config.keys(path_to_name, constraint_types); @@ -449,11 +447,21 @@ namespace profile_element.min_value = Settings::stringToValueUtil(setting_name, config.getString(path_to_name + "." + constraint_type)); else if (constraint_type == "max") profile_element.max_value = Settings::stringToValueUtil(setting_name, config.getString(path_to_name + "." + constraint_type)); - else if (constraint_type == "readonly") - profile_element.readonly = true; + else if (constraint_type == "readonly" || constraint_type == "const") + profile_element.is_const = true; + else if (constraint_type == "changeable_in_readonly") + { + if (access_control.doesSettingsConstraintsReplacePrevious()) + profile_element.changeable_in_readonly = true; + else + throw Exception("Setting changeable_in_readonly for " + setting_name + " is not allowed unless settings_constraints_replace_previous is enabled", ErrorCodes::NOT_IMPLEMENTED); + } else throw Exception("Setting " + constraint_type + " value for " + setting_name + " isn't supported", ErrorCodes::NOT_IMPLEMENTED); } + if (profile_element.is_const && profile_element.changeable_in_readonly) + throw Exception("Both settings changeable_in_readonly and const/readonly cannot be used for " + setting_name, ErrorCodes::NOT_IMPLEMENTED); + profile_elements.push_back(std::move(profile_element)); } @@ -489,13 +497,7 @@ namespace if (key == "constraints" || key.starts_with("constraints[")) { - profile->elements.merge(parseSettingsConstraints(config, profile_config + "." + key, access_control, SettingsProfileElement::RangeKind::Constrain)); - continue; - } - - if (key == "allow" || key.starts_with("allow[")) - { - profile->elements.merge(parseSettingsConstraints(config, profile_config + "." + key, access_control, SettingsProfileElement::RangeKind::Allow)); + profile->elements.merge(parseSettingsConstraints(config, profile_config + "." + key, access_control)); continue; } diff --git a/src/Parsers/Access/ParserSettingsProfileElement.cpp b/src/Parsers/Access/ParserSettingsProfileElement.cpp index f4ca52dea2a..7c39c1fbb7b 100644 --- a/src/Parsers/Access/ParserSettingsProfileElement.cpp +++ b/src/Parsers/Access/ParserSettingsProfileElement.cpp @@ -95,18 +95,23 @@ namespace } - bool parseReadonlyOrWritableKeyword(IParserBase::Pos & pos, Expected & expected, std::optional & readonly) + bool parseConstraintTypeKeyword(IParserBase::Pos & pos, Expected & expected, ASTSettingsProfileElement::ConstraintType & type) { return IParserBase::wrapParseImpl(pos, [&] { - if (ParserKeyword{"READONLY"}.ignore(pos, expected)) + if (ParserKeyword{"READONLY"}.ignore(pos, expected) || ParserKeyword{"CONST"}.ignore(pos, expected)) { - readonly = true; + type = ASTSettingsProfileElement::ConstraintType::CONST; return true; } else if (ParserKeyword{"WRITABLE"}.ignore(pos, expected)) { - readonly = false; + type = ASTSettingsProfileElement::ConstraintType::WRITABLE; + return true; + } + else if (ParserKeyword{"CHANGEABLE_IN_READONLY"}.ignore(pos, expected)) + { + type = ASTSettingsProfileElement::ConstraintType::CHANGEABLE_IN_READONLY; return true; } else @@ -122,7 +127,7 @@ namespace Field & value, Field & min_value, Field & max_value, - std::optional & readonly) + ASTSettingsProfileElement::ConstraintType & type) { return IParserBase::wrapParseImpl(pos, [&] { @@ -134,11 +139,11 @@ namespace Field res_value; Field res_min_value; Field res_max_value; - std::optional res_readonly; + ASTSettingsProfileElement::ConstraintType res_type; bool has_value_or_constraint = false; while (parseValue(pos, expected, res_value) || parseMinMaxValue(pos, expected, res_min_value, res_max_value) - || parseReadonlyOrWritableKeyword(pos, expected, res_readonly)) + || parseConstraintTypeKeyword(pos, expected, res_type)) { has_value_or_constraint = true; } @@ -147,7 +152,7 @@ namespace return false; if (boost::iequals(res_setting_name, "PROFILE") && res_value.isNull() && res_min_value.isNull() && res_max_value.isNull() - && res_readonly) + && res_type == ASTSettingsProfileElement::ConstraintType::CONST) { /// Ambiguity: "profile readonly" can be treated either as a profile named "readonly" or /// as a setting named 'profile' with the readonly constraint. @@ -159,7 +164,7 @@ namespace value = std::move(res_value); min_value = std::move(res_min_value); max_value = std::move(res_max_value); - readonly = res_readonly; + type = res_type; return true; }); } @@ -179,9 +184,9 @@ namespace Field value; Field min_value; Field max_value; - std::optional readonly; + ASTSettingsProfileElement::ConstraintType type; - bool ok = parseSettingNameWithValueOrConstraints(pos, expected, setting_name, value, min_value, max_value, readonly); + bool ok = parseSettingNameWithValueOrConstraints(pos, expected, setting_name, value, min_value, max_value, type); if (!ok && (parseProfileKeyword(pos, expected, use_inherit_keyword) || previous_element_was_parent_profile)) ok = parseProfileNameOrID(pos, expected, id_mode, parent_profile); @@ -195,7 +200,7 @@ namespace result->value = std::move(value); result->min_value = std::move(min_value); result->max_value = std::move(max_value); - result->readonly = readonly; + result->type = type; result->id_mode = id_mode; result->use_inherit_keyword = use_inherit_keyword; return true; diff --git a/src/Storages/System/StorageSystemSettings.cpp b/src/Storages/System/StorageSystemSettings.cpp index 35c9ea442d4..858d1973e25 100644 --- a/src/Storages/System/StorageSystemSettings.cpp +++ b/src/Storages/System/StorageSystemSettings.cpp @@ -41,8 +41,7 @@ void StorageSystemSettings::fillData(MutableColumns & res_columns, ContextPtr co Field min, max; bool is_const = false; - bool changeable_in_readonly = false; - constraints.get(settings, setting_name, min, max, is_const, changeable_in_readonly); + constraints.get(settings, setting_name, min, max, is_const); /// These two columns can accept strings only. if (!min.isNull()) @@ -52,7 +51,7 @@ void StorageSystemSettings::fillData(MutableColumns & res_columns, ContextPtr co res_columns[4]->insert(min); res_columns[5]->insert(max); - res_columns[6]->insert(read_only); + res_columns[6]->insert(is_const); res_columns[7]->insert(setting.getTypeName()); } } diff --git a/src/Storages/System/StorageSystemSettingsProfileElements.cpp b/src/Storages/System/StorageSystemSettingsProfileElements.cpp index ea23b052764..3a79aa632d0 100644 --- a/src/Storages/System/StorageSystemSettingsProfileElements.cpp +++ b/src/Storages/System/StorageSystemSettingsProfileElements.cpp @@ -106,7 +106,7 @@ void StorageSystemSettingsProfileElements::fillData(MutableColumns & res_columns bool inserted_readonly = false; if (element.is_const && !element.setting_name.empty()) { - column_readonly.push_back(*element.readonly); + column_readonly.push_back(*element.is_const); column_readonly_null_map.push_back(false); inserted_readonly = true; }