Implement max constraints for number settings.

This commit is contained in:
Vitaly Baranov 2019-04-19 03:45:15 +03:00
parent 8277e9d8f1
commit d43e7a8960
6 changed files with 170 additions and 38 deletions

View File

@ -426,6 +426,7 @@ namespace ErrorCodes
extern const int BROTLI_WRITE_FAILED = 449;
extern const int BAD_TTL_EXPRESSION = 450;
extern const int BAD_TTL_FILE = 451;
extern const int SETTING_CONSTRAINT_VIOLATION = 452;
extern const int KEEPER_EXCEPTION = 999;
extern const int POCO_EXCEPTION = 1000;

View File

@ -34,6 +34,8 @@ void Settings::setProfile(const String & profile_name, const Poco::Util::Abstrac
for (const std::string & key : config_keys)
{
if (key == "constraints")
continue;
if (key == "profile") /// Inheritance of one profile from another.
setProfile(config.getString(elem + "." + key), config);
else

View File

@ -620,14 +620,22 @@ void Context::calculateUserSettings()
/// NOTE: we ignore global_context settings (from which it is usually copied)
/// NOTE: global_context settings are immutable and not auto updated
settings = Settings();
settings_constraints = SettingsConstraints();
/// 2) Apply settings from default profile
auto default_profile_name = getDefaultProfileName();
if (profile != default_profile_name)
settings.setProfile(default_profile_name, *shared->users_config);
setProfile(default_profile_name);
/// 3) Apply settings from current user
setProfile(profile);
}
void Context::setProfile(const String & profile)
{
settings.setProfile(profile, *shared->users_config);
settings_constraints.setProfile(profile, *shared->users_config);
}
@ -1033,7 +1041,7 @@ void Context::setSetting(const String & name, const String & value)
auto lock = getLock();
if (name == "profile")
{
settings.setProfile(value, *shared->users_config);
setProfile(value);
return;
}
settings.set(name, value);
@ -1045,7 +1053,7 @@ void Context::setSetting(const String & name, const Field & value)
auto lock = getLock();
if (name == "profile")
{
settings.setProfile(value.safeGet<String>(), *shared->users_config);
setProfile(value.safeGet<String>());
return;
}
settings.set(name, value);
@ -1068,13 +1076,13 @@ void Context::applySettingsChanges(const SettingsChanges & changes)
void Context::checkSettingsConstraints(const SettingChange & change)
{
SettingsConstraints::check(settings, change);
settings_constraints.check(settings, change);
}
void Context::checkSettingsConstraints(const SettingsChanges & changes)
{
SettingsConstraints::check(settings, changes);
settings_constraints.check(settings, changes);
}

View File

@ -4,6 +4,7 @@
#include <Core/NamesAndTypes.h>
#include <Core/Types.h>
#include <Interpreters/ClientInfo.h>
#include <Interpreters/SettingsConstraints.h>
#include <Core/Settings.h>
#include <Parsers/IAST_fwd.h>
#include <Common/LRUCache.h>
@ -125,6 +126,7 @@ private:
std::shared_ptr<QuotaForIntervals> quota; /// Current quota. By default - empty quota, that have no limits.
String current_database;
Settings settings; /// Setting for query execution.
SettingsConstraints settings_constraints;
using ProgressCallback = std::function<void(const Progress & progress)>;
ProgressCallback progress_callback; /// Callback for tracking progress of query execution.
QueryStatus * process_list_elem = nullptr; /// For tracking total resource usage for query.
@ -484,6 +486,8 @@ private:
*/
void checkDatabaseAccessRightsImpl(const std::string & database_name) const;
void setProfile(const String & profile);
EmbeddedDictionaries & getEmbeddedDictionariesImpl(bool throw_on_error) const;
ExternalDictionaries & getExternalDictionariesImpl(bool throw_on_error) const;
ExternalModels & getExternalModelsImpl(bool throw_on_error) const;

View File

@ -1,5 +1,8 @@
#include <Interpreters/SettingsConstraints.h>
#include <Core/Settings.h>
#include <Common/FieldVisitors.h>
#include <IO/WriteHelpers.h>
#include <Poco/Util/AbstractConfiguration.h>
namespace DB
@ -9,43 +12,35 @@ namespace ErrorCodes
extern const int READONLY;
extern const int QUERY_IS_PROHIBITED;
extern const int NO_ELEMENTS_IN_CONFIG;
extern const int SETTING_CONSTRAINT_VIOLATION;
}
namespace
{
thread_local Settings temp_settings;
void checkImpl(const Settings & current_settings, size_t index)
{
const auto & new_setting = temp_settings[index];
Field new_value = new_setting.getValue();
const auto & current_setting = current_settings[index];
Field current_value = current_setting.getValue();
/// Setting isn't checked if value wasn't changed.
if (current_value == new_value)
return;
const StringRef & name = new_setting.getName();
if (!current_settings.allow_ddl && name == "allow_ddl")
throw Exception("Cannot modify 'allow_ddl' setting when DDL queries are prohibited for the user", ErrorCodes::QUERY_IS_PROHIBITED);
/** The `readonly` value is understood as follows:
* 0 - everything allowed.
* 1 - only read queries can be made; you can not change the settings.
* 2 - You can only do read queries and you can change the settings, except for the `readonly` setting.
*/
if (current_settings.readonly == 1)
throw Exception("Cannot modify '" + name.toString() + "' setting in readonly mode", ErrorCodes::READONLY);
if (current_settings.readonly > 1 && name == "readonly")
throw Exception("Cannot modify 'readonly' setting in readonly mode", ErrorCodes::READONLY);
}
}
void SettingsConstraints::check(const Settings & current_settings, const SettingChange & change)
SettingsConstraints::SettingsConstraints() = default;
SettingsConstraints::SettingsConstraints(const SettingsConstraints & src) = default;
SettingsConstraints & SettingsConstraints::operator=(const SettingsConstraints & src) = default;
SettingsConstraints::SettingsConstraints(SettingsConstraints && src) = default;
SettingsConstraints & SettingsConstraints::operator=(SettingsConstraints && src) = default;
SettingsConstraints::~SettingsConstraints() = default;
void SettingsConstraints::setMaxValue(const String & name, const String & max_value)
{
max_settings.set(name, max_value);
}
void SettingsConstraints::setMaxValue(const String & name, const Field & max_value)
{
max_settings.set(name, max_value);
}
void SettingsConstraints::check(const Settings & current_settings, const SettingChange & change) const
{
size_t index = current_settings.find(change.name);
if (index == Settings::npos)
@ -56,10 +51,86 @@ void SettingsConstraints::check(const Settings & current_settings, const Setting
}
void SettingsConstraints::check(const Settings & current_settings, const SettingsChanges & changes)
void SettingsConstraints::check(const Settings & current_settings, const SettingsChanges & changes) const
{
for (const auto & change : changes)
check(current_settings, change);
}
void SettingsConstraints::checkImpl(const Settings & current_settings, size_t index) const
{
const auto & new_setting = temp_settings[index];
Field new_value = new_setting.getValue();
const auto & current_setting = current_settings[index];
Field current_value = current_setting.getValue();
/// Setting isn't checked if value wasn't changed.
if (current_value == new_value)
return;
const StringRef & name = new_setting.getName();
if (!current_settings.allow_ddl && name == "allow_ddl")
throw Exception("Cannot modify 'allow_ddl' setting when DDL queries are prohibited for the user", ErrorCodes::QUERY_IS_PROHIBITED);
/** The `readonly` value is understood as follows:
* 0 - everything allowed.
* 1 - only read queries can be made; you can not change the settings.
* 2 - You can only do read queries and you can change the settings, except for the `readonly` setting.
*/
if (current_settings.readonly == 1)
throw Exception("Cannot modify '" + name.toString() + "' setting in readonly mode", ErrorCodes::READONLY);
if (current_settings.readonly > 1 && name == "readonly")
throw Exception("Cannot modify 'readonly' setting in readonly mode", ErrorCodes::READONLY);
const auto & max_setting = max_settings[index];
if (max_setting.isChanged())
{
Field max_value = max_setting.getValue();
if (new_value > max_value)
throw Exception(
"Setting " + name.toString() + " shouldn't be greater than " + applyVisitor(FieldVisitorToString(), max_value),
ErrorCodes::SETTING_CONSTRAINT_VIOLATION);
}
}
void SettingsConstraints::setProfile(const String & profile_name, const Poco::Util::AbstractConfiguration & config)
{
String parent_profile = "profiles." + profile_name + ".profile";
if (config.has(parent_profile))
setProfile(parent_profile, config); // Inheritance of one profile from another.
String path_to_constraints = "profiles." + profile_name + ".constraints";
if (config.has(path_to_constraints))
loadFromConfig(path_to_constraints, config);
}
void SettingsConstraints::loadFromConfig(const String & path_to_constraints, const Poco::Util::AbstractConfiguration & config)
{
if (!config.has(path_to_constraints))
throw Exception("There is no path '" + path_to_constraints + "' in configuration file.", ErrorCodes::NO_ELEMENTS_IN_CONFIG);
Poco::Util::AbstractConfiguration::Keys names;
config.keys(path_to_constraints, names);
for (const String & name : names)
{
String path_to_name = path_to_constraints + "." + name;
Poco::Util::AbstractConfiguration::Keys constraint_types;
config.keys(path_to_name, constraint_types);
for (const String & constraint_type : constraint_types)
{
String path_to_type = path_to_name + "." + constraint_type;
if (constraint_type == "max")
setMaxValue(name, config.getString(path_to_type));
else
throw Exception("Setting " + constraint_type + " value for " + name + " isn't supported", ErrorCodes::NOT_IMPLEMENTED);
}
}
}
}

View File

@ -1,6 +1,16 @@
#pragma once
#include <Common/SettingsChanges.h>
#include <Core/Settings.h>
namespace Poco
{
namespace Util
{
class AbstractConfiguration;
}
}
namespace DB
{
@ -8,7 +18,20 @@ struct Settings;
/** Checks if specified changes of settings are allowed or not.
* If the changes are not allowed (i.e. violates some constraints) this class throws an exception.
* This class checks that we are not in the read-only mode.
* The constraints are set by editing the `users.xml` file.
* For examples, the following lines in `users.xml` will set that `max_memory_usage` cannot be greater than 20000000000:
* <profiles>
* <user_profile>
* <max_memory_usage>10000000000</max_memory_usage>
* ...
* <constraints>
* <max_memory_usage>
* <max>20000000000</max>
* </max_memory_usage>
* </constraints>
* </user_profile>
* </profiles>
* This class also checks that we are not in the read-only mode.
* If a setting cannot be change due to the read-only mode this class throws an exception.
* The value of `readonly` value is understood as follows:
* 0 - everything allowed.
@ -18,8 +41,31 @@ struct Settings;
class SettingsConstraints
{
public:
static void check(const Settings & current_settings, const SettingChange & change);
static void check(const Settings & current_settings, const SettingsChanges & changes);
SettingsConstraints();
SettingsConstraints(const SettingsConstraints & src);
SettingsConstraints & operator =(const SettingsConstraints & src);
SettingsConstraints(SettingsConstraints && src);
SettingsConstraints & operator =(SettingsConstraints && src);
~SettingsConstraints();
void setMaxValue(const String & name, const String & max_value);
void setMaxValue(const String & name, const Field & max_value);
void check(const Settings & current_settings, const SettingChange & change) const;
void check(const Settings & current_settings, const SettingsChanges & changes) const;
/** Set multiple settings from "profile" (in server configuration file (users.xml), profiles contain groups of multiple settings).
* The profile can also be set using the `set` functions, like the profile setting.
*/
void setProfile(const String & profile_name, const Poco::Util::AbstractConfiguration & config);
/// Loads the constraints from configuration file, at "path" prefix in configuration.
void loadFromConfig(const String & path, const Poco::Util::AbstractConfiguration & config);
private:
void checkImpl(const Settings & current_settings, size_t index) const;
Settings max_settings;
};
}