diff --git a/dbms/src/Access/SettingsConstraints.cpp b/dbms/src/Access/SettingsConstraints.cpp index a044b7a0dc1..64460aaa8f1 100644 --- a/dbms/src/Access/SettingsConstraints.cpp +++ b/dbms/src/Access/SettingsConstraints.cpp @@ -217,9 +217,18 @@ const SettingsConstraints::Constraint * SettingsConstraints::tryGetConstraint(si 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 elem = "profiles." + profile_name; + + Poco::Util::AbstractConfiguration::Keys config_keys; + config.keys(elem, config_keys); + + for (const std::string & key : config_keys) + { + if (key == "profile" || 0 == key.compare(0, strlen("profile["), "profile[")) /// Inheritance of profiles from the current one. + setProfile(config.getString(elem + "." + key), config); + else + continue; + } String path_to_constraints = "profiles." + profile_name + ".constraints"; if (config.has(path_to_constraints)) diff --git a/dbms/src/Core/Settings.cpp b/dbms/src/Core/Settings.cpp index 717261e298d..fe6e0c18b85 100644 --- a/dbms/src/Core/Settings.cpp +++ b/dbms/src/Core/Settings.cpp @@ -37,7 +37,7 @@ void Settings::setProfile(const String & profile_name, const Poco::Util::Abstrac { if (key == "constraints") continue; - if (key == "profile") /// Inheritance of one profile from another. + if (key == "profile" || 0 == key.compare(0, strlen("profile["), "profile[")) /// Inheritance of profiles from the current one. setProfile(config.getString(elem + "." + key), config); else set(key, config.getString(elem + "." + key)); diff --git a/dbms/src/Storages/StorageReplicatedMergeTree.cpp b/dbms/src/Storages/StorageReplicatedMergeTree.cpp index d7f7a22895b..e37c3b893d7 100644 --- a/dbms/src/Storages/StorageReplicatedMergeTree.cpp +++ b/dbms/src/Storages/StorageReplicatedMergeTree.cpp @@ -534,7 +534,7 @@ void StorageReplicatedMergeTree::setTableStructure(ColumnsDescription new_column if (metadata_diff.ttl_table_changed) { - ParserExpression parser; + ParserTTLExpressionList parser; new_ttl_table_ast = parseQuery(parser, metadata_diff.new_ttl_table, 0); } diff --git a/dbms/tests/integration/test_inherit_multiple_profiles/__init__.py b/dbms/tests/integration/test_inherit_multiple_profiles/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/integration/test_inherit_multiple_profiles/configs/combined_profile.xml b/dbms/tests/integration/test_inherit_multiple_profiles/configs/combined_profile.xml new file mode 100644 index 00000000000..4fbb7dcf3ff --- /dev/null +++ b/dbms/tests/integration/test_inherit_multiple_profiles/configs/combined_profile.xml @@ -0,0 +1,59 @@ + + + + 2 + + 200000000 + + + 100000000 + + + + + + + + 1234567890 + 300000000 + + + 200000000 + + + 1234567889 + 1234567891 + + + + + 654321 + 400000000 + + + 300000000 + + + 654320 + 654322 + + + + + profile_1 + profile_2 + profile_3 + 2 + + + + + + + ::/0 + + default + combined_profile + + + diff --git a/dbms/tests/integration/test_inherit_multiple_profiles/test.py b/dbms/tests/integration/test_inherit_multiple_profiles/test.py new file mode 100644 index 00000000000..1540196f9b6 --- /dev/null +++ b/dbms/tests/integration/test_inherit_multiple_profiles/test.py @@ -0,0 +1,74 @@ +import pytest + +from helpers.client import QueryRuntimeException +from helpers.cluster import ClickHouseCluster +from helpers.test_tools import TSV + + +cluster = ClickHouseCluster(__file__) +instance = cluster.add_instance('instance', + user_configs=['configs/combined_profile.xml']) +q = instance.query + + +@pytest.fixture(scope="module") +def started_cluster(): + try: + cluster.start() + + yield cluster + + finally: + cluster.shutdown() + + +def test_combined_profile(started_cluster): + settings = q(''' +SELECT name, value FROM system.settings + WHERE name IN + ('max_insert_block_size', 'max_network_bytes', 'max_query_size', + 'max_parallel_replicas', 'readonly') + AND changed +ORDER BY name +''', user='test_combined_profile') + + expected1 = '''\ +max_insert_block_size 654321 +max_network_bytes 1234567890 +max_parallel_replicas 2 +max_query_size 400000000 +readonly 2''' + + assert TSV(settings) == TSV(expected1) + + with pytest.raises(QueryRuntimeException) as exc: + q(''' + SET max_insert_block_size = 1000; + ''', user='test_combined_profile') + + assert ("max_insert_block_size shouldn't be less than 654320." in + str(exc.value)) + + with pytest.raises(QueryRuntimeException) as exc: + q(''' + SET max_network_bytes = 2000000000; + ''', user='test_combined_profile') + + assert ("max_network_bytes shouldn't be greater than 1234567891." in + str(exc.value)) + + with pytest.raises(QueryRuntimeException) as exc: + q(''' + SET max_parallel_replicas = 1000; + ''', user='test_combined_profile') + + assert ('max_parallel_replicas should not be changed.' in + str(exc.value)) + + with pytest.raises(QueryRuntimeException) as exc: + q(''' + SET max_memory_usage = 1000; + ''', user='test_combined_profile') + + assert ("max_memory_usage shouldn't be less than 300000000." in + str(exc.value)) diff --git a/dbms/tests/integration/test_ttl_move/test.py b/dbms/tests/integration/test_ttl_move/test.py index a03575cde98..071257d24ca 100644 --- a/dbms/tests/integration/test_ttl_move/test.py +++ b/dbms/tests/integration/test_ttl_move/test.py @@ -538,3 +538,85 @@ def test_ttls_do_not_work_after_alter(started_cluster, name, engine, positive): finally: node1.query("DROP TABLE IF EXISTS {}".format(name)) + + +@pytest.mark.parametrize("name,engine,positive", [ + ("mt_test_alter_multiple_ttls_positive", "MergeTree()", True), + ("mt_replicated_test_alter_multiple_ttls_positive", "ReplicatedMergeTree('/clickhouse/replicated_test_alter_multiple_ttls_positive', '1')", True), + ("mt_test_alter_multiple_ttls_negative", "MergeTree()", False), + ("mt_replicated_test_alter_multiple_ttls_negative", "ReplicatedMergeTree('/clickhouse/replicated_test_alter_multiple_ttls_negative', '1')", False), +]) +def test_alter_multiple_ttls(started_cluster, name, engine, positive): + """Copyright 2019, Altinity LTD + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.""" + """Check that when multiple TTL expressions are set + and before any parts are inserted the TTL expressions + are changed with ALTER command then all old + TTL expressions are removed and the + the parts are moved to the specified disk or volume or + deleted if the new TTL expression is triggered + and are not moved or deleted when it is not. + """ + now = time.time() + try: + node1.query(""" + CREATE TABLE {name} ( + p1 Int64, + s1 String, + d1 DateTime + ) ENGINE = {engine} + ORDER BY tuple() + PARTITION BY p1 + TTL d1 + INTERVAL 30 SECOND TO DISK 'jbod2', + d1 + INTERVAL 60 SECOND TO VOLUME 'external' + SETTINGS storage_policy='jbods_with_external', merge_with_ttl_timeout=0 + """.format(name=name, engine=engine)) + + node1.query(""" + ALTER TABLE {name} MODIFY + TTL d1 + INTERVAL 0 SECOND TO DISK 'jbod2', + d1 + INTERVAL 5 SECOND TO VOLUME 'external', + d1 + INTERVAL 10 SECOND DELETE + """.format(name=name)) + + for p in range(3): + data = [] # 6MB in total + now = time.time() + for i in range(2): + p1 = p + s1 = get_random_string(1024 * 1024) # 1MB + d1 = now - 1 if i > 0 or positive else now + 300 + data.append("({}, '{}', toDateTime({}))".format(p1, s1, d1)) + node1.query("INSERT INTO {name} (p1, s1, d1) VALUES {values}".format(name=name, values=",".join(data))) + + used_disks = get_used_disks_for_table(node1, name) + assert set(used_disks) == {"jbod2"} if positive else {"jbod1", "jbod2"} + + assert node1.query("SELECT count() FROM {name}".format(name=name)).splitlines() == ["6"] + + time.sleep(5) + + used_disks = get_used_disks_for_table(node1, name) + assert set(used_disks) == {"external"} if positive else {"jbod1", "jbod2"} + + assert node1.query("SELECT count() FROM {name}".format(name=name)).splitlines() == ["6"] + + time.sleep(5) + + node1.query("OPTIMIZE TABLE {name} FINAL".format(name=name)) + + assert node1.query("SELECT count() FROM {name}".format(name=name)).splitlines() == ["0"] if positive else ["3"] + + finally: + node1.query("DROP TABLE IF EXISTS {name}".format(name=name)) diff --git a/docs/en/operations/settings/settings_profiles.md b/docs/en/operations/settings/settings_profiles.md index c335e249212..095bb362936 100644 --- a/docs/en/operations/settings/settings_profiles.md +++ b/docs/en/operations/settings/settings_profiles.md @@ -60,7 +60,7 @@ Example: The example specifies two profiles: `default` and `web`. The `default` profile has a special purpose: it must always be present and is applied when starting the server. In other words, the `default` profile contains default settings. The `web` profile is a regular profile that can be set using the `SET` query or using a URL parameter in an HTTP query. -Settings profiles can inherit from each other. To use inheritance, indicate the `profile` setting before the other settings that are listed in the profile. +Settings profiles can inherit from each other. To use inheritance, indicate one or multiple `profile` settings before the other settings that are listed in the profile. In case when one setting is defined in different profiles, the latest defined is used. [Original article](https://clickhouse.yandex/docs/en/operations/settings/settings_profiles/) diff --git a/docs/ru/operations/settings/settings_profiles.md b/docs/ru/operations/settings/settings_profiles.md index a120c388880..8b4e2316fe6 100644 --- a/docs/ru/operations/settings/settings_profiles.md +++ b/docs/ru/operations/settings/settings_profiles.md @@ -60,6 +60,6 @@ SET profile = 'web' В примере задано два профиля: `default` и `web`. Профиль `default` имеет специальное значение - он всегда обязан присутствовать и применяется при запуске сервера. То есть, профиль `default` содержит настройки по умолчанию. Профиль `web` - обычный профиль, который может быть установлен с помощью запроса `SET` или с помощью параметра URL при запросе по HTTP. -Профили настроек могут наследоваться от друг-друга - это реализуется указанием настройки `profile` перед остальными настройками, перечисленными в профиле. +Профили настроек могут наследоваться от друг-друга - это реализуется указанием одной или нескольких настроек `profile` перед остальными настройками, перечисленными в профиле. Если одна настройка указана в нескольких профилях, используется последнее из значений. [Оригинальная статья](https://clickhouse.yandex/docs/ru/operations/settings/settings_profiles/) diff --git a/docs/zh/operations/settings/settings_profiles.md b/docs/zh/operations/settings/settings_profiles.md deleted file mode 100644 index c335e249212..00000000000 --- a/docs/zh/operations/settings/settings_profiles.md +++ /dev/null @@ -1,66 +0,0 @@ - -# Settings profiles - -A settings profile is a collection of settings grouped under the same name. Each ClickHouse user has a profile. -To apply all the settings in a profile, set the `profile` setting. - -Example: - -Install the `web` profile. - -``` sql -SET profile = 'web' -``` - -Settings profiles are declared in the user config file. This is usually `users.xml`. - -Example: - -```xml - - - - - - 8 - - - - - 1000000000 - 100000000000 - - 1000000 - any - - 1000000 - 1000000000 - - 100000 - 100000000 - break - - 600 - 1000000 - 15 - - 25 - 100 - 50 - - 2 - 25 - 50 - 100 - - 1 - - -``` - -The example specifies two profiles: `default` and `web`. The `default` profile has a special purpose: it must always be present and is applied when starting the server. In other words, the `default` profile contains default settings. The `web` profile is a regular profile that can be set using the `SET` query or using a URL parameter in an HTTP query. - -Settings profiles can inherit from each other. To use inheritance, indicate the `profile` setting before the other settings that are listed in the profile. - - -[Original article](https://clickhouse.yandex/docs/en/operations/settings/settings_profiles/) diff --git a/docs/zh/operations/settings/settings_profiles.md b/docs/zh/operations/settings/settings_profiles.md new file mode 120000 index 00000000000..35d9747ad56 --- /dev/null +++ b/docs/zh/operations/settings/settings_profiles.md @@ -0,0 +1 @@ +../../../en/operations/settings/settings_profiles.md \ No newline at end of file