aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/clickhouse/src/Access/SettingsConstraints.h
blob: c5cd79c96f96c5c17e9c69d5fe4acbe9262e9d7f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#pragma once

#include <Access/SettingsProfileElement.h>
#include <Common/SettingsChanges.h>
#include <Common/SettingSource.h>
#include <unordered_map>

namespace Poco::Util
{
    class AbstractConfiguration;
}

namespace DB
{
struct Settings;
struct MergeTreeSettings;
struct SettingChange;
class SettingsChanges;
class AccessControl;


/** 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.
  * 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,
  * and `force_index_by_date` should be always equal to 0:
  *
  * <profiles>
  *   <user_profile>
  *       <max_memory_usage>10000000000</max_memory_usage>
  *       <force_index_by_date>0</force_index_by_date>
  *       ...
  *       <constraints>
  *           <max_memory_usage>
  *               <min>200000</min>
  *               <max>20000000000</max>
  *           </max_memory_usage>
  *           <force_index_by_date>
  *               <const/>
  *           </force_index_by_date>
  *           <max_threads>
  *               <changable_in_readonly/>
  *           </max_threads>
  *       </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` is understood as follows:
  * 0 - not read-only mode, no additional checks.
  * 1 - only read queries, as well as changing settings with <changable_in_readonly/> flag.
  * 2 - only read queries and you can change the settings, except for the `readonly` setting.
  *
  */
class SettingsConstraints
{
public:
    explicit SettingsConstraints(const AccessControl & access_control_);
    SettingsConstraints(const SettingsConstraints & src);
    SettingsConstraints & operator=(const SettingsConstraints & src);
    SettingsConstraints(SettingsConstraints && src) noexcept;
    SettingsConstraints & operator=(SettingsConstraints && src) noexcept;
    ~SettingsConstraints();

    void clear();
    bool empty() const { return constraints.empty(); }

    void set(const String & full_name, const Field & min_value, const Field & max_value, SettingConstraintWritability writability);
    void get(const Settings & current_settings, std::string_view short_name, Field & min_value, Field & max_value, SettingConstraintWritability & writability) const;
    void get(const MergeTreeSettings & current_settings, std::string_view short_name, Field & min_value, Field & max_value, SettingConstraintWritability & writability) const;

    void merge(const SettingsConstraints & other);

    /// Checks whether `change` violates these constraints and throws an exception if so.
    void check(const Settings & current_settings, const SettingsProfileElements & profile_elements, SettingSource source) const;
    void check(const Settings & current_settings, const SettingChange & change, SettingSource source) const;
    void check(const Settings & current_settings, const SettingsChanges & changes, SettingSource source) const;
    void check(const Settings & current_settings, SettingsChanges & changes, SettingSource source) const;

    /// Checks whether `change` violates these constraints and throws an exception if so. (setting short name is expected inside `changes`)
    void check(const MergeTreeSettings & current_settings, const SettingChange & change) const;
    void check(const MergeTreeSettings & current_settings, const SettingsChanges & changes) const;

    /// Checks whether `change` violates these and clamps the `change` if so.
    void clamp(const Settings & current_settings, SettingsChanges & changes, SettingSource source) const;


    friend bool operator ==(const SettingsConstraints & left, const SettingsConstraints & right);
    friend bool operator !=(const SettingsConstraints & left, const SettingsConstraints & right) { return !(left == right); }

private:
    enum ReactionOnViolation
    {
        THROW_ON_VIOLATION,
        CLAMP_ON_VIOLATION,
    };

    struct Constraint
    {
        SettingConstraintWritability writability = SettingConstraintWritability::WRITABLE;
        Field min_value{};
        Field max_value{};

        bool operator ==(const Constraint & other) const;
        bool operator !=(const Constraint & other) const { return !(*this == other); }
    };

    struct Checker
    {
        Constraint constraint;
        using NameResolver = std::function<std::string_view(std::string_view)>;
        NameResolver setting_name_resolver;

        String explain;
        int code = 0;

        // Allows everything
        explicit Checker(NameResolver setting_name_resolver_)
            : setting_name_resolver(std::move(setting_name_resolver_))
        {}

        // Forbidden with explanation
        Checker(const String & explain_, int code_)
            : constraint{.writability = SettingConstraintWritability::CONST}
            , explain(explain_)
            , code(code_)
        {}

        // Allow or forbid depending on range defined by constraint, also used to return stored constraint
        explicit Checker(const Constraint & constraint_, NameResolver setting_name_resolver_)
            : constraint(constraint_)
            , setting_name_resolver(std::move(setting_name_resolver_))
        {}

        // Perform checking
        bool check(SettingChange & change,
                   const Field & new_value,
                   ReactionOnViolation reaction,
                   SettingSource source) const;
    };

    struct StringHash
    {
        using is_transparent = void;
        size_t operator()(std::string_view txt) const
        {
            return std::hash<std::string_view>{}(txt);
        }
    };

    bool checkImpl(const Settings & current_settings,
                  SettingChange & change,
                  ReactionOnViolation reaction,
                  SettingSource source) const;

    bool checkImpl(const MergeTreeSettings & current_settings, SettingChange & change, ReactionOnViolation reaction) const;

    Checker getChecker(const Settings & current_settings, std::string_view setting_name) const;
    Checker getMergeTreeChecker(std::string_view short_name) const;

    std::string_view resolveSettingNameWithCache(std::string_view name) const;

    // Special container for heterogeneous lookups: to avoid `String` construction during `find(std::string_view)`
    using Constraints = std::unordered_map<String, Constraint, StringHash, std::equal_to<>>;
    Constraints constraints;
    /// to avoid creating new string every time we cache the alias resolution
    /// we cannot use resolveName from BaseSettings::Traits because MergeTreeSettings have added prefix
    /// we store only resolved aliases inside the Constraints so to correctly search the container we always need to use resolved name
    std::unordered_map<std::string, std::string, StringHash, std::equal_to<>> settings_alias_cache;

    const AccessControl * access_control;
};

}