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