fix build, fix docs, fix comments, logical fixes, test are still to be fixed and new test are to be added

This commit is contained in:
Sergei Trifonov 2022-09-02 16:20:09 +02:00
parent 7fec55eea4
commit 014d109175
9 changed files with 72 additions and 67 deletions

View File

@ -26,15 +26,33 @@ The constraints are defined as the following:
<setting_name_4>
<readonly/>
</setting_name_4>
<setting_name_5>
<min>lower_boundary</min>
<max>upper_boundary</max>
<changeable_in_readonly/>
</setting_name_5>
</constraints>
</user_name>
</profiles>
```
If the user tries to violate the constraints an exception is thrown and the setting isnt 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
<access_control_improvements>
<settings_constraints_replace_previous>true<settings_constraints_replace_previous>
</access_control_improvements>
```
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
<profiles>
<user_name>
<allow>
<setting_name_1>
<min>lower_boundary</min>
</setting_name_1>
<setting_name_2>
<max>upper_boundary</max>
</setting_name_2>
<setting_name_3>
<min>lower_boundary</min>
<max>upper_boundary</max>
</setting_name_3>
<setting_name_4>
<readonly/>
</setting_name_4>
<setting_name_5/><!-- allow any value -->
</allow>
</user_name>
</profiles>
```
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 theyre overridden explicitly for these users.
[Original article](https://clickhouse.com/docs/en/operations/settings/constraints_on_settings/) <!--hide-->

View File

@ -644,7 +644,8 @@
<!-- By default, for backward compatibility a settings profile constraint for a specific setting inherit every not set field from
previous profile. You can change this behaviour by setting this to true.
If it's set to true then if settings profile has a constraint for a specific setting, then this constraint completely cancels all
actions of previous constraint (defined in other profiles) for the same specific setting, including fields that are not set by new constraint. -->
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 -->
<settings_constraints_replace_previous>false</settings_constraints_replace_previous>
</access_control_improvements>

View File

@ -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;

View File

@ -140,7 +140,7 @@ private:
using RangeMap = std::unordered_map<String, Range, StringHash, std::equal_to<>>;
RangeMap constraints;
const AccessControl * access_control = nullptr;
const AccessControl * access_control;
};
}

View File

@ -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<ASTSettingsProfileElement> 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<ASTSettingsProfileElement> 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;
}

View File

@ -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;
}

View File

@ -95,18 +95,23 @@ namespace
}
bool parseReadonlyOrWritableKeyword(IParserBase::Pos & pos, Expected & expected, std::optional<bool> & 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<bool> & 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<bool> 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<bool> 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;

View File

@ -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());
}
}

View File

@ -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;
}