From 640d87b04f72f37bd03a41674470866c3f544f95 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 27 Jul 2022 16:21:56 +0000 Subject: [PATCH 001/253] add more settings for randomization --- tests/clickhouse-test | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 952fc7fb0a9..c69ff7d0721 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -395,6 +395,16 @@ class FailureReason(enum.Enum): INTERNAL_ERROR = "Test internal error: " +def default_generator_for_bytes_setting(): + return ( + lambda: 0 + if random.random() < 0.5 + else 1 + if random.random() < 0.2 + else random.randint(1, 10 * 1024 * 1024 * 1024) + ) + + class SettingsRandomizer: settings = { "max_insert_threads": lambda: 0 @@ -427,16 +437,8 @@ class SettingsRandomizer: "optimize_aggregation_in_order": lambda: random.randint(0, 1), "aggregation_in_order_max_block_bytes": lambda: random.randint(0, 50000000), "use_uncompressed_cache": lambda: random.randint(0, 1), - "min_bytes_to_use_direct_io": lambda: 0 - if random.random() < 0.5 - else 1 - if random.random() < 0.2 - else random.randint(1, 1024 * 1024 * 1024), - "min_bytes_to_use_mmap_io": lambda: 0 - if random.random() < 0.5 - else 1 - if random.random() < 0.2 - else random.randint(1, 1024 * 1024 * 1024), + "min_bytes_to_use_direct_io": default_generator_for_bytes_setting(), + "min_bytes_to_use_mmap_io": default_generator_for_bytes_setting(), "local_filesystem_read_method": lambda: random.choice( ["read", "pread", "mmap", "pread_threadpool"] ), @@ -448,6 +450,9 @@ class SettingsRandomizer: "compile_sort_description": lambda: random.randint(0, 1), "merge_tree_coarse_index_granularity": lambda: random.randint(2, 32), "optimize_distinct_in_order": lambda: random.randint(0, 1), + "max_bytes_before_external_sort": default_generator_for_bytes_setting(), + "max_bytes_before_external_group_by": default_generator_for_bytes_setting(), + "max_bytes_before_remerge_sort": lambda: random.randint(1, 3000000000), } @staticmethod From 3f53477be7e468886e591a6c48c7483541174bfa Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 20 Mar 2023 14:44:11 +0000 Subject: [PATCH 002/253] remove unused code --- tests/clickhouse-test | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 10600491b4a..e37c3900b9c 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -491,16 +491,6 @@ class FailureReason(enum.Enum): INTERNAL_ERROR = "Test internal error: " -def default_generator_for_bytes_setting(): - return ( - lambda: 0 - if random.random() < 0.5 - else 1 - if random.random() < 0.2 - else random.randint(1, 10 * 1024 * 1024 * 1024) - ) - - def threshold_generator(always_on_prob, always_off_prob, min_val, max_val): def gen(): tmp = random.random() From d2360302cf52dd7cc556b297b87784d01044b83a Mon Sep 17 00:00:00 2001 From: guoxiaolong <467887319@qq.com> Date: Tue, 17 Oct 2023 15:33:21 +0800 Subject: [PATCH 003/253] clickhouse-test print nowTime in one test case. Because the end time of all test cases is long, adding a specific test case time to print can observe the specific execution status and progress --- tests/clickhouse-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index cab7d7e79ff..4ea8129f0c1 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1751,7 +1751,7 @@ def run_tests_array(all_tests_with_params: Tuple[List[str], int, TestSuite]): test_cace_name = removesuffix(test_case.name, ".gen", ".sql") + ": " if not is_concurrent: sys.stdout.flush() - sys.stdout.write(f"{test_cace_name:72}") + sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} {test_cace_name:72}") # This flush is needed so you can see the test name of the long # running test before it will finish. But don't do it in parallel # mode, so that the lines don't mix. From c552a912608ff213c331383da2805fcb245621de Mon Sep 17 00:00:00 2001 From: guoxiaolong <467887319@qq.com> Date: Tue, 17 Oct 2023 19:27:42 +0800 Subject: [PATCH 004/253] clickhouse-test case No --- tests/clickhouse-test | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 4ea8129f0c1..f70f8fb8a10 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1724,6 +1724,7 @@ def run_tests_array(all_tests_with_params: Tuple[List[str], int, TestSuite]): proc_name = multiprocessing.current_process().name print(f"\nRunning {about}{num_tests} {test_suite.suite} tests ({proc_name}).\n") + seria_num=1 while True: if is_concurrent: case = queue.get(timeout=args.timeout * 1.1) @@ -1751,7 +1752,7 @@ def run_tests_array(all_tests_with_params: Tuple[List[str], int, TestSuite]): test_cace_name = removesuffix(test_case.name, ".gen", ".sql") + ": " if not is_concurrent: sys.stdout.flush() - sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} {test_cace_name:72}") + sys.stdout.write(f"Case SN: {seria_num} | Current Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | Case Name: {test_cace_name:72}") # This flush is needed so you can see the test name of the long # running test before it will finish. But don't do it in parallel # mode, so that the lines don't mix. @@ -1797,6 +1798,7 @@ def run_tests_array(all_tests_with_params: Tuple[List[str], int, TestSuite]): if failures_chain >= args.max_failures_chain: stop_tests() break + seria_num += 1 if failures_total > 0: print( From edcda1ed6d199912ff5fe50f04caa595b8acc61c Mon Sep 17 00:00:00 2001 From: guoxiaolong <467887319@qq.com> Date: Wed, 18 Oct 2023 10:12:42 +0800 Subject: [PATCH 005/253] fix checkstyle --- tests/clickhouse-test | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index f70f8fb8a10..38f79a8c0b2 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1724,7 +1724,7 @@ def run_tests_array(all_tests_with_params: Tuple[List[str], int, TestSuite]): proc_name = multiprocessing.current_process().name print(f"\nRunning {about}{num_tests} {test_suite.suite} tests ({proc_name}).\n") - seria_num=1 + seria_num = 1 while True: if is_concurrent: case = queue.get(timeout=args.timeout * 1.1) @@ -1752,7 +1752,9 @@ def run_tests_array(all_tests_with_params: Tuple[List[str], int, TestSuite]): test_cace_name = removesuffix(test_case.name, ".gen", ".sql") + ": " if not is_concurrent: sys.stdout.flush() - sys.stdout.write(f"Case SN: {seria_num} | Current Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | Case Name: {test_cace_name:72}") + sys.stdout.write( + f"Case SN: {seria_num} | Current Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | Case Name: {test_cace_name:72}" + ) # This flush is needed so you can see the test name of the long # running test before it will finish. But don't do it in parallel # mode, so that the lines don't mix. From c59d7003924bdf2f2769db0b04558ecfe301b5a2 Mon Sep 17 00:00:00 2001 From: Sergey Suvorov Date: Mon, 30 Oct 2023 20:54:51 +0100 Subject: [PATCH 006/253] Add documentation for minSampleSizeConversion and minSampleSizeContinous --- .../functions/other-functions.md | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/docs/en/sql-reference/functions/other-functions.md b/docs/en/sql-reference/functions/other-functions.md index 6b092cf384d..db231166b74 100644 --- a/docs/en/sql-reference/functions/other-functions.md +++ b/docs/en/sql-reference/functions/other-functions.md @@ -2823,3 +2823,90 @@ Result: │ SELECT a, b FROM tab WHERE (a > 3) AND (b < 3) │ └─────────────────────────────────────────────────────────────────────────┘ ``` + +## minSampleSizeConversion + +Calculates minimum required sample size for an A/B test comparing conversions (proportions) in two samples. + +**Syntax** + +``` sql +minSampleSizeConversion(baseline, mde, power, alpha) +``` + +Uses the formula described in [this article](https://towardsdatascience.com/required-sample-size-for-a-b-testing-6f6608dd330a). Assumes equal sizes of treatment and control groups. Returns the sample size required for one group (i.e. the sample size required for the whole experiment is twice the returned value). + +**Arguments** + +- `baseline` — Baseline conversion. [Float](../data-types/float.md). +- `mde` — Minimum detectable effect (MDE) as percentage points (e.g. for a baseline conversion 0.25 the MDE 0.03 means an expected change to 0.25 ± 0.03). [Float](../data-types/float.md). +- `power` — Required statistical power of a test (1 - probability of Type II error). [Float](../data-types/float.md). +- `alpha` — Required significance level of a test (probability of Type I error). [Float](../data-types/float.md). + +**Returned value** + +A named [Tuple](../data-types/tuple.md) with 3 elements: + +- `"minimum_sample_size"` — Required sample size. [Float64](../data-types/float.md). +- `"detect_range_lower"` — Lower bound of the range of values not detectable with the returned required sample size (i.e. all values less than or equal to `"detect_range_lower"` are detectable with the provided `alpha` and `power`). Calculated as `baseline - mde`. [Float64](../data-types/float.md). +- `"detect_range_upper"` — Upper bound of the range of values not detectable with the returned required sample size (i.e. all values greater than or equal to `"detect_range_upper"` are detectable with the provided `alpha` and `power`). Calculated as `baseline + mde`. [Float64](../data-types/float.md). + +**Example** + +The following query calculates the required sample size for an A/B test with baseline conversion of 25%, MDE of 3%, significance level of 5%, and the desired statistical power of 80%: + +``` sql +SELECT minSampleSizeConversion(0.25, 0.03, 0.80, 0.05) AS sample_size; +``` + +Result: + +``` text +┌─sample_size───────────────────┐ +│ (3396.077603219163,0.22,0.28) │ +└───────────────────────────────┘ +``` + +## minSampleSizeContinous + +Calculates minimum required sample size for an A/B test comparing means of a continuous metric in two samples. + +**Syntax** + +``` sql +minSampleSizeContinous(baseline, sigma, mde, power, alpha) +``` + +Uses the formula described in [this article](https://towardsdatascience.com/required-sample-size-for-a-b-testing-6f6608dd330a). Assumes equal sizes of treatment and control groups. Returns the required sample size for one group (i.e. the sample size required for the whole experiment is twice the returned value). Also assumes equal variance of the test metric in treatment and control groups. + +**Arguments** + +- `baseline` — Baseline value of a metric. [Integer](../data-types/int-uint.md) or [Float](../data-types/float.md). +- `sigma` — Baseline standard deviation of a metric. [Integer](../data-types/int-uint.md) or [Float](../data-types/float.md). +- `mde` — Minimum detectable effect (MDE) as percentage of the baseline value (e.g. for a baseline value 112.25 the MDE 0.03 means an expected change to 112.25 ± 112.25*0.03). [Integer](../data-types/int-uint.md) or [Float](../data-types/float.md). +- `power` — Required statistical power of a test (1 - probability of Type II error). [Integer](../data-types/int-uint.md) or [Float](../data-types/float.md). +- `alpha` — Required significance level of a test (probability of Type I error). [Integer](../data-types/int-uint.md) or [Float](../data-types/float.md). + +**Returned value** + +A named [Tuple](../data-types/tuple.md) with 3 elements: + +- `"minimum_sample_size"` — Required sample size. [Float64](../data-types/float.md). +- `"detect_range_lower"` — Lower bound of the range of values not detectable with the returned required sample size (i.e. all values less than or equal to `"detect_range_lower"` are detectable with the provided `alpha` and `power`). Calculated as `baseline * (1 - mde)`. [Float64](../data-types/float.md). +- `"detect_range_upper"` — Upper bound of the range of values not detectable with the returned required sample size (i.e. all values greater than or equal to `"detect_range_upper"` are detectable with the provided `alpha` and `power`). Calculated as `baseline * (1 + mde)`. [Float64](../data-types/float.md). + +**Example** + +The following query calculates the required sample size for an A/B test on a metric with baseline value of 112.25, standard deviation of 21.1, MDE of 3%, significance level of 5%, and the desired statistical power of 80%: + +``` sql +SELECT minSampleSizeContinous(112.25, 21.1, 0.03, 0.80, 0.05) AS sample_size; +``` + +Result: + +``` text +┌─sample_size───────────────────────────┐ +│ (616.2931945826209,108.8825,115.6175) │ +└───────────────────────────────────────┘ +``` From 37843b0c285018c2500a50a5d4eff878271de2a8 Mon Sep 17 00:00:00 2001 From: Sergey Suvorov Date: Tue, 31 Oct 2023 13:05:41 +0100 Subject: [PATCH 007/253] Add alias for minSampleSizeContinuous after typo fix in #56143 --- docs/en/sql-reference/functions/other-functions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/sql-reference/functions/other-functions.md b/docs/en/sql-reference/functions/other-functions.md index db231166b74..202100c3832 100644 --- a/docs/en/sql-reference/functions/other-functions.md +++ b/docs/en/sql-reference/functions/other-functions.md @@ -2867,7 +2867,7 @@ Result: └───────────────────────────────┘ ``` -## minSampleSizeContinous +## minSampleSizeContinuous Calculates minimum required sample size for an A/B test comparing means of a continuous metric in two samples. @@ -2877,6 +2877,8 @@ Calculates minimum required sample size for an A/B test comparing means of a con minSampleSizeContinous(baseline, sigma, mde, power, alpha) ``` +Alias: `minSampleSizeContinous` + Uses the formula described in [this article](https://towardsdatascience.com/required-sample-size-for-a-b-testing-6f6608dd330a). Assumes equal sizes of treatment and control groups. Returns the required sample size for one group (i.e. the sample size required for the whole experiment is twice the returned value). Also assumes equal variance of the test metric in treatment and control groups. **Arguments** From 0a5888b6f852435f438f4fc9f389dabe375a8b28 Mon Sep 17 00:00:00 2001 From: Sergey Suvorov Date: Tue, 31 Oct 2023 17:04:37 +0100 Subject: [PATCH 008/253] Add new function names to aspell-dict.txt --- utils/check-style/aspell-ignore/en/aspell-dict.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/check-style/aspell-ignore/en/aspell-dict.txt b/utils/check-style/aspell-ignore/en/aspell-dict.txt index 6c4edc37e24..39b637238c0 100644 --- a/utils/check-style/aspell-ignore/en/aspell-dict.txt +++ b/utils/check-style/aspell-ignore/en/aspell-dict.txt @@ -1821,6 +1821,8 @@ mininum miniselect minmap minmax +minSampleSizeContinuous +minSampleSizeConversion mins misconfiguration mispredictions From 89fa729235106511423c8fde75a328f907f8cf1f Mon Sep 17 00:00:00 2001 From: Chen Lixiang Date: Fri, 24 Nov 2023 12:45:59 +0800 Subject: [PATCH 009/253] show uncompressed size in system.tables --- src/Storages/IStorage.h | 7 +++++++ src/Storages/MergeTree/IMergeTreeDataPart.cpp | 2 ++ src/Storages/MergeTree/IMergeTreeDataPart.h | 3 +++ src/Storages/MergeTree/MergeTreeDataPartChecksum.cpp | 9 +++++++++ src/Storages/MergeTree/MergeTreeDataPartChecksum.h | 1 + src/Storages/MergeTree/MergedBlockOutputStream.cpp | 1 + src/Storages/StorageMaterializedView.cpp | 10 ++++++++++ src/Storages/StorageMaterializedView.h | 1 + src/Storages/StorageMergeTree.cpp | 9 +++++++++ src/Storages/StorageMergeTree.h | 1 + src/Storages/StorageReplicatedMergeTree.cpp | 6 ++++++ src/Storages/StorageReplicatedMergeTree.h | 2 ++ src/Storages/System/StorageSystemTables.cpp | 10 ++++++++++ 13 files changed, 62 insertions(+) diff --git a/src/Storages/IStorage.h b/src/Storages/IStorage.h index e70e9a61062..26ad859983d 100644 --- a/src/Storages/IStorage.h +++ b/src/Storages/IStorage.h @@ -689,6 +689,13 @@ public: /// when considering in-memory blocks. virtual std::optional totalBytes(const Settings &) const { return {}; } + /// If it is possible to quickly determine exact number of uncompressed bytes for the table on storage: + /// - disk (uncompressed) + /// + /// Used for: + /// - For total_bytes_uncompressed column in system.tables + virtual std::optional totalBytesUncompressed(const Settings &) const { return {}; } + /// Number of rows INSERTed since server start. /// /// Does not take the underlying Storage (if any) into account. diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 9bc72577b25..2b71faa4cd9 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1116,6 +1116,7 @@ void IMergeTreeDataPart::loadChecksums(bool require) { assertEOF(*buf); bytes_on_disk = checksums.getTotalSizeOnDisk(); + bytes_uncompressed_on_disk = checksums.getTotalSizeUncompressedOnDisk(); } else bytes_on_disk = getDataPartStorage().calculateTotalSizeOnDisk(); @@ -1133,6 +1134,7 @@ void IMergeTreeDataPart::loadChecksums(bool require) writeChecksums(checksums, {}); bytes_on_disk = checksums.getTotalSizeOnDisk(); + bytes_uncompressed_on_disk = checksums.getTotalSizeUncompressedOnDisk(); } } diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index a9659d2f5f4..03d81a570d6 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -364,7 +364,9 @@ public: UInt64 getIndexSizeFromFile() const; UInt64 getBytesOnDisk() const { return bytes_on_disk; } + UInt64 getBytesUncompressedOnDisk() const { return bytes_uncompressed_on_disk; } void setBytesOnDisk(UInt64 bytes_on_disk_) { bytes_on_disk = bytes_on_disk_; } + void setBytesUncompressedOnDisk(UInt64 bytes_uncompressed_on_disk_) { bytes_uncompressed_on_disk = bytes_uncompressed_on_disk_; } size_t getFileSizeOrZero(const String & file_name) const; @@ -554,6 +556,7 @@ protected: /// Total size on disk, not only columns. May not contain size of /// checksums.txt and columns.txt. 0 - if not counted; UInt64 bytes_on_disk{0}; + UInt64 bytes_uncompressed_on_disk{0}; /// Columns description. Cannot be changed, after part initialization. NamesAndTypesList columns; diff --git a/src/Storages/MergeTree/MergeTreeDataPartChecksum.cpp b/src/Storages/MergeTree/MergeTreeDataPartChecksum.cpp index ed2202fcb19..c5d3865a72d 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartChecksum.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartChecksum.cpp @@ -1,5 +1,6 @@ #include "MergeTreeDataPartChecksum.h" #include +#include "base/types.h" #include #include #include @@ -100,6 +101,14 @@ UInt64 MergeTreeDataPartChecksums::getTotalSizeOnDisk() const return res; } +UInt64 MergeTreeDataPartChecksums::getTotalSizeUncompressedOnDisk() const +{ + UInt64 res = 0; + for (const auto & [_, checksum] : files) + res += checksum.uncompressed_size; + return res; +} + bool MergeTreeDataPartChecksums::read(ReadBuffer & in, size_t format_version) { switch (format_version) diff --git a/src/Storages/MergeTree/MergeTreeDataPartChecksum.h b/src/Storages/MergeTree/MergeTreeDataPartChecksum.h index 8e5e8c8c448..837b940e354 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartChecksum.h +++ b/src/Storages/MergeTree/MergeTreeDataPartChecksum.h @@ -88,6 +88,7 @@ struct MergeTreeDataPartChecksums static MergeTreeDataPartChecksums deserializeFrom(const String & s); UInt64 getTotalSizeOnDisk() const; + UInt64 getTotalSizeUncompressedOnDisk() const; }; /// A kind of MergeTreeDataPartChecksums intended to be stored in ZooKeeper (to save its RAM) diff --git a/src/Storages/MergeTree/MergedBlockOutputStream.cpp b/src/Storages/MergeTree/MergedBlockOutputStream.cpp index adea78429c4..cf3ef716eb1 100644 --- a/src/Storages/MergeTree/MergedBlockOutputStream.cpp +++ b/src/Storages/MergeTree/MergedBlockOutputStream.cpp @@ -182,6 +182,7 @@ MergedBlockOutputStream::Finalizer MergedBlockOutputStream::finalizePartAsync( new_part->index = writer->releaseIndexColumns(); new_part->checksums = checksums; new_part->setBytesOnDisk(checksums.getTotalSizeOnDisk()); + new_part->setBytesUncompressedOnDisk(checksums.getTotalSizeUncompressedOnDisk()); new_part->index_granularity = writer->getIndexGranularity(); new_part->calculateColumnsAndSecondaryIndicesSizesOnDisk(); diff --git a/src/Storages/StorageMaterializedView.cpp b/src/Storages/StorageMaterializedView.cpp index 97cfd550769..c5941322f20 100644 --- a/src/Storages/StorageMaterializedView.cpp +++ b/src/Storages/StorageMaterializedView.cpp @@ -477,6 +477,16 @@ std::optional StorageMaterializedView::totalBytes(const Settings & setti return {}; } +std::optional StorageMaterializedView::totalBytesUncompressed(const Settings & settings) const +{ + if (hasInnerTable()) + { + if (auto table = tryGetTargetTable()) + return table->totalBytesUncompressed(settings); + } + return {}; +} + ActionLock StorageMaterializedView::getActionLock(StorageActionBlockType type) { if (has_inner_table) diff --git a/src/Storages/StorageMaterializedView.h b/src/Storages/StorageMaterializedView.h index ae38cfb7e59..55843197d50 100644 --- a/src/Storages/StorageMaterializedView.h +++ b/src/Storages/StorageMaterializedView.h @@ -103,6 +103,7 @@ public: std::optional totalRows(const Settings & settings) const override; std::optional totalBytes(const Settings & settings) const override; + std::optional totalBytesUncompressed(const Settings & settings) const override; private: /// Will be initialized in constructor diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index e9a0dd5fbf3..d9aa7d261cc 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -271,6 +271,15 @@ std::optional StorageMergeTree::totalBytes(const Settings &) const return getTotalActiveSizeInBytes(); } +std::optional StorageMergeTree::totalBytesUncompressed(const Settings &) const +{ + UInt64 res = 0; + auto parts = getDataPartsForInternalUsage(); + for (const auto & part : parts) + res += part->getBytesUncompressedOnDisk(); + return res; +} + SinkToStoragePtr StorageMergeTree::write(const ASTPtr & /*query*/, const StorageMetadataPtr & metadata_snapshot, ContextPtr local_context, bool /*async_insert*/) { diff --git a/src/Storages/StorageMergeTree.h b/src/Storages/StorageMergeTree.h index 539037a90ae..a0d0a2519a0 100644 --- a/src/Storages/StorageMergeTree.h +++ b/src/Storages/StorageMergeTree.h @@ -70,6 +70,7 @@ public: std::optional totalRows(const Settings &) const override; std::optional totalRowsByPartitionPredicate(const SelectQueryInfo &, ContextPtr) const override; std::optional totalBytes(const Settings &) const override; + std::optional totalBytesUncompressed(const Settings &) const override; SinkToStoragePtr write(const ASTPtr & query, const StorageMetadataPtr & /*metadata_snapshot*/, ContextPtr context, bool async_insert) override; diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index f841ca21938..576e6fde1c8 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -5459,6 +5459,12 @@ std::optional StorageReplicatedMergeTree::totalBytes(const Settings & se return res; } +std::optional StorageReplicatedMergeTree::totalBytesUncompressed(const Settings & settings) const +{ + UInt64 res = 0; + foreachActiveParts([&res](auto & part) { res += part->getBytesUncompressedOnDisk(); }, settings.select_sequential_consistency); + return res; +} void StorageReplicatedMergeTree::assertNotReadonly() const { diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index b2a67572adc..a1d222db57a 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -165,6 +166,7 @@ public: std::optional totalRows(const Settings & settings) const override; std::optional totalRowsByPartitionPredicate(const SelectQueryInfo & query_info, ContextPtr context) const override; std::optional totalBytes(const Settings & settings) const override; + std::optional totalBytesUncompressed(const Settings & settings) const override; SinkToStoragePtr write(const ASTPtr & query, const StorageMetadataPtr & /*metadata_snapshot*/, ContextPtr context, bool async_insert) override; diff --git a/src/Storages/System/StorageSystemTables.cpp b/src/Storages/System/StorageSystemTables.cpp index d888813f6ce..267fbfc55e9 100644 --- a/src/Storages/System/StorageSystemTables.cpp +++ b/src/Storages/System/StorageSystemTables.cpp @@ -51,6 +51,7 @@ StorageSystemTables::StorageSystemTables(const StorageID & table_id_) {"storage_policy", std::make_shared()}, {"total_rows", std::make_shared(std::make_shared())}, {"total_bytes", std::make_shared(std::make_shared())}, + {"total_bytes_uncompressed", std::make_shared(std::make_shared())}, {"parts", std::make_shared(std::make_shared())}, {"active_parts", std::make_shared(std::make_shared())}, {"total_marks", std::make_shared(std::make_shared())}, @@ -488,6 +489,15 @@ protected: res_columns[res_index++]->insertDefault(); } + if (columns_mask[src_index++]) + { + auto total_bytes_uncompressed = table->totalBytesUncompressed(settings); + if (total_bytes_uncompressed) + res_columns[res_index++]->insert(*total_bytes_uncompressed); + else + res_columns[res_index++]->insertDefault(); + } + auto table_merge_tree = std::dynamic_pointer_cast(table); if (columns_mask[src_index++]) { From 2b35e98ef856258fbc0700926e9918882b8825b4 Mon Sep 17 00:00:00 2001 From: Chen Lixiang Date: Fri, 24 Nov 2023 16:20:11 +0800 Subject: [PATCH 010/253] add comments and doc --- docs/en/operations/system-tables/tables.md | 2 ++ src/Storages/IStorage.h | 2 ++ src/Storages/MergeTree/MergeTreeDataPartChecksum.cpp | 1 - src/Storages/StorageReplicatedMergeTree.h | 1 - 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/en/operations/system-tables/tables.md b/docs/en/operations/system-tables/tables.md index e4461e14236..231be6404a3 100644 --- a/docs/en/operations/system-tables/tables.md +++ b/docs/en/operations/system-tables/tables.md @@ -57,6 +57,8 @@ Columns: - If the table stores data on disk, returns used space on disk (i.e. compressed). - If the table stores data in memory, returns approximated number of used bytes in memory. +- `total_bytes_uncompressed` ([Nullable](../../sql-reference/data-types/nullable.md)([UInt64](../../sql-reference/data-types/int-uint.md))) - Total number of uncompressed bytes, if it is possible to quickly determine exact number of bytes from checksums for the table on storage, otherwise `NULL` (does not includes any underlying storage). + - `lifetime_rows` ([Nullable](../../sql-reference/data-types/nullable.md)([UInt64](../../sql-reference/data-types/int-uint.md))) - Total number of rows INSERTed since server start (only for `Buffer` tables). - `lifetime_bytes` ([Nullable](../../sql-reference/data-types/nullable.md)([UInt64](../../sql-reference/data-types/int-uint.md))) - Total number of bytes INSERTed since server start (only for `Buffer` tables). diff --git a/src/Storages/IStorage.h b/src/Storages/IStorage.h index 26ad859983d..6bae60bbfd6 100644 --- a/src/Storages/IStorage.h +++ b/src/Storages/IStorage.h @@ -694,6 +694,8 @@ public: /// /// Used for: /// - For total_bytes_uncompressed column in system.tables + /// + /// Does not takes underlying Storage (if any) into account virtual std::optional totalBytesUncompressed(const Settings &) const { return {}; } /// Number of rows INSERTed since server start. diff --git a/src/Storages/MergeTree/MergeTreeDataPartChecksum.cpp b/src/Storages/MergeTree/MergeTreeDataPartChecksum.cpp index c5d3865a72d..ef7d38df4b2 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartChecksum.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartChecksum.cpp @@ -1,6 +1,5 @@ #include "MergeTreeDataPartChecksum.h" #include -#include "base/types.h" #include #include #include diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index a1d222db57a..df68572e1e4 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -2,7 +2,6 @@ #include #include -#include #include #include #include From 51e3899f73c0581a4fe19c09dd69b8f93c86d36a Mon Sep 17 00:00:00 2001 From: Tim Windelschmidt Date: Wed, 13 Sep 2023 18:47:33 +0200 Subject: [PATCH 011/253] Add setting http_make_head_request Clickhouse always does a HEAD request before actually executing the GET request, this adds a settings to skip that request. --- docs/en/operations/settings/settings.md | 11 +++++++++++ src/Core/Settings.h | 1 + src/IO/ReadSettings.h | 1 + src/IO/ReadWriteBufferFromHTTP.cpp | 5 +++++ src/Interpreters/Context.cpp | 1 + 5 files changed, 19 insertions(+) diff --git a/docs/en/operations/settings/settings.md b/docs/en/operations/settings/settings.md index edc1c9bdfd7..39542acdaae 100644 --- a/docs/en/operations/settings/settings.md +++ b/docs/en/operations/settings/settings.md @@ -460,6 +460,17 @@ Possible values: Default value: 1048576. +## http_make_head_request {#http-make-head-request} + +Enables or disables execution of a HEAD request before the actual GET request + +Possible values: + +- 0 — Disabled. +- 1 — Enabled. + +Default value: 1. + ## table_function_remote_max_addresses {#table_function_remote_max_addresses} Sets the maximum number of addresses generated from patterns for the [remote](../../sql-reference/table-functions/remote.md) function. diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 448656aa435..c29a4ef3750 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -333,6 +333,7 @@ class IColumn; M(UInt64, http_max_field_value_size, 128 * 1024, "Maximum length of field value in HTTP header", 0) \ M(UInt64, http_max_chunk_size, 100_GiB, "Maximum value of a chunk size in HTTP chunked transfer encoding", 0) \ M(Bool, http_skip_not_found_url_for_globs, true, "Skip url's for globs with HTTP_NOT_FOUND error", 0) \ + M(Bool, http_make_head_request, true, "If it is set to true, execute a HEAD request before the actual GET request", 0) \ M(Bool, optimize_throw_if_noop, false, "If setting is enabled and OPTIMIZE query didn't actually assign a merge then an explanatory exception is thrown", 0) \ M(Bool, use_index_for_in_with_subqueries, true, "Try using an index if there is a subquery or a table expression on the right side of the IN operator.", 0) \ M(UInt64, use_index_for_in_with_subqueries_max_values, 0, "The maximum size of set in the right hand side of the IN operator to use table index for filtering. It allows to avoid performance degradation and higher memory usage due to preparation of additional data structures for large queries. Zero means no limit.", 0) \ diff --git a/src/IO/ReadSettings.h b/src/IO/ReadSettings.h index 4c8a6cb020a..a8a31d82e56 100644 --- a/src/IO/ReadSettings.h +++ b/src/IO/ReadSettings.h @@ -120,6 +120,7 @@ struct ReadSettings size_t http_retry_initial_backoff_ms = 100; size_t http_retry_max_backoff_ms = 1600; bool http_skip_not_found_url_for_globs = true; + bool http_make_head_request = true; /// Monitoring bool for_object_storage = false; // to choose which profile events should be incremented diff --git a/src/IO/ReadWriteBufferFromHTTP.cpp b/src/IO/ReadWriteBufferFromHTTP.cpp index 6dd6269e16f..d54e60e9950 100644 --- a/src/IO/ReadWriteBufferFromHTTP.cpp +++ b/src/IO/ReadWriteBufferFromHTTP.cpp @@ -808,6 +808,11 @@ std::optional ReadWriteBufferFromHTTPBase::tryGetLa template HTTPFileInfo ReadWriteBufferFromHTTPBase::getFileInfo() { + if (!settings.http_make_head_request) + { + return HTTPFileInfo{}; + } + Poco::Net::HTTPResponse response; try { diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 185f9782da5..ba45e7c064e 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -4843,6 +4843,7 @@ ReadSettings Context::getReadSettings() const res.http_retry_initial_backoff_ms = settings.http_retry_initial_backoff_ms; res.http_retry_max_backoff_ms = settings.http_retry_max_backoff_ms; res.http_skip_not_found_url_for_globs = settings.http_skip_not_found_url_for_globs; + res.http_make_head_request = settings.http_make_head_request; res.mmap_cache = getMMappedFileCache().get(); From 1044e3366b392fd233634ddd85588c8a538e05ad Mon Sep 17 00:00:00 2001 From: vdimir Date: Fri, 24 Nov 2023 10:29:01 +0100 Subject: [PATCH 012/253] Update http_make_head_request doc --- docs/en/operations/settings/settings.md | 9 ++------- src/Core/Settings.h | 2 +- src/IO/ReadWriteBufferFromHTTP.cpp | 6 +++--- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/en/operations/settings/settings.md b/docs/en/operations/settings/settings.md index 39542acdaae..4bbac98a579 100644 --- a/docs/en/operations/settings/settings.md +++ b/docs/en/operations/settings/settings.md @@ -462,14 +462,9 @@ Default value: 1048576. ## http_make_head_request {#http-make-head-request} -Enables or disables execution of a HEAD request before the actual GET request +The `http_make_head_request` setting allows the execution of a `HEAD` request while reading data from HTTP to retrieve information about the file to be read, such as its size. Since it's enabled by default, it may be desirable to disable this setting in cases where the server does not support `HEAD` requests. -Possible values: - -- 0 — Disabled. -- 1 — Enabled. - -Default value: 1. +Default value: `true`. ## table_function_remote_max_addresses {#table_function_remote_max_addresses} diff --git a/src/Core/Settings.h b/src/Core/Settings.h index c29a4ef3750..03d5853261c 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -333,7 +333,7 @@ class IColumn; M(UInt64, http_max_field_value_size, 128 * 1024, "Maximum length of field value in HTTP header", 0) \ M(UInt64, http_max_chunk_size, 100_GiB, "Maximum value of a chunk size in HTTP chunked transfer encoding", 0) \ M(Bool, http_skip_not_found_url_for_globs, true, "Skip url's for globs with HTTP_NOT_FOUND error", 0) \ - M(Bool, http_make_head_request, true, "If it is set to true, execute a HEAD request before the actual GET request", 0) \ + M(Bool, http_make_head_request, true, "Allows the execution of a `HEAD` request while reading data from HTTP to retrieve information about the file to be read, such as its size", 0) \ M(Bool, optimize_throw_if_noop, false, "If setting is enabled and OPTIMIZE query didn't actually assign a merge then an explanatory exception is thrown", 0) \ M(Bool, use_index_for_in_with_subqueries, true, "Try using an index if there is a subquery or a table expression on the right side of the IN operator.", 0) \ M(UInt64, use_index_for_in_with_subqueries_max_values, 0, "The maximum size of set in the right hand side of the IN operator to use table index for filtering. It allows to avoid performance degradation and higher memory usage due to preparation of additional data structures for large queries. Zero means no limit.", 0) \ diff --git a/src/IO/ReadWriteBufferFromHTTP.cpp b/src/IO/ReadWriteBufferFromHTTP.cpp index d54e60e9950..96f05889882 100644 --- a/src/IO/ReadWriteBufferFromHTTP.cpp +++ b/src/IO/ReadWriteBufferFromHTTP.cpp @@ -808,10 +808,10 @@ std::optional ReadWriteBufferFromHTTPBase::tryGetLa template HTTPFileInfo ReadWriteBufferFromHTTPBase::getFileInfo() { + /// May be disabled in case the user knows in advance that the server doesn't support HEAD requests. + /// Allows to avoid making unnecessary requests in such cases. if (!settings.http_make_head_request) - { - return HTTPFileInfo{}; - } + return HTTPFileInfo{}; Poco::Net::HTTPResponse response; try From 3f8cfa0060cedf03dfa94dee5e0c5465d5df5a29 Mon Sep 17 00:00:00 2001 From: vdimir Date: Fri, 24 Nov 2023 09:35:48 +0000 Subject: [PATCH 013/253] Add http_make_head_request function to SettingsRandomizer --- tests/clickhouse-test | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 4b551ed3663..60416521ed1 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -631,6 +631,7 @@ class SettingsRandomizer: get_localzone(), ] ), + "http_make_head_request": lambda: random.randint(0, 1), } @staticmethod From 67f0a0e3e44649c8ce46ec31e1fda329b9c132e6 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 28 Nov 2023 23:57:55 +0100 Subject: [PATCH 014/253] don't throw if noop when dropping db replica in batch --- src/Databases/DatabaseReplicated.cpp | 6 +++++- src/Databases/DatabaseReplicated.h | 2 +- src/Interpreters/InterpreterSystemQuery.cpp | 6 +++--- tests/queries/0_stateless/02447_drop_database_replica.sh | 8 ++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Databases/DatabaseReplicated.cpp b/src/Databases/DatabaseReplicated.cpp index 5da20c42465..0ebc5aa6023 100644 --- a/src/Databases/DatabaseReplicated.cpp +++ b/src/Databases/DatabaseReplicated.cpp @@ -1175,7 +1175,7 @@ ASTPtr DatabaseReplicated::parseQueryFromMetadataInZooKeeper(const String & node } void DatabaseReplicated::dropReplica( - DatabaseReplicated * database, const String & database_zookeeper_path, const String & shard, const String & replica) + DatabaseReplicated * database, const String & database_zookeeper_path, const String & shard, const String & replica, bool throw_if_noop) { assert(!database || database_zookeeper_path == database->zookeeper_path); @@ -1192,8 +1192,12 @@ void DatabaseReplicated::dropReplica( String database_replica_path = fs::path(database_zookeeper_path) / "replicas" / full_replica_name; if (!zookeeper->exists(database_replica_path)) + { + if (!throw_if_noop) + return; throw Exception(ErrorCodes::BAD_ARGUMENTS, "Replica {} does not exist (database path: {})", full_replica_name, database_zookeeper_path); + } if (zookeeper->exists(database_replica_path + "/active")) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Replica {} is active, cannot drop it (database path: {})", diff --git a/src/Databases/DatabaseReplicated.h b/src/Databases/DatabaseReplicated.h index 1387ba1cb96..5a90cf9c8b7 100644 --- a/src/Databases/DatabaseReplicated.h +++ b/src/Databases/DatabaseReplicated.h @@ -81,7 +81,7 @@ public: bool shouldReplicateQuery(const ContextPtr & query_context, const ASTPtr & query_ptr) const override; - static void dropReplica(DatabaseReplicated * database, const String & database_zookeeper_path, const String & shard, const String & replica); + static void dropReplica(DatabaseReplicated * database, const String & database_zookeeper_path, const String & shard, const String & replica, bool throw_if_noop); std::vector tryGetAreReplicasActive(const ClusterPtr & cluster_) const; diff --git a/src/Interpreters/InterpreterSystemQuery.cpp b/src/Interpreters/InterpreterSystemQuery.cpp index 9c3db6cfdbd..6623ece8d6d 100644 --- a/src/Interpreters/InterpreterSystemQuery.cpp +++ b/src/Interpreters/InterpreterSystemQuery.cpp @@ -954,7 +954,7 @@ void InterpreterSystemQuery::dropDatabaseReplica(ASTSystemQuery & query) if (auto * replicated = dynamic_cast(database.get())) { check_not_local_replica(replicated, query); - DatabaseReplicated::dropReplica(replicated, replicated->getZooKeeperPath(), query.shard, query.replica); + DatabaseReplicated::dropReplica(replicated, replicated->getZooKeeperPath(), query.shard, query.replica, /*throw_if_noop*/ true); } else throw Exception(ErrorCodes::BAD_ARGUMENTS, "Database {} is not Replicated, cannot drop replica", query.getDatabase()); @@ -979,7 +979,7 @@ void InterpreterSystemQuery::dropDatabaseReplica(ASTSystemQuery & query) } check_not_local_replica(replicated, query); - DatabaseReplicated::dropReplica(replicated, replicated->getZooKeeperPath(), query.shard, query.replica); + DatabaseReplicated::dropReplica(replicated, replicated->getZooKeeperPath(), query.shard, query.replica, /*throw_if_noop*/ false); LOG_TRACE(log, "Dropped replica {} of Replicated database {}", query.replica, backQuoteIfNeed(database->getDatabaseName())); } } @@ -992,7 +992,7 @@ void InterpreterSystemQuery::dropDatabaseReplica(ASTSystemQuery & query) if (auto * replicated = dynamic_cast(elem.second.get())) check_not_local_replica(replicated, query); - DatabaseReplicated::dropReplica(nullptr, query.replica_zk_path, query.shard, query.replica); + DatabaseReplicated::dropReplica(nullptr, query.replica_zk_path, query.shard, query.replica, /*throw_if_noop*/ true); LOG_INFO(log, "Dropped replica {} of Replicated database with path {}", query.replica, query.replica_zk_path); } else diff --git a/tests/queries/0_stateless/02447_drop_database_replica.sh b/tests/queries/0_stateless/02447_drop_database_replica.sh index 47a6cf10bda..d5b3ceef46a 100755 --- a/tests/queries/0_stateless/02447_drop_database_replica.sh +++ b/tests/queries/0_stateless/02447_drop_database_replica.sh @@ -55,7 +55,15 @@ $CLICKHOUSE_CLIENT --allow_experimental_database_replicated=1 -q "create databas $CLICKHOUSE_CLIENT -q "system sync database replica $db4" $CLICKHOUSE_CLIENT -q "select cluster, shard_num, replica_num, database_shard_name, database_replica_name, is_active from system.clusters where cluster='$db4'" +# Don't throw "replica doesn't exist" when removing all replicas [from a database] +$CLICKHOUSE_CLIENT -q "system drop database replica 'doesntexist$CLICKHOUSE_DATABASE' from shard 'doesntexist'" + $CLICKHOUSE_CLIENT -q "drop database $db" $CLICKHOUSE_CLIENT -q "drop database $db2" $CLICKHOUSE_CLIENT -q "drop database $db3" + +$CLICKHOUSE_CLIENT --distributed_ddl_output_mode=none -q "create table $db4.rmt (n int) engine=ReplicatedMergeTree order by n" +$CLICKHOUSE_CLIENT -q "system drop replica 'doesntexist$CLICKHOUSE_DATABASE' from database $db4" +$CLICKHOUSE_CLIENT -q "system drop replica 'doesntexist$CLICKHOUSE_DATABASE'" + $CLICKHOUSE_CLIENT -q "drop database $db4" From 88920f08f131ccca14aa8e1e394264e73725181b Mon Sep 17 00:00:00 2001 From: Chen Lixiang Date: Wed, 29 Nov 2023 17:09:56 +0800 Subject: [PATCH 015/253] add tests and fix whitespace issue --- src/Storages/IStorage.h | 4 ++-- ...m_columns_and_system_tables_long.reference | 5 +++- ..._system_columns_and_system_tables_long.sql | 24 ++++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/Storages/IStorage.h b/src/Storages/IStorage.h index 6bae60bbfd6..db979f38535 100644 --- a/src/Storages/IStorage.h +++ b/src/Storages/IStorage.h @@ -696,8 +696,8 @@ public: /// - For total_bytes_uncompressed column in system.tables /// /// Does not takes underlying Storage (if any) into account - virtual std::optional totalBytesUncompressed(const Settings &) const { return {}; } - + virtual std::optional totalBytesUncompressed(const Settings &) const { return {}; } + /// Number of rows INSERTed since server start. /// /// Does not take the underlying Storage (if any) into account. diff --git a/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.reference b/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.reference index 58f8b7abfb3..9a7bb683cde 100644 --- a/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.reference +++ b/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.reference @@ -6,7 +6,7 @@ │ name2 │ 1 │ 0 │ 0 │ 0 │ │ name3 │ 0 │ 0 │ 0 │ 0 │ └───────┴─────────────────────┴───────────────────┴───────────────────┴────────────────────┘ -231 1 +3 231 1 ┌─name────────────────┬─partition_key─┬─sorting_key───┬─primary_key─┬─sampling_key─┐ │ check_system_tables │ date │ date, version │ date │ │ └─────────────────────┴───────────────┴───────────────┴─────────────┴──────────────┘ @@ -51,3 +51,6 @@ Check total_bytes/total_rows for Set Check total_bytes/total_rows for Join 10240 50 10240 100 +Check total_uncompressed_bytes/total_bytes/total_rows for Materialized views +0 0 0 +117 397 1 diff --git a/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql b/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql index 16085c8a995..0ffcc77a405 100644 --- a/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql +++ b/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql @@ -23,7 +23,7 @@ FROM system.columns WHERE table = 'check_system_tables' AND database = currentDa FORMAT PrettyCompactNoEscapes; INSERT INTO check_system_tables VALUES (1, 1, 1); -SELECT total_bytes, total_rows FROM system.tables WHERE name = 'check_system_tables' AND database = currentDatabase(); +SELECT total_bytes_uncompressed, total_bytes, total_rows FROM system.tables WHERE name = 'check_system_tables' AND database = currentDatabase(); DROP TABLE IF EXISTS check_system_tables; @@ -68,6 +68,8 @@ FORMAT PrettyCompactNoEscapes; DROP TABLE IF EXISTS check_system_tables; + + SELECT 'Check total_bytes/total_rows for TinyLog'; CREATE TABLE check_system_tables (key UInt8) ENGINE = TinyLog(); SELECT total_bytes, total_rows FROM system.tables WHERE name = 'check_system_tables' AND database = currentDatabase(); @@ -138,3 +140,23 @@ SELECT total_bytes, total_rows FROM system.tables WHERE name = 'check_system_tab INSERT INTO check_system_tables SELECT number+50 FROM numbers(50); SELECT total_bytes, total_rows FROM system.tables WHERE name = 'check_system_tables' AND database = currentDatabase(); DROP TABLE check_system_tables; + +-- Build MergeTree table for Materialized view +CREATE TABLE check_system_tables + ( + name1 UInt8, + name2 UInt8, + name3 UInt8 + ) ENGINE = MergeTree() + ORDER BY name1 + PARTITION BY name2 + SAMPLE BY name1 + SETTINGS min_bytes_for_wide_part = 0, compress_marks = false, compress_primary_key = false, ratio_of_defaults_for_sparse_serialization = 1; + +SELECT 'Check total_uncompressed_bytes/total_bytes/total_rows for Materialized views'; +CREATE MATERIALIZED VIEW check_system_tables_mv ENGINE = MergeTree() ORDER BY name2 AS SELECT name1, name2, name3 FROM check_system_tables; +SELECT total_bytes_uncompressed, total_bytes, total_rows FROM system.tables WHERE name = 'check_system_tables_mv' AND database = currentDatabase(); +INSERT INTO check_system_tables VALUES (1, 1, 1); +SELECT total_bytes_uncompressed, total_bytes, total_rows FROM system.tables WHERE name = 'check_system_tables_mv' AND database = currentDatabase(); +DROP TABLE check_system_tables_mv; +DROP TABLE check_system_tables; From 9f849fbe06fec0ba6b9ac9c37bb259ca214d3457 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 29 Nov 2023 17:12:04 +0100 Subject: [PATCH 016/253] fix --- src/Databases/DatabaseReplicated.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Databases/DatabaseReplicated.cpp b/src/Databases/DatabaseReplicated.cpp index 0ebc5aa6023..7104c53de43 100644 --- a/src/Databases/DatabaseReplicated.cpp +++ b/src/Databases/DatabaseReplicated.cpp @@ -1186,7 +1186,10 @@ void DatabaseReplicated::dropReplica( auto zookeeper = Context::getGlobalContextInstance()->getZooKeeper(); - String database_mark = zookeeper->get(database_zookeeper_path); + String database_mark; + bool db_path_exists = zookeeper->tryGet(database_zookeeper_path, database_mark); + if (!db_path_exists && !throw_if_noop) + return; if (database_mark != REPLICATED_DATABASE_MARK) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Path {} does not look like a path of Replicated database", database_zookeeper_path); From 7fbe7462b68bc7c7a6c8145d0f8612424b17550a Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 1 Dec 2023 19:12:05 +0000 Subject: [PATCH 017/253] add mutation command to apply deleted mask --- src/Interpreters/InterpreterAlterQuery.cpp | 2 + src/Interpreters/MutationsInterpreter.cpp | 147 ++++++++++++------ src/Interpreters/MutationsInterpreter.h | 3 + src/Parsers/ASTAlterQuery.cpp | 16 ++ src/Parsers/ASTAlterQuery.h | 1 + src/Parsers/ParserAlterQuery.cpp | 17 ++ src/Storages/MergeTree/MutateTask.cpp | 47 +++--- src/Storages/MutationCommands.cpp | 9 ++ src/Storages/MutationCommands.h | 1 + src/Storages/StorageSnapshot.cpp | 1 + .../02932_apply_deleted_mask.reference | 6 + .../0_stateless/02932_apply_deleted_mask.sql | 22 +++ 12 files changed, 207 insertions(+), 65 deletions(-) create mode 100644 tests/queries/0_stateless/02932_apply_deleted_mask.reference create mode 100644 tests/queries/0_stateless/02932_apply_deleted_mask.sql diff --git a/src/Interpreters/InterpreterAlterQuery.cpp b/src/Interpreters/InterpreterAlterQuery.cpp index 96bb7615416..0afdb3bab57 100644 --- a/src/Interpreters/InterpreterAlterQuery.cpp +++ b/src/Interpreters/InterpreterAlterQuery.cpp @@ -155,6 +155,7 @@ BlockIO InterpreterAlterQuery::executeToTable(const ASTAlterQuery & alter) } else throw Exception(ErrorCodes::LOGICAL_ERROR, "Wrong parameter type in ALTER query"); + if (!getContext()->getSettings().allow_experimental_statistic && ( command_ast->type == ASTAlterCommand::ADD_STATISTIC || command_ast->type == ASTAlterCommand::DROP_STATISTIC || @@ -407,6 +408,7 @@ AccessRightsElements InterpreterAlterQuery::getRequiredAccessForCommand(const AS break; } case ASTAlterCommand::DELETE: + case ASTAlterCommand::APPLY_DELETED_MASK: case ASTAlterCommand::DROP_PARTITION: case ASTAlterCommand::DROP_DETACHED_PARTITION: { diff --git a/src/Interpreters/MutationsInterpreter.cpp b/src/Interpreters/MutationsInterpreter.cpp index 8e56b08f1ed..4cef15f6220 100644 --- a/src/Interpreters/MutationsInterpreter.cpp +++ b/src/Interpreters/MutationsInterpreter.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -153,19 +154,29 @@ bool isStorageTouchedByMutations( return false; bool all_commands_can_be_skipped = true; - for (const MutationCommand & command : commands) + for (const auto & command : commands) { - if (!command.predicate) /// The command touches all rows. - return true; - - if (command.partition) + if (command.type == MutationCommand::APPLY_DELETED_MASK) { - const String partition_id = storage.getPartitionIDFromQuery(command.partition, context); - if (partition_id == source_part->info.partition_id) - all_commands_can_be_skipped = false; + if (source_part->hasLightweightDelete()) + return true; } else - all_commands_can_be_skipped = false; + { + if (!command.predicate) /// The command touches all rows. + return true; + + if (command.partition) + { + const String partition_id = storage.getPartitionIDFromQuery(command.partition, context); + if (partition_id == source_part->info.partition_id) + all_commands_can_be_skipped = false; + } + else + { + all_commands_can_be_skipped = false; + } + } } if (all_commands_can_be_skipped) @@ -211,7 +222,6 @@ bool isStorageTouchedByMutations( return count != 0; } - ASTPtr getPartitionAndPredicateExpressionForMutationCommand( const MutationCommand & command, const StoragePtr & storage, @@ -244,6 +254,32 @@ ASTPtr getPartitionAndPredicateExpressionForMutationCommand( return command.predicate ? command.predicate->clone() : partition_predicate_as_ast_func; } + +MutationCommand createCommandToApplyDeletedMask(const MutationCommand & command) +{ + if (command.type != MutationCommand::APPLY_DELETED_MASK) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Expected APPLY_DELETED_MASK mutation command, got: {}", magic_enum::enum_name(command.type)); + + auto alter_command = std::make_shared(); + alter_command->type = ASTAlterCommand::DELETE; + alter_command->partition = command.partition; + + auto row_exists_predicate = makeASTFunction("equals", + std::make_shared(LightweightDeleteDescription::FILTER_COLUMN.name), + std::make_shared(Field(0))); + + if (command.predicate) + alter_command->predicate = makeASTFunction("and", row_exists_predicate, command.predicate); + else + alter_command->predicate = row_exists_predicate; + + auto mutation_command = MutationCommand::parse(alter_command.get()); + if (!mutation_command) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Failed to parse command {}. It's a bug", queryToString(alter_command)); + + return *mutation_command; +} + MutationsInterpreter::Source::Source(StoragePtr storage_) : storage(std::move(storage_)) { } @@ -517,15 +553,18 @@ void MutationsInterpreter::prepare(bool dry_run) NameSet updated_columns; bool materialize_ttl_recalculate_only = source.materializeTTLRecalculateOnly(); - for (const MutationCommand & command : commands) + for (auto & command : commands) { - if (command.type == MutationCommand::Type::UPDATE - || command.type == MutationCommand::Type::DELETE) + if (command.type == MutationCommand::Type::APPLY_DELETED_MASK) + command = createCommandToApplyDeletedMask(command); + + if (command.type == MutationCommand::Type::UPDATE || command.type == MutationCommand::Type::DELETE) materialize_ttl_recalculate_only = false; for (const auto & [name, _] : command.column_to_update_expression) { - if (!available_columns_set.contains(name) && name != LightweightDeleteDescription::FILTER_COLUMN.name + if (!available_columns_set.contains(name) + && name != LightweightDeleteDescription::FILTER_COLUMN.name && name != BlockNumberColumn::name) throw Exception(ErrorCodes::THERE_IS_NO_COLUMN, "Column {} is updated but not requested to read", name); @@ -574,7 +613,7 @@ void MutationsInterpreter::prepare(bool dry_run) std::vector read_columns; /// First, break a sequence of commands into stages. - for (auto & command : commands) + for (const auto & command : commands) { // we can return deleted rows only if it's the only present command assert(command.type == MutationCommand::DELETE || command.type == MutationCommand::UPDATE || !settings.return_mutated_rows); @@ -585,7 +624,7 @@ void MutationsInterpreter::prepare(bool dry_run) if (stages.empty() || !stages.back().column_to_updated.empty()) stages.emplace_back(context); - auto predicate = getPartitionAndPredicateExpressionForMutationCommand(command); + auto predicate = getPartitionAndPredicateExpressionForMutationCommand(command); if (!settings.return_mutated_rows) predicate = makeASTFunction("isZeroOrNull", predicate); @@ -605,16 +644,12 @@ void MutationsInterpreter::prepare(bool dry_run) NameSet affected_materialized; - for (const auto & kv : command.column_to_update_expression) + for (const auto & [column_name, update_expr] : command.column_to_update_expression) { - const String & column = kv.first; - - auto materialized_it = column_to_affected_materialized.find(column); + auto materialized_it = column_to_affected_materialized.find(column_name); if (materialized_it != column_to_affected_materialized.end()) - { - for (const String & mat_column : materialized_it->second) + for (const auto & mat_column : materialized_it->second) affected_materialized.emplace(mat_column); - } /// When doing UPDATE column = expression WHERE condition /// we will replace column to the result of the following expression: @@ -627,33 +662,39 @@ void MutationsInterpreter::prepare(bool dry_run) /// Outer CAST is added just in case if we don't trust the returning type of 'if'. DataTypePtr type; - if (auto physical_column = columns_desc.tryGetPhysical(column)) + if (auto physical_column = columns_desc.tryGetPhysical(column_name)) + { type = physical_column->type; - else if (column == LightweightDeleteDescription::FILTER_COLUMN.name) + } + else if (column_name == LightweightDeleteDescription::FILTER_COLUMN.name) + { type = LightweightDeleteDescription::FILTER_COLUMN.type; - else if (column == BlockNumberColumn::name) + deleted_mask_updated = true; + } + else if (column_name == BlockNumberColumn::name) + { type = BlockNumberColumn::type; + } else - throw Exception(ErrorCodes::LOGICAL_ERROR, "Unknown column {}", column); + { + throw Exception(ErrorCodes::LOGICAL_ERROR, "Unknown column {}", column_name); + } auto type_literal = std::make_shared(type->getName()); - - const auto & update_expr = kv.second; - ASTPtr condition = getPartitionAndPredicateExpressionForMutationCommand(command); /// And new check validateNestedArraySizes for Nested subcolumns - if (isArray(type) && !Nested::splitName(column).second.empty()) + if (isArray(type) && !Nested::splitName(column_name).second.empty()) { std::shared_ptr function = nullptr; - auto nested_update_exprs = getExpressionsOfUpdatedNestedSubcolumns(column, all_columns, command.column_to_update_expression); + auto nested_update_exprs = getExpressionsOfUpdatedNestedSubcolumns(column_name, all_columns, command.column_to_update_expression); if (!nested_update_exprs) { function = makeASTFunction("validateNestedArraySizes", condition, update_expr->clone(), - std::make_shared(column)); + std::make_shared(column_name)); condition = makeASTFunction("and", condition, function); } else if (nested_update_exprs->size() > 1) @@ -675,10 +716,10 @@ void MutationsInterpreter::prepare(bool dry_run) makeASTFunction("_CAST", update_expr->clone(), type_literal), - std::make_shared(column)), + std::make_shared(column_name)), type_literal); - stages.back().column_to_updated.emplace(column, updated_column); + stages.back().column_to_updated.emplace(column_name, updated_column); if (condition && settings.return_mutated_rows) stages.back().filters.push_back(condition); @@ -987,26 +1028,41 @@ void MutationsInterpreter::prepareMutationStages(std::vector & prepared_s /// Add _row_exists column if it is present in the part if (source.hasLightweightDeleteMask()) - all_columns.push_back({LightweightDeleteDescription::FILTER_COLUMN}); + all_columns.push_back(LightweightDeleteDescription::FILTER_COLUMN); + + bool has_filters = false; /// Next, for each stage calculate columns changed by this and previous stages. for (size_t i = 0; i < prepared_stages.size(); ++i) { if (settings.return_all_columns || !prepared_stages[i].filters.empty()) { for (const auto & column : all_columns) + { + if (column.name == LightweightDeleteDescription::FILTER_COLUMN.name && !deleted_mask_updated) + continue; + prepared_stages[i].output_columns.insert(column.name); - continue; + } + + has_filters = true; } + else + { + if (i > 0) + prepared_stages[i].output_columns = prepared_stages[i - 1].output_columns; - if (i > 0) - prepared_stages[i].output_columns = prepared_stages[i - 1].output_columns; + /// Make sure that all updated columns are included into output_columns set. + /// This is important for a "hidden" column like _row_exists gets because it is a virtual column + /// and so it is not in the list of AllPhysical columns. + for (const auto & [column_name, _] : prepared_stages[i].column_to_updated) + { + if (column_name == LightweightDeleteDescription::FILTER_COLUMN.name && has_filters && !deleted_mask_updated) + continue; - /// Make sure that all updated columns are included into output_columns set. - /// This is important for a "hidden" column like _row_exists gets because it is a virtual column - /// and so it is not in the list of AllPhysical columns. - for (const auto & kv : prepared_stages[i].column_to_updated) - prepared_stages[i].output_columns.insert(kv.first); + prepared_stages[i].output_columns.insert(column_name); + } + } } /// Now, calculate `expressions_chain` for each stage except the first. @@ -1024,7 +1080,7 @@ void MutationsInterpreter::prepareMutationStages(std::vector & prepared_s all_asts->children.push_back(kv.second); /// Add all output columns to prevent ExpressionAnalyzer from deleting them from source columns. - for (const String & column : stage.output_columns) + for (const auto & column : stage.output_columns) all_asts->children.push_back(std::make_shared(column)); /// Executing scalar subquery on that stage can lead to deadlock @@ -1081,7 +1137,6 @@ void MutationsInterpreter::prepareMutationStages(std::vector & prepared_s actions_chain.getLastStep().addRequiredOutput(name); actions_chain.getLastActions(); - actions_chain.finalize(); if (i) diff --git a/src/Interpreters/MutationsInterpreter.h b/src/Interpreters/MutationsInterpreter.h index 1372ea77f4f..eda94190185 100644 --- a/src/Interpreters/MutationsInterpreter.h +++ b/src/Interpreters/MutationsInterpreter.h @@ -32,6 +32,8 @@ ASTPtr getPartitionAndPredicateExpressionForMutationCommand( ContextPtr context ); +MutationCommand createCommandToApplyDeletedMask(const MutationCommand & command); + /// Create an input stream that will read data from storage and apply mutation commands (UPDATEs, DELETEs, MATERIALIZEs) /// to this data. class MutationsInterpreter @@ -213,6 +215,7 @@ private: std::unique_ptr updated_header; std::vector stages; bool is_prepared = false; /// Has the sequence of stages been prepared. + bool deleted_mask_updated = false; NameSet materialized_indices; NameSet materialized_projections; diff --git a/src/Parsers/ASTAlterQuery.cpp b/src/Parsers/ASTAlterQuery.cpp index 84893011222..ea116e6ccfd 100644 --- a/src/Parsers/ASTAlterQuery.cpp +++ b/src/Parsers/ASTAlterQuery.cpp @@ -466,6 +466,22 @@ void ASTAlterCommand::formatImpl(const FormatSettings & settings, FormatState & settings.ostr << (settings.hilite ? hilite_keyword : "") << " TO "; rename_to->formatImpl(settings, state, frame); } + else if (type == ASTAlterCommand::APPLY_DELETED_MASK) + { + settings.ostr << (settings.hilite ? hilite_keyword : "") << "APPLY DELETED MASK" << (settings.hilite ? hilite_none : ""); + + if (partition) + { + settings.ostr << (settings.hilite ? hilite_keyword : "") << " IN PARTITION " << (settings.hilite ? hilite_none : ""); + partition->formatImpl(settings, state, frame); + } + + if (predicate) + { + settings.ostr << (settings.hilite ? hilite_keyword : "") << " WHERE " << (settings.hilite ? hilite_none : ""); + predicate->formatImpl(settings, state, frame); + } + } else throw Exception(ErrorCodes::UNEXPECTED_AST_STRUCTURE, "Unexpected type of ALTER"); } diff --git a/src/Parsers/ASTAlterQuery.h b/src/Parsers/ASTAlterQuery.h index e601739595f..77c540aed33 100644 --- a/src/Parsers/ASTAlterQuery.h +++ b/src/Parsers/ASTAlterQuery.h @@ -71,6 +71,7 @@ public: DELETE, UPDATE, + APPLY_DELETED_MASK, NO_TYPE, diff --git a/src/Parsers/ParserAlterQuery.cpp b/src/Parsers/ParserAlterQuery.cpp index c616c6e0441..3522611ec4c 100644 --- a/src/Parsers/ParserAlterQuery.cpp +++ b/src/Parsers/ParserAlterQuery.cpp @@ -111,6 +111,7 @@ bool ParserAlterCommand::parseImpl(Pos & pos, ASTPtr & node, Expected & expected ParserKeyword s_remove_ttl("REMOVE TTL"); ParserKeyword s_remove_sample_by("REMOVE SAMPLE BY"); + ParserKeyword s_apply_deleted_mask("APPLY DELETED MASK"); ParserCompoundIdentifier parser_name; ParserStringLiteral parser_string_literal; @@ -823,6 +824,22 @@ bool ParserAlterCommand::parseImpl(Pos & pos, ASTPtr & node, Expected & expected command->type = ASTAlterCommand::MODIFY_COMMENT; } + else if (s_apply_deleted_mask.ignore(pos, expected)) + { + command->type = ASTAlterCommand::APPLY_DELETED_MASK; + + if (s_in_partition.ignore(pos, expected)) + { + if (!parser_partition.parse(pos, command->partition, expected)) + return false; + } + + if (s_where.ignore(pos, expected)) + { + if (!parser_exp_elem.parse(pos, command->predicate, expected)) + return false; + } + } else return false; } diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 6b6b5947581..fc36840cf92 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -51,7 +51,6 @@ static bool checkOperationIsNotCanceled(ActionBlocker & merges_blocker, MergeLis return true; } - /** Split mutation commands into two parts: * First part should be executed by mutations interpreter. * Other is just simple drop/renames, so they can be executed without interpreter. @@ -79,7 +78,8 @@ static void splitAndModifyMutationCommands( || command.type == MutationCommand::Type::MATERIALIZE_PROJECTION || command.type == MutationCommand::Type::MATERIALIZE_TTL || command.type == MutationCommand::Type::DELETE - || command.type == MutationCommand::Type::UPDATE) + || command.type == MutationCommand::Type::UPDATE + || command.type == MutationCommand::Type::APPLY_DELETED_MASK) { for_interpreter.push_back(command); for (const auto & [column_name, expr] : command.column_to_update_expression) @@ -202,7 +202,8 @@ static void splitAndModifyMutationCommands( || command.type == MutationCommand::Type::MATERIALIZE_PROJECTION || command.type == MutationCommand::Type::MATERIALIZE_TTL || command.type == MutationCommand::Type::DELETE - || command.type == MutationCommand::Type::UPDATE) + || command.type == MutationCommand::Type::UPDATE + || command.type == MutationCommand::Type::APPLY_DELETED_MASK) { for_interpreter.push_back(command); } @@ -257,15 +258,12 @@ getColumnsForNewDataPart( NameToNameMap renamed_columns_from_to; ColumnsDescription part_columns(source_part->getColumns()); NamesAndTypesList system_columns; - if (source_part->supportLightweightDeleteMutate()) - system_columns.push_back(LightweightDeleteDescription::FILTER_COLUMN); - /// Preserve system columns that have persisted values in the source_part - for (const auto & column : system_columns) - { - if (part_columns.has(column.name) && !storage_columns.contains(column.name)) - storage_columns.emplace_back(column); - } + const auto & deleted_mask_column = LightweightDeleteDescription::FILTER_COLUMN; + bool supports_lightweight_deletes = source_part->supportLightweightDeleteMutate(); + + bool deleted_mask_updated = false; + bool has_delete_command = false; NameSet storage_columns_set; for (const auto & [name, _] : storage_columns) @@ -277,23 +275,22 @@ getColumnsForNewDataPart( { for (const auto & [column_name, _] : command.column_to_update_expression) { - /// Allow to update and persist values of system column - auto column = system_columns.tryGetByName(column_name); - if (column && !storage_columns.contains(column_name)) - storage_columns.emplace_back(column_name, column->type); + if (column_name == deleted_mask_column.name + && supports_lightweight_deletes + && !storage_columns_set.contains(deleted_mask_column.name)) + deleted_mask_updated = true; } } + if (command.type == MutationCommand::DELETE || command.type == MutationCommand::APPLY_DELETED_MASK) + has_delete_command = true; + /// If we don't have this column in source part, than we don't need to materialize it if (!part_columns.has(command.column_name)) - { continue; - } if (command.type == MutationCommand::DROP_COLUMN) - { removed_columns.insert(command.column_name); - } if (command.type == MutationCommand::RENAME_COLUMN) { @@ -302,6 +299,15 @@ getColumnsForNewDataPart( } } + if (!storage_columns_set.contains(deleted_mask_column.name)) + { + if (deleted_mask_updated || (part_columns.has(deleted_mask_column.name) && !has_delete_command)) + { + storage_columns.push_back(deleted_mask_column); + storage_columns_set.insert(deleted_mask_column.name); + } + } + SerializationInfoByName new_serialization_infos; for (const auto & [name, old_info] : serialization_infos) { @@ -1900,6 +1906,9 @@ static bool canSkipMutationCommandForPart(const MergeTreeDataPartPtr & part, con return true; } + if (command.type == MutationCommand::APPLY_DELETED_MASK && !part->hasLightweightDelete()) + return true; + if (canSkipConversionToNullable(part, command)) return true; diff --git a/src/Storages/MutationCommands.cpp b/src/Storages/MutationCommands.cpp index 03200d0d9fa..36388a32b41 100644 --- a/src/Storages/MutationCommands.cpp +++ b/src/Storages/MutationCommands.cpp @@ -59,6 +59,15 @@ std::optional MutationCommand::parse(ASTAlterCommand * command, } return res; } + else if (command->type == ASTAlterCommand::APPLY_DELETED_MASK) + { + MutationCommand res; + res.ast = command->ptr(); + res.type = APPLY_DELETED_MASK; + res.predicate = command->predicate; + res.partition = command->partition; + return res; + } else if (command->type == ASTAlterCommand::MATERIALIZE_INDEX) { MutationCommand res; diff --git a/src/Storages/MutationCommands.h b/src/Storages/MutationCommands.h index 014a227dff3..6e10f7d9b2d 100644 --- a/src/Storages/MutationCommands.h +++ b/src/Storages/MutationCommands.h @@ -39,6 +39,7 @@ struct MutationCommand MATERIALIZE_TTL, RENAME_COLUMN, MATERIALIZE_COLUMN, + APPLY_DELETED_MASK, ALTER_WITHOUT_MUTATION, /// pure metadata command, currently unusned }; diff --git a/src/Storages/StorageSnapshot.cpp b/src/Storages/StorageSnapshot.cpp index a22ba6586ac..34c092c7208 100644 --- a/src/Storages/StorageSnapshot.cpp +++ b/src/Storages/StorageSnapshot.cpp @@ -35,6 +35,7 @@ void StorageSnapshot::init() if (storage.hasLightweightDeletedMask()) system_columns[LightweightDeleteDescription::FILTER_COLUMN.name] = LightweightDeleteDescription::FILTER_COLUMN.type; + system_columns[BlockNumberColumn::name] = BlockNumberColumn::type; } diff --git a/tests/queries/0_stateless/02932_apply_deleted_mask.reference b/tests/queries/0_stateless/02932_apply_deleted_mask.reference new file mode 100644 index 00000000000..0d75f7c1b30 --- /dev/null +++ b/tests/queries/0_stateless/02932_apply_deleted_mask.reference @@ -0,0 +1,6 @@ +10 45 +all_1_1_0 10 0 +7 33 +all_1_1_0_2 10 1 +7 33 +all_1_1_0_3 7 0 diff --git a/tests/queries/0_stateless/02932_apply_deleted_mask.sql b/tests/queries/0_stateless/02932_apply_deleted_mask.sql new file mode 100644 index 00000000000..602c67de52e --- /dev/null +++ b/tests/queries/0_stateless/02932_apply_deleted_mask.sql @@ -0,0 +1,22 @@ +DROP TABLE IF EXISTS t_materialize_delete; + +CREATE TABLE t_materialize_delete (id UInt64, v UInt64) ENGINE = MergeTree ORDER BY tuple() settings min_bytes_for_wide_part = 0; + +SET mutations_sync = 2; + +INSERT INTO t_materialize_delete SELECT number, number FROM numbers(10); + +SELECT count(), sum(v) FROM t_materialize_delete; +SELECT name, rows, has_lightweight_delete FROM system.parts WHERE database = currentDatabase() AND table = 't_materialize_delete' AND active; + +DELETE FROM t_materialize_delete WHERE id % 3 = 1; + +SELECT count(), sum(v) FROM t_materialize_delete; +SELECT name, rows, has_lightweight_delete FROM system.parts WHERE database = currentDatabase() AND table = 't_materialize_delete' AND active; + +ALTER TABLE t_materialize_delete APPLY DELETED MASK; + +SELECT count(), sum(v) FROM t_materialize_delete; +SELECT name, rows, has_lightweight_delete FROM system.parts WHERE database = currentDatabase() AND table = 't_materialize_delete' AND active; + +DROP TABLE t_materialize_delete; From 8d30e22a09613257e049fd07414eeaf0b7bb9097 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 4 Dec 2023 13:00:50 +0000 Subject: [PATCH 018/253] fix lighweight delete with heavy delete --- src/Interpreters/MutationsInterpreter.cpp | 4 +-- src/Storages/MergeTree/MergeTask.cpp | 4 +++ .../MergeTree/MergeTreeSequentialSource.cpp | 36 +++++++++++++------ .../MergeTree/MergeTreeSequentialSource.h | 4 ++- .../02352_lightweight_delete.reference | 2 +- ...02521_lightweight_delete_and_ttl.reference | 2 +- 6 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/Interpreters/MutationsInterpreter.cpp b/src/Interpreters/MutationsInterpreter.cpp index 4cef15f6220..d333477f36e 100644 --- a/src/Interpreters/MutationsInterpreter.cpp +++ b/src/Interpreters/MutationsInterpreter.cpp @@ -1030,7 +1030,6 @@ void MutationsInterpreter::prepareMutationStages(std::vector & prepared_s if (source.hasLightweightDeleteMask()) all_columns.push_back(LightweightDeleteDescription::FILTER_COLUMN); - bool has_filters = false; /// Next, for each stage calculate columns changed by this and previous stages. for (size_t i = 0; i < prepared_stages.size(); ++i) @@ -1046,6 +1045,7 @@ void MutationsInterpreter::prepareMutationStages(std::vector & prepared_s } has_filters = true; + settings.apply_deleted_mask = true; } else { @@ -1279,7 +1279,7 @@ void MutationsInterpreter::Source::read( VirtualColumns virtual_columns(std::move(required_columns), part); - createMergeTreeSequentialSource( + createReadFromPartStep( plan, *data, storage_snapshot, part, std::move(virtual_columns.columns_to_read), apply_deleted_mask_, filter, context_, diff --git a/src/Storages/MergeTree/MergeTask.cpp b/src/Storages/MergeTree/MergeTask.cpp index e8e307bb148..94bd0f98986 100644 --- a/src/Storages/MergeTree/MergeTask.cpp +++ b/src/Storages/MergeTree/MergeTask.cpp @@ -556,6 +556,8 @@ void MergeTask::VerticalMergeStage::prepareVerticalMergeForOneColumn() const global_ctx->storage_snapshot, global_ctx->future_part->parts[part_num], column_names, + /*mark_ranges=*/ {}, + /*apply_deleted_mask=*/ true, ctx->read_with_direct_io, /*take_column_types_from_storage=*/ true, /*quiet=*/ false, @@ -909,6 +911,8 @@ void MergeTask::ExecuteAndFinalizeHorizontalPart::createMergedStream() global_ctx->storage_snapshot, part, global_ctx->merging_column_names, + /*mark_ranges=*/ {}, + /*apply_deleted_mask=*/ true, ctx->read_with_direct_io, /*take_column_types_from_storage=*/ true, /*quiet=*/ false, diff --git a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp index a586997360a..5075e43448a 100644 --- a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp +++ b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp @@ -131,6 +131,7 @@ MergeTreeSequentialSource::MergeTreeSequentialSource( auto options = GetColumnsOptions(GetColumnsOptions::AllPhysical) .withExtendedObjects() .withSystemColumns(); + if (storage.supportsSubcolumns()) options.withSubcolumns(); columns_for_reader = storage_snapshot->getColumnsByNames(options, columns_to_read); @@ -241,19 +242,24 @@ Pipe createMergeTreeSequentialSource( const StorageSnapshotPtr & storage_snapshot, MergeTreeData::DataPartPtr data_part, Names columns_to_read, + std::optional mark_ranges, + bool apply_deleted_mask, bool read_with_direct_io, bool take_column_types_from_storage, bool quiet, std::shared_ptr> filtered_rows_count) { + const auto & filter_column = LightweightDeleteDescription::FILTER_COLUMN; + /// The part might have some rows masked by lightweight deletes - const bool need_to_filter_deleted_rows = data_part->hasLightweightDelete(); - auto columns = columns_to_read; - if (need_to_filter_deleted_rows) - columns.emplace_back(LightweightDeleteDescription::FILTER_COLUMN.name); + const bool need_to_filter_deleted_rows = apply_deleted_mask && data_part->hasLightweightDelete(); + const bool has_filter_column = std::ranges::find(columns_to_read, filter_column.name) != columns_to_read.end(); + + if (need_to_filter_deleted_rows && !has_filter_column) + columns_to_read.emplace_back(filter_column.name); auto column_part_source = std::make_shared( - storage, storage_snapshot, data_part, columns, std::optional{}, + storage, storage_snapshot, data_part, columns_to_read, std::move(mark_ranges), /*apply_deleted_mask=*/ false, read_with_direct_io, take_column_types_from_storage, quiet); Pipe pipe(std::move(column_part_source)); @@ -261,10 +267,10 @@ Pipe createMergeTreeSequentialSource( /// Add filtering step that discards deleted rows if (need_to_filter_deleted_rows) { - pipe.addSimpleTransform([filtered_rows_count](const Block & header) + pipe.addSimpleTransform([filtered_rows_count, has_filter_column](const Block & header) { return std::make_shared( - header, nullptr, LightweightDeleteDescription::FILTER_COLUMN.name, true, false, filtered_rows_count); + header, nullptr, filter_column.name, !has_filter_column, false, filtered_rows_count); }); } @@ -325,9 +331,17 @@ public: } } - auto source = std::make_unique( - storage, storage_snapshot, data_part, columns_to_read, - std::move(mark_ranges), apply_deleted_mask, false, true); + auto source = createMergeTreeSequentialSource( + storage, + storage_snapshot, + data_part, + columns_to_read, + std::move(mark_ranges), + apply_deleted_mask, + /*read_with_direct_io=*/ false, + /*take_column_types_from_storage=*/ true, + /*quiet=*/ false, + /*filtered_rows_count=*/ nullptr); pipeline.init(Pipe(std::move(source))); } @@ -343,7 +357,7 @@ private: Poco::Logger * log; }; -void createMergeTreeSequentialSource( +void createReadFromPartStep( QueryPlan & plan, const MergeTreeData & storage, const StorageSnapshotPtr & storage_snapshot, diff --git a/src/Storages/MergeTree/MergeTreeSequentialSource.h b/src/Storages/MergeTree/MergeTreeSequentialSource.h index fb249568e8f..396d3f76886 100644 --- a/src/Storages/MergeTree/MergeTreeSequentialSource.h +++ b/src/Storages/MergeTree/MergeTreeSequentialSource.h @@ -15,6 +15,8 @@ Pipe createMergeTreeSequentialSource( const StorageSnapshotPtr & storage_snapshot, MergeTreeData::DataPartPtr data_part, Names columns_to_read, + std::optional mark_ranges, + bool apply_deleted_mask, bool read_with_direct_io, bool take_column_types_from_storage, bool quiet, @@ -22,7 +24,7 @@ Pipe createMergeTreeSequentialSource( class QueryPlan; -void createMergeTreeSequentialSource( +void createReadFromPartStep( QueryPlan & plan, const MergeTreeData & storage, const StorageSnapshotPtr & storage_snapshot, diff --git a/tests/queries/0_stateless/02352_lightweight_delete.reference b/tests/queries/0_stateless/02352_lightweight_delete.reference index 3386b3294c3..ce7c6e81ac8 100644 --- a/tests/queries/0_stateless/02352_lightweight_delete.reference +++ b/tests/queries/0_stateless/02352_lightweight_delete.reference @@ -26,7 +26,7 @@ Rows in parts 800000 Count 700000 First row 300000 1 Do ALTER DELETE mutation that does a "heavyweight" delete -Rows in parts 533333 +Rows in parts 466666 Count 466666 First row 300001 10 Delete 100K more rows using lightweight DELETE diff --git a/tests/queries/0_stateless/02521_lightweight_delete_and_ttl.reference b/tests/queries/0_stateless/02521_lightweight_delete_and_ttl.reference index 3b40d9048cd..e60b2a184db 100644 --- a/tests/queries/0_stateless/02521_lightweight_delete_and_ttl.reference +++ b/tests/queries/0_stateless/02521_lightweight_delete_and_ttl.reference @@ -15,7 +15,7 @@ SELECT 'Count', count() FROM lwd_test_02521; Count 25000 ALTER TABLE lwd_test_02521 DELETE WHERE id >= 40000 SETTINGS mutations_sync = 1; SELECT 'Rows in parts', SUM(rows) FROM system.parts WHERE database = currentDatabase() AND table = 'lwd_test_02521' AND active; -Rows in parts 40000 +Rows in parts 15000 SELECT 'Count', count() FROM lwd_test_02521; Count 15000 OPTIMIZE TABLE lwd_test_02521 FINAL SETTINGS mutations_sync = 1; From 4ec21e94a528a14c73bddd083c4ecda44b3488bc Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 4 Dec 2023 16:58:51 +0100 Subject: [PATCH 019/253] Set http_make_head_request onlt in stress tests --- tests/ci/stress.py | 3 +++ tests/clickhouse-test | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/ci/stress.py b/tests/ci/stress.py index ae918363df7..91b8694623b 100755 --- a/tests/ci/stress.py +++ b/tests/ci/stress.py @@ -68,6 +68,9 @@ def get_options(i: int, upgrade_check: bool) -> str: if random.random() < 0.1: client_options.append("optimize_trivial_approximate_count_query=1") + if random.random() < 0.3: + client_options.append(f"http_make_head_request={random.randint(0, 1)}") + if client_options: options.append(" --client-option " + " ".join(client_options)) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 60416521ed1..4b551ed3663 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -631,7 +631,6 @@ class SettingsRandomizer: get_localzone(), ] ), - "http_make_head_request": lambda: random.randint(0, 1), } @staticmethod From 04d167c6d9db3ccd91e3e36b587082e09e3c305f Mon Sep 17 00:00:00 2001 From: Nikita Mikhaylov Date: Tue, 5 Dec 2023 13:34:37 +0100 Subject: [PATCH 020/253] Better --- programs/server/Server.cpp | 6 ++--- src/BridgeHelper/LibraryBridgeHelper.cpp | 2 +- src/BridgeHelper/XDBCBridgeHelper.h | 2 +- src/Core/ServerSettings.h | 4 ++++ src/Dictionaries/HTTPDictionarySource.cpp | 4 ++-- src/Dictionaries/XDBCDictionarySource.cpp | 2 +- src/Disks/IO/ReadBufferFromWebServer.cpp | 5 ++-- .../ObjectStorages/Web/WebObjectStorage.cpp | 2 +- src/IO/ConnectionTimeouts.cpp | 16 +++++++++++++ src/IO/ConnectionTimeouts.h | 3 +++ src/IO/ReadWriteBufferFromHTTP.cpp | 5 ++-- src/IO/ReadWriteBufferFromHTTP.h | 15 ++++++------ src/Interpreters/Context.cpp | 14 +++++++++++ src/Interpreters/Context.h | 4 ++++ .../HTTP/WriteBufferFromHTTPServerResponse.h | 2 +- src/Server/HTTPHandler.cpp | 4 +--- src/Server/InterserverIOHTTPHandler.cpp | 3 +-- src/Server/PrometheusRequestHandler.cpp | 5 ++-- src/Server/ReplicasStatusHandler.cpp | 4 ++-- src/Server/StaticRequestHandler.cpp | 4 ++-- src/Server/WebUIRequestHandler.cpp | 3 ++- src/Storages/MergeTree/DataPartsExchange.cpp | 3 +-- src/Storages/MergeTree/MergeTreeSettings.h | 8 +++---- src/Storages/StorageReplicatedMergeTree.cpp | 23 +++---------------- src/Storages/StorageReplicatedMergeTree.h | 4 ---- src/Storages/StorageURL.cpp | 2 +- src/Storages/StorageXDBC.cpp | 2 +- src/TableFunctions/ITableFunctionXDBC.cpp | 2 +- 28 files changed, 83 insertions(+), 70 deletions(-) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index de0cfb9b9fa..778adfeb84d 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -2086,10 +2086,9 @@ void Server::createServers( { const Settings & settings = global_context->getSettingsRef(); - Poco::Timespan keep_alive_timeout(config.getUInt("keep_alive_timeout", 10), 0); Poco::Net::HTTPServerParams::Ptr http_params = new Poco::Net::HTTPServerParams; http_params->setTimeout(settings.http_receive_timeout); - http_params->setKeepAliveTimeout(keep_alive_timeout); + http_params->setKeepAliveTimeout(global_context->getServerSettings().keep_alive_timeout); Poco::Util::AbstractConfiguration::Keys protocols; config.keys("protocols", protocols); @@ -2343,10 +2342,9 @@ void Server::createInterserverServers( { const Settings & settings = global_context->getSettingsRef(); - Poco::Timespan keep_alive_timeout(config.getUInt("keep_alive_timeout", 10), 0); Poco::Net::HTTPServerParams::Ptr http_params = new Poco::Net::HTTPServerParams; http_params->setTimeout(settings.http_receive_timeout); - http_params->setKeepAliveTimeout(keep_alive_timeout); + http_params->setKeepAliveTimeout(global_context->getServerSettings().keep_alive_timeout); /// Now iterate over interserver_listen_hosts for (const auto & interserver_listen_host : interserver_listen_hosts) diff --git a/src/BridgeHelper/LibraryBridgeHelper.cpp b/src/BridgeHelper/LibraryBridgeHelper.cpp index 60588951c32..e83707595b9 100644 --- a/src/BridgeHelper/LibraryBridgeHelper.cpp +++ b/src/BridgeHelper/LibraryBridgeHelper.cpp @@ -12,7 +12,7 @@ LibraryBridgeHelper::LibraryBridgeHelper(ContextPtr context_) , http_timeout(context_->getGlobalContext()->getSettingsRef().http_receive_timeout.value) , bridge_host(config.getString("library_bridge.host", DEFAULT_HOST)) , bridge_port(config.getUInt("library_bridge.port", DEFAULT_PORT)) - , http_timeouts(ConnectionTimeouts::getHTTPTimeouts(context_->getSettingsRef(), {context_->getConfigRef().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT), 0})) + , http_timeouts(ConnectionTimeouts::getHTTPTimeouts(context_->getSettingsRef(), context_->getServerSettings().keep_alive_timeout)) { } diff --git a/src/BridgeHelper/XDBCBridgeHelper.h b/src/BridgeHelper/XDBCBridgeHelper.h index 44104f26f63..060de74b5b1 100644 --- a/src/BridgeHelper/XDBCBridgeHelper.h +++ b/src/BridgeHelper/XDBCBridgeHelper.h @@ -162,7 +162,7 @@ private: ConnectionTimeouts getHTTPTimeouts() { - return ConnectionTimeouts::getHTTPTimeouts(getContext()->getSettingsRef(), {getContext()->getConfigRef().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT), 0}); + return ConnectionTimeouts::getHTTPTimeouts(getContext()->getSettingsRef(), getContext()->getServerSettings().keep_alive_timeout); } protected: diff --git a/src/Core/ServerSettings.h b/src/Core/ServerSettings.h index 6785eea26ea..f6ceb652900 100644 --- a/src/Core/ServerSettings.h +++ b/src/Core/ServerSettings.h @@ -97,6 +97,10 @@ namespace DB M(Bool, async_load_databases, false, "Enable asynchronous loading of databases and tables to speedup server startup. Queries to not yet loaded entity will be blocked until load is finished.", 0) \ M(Bool, display_secrets_in_show_and_select, false, "Allow showing secrets in SHOW and SELECT queries via a format setting and a grant", 0) \ \ + M(Seconds, keep_alive_timeout, DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT, "The number of seconds that ClickHouse waits for incoming requests before closing the connection.", 0) \ + M(Seconds, replicated_fetches_http_connection_timeout, 0, "HTTP connection timeout for part fetch requests. Inherited from default profile `http_connection_timeout` if not set explicitly.", 0) \ + M(Seconds, replicated_fetches_http_send_timeout, 0, "HTTP send timeout for part fetch requests. Inherited from default profile `http_send_timeout` if not set explicitly.", 0) \ + M(Seconds, replicated_fetches_http_receive_timeout, 0, "HTTP receive timeout for fetch part requests. Inherited from default profile `http_receive_timeout` if not set explicitly.", 0) \ M(UInt64, total_memory_profiler_step, 0, "Whenever server memory usage becomes larger than every next step in number of bytes the memory profiler will collect the allocating stack trace. Zero means disabled memory profiler. Values lower than a few megabytes will slow down server.", 0) \ M(Double, total_memory_tracker_sample_probability, 0, "Collect random allocations and deallocations and write them into system.trace_log with 'MemorySample' trace_type. The probability is for every alloc/free regardless to the size of the allocation (can be changed with `memory_profiler_sample_min_allocation_size` and `memory_profiler_sample_max_allocation_size`). Note that sampling happens only when the amount of untracked memory exceeds 'max_untracked_memory'. You may want to set 'max_untracked_memory' to 0 for extra fine grained sampling.", 0) \ M(UInt64, total_memory_profiler_sample_min_allocation_size, 0, "Collect random allocations of size greater or equal than specified value with probability equal to `total_memory_profiler_sample_probability`. 0 means disabled. You may want to set 'max_untracked_memory' to 0 to make this threshold to work as expected.", 0) \ diff --git a/src/Dictionaries/HTTPDictionarySource.cpp b/src/Dictionaries/HTTPDictionarySource.cpp index c12f4fedf3f..689593a969e 100644 --- a/src/Dictionaries/HTTPDictionarySource.cpp +++ b/src/Dictionaries/HTTPDictionarySource.cpp @@ -38,7 +38,7 @@ HTTPDictionarySource::HTTPDictionarySource( , configuration(configuration_) , sample_block(sample_block_) , context(context_) - , timeouts(ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), {context->getConfigRef().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT), 0})) + , timeouts(ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), context->getServerSettings().keep_alive_timeout)) { credentials.setUsername(credentials_.getUsername()); credentials.setPassword(credentials_.getPassword()); @@ -51,7 +51,7 @@ HTTPDictionarySource::HTTPDictionarySource(const HTTPDictionarySource & other) , configuration(other.configuration) , sample_block(other.sample_block) , context(Context::createCopy(other.context)) - , timeouts(ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), {context->getConfigRef().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT), 0})) + , timeouts(ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), context->getServerSettings().keep_alive_timeout)) { credentials.setUsername(other.credentials.getUsername()); credentials.setPassword(other.credentials.getPassword()); diff --git a/src/Dictionaries/XDBCDictionarySource.cpp b/src/Dictionaries/XDBCDictionarySource.cpp index 23dc7db508d..080f7db96be 100644 --- a/src/Dictionaries/XDBCDictionarySource.cpp +++ b/src/Dictionaries/XDBCDictionarySource.cpp @@ -76,7 +76,7 @@ XDBCDictionarySource::XDBCDictionarySource( , load_all_query(query_builder.composeLoadAllQuery()) , bridge_helper(bridge_) , bridge_url(bridge_helper->getMainURI()) - , timeouts(ConnectionTimeouts::getHTTPTimeouts(context_->getSettingsRef(), {context_->getConfigRef().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT), 0})) + , timeouts(ConnectionTimeouts::getHTTPTimeouts(context_->getSettingsRef(), context_->getServerSettings().keep_alive_timeout)) { auto url_params = bridge_helper->getURLParams(max_block_size); for (const auto & [name, value] : url_params) diff --git a/src/Disks/IO/ReadBufferFromWebServer.cpp b/src/Disks/IO/ReadBufferFromWebServer.cpp index 46d8c41ff78..90cd5285875 100644 --- a/src/Disks/IO/ReadBufferFromWebServer.cpp +++ b/src/Disks/IO/ReadBufferFromWebServer.cpp @@ -54,8 +54,7 @@ std::unique_ptr ReadBufferFromWebServer::initialize() } const auto & settings = context->getSettingsRef(); - const auto & config = context->getConfigRef(); - Poco::Timespan http_keep_alive_timeout{config.getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT), 0}; + const auto & server_settings = context->getServerSettings(); auto res = std::make_unique( uri, @@ -65,7 +64,7 @@ std::unique_ptr ReadBufferFromWebServer::initialize() settings.http_send_timeout, std::max(Poco::Timespan(settings.http_receive_timeout.totalSeconds(), 0), Poco::Timespan(20, 0)), settings.tcp_keep_alive_timeout, - http_keep_alive_timeout), + server_settings.keep_alive_timeout), credentials, 0, buf_size, diff --git a/src/Disks/ObjectStorages/Web/WebObjectStorage.cpp b/src/Disks/ObjectStorages/Web/WebObjectStorage.cpp index 0103188b562..f3b0cb8b9a0 100644 --- a/src/Disks/ObjectStorages/Web/WebObjectStorage.cpp +++ b/src/Disks/ObjectStorages/Web/WebObjectStorage.cpp @@ -47,7 +47,7 @@ void WebObjectStorage::initialize(const String & uri_path, const std::unique_loc ReadWriteBufferFromHTTP::OutStreamCallback(), ConnectionTimeouts::getHTTPTimeouts( getContext()->getSettingsRef(), - {getContext()->getConfigRef().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT), 0}), + getContext()->getServerSettings().keep_alive_timeout), credentials, /* max_redirects= */ 0, /* buffer_size_= */ DBMS_DEFAULT_BUFFER_SIZE, diff --git a/src/IO/ConnectionTimeouts.cpp b/src/IO/ConnectionTimeouts.cpp index 970afc75ec3..88073a72d78 100644 --- a/src/IO/ConnectionTimeouts.cpp +++ b/src/IO/ConnectionTimeouts.cpp @@ -133,6 +133,22 @@ ConnectionTimeouts ConnectionTimeouts::getHTTPTimeouts(const Settings & settings settings.http_receive_timeout); } +ConnectionTimeouts ConnectionTimeouts::getFetchPartHTTPTimeouts(const ServerSettings & server_settings, const Settings & user_settings) +{ + auto timeouts = getHTTPTimeouts(user_settings, server_settings.keep_alive_timeout); + + if (server_settings.replicated_fetches_http_connection_timeout.changed) + timeouts.connection_timeout = server_settings.replicated_fetches_http_connection_timeout; + + if (server_settings.replicated_fetches_http_send_timeout.changed) + timeouts.send_timeout = server_settings.replicated_fetches_http_send_timeout; + + if (server_settings.replicated_fetches_http_receive_timeout.changed) + timeouts.receive_timeout = server_settings.replicated_fetches_http_receive_timeout; + + return timeouts; +} + class SendReceiveTimeoutsForFirstAttempt { private: diff --git a/src/IO/ConnectionTimeouts.h b/src/IO/ConnectionTimeouts.h index aabebdb836d..42c4312d1d8 100644 --- a/src/IO/ConnectionTimeouts.h +++ b/src/IO/ConnectionTimeouts.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -68,6 +69,8 @@ struct ConnectionTimeouts static ConnectionTimeouts getTCPTimeoutsWithFailover(const Settings & settings); static ConnectionTimeouts getHTTPTimeouts(const Settings & settings, Poco::Timespan http_keep_alive_timeout); + static ConnectionTimeouts getFetchPartHTTPTimeouts(const ServerSettings & server_settings, const Settings & user_settings); + ConnectionTimeouts getAdaptiveTimeouts(const String & method, bool first_attempt, bool first_byte) const; }; diff --git a/src/IO/ReadWriteBufferFromHTTP.cpp b/src/IO/ReadWriteBufferFromHTTP.cpp index 6dd6269e16f..858b2f7cb03 100644 --- a/src/IO/ReadWriteBufferFromHTTP.cpp +++ b/src/IO/ReadWriteBufferFromHTTP.cpp @@ -920,13 +920,12 @@ PooledReadWriteBufferFromHTTP::PooledReadWriteBufferFromHTTP( Poco::URI uri_, const std::string & method_, OutStreamCallback out_stream_callback_, - const ConnectionTimeouts & timeouts_, const Poco::Net::HTTPBasicCredentials & credentials_, size_t buffer_size_, const UInt64 max_redirects, - size_t max_connections_per_endpoint) + PooledSessionFactoryPtr session_factory) : Parent( - std::make_shared(uri_, max_redirects, std::make_shared(timeouts_, max_connections_per_endpoint)), + std::make_shared(uri_, max_redirects, session_factory), uri_, credentials_, method_, diff --git a/src/IO/ReadWriteBufferFromHTTP.h b/src/IO/ReadWriteBufferFromHTTP.h index 8f0e2388e5b..29c0804bb28 100644 --- a/src/IO/ReadWriteBufferFromHTTP.h +++ b/src/IO/ReadWriteBufferFromHTTP.h @@ -265,6 +265,8 @@ private: size_t per_endpoint_pool_size; }; +using PooledSessionFactoryPtr = std::shared_ptr; + class PooledReadWriteBufferFromHTTP : public detail::ReadWriteBufferFromHTTPBase>> { using SessionType = UpdatableSession; @@ -273,13 +275,12 @@ class PooledReadWriteBufferFromHTTP : public detail::ReadWriteBufferFromHTTPBase public: explicit PooledReadWriteBufferFromHTTP( Poco::URI uri_, - const std::string & method_ = {}, - OutStreamCallback out_stream_callback_ = {}, - const ConnectionTimeouts & timeouts_ = {}, - const Poco::Net::HTTPBasicCredentials & credentials_ = {}, - size_t buffer_size_ = DBMS_DEFAULT_BUFFER_SIZE, - const UInt64 max_redirects = 0, - size_t max_connections_per_endpoint = DEFAULT_COUNT_OF_HTTP_CONNECTIONS_PER_ENDPOINT); + const std::string & method_, + OutStreamCallback out_stream_callback_, + const Poco::Net::HTTPBasicCredentials & credentials_, + size_t buffer_size_, + const UInt64 max_redirects, + PooledSessionFactoryPtr session_factory); }; diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 1c8a505a128..3623f7e4d0a 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -76,6 +76,7 @@ #include #include #include +#include #include #include #include @@ -354,6 +355,8 @@ struct ContextSharedPart : boost::noncopyable OrdinaryBackgroundExecutorPtr moves_executor TSA_GUARDED_BY(background_executors_mutex); OrdinaryBackgroundExecutorPtr fetch_executor TSA_GUARDED_BY(background_executors_mutex); OrdinaryBackgroundExecutorPtr common_executor TSA_GUARDED_BY(background_executors_mutex); + /// The global pool of HTTP sessions for background fetches. + PooledSessionFactoryPtr fetches_session_factory TSA_GUARDED_BY(background_executors_mutex); RemoteHostFilter remote_host_filter TSA_GUARDED_BY(mutex); /// Allowed URL from config.xml HTTPHeaderFilter http_header_filter TSA_GUARDED_BY(mutex); /// Forbidden HTTP headers from config.xml @@ -4779,6 +4782,11 @@ void Context::initializeBackgroundExecutorsIfNeeded() ); LOG_INFO(shared->log, "Initialized background executor for move operations with num_threads={}, num_tasks={}", background_move_pool_size, background_move_pool_size); + auto timeouts = ConnectionTimeouts::getFetchPartHTTPTimeouts(getServerSettings(), getSettingsRef()); + /// The number of background fetches is limited by the number of threads in the background thread pool. + /// It doesn't make any sense to limit the number of connections per host any further. + shared->fetches_session_factory = std::make_shared(timeouts, background_fetches_pool_size); + shared->fetch_executor = std::make_shared ( "Fetch", @@ -4832,6 +4840,12 @@ OrdinaryBackgroundExecutorPtr Context::getCommonExecutor() const return shared->common_executor; } +PooledSessionFactoryPtr Context::getCommonFetchesSessionFactory() const +{ + SharedLockGuard lock(shared->background_executors_mutex); + return shared->fetches_session_factory; +} + IAsynchronousReader & Context::getThreadPoolReader(FilesystemReaderType type) const { callOnce(shared->readers_initialized, [&] { diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index 8e0522326f5..7a3a1236f27 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -201,6 +201,9 @@ using TemporaryDataOnDiskScopePtr = std::shared_ptr; class PreparedSetsCache; using PreparedSetsCachePtr = std::shared_ptr; +class PooledSessionFactory; +using PooledSessionFactoryPtr = std::shared_ptr; + class SessionTracker; struct ServerSettings; @@ -1207,6 +1210,7 @@ public: OrdinaryBackgroundExecutorPtr getMovesExecutor() const; OrdinaryBackgroundExecutorPtr getFetchesExecutor() const; OrdinaryBackgroundExecutorPtr getCommonExecutor() const; + PooledSessionFactoryPtr getCommonFetchesSessionFactory() const; IAsynchronousReader & getThreadPoolReader(FilesystemReaderType type) const; diff --git a/src/Server/HTTP/WriteBufferFromHTTPServerResponse.h b/src/Server/HTTP/WriteBufferFromHTTPServerResponse.h index 94202e1e0e8..38345f27952 100644 --- a/src/Server/HTTP/WriteBufferFromHTTPServerResponse.h +++ b/src/Server/HTTP/WriteBufferFromHTTPServerResponse.h @@ -36,7 +36,7 @@ public: WriteBufferFromHTTPServerResponse( HTTPServerResponse & response_, bool is_http_method_head_, - size_t keep_alive_timeout_, + UInt64 keep_alive_timeout_, bool compress_ = false, /// If true - set Content-Encoding header and compress the result. CompressionMethod compression_method_ = CompressionMethod::None); diff --git a/src/Server/HTTPHandler.cpp b/src/Server/HTTPHandler.cpp index f9cd3b40f4a..f092e5f5cc5 100644 --- a/src/Server/HTTPHandler.cpp +++ b/src/Server/HTTPHandler.cpp @@ -616,12 +616,10 @@ void HTTPHandler::processQuery( size_t buffer_size_http = DBMS_DEFAULT_BUFFER_SIZE; size_t buffer_size_memory = (buffer_size_total > buffer_size_http) ? buffer_size_total : 0; - unsigned keep_alive_timeout = config.getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT); - used_output.out = std::make_shared( response, request.getMethod() == HTTPRequest::HTTP_HEAD, - keep_alive_timeout, + context->getServerSettings().keep_alive_timeout.totalSeconds(), client_supports_http_compression, http_response_compression_method); diff --git a/src/Server/InterserverIOHTTPHandler.cpp b/src/Server/InterserverIOHTTPHandler.cpp index 5f6da208778..53773a83b40 100644 --- a/src/Server/InterserverIOHTTPHandler.cpp +++ b/src/Server/InterserverIOHTTPHandler.cpp @@ -87,8 +87,7 @@ void InterserverIOHTTPHandler::handleRequest(HTTPServerRequest & request, HTTPSe response.setChunkedTransferEncoding(true); Output used_output; - const auto & config = server.config(); - unsigned keep_alive_timeout = config.getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT); + const auto keep_alive_timeout = server.context()->getServerSettings().keep_alive_timeout.totalSeconds(); used_output.out = std::make_shared( response, request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD, keep_alive_timeout); diff --git a/src/Server/PrometheusRequestHandler.cpp b/src/Server/PrometheusRequestHandler.cpp index 6d35386bfb5..b871d1b0ddc 100644 --- a/src/Server/PrometheusRequestHandler.cpp +++ b/src/Server/PrometheusRequestHandler.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -17,9 +18,7 @@ void PrometheusRequestHandler::handleRequest(HTTPServerRequest & request, HTTPSe { try { - const auto & config = server.config(); - unsigned keep_alive_timeout = config.getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT); - + const auto keep_alive_timeout = server.context()->getServerSettings().keep_alive_timeout.totalSeconds(); setResponseDefaultHeaders(response, keep_alive_timeout); response.setContentType("text/plain; version=0.0.4; charset=UTF-8"); diff --git a/src/Server/ReplicasStatusHandler.cpp b/src/Server/ReplicasStatusHandler.cpp index ad54b24f31d..b50fb955563 100644 --- a/src/Server/ReplicasStatusHandler.cpp +++ b/src/Server/ReplicasStatusHandler.cpp @@ -78,8 +78,8 @@ void ReplicasStatusHandler::handleRequest(HTTPServerRequest & request, HTTPServe } } - const auto & config = getContext()->getConfigRef(); - setResponseDefaultHeaders(response, config.getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT)); + const auto & server_settings = getContext()->getServerSettings(); + setResponseDefaultHeaders(response, server_settings.keep_alive_timeout.totalSeconds()); if (!ok) { diff --git a/src/Server/StaticRequestHandler.cpp b/src/Server/StaticRequestHandler.cpp index a7e85d161c1..34cb5d2d169 100644 --- a/src/Server/StaticRequestHandler.cpp +++ b/src/Server/StaticRequestHandler.cpp @@ -34,7 +34,7 @@ namespace ErrorCodes } static inline WriteBufferPtr -responseWriteBuffer(HTTPServerRequest & request, HTTPServerResponse & response, unsigned int keep_alive_timeout) +responseWriteBuffer(HTTPServerRequest & request, HTTPServerResponse & response, UInt64 keep_alive_timeout) { /// The client can pass a HTTP header indicating supported compression method (gzip or deflate). String http_response_compression_methods = request.get("Accept-Encoding", ""); @@ -90,7 +90,7 @@ static inline void trySendExceptionToClient( void StaticRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse & response) { - auto keep_alive_timeout = server.config().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT); + auto keep_alive_timeout = server.context()->getServerSettings().keep_alive_timeout.totalSeconds(); const auto & out = responseWriteBuffer(request, response, keep_alive_timeout); try diff --git a/src/Server/WebUIRequestHandler.cpp b/src/Server/WebUIRequestHandler.cpp index 12d2588723e..6277131fd5c 100644 --- a/src/Server/WebUIRequestHandler.cpp +++ b/src/Server/WebUIRequestHandler.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #ifdef __clang__ @@ -37,7 +38,7 @@ WebUIRequestHandler::WebUIRequestHandler(IServer & server_) void WebUIRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse & response) { - auto keep_alive_timeout = server.config().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT); + auto keep_alive_timeout = server.context()->getServerSettings().keep_alive_timeout.totalSeconds(); response.setContentType("text/html; charset=UTF-8"); diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index 2bf2f9fddc7..c54947dde8e 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -522,11 +522,10 @@ std::pair Fetcher::fetchSelected uri, Poco::Net::HTTPRequest::HTTP_POST, nullptr, - timeouts, creds, DBMS_DEFAULT_BUFFER_SIZE, 0, /* no redirects */ - static_cast(data_settings->replicated_max_parallel_fetches_for_host)); + context->getCommonFetchesSessionFactory()); int server_protocol_version = parse(in->getResponseCookie("server_protocol_version", "0")); String remote_fs_metadata = parse(in->getResponseCookie("remote_fs_metadata", "")); diff --git a/src/Storages/MergeTree/MergeTreeSettings.h b/src/Storages/MergeTree/MergeTreeSettings.h index f55fb5b8fac..922eb266512 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -110,10 +110,6 @@ struct Settings; M(UInt64, max_files_to_modify_in_alter_columns, 75, "Not apply ALTER if number of files for modification(deletion, addition) more than this.", 0) \ M(UInt64, max_files_to_remove_in_alter_columns, 50, "Not apply ALTER, if number of files for deletion more than this.", 0) \ M(Float, replicated_max_ratio_of_wrong_parts, 0.5, "If ratio of wrong parts to total number of parts is less than this - allow to start.", 0) \ - M(UInt64, replicated_max_parallel_fetches_for_host, DEFAULT_COUNT_OF_HTTP_CONNECTIONS_PER_ENDPOINT, "Limit parallel fetches from endpoint (actually pool size).", 0) \ - M(Seconds, replicated_fetches_http_connection_timeout, 0, "HTTP connection timeout for part fetch requests. Inherited from default profile `http_connection_timeout` if not set explicitly.", 0) \ - M(Seconds, replicated_fetches_http_send_timeout, 0, "HTTP send timeout for part fetch requests. Inherited from default profile `http_send_timeout` if not set explicitly.", 0) \ - M(Seconds, replicated_fetches_http_receive_timeout, 0, "HTTP receive timeout for fetch part requests. Inherited from default profile `http_receive_timeout` if not set explicitly.", 0) \ M(Bool, replicated_can_become_leader, true, "If true, Replicated tables replicas on this node will try to acquire leadership.", 0) \ M(Seconds, zookeeper_session_expiration_check_period, 60, "ZooKeeper session expiration check period, in seconds.", 0) \ M(Seconds, initialization_retry_period, 60, "Retry period for table initialization, in seconds.", 0) \ @@ -214,6 +210,10 @@ struct Settings; MAKE_OBSOLETE_MERGE_TREE_SETTING(M, Bool, use_metadata_cache, false) \ MAKE_OBSOLETE_MERGE_TREE_SETTING(M, UInt64, merge_tree_enable_clear_old_broken_detached, 0) \ MAKE_OBSOLETE_MERGE_TREE_SETTING(M, UInt64, merge_tree_clear_old_broken_detached_parts_ttl_timeout_seconds, 1ULL * 3600 * 24 * 30) \ + MAKE_OBSOLETE_MERGE_TREE_SETTING(M, Seconds, replicated_fetches_http_connection_timeout, 0) \ + MAKE_OBSOLETE_MERGE_TREE_SETTING(M, Seconds, replicated_fetches_http_send_timeout, 0) \ + MAKE_OBSOLETE_MERGE_TREE_SETTING(M, Seconds, replicated_fetches_http_receive_timeout, 0) \ + MAKE_OBSOLETE_MERGE_TREE_SETTING(M, UInt64, replicated_max_parallel_fetches_for_host, DEFAULT_COUNT_OF_HTTP_CONNECTIONS_PER_ENDPOINT) \ /// Settings that should not change after the creation of a table. /// NOLINTNEXTLINE diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 8114db9241f..8f9147fd743 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -264,7 +264,7 @@ String StorageReplicatedMergeTree::getEndpointName() const static ConnectionTimeouts getHTTPTimeouts(ContextPtr context) { - return ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), {context->getConfigRef().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT), 0}); + return ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), context->getServerSettings().keep_alive_timeout); } static MergeTreePartInfo makeDummyDropRangeForMovePartitionOrAttachPartitionFrom(const String & partition_id) @@ -2711,7 +2711,7 @@ bool StorageReplicatedMergeTree::executeReplaceRange(LogEntry & entry) { String source_replica_path = fs::path(zookeeper_path) / "replicas" / part_desc->replica; ReplicatedMergeTreeAddress address(getZooKeeper()->get(fs::path(source_replica_path) / "host")); - auto timeouts = getFetchPartHTTPTimeouts(getContext()); + auto timeouts = ConnectionTimeouts::getFetchPartHTTPTimeouts(getContext()->getServerSettings(), getContext()->getSettingsRef()); auto credentials = getContext()->getInterserverCredentials(); String interserver_scheme = getContext()->getInterserverScheme(); @@ -4183,23 +4183,6 @@ void StorageReplicatedMergeTree::stopBeingLeader() is_leader = false; } -ConnectionTimeouts StorageReplicatedMergeTree::getFetchPartHTTPTimeouts(ContextPtr local_context) -{ - auto timeouts = getHTTPTimeouts(local_context); - auto settings = getSettings(); - - if (settings->replicated_fetches_http_connection_timeout.changed) - timeouts.connection_timeout = settings->replicated_fetches_http_connection_timeout; - - if (settings->replicated_fetches_http_send_timeout.changed) - timeouts.send_timeout = settings->replicated_fetches_http_send_timeout; - - if (settings->replicated_fetches_http_receive_timeout.changed) - timeouts.receive_timeout = settings->replicated_fetches_http_receive_timeout; - - return timeouts; -} - bool StorageReplicatedMergeTree::checkReplicaHavePart(const String & replica, const String & part_name) { auto zookeeper = getZooKeeper(); @@ -4798,7 +4781,7 @@ bool StorageReplicatedMergeTree::fetchPart( else { address.fromString(zookeeper->get(fs::path(source_replica_path) / "host")); - timeouts = getFetchPartHTTPTimeouts(getContext()); + timeouts = ConnectionTimeouts::getFetchPartHTTPTimeouts(getContext()->getServerSettings(), getContext()->getSettingsRef()); credentials = getContext()->getInterserverCredentials(); interserver_scheme = getContext()->getInterserverScheme(); diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index a8ab8eb7013..d919bae9b82 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -761,10 +761,6 @@ private: int32_t alter_version, int32_t log_version); - /// Exchange parts. - - ConnectionTimeouts getFetchPartHTTPTimeouts(ContextPtr context); - /** Returns an empty string if no one has a part. */ String findReplicaHavingPart(const String & part_name, bool active); diff --git a/src/Storages/StorageURL.cpp b/src/Storages/StorageURL.cpp index cf3aa5ac175..50eda3eac78 100644 --- a/src/Storages/StorageURL.cpp +++ b/src/Storages/StorageURL.cpp @@ -93,7 +93,7 @@ static bool urlWithGlobs(const String & uri) static ConnectionTimeouts getHTTPTimeouts(ContextPtr context) { - return ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), {context->getConfigRef().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT), 0}); + return ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), context->getServerSettings().keep_alive_timeout); } IStorageURLBase::IStorageURLBase( diff --git a/src/Storages/StorageXDBC.cpp b/src/Storages/StorageXDBC.cpp index 0ba8838d4c3..a569c50835c 100644 --- a/src/Storages/StorageXDBC.cpp +++ b/src/Storages/StorageXDBC.cpp @@ -142,7 +142,7 @@ SinkToStoragePtr StorageXDBC::write(const ASTPtr & /* query */, const StorageMet local_context, ConnectionTimeouts::getHTTPTimeouts( local_context->getSettingsRef(), - {local_context->getConfigRef().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT), 0}), + local_context->getServerSettings().keep_alive_timeout), compression_method); } diff --git a/src/TableFunctions/ITableFunctionXDBC.cpp b/src/TableFunctions/ITableFunctionXDBC.cpp index b1746ea769f..ca6d40a05a3 100644 --- a/src/TableFunctions/ITableFunctionXDBC.cpp +++ b/src/TableFunctions/ITableFunctionXDBC.cpp @@ -159,7 +159,7 @@ ColumnsDescription ITableFunctionXDBC::getActualTableStructure(ContextPtr contex {}, ConnectionTimeouts::getHTTPTimeouts( context->getSettingsRef(), - {context->getConfigRef().getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT), 0}), + context->getServerSettings().keep_alive_timeout), credentials); std::string columns_info; From c0e45c15fbb7ba5ee9f8f22c82fdaa613370bdb5 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 5 Dec 2023 13:38:25 +0000 Subject: [PATCH 021/253] add test for lightweight deletes and mutations --- src/Interpreters/MutationsInterpreter.cpp | 2 +- src/Parsers/ASTAlterQuery.cpp | 6 --- src/Parsers/ParserAlterQuery.cpp | 6 --- .../02932_apply_deleted_mask.reference | 21 ++++++--- .../0_stateless/02932_apply_deleted_mask.sql | 33 +++++++++++--- .../02932_lwd_and_mutations.reference | 14 ++++++ .../0_stateless/02932_lwd_and_mutations.sql | 43 +++++++++++++++++++ 7 files changed, 100 insertions(+), 25 deletions(-) create mode 100644 tests/queries/0_stateless/02932_lwd_and_mutations.reference create mode 100644 tests/queries/0_stateless/02932_lwd_and_mutations.sql diff --git a/src/Interpreters/MutationsInterpreter.cpp b/src/Interpreters/MutationsInterpreter.cpp index d333477f36e..a492ea266cf 100644 --- a/src/Interpreters/MutationsInterpreter.cpp +++ b/src/Interpreters/MutationsInterpreter.cpp @@ -1027,7 +1027,7 @@ void MutationsInterpreter::prepareMutationStages(std::vector & prepared_s auto all_columns = storage_snapshot->getColumnsByNames(options, available_columns); /// Add _row_exists column if it is present in the part - if (source.hasLightweightDeleteMask()) + if (source.hasLightweightDeleteMask() || deleted_mask_updated) all_columns.push_back(LightweightDeleteDescription::FILTER_COLUMN); bool has_filters = false; diff --git a/src/Parsers/ASTAlterQuery.cpp b/src/Parsers/ASTAlterQuery.cpp index ea116e6ccfd..ed9de6a46eb 100644 --- a/src/Parsers/ASTAlterQuery.cpp +++ b/src/Parsers/ASTAlterQuery.cpp @@ -475,12 +475,6 @@ void ASTAlterCommand::formatImpl(const FormatSettings & settings, FormatState & settings.ostr << (settings.hilite ? hilite_keyword : "") << " IN PARTITION " << (settings.hilite ? hilite_none : ""); partition->formatImpl(settings, state, frame); } - - if (predicate) - { - settings.ostr << (settings.hilite ? hilite_keyword : "") << " WHERE " << (settings.hilite ? hilite_none : ""); - predicate->formatImpl(settings, state, frame); - } } else throw Exception(ErrorCodes::UNEXPECTED_AST_STRUCTURE, "Unexpected type of ALTER"); diff --git a/src/Parsers/ParserAlterQuery.cpp b/src/Parsers/ParserAlterQuery.cpp index 3522611ec4c..6c772db0193 100644 --- a/src/Parsers/ParserAlterQuery.cpp +++ b/src/Parsers/ParserAlterQuery.cpp @@ -833,12 +833,6 @@ bool ParserAlterCommand::parseImpl(Pos & pos, ASTPtr & node, Expected & expected if (!parser_partition.parse(pos, command->partition, expected)) return false; } - - if (s_where.ignore(pos, expected)) - { - if (!parser_exp_elem.parse(pos, command->predicate, expected)) - return false; - } } else return false; diff --git a/tests/queries/0_stateless/02932_apply_deleted_mask.reference b/tests/queries/0_stateless/02932_apply_deleted_mask.reference index 0d75f7c1b30..22499472f84 100644 --- a/tests/queries/0_stateless/02932_apply_deleted_mask.reference +++ b/tests/queries/0_stateless/02932_apply_deleted_mask.reference @@ -1,6 +1,15 @@ -10 45 -all_1_1_0 10 0 -7 33 -all_1_1_0_2 10 1 -7 33 -all_1_1_0_3 7 0 +Inserted +100 4950 +10 100 0 +Lighweight deleted +86 4271 +10 100 10 +Mask applied +86 4271 +10 86 0 +Lighweight deleted +72 3578 +10 86 10 +Mask applied in partition +72 3578 +10 84 9 diff --git a/tests/queries/0_stateless/02932_apply_deleted_mask.sql b/tests/queries/0_stateless/02932_apply_deleted_mask.sql index 602c67de52e..0ada0640a8f 100644 --- a/tests/queries/0_stateless/02932_apply_deleted_mask.sql +++ b/tests/queries/0_stateless/02932_apply_deleted_mask.sql @@ -1,22 +1,43 @@ DROP TABLE IF EXISTS t_materialize_delete; -CREATE TABLE t_materialize_delete (id UInt64, v UInt64) ENGINE = MergeTree ORDER BY tuple() settings min_bytes_for_wide_part = 0; +CREATE TABLE t_materialize_delete (id UInt64, v UInt64) +ENGINE = MergeTree ORDER BY id PARTITION BY id % 10; SET mutations_sync = 2; -INSERT INTO t_materialize_delete SELECT number, number FROM numbers(10); +INSERT INTO t_materialize_delete SELECT number, number FROM numbers(100); + +SELECT 'Inserted'; SELECT count(), sum(v) FROM t_materialize_delete; -SELECT name, rows, has_lightweight_delete FROM system.parts WHERE database = currentDatabase() AND table = 't_materialize_delete' AND active; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_materialize_delete' AND active; -DELETE FROM t_materialize_delete WHERE id % 3 = 1; +SELECT 'Lighweight deleted'; + +DELETE FROM t_materialize_delete WHERE id % 7 = 3; SELECT count(), sum(v) FROM t_materialize_delete; -SELECT name, rows, has_lightweight_delete FROM system.parts WHERE database = currentDatabase() AND table = 't_materialize_delete' AND active; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_materialize_delete' AND active; + +SELECT 'Mask applied'; ALTER TABLE t_materialize_delete APPLY DELETED MASK; SELECT count(), sum(v) FROM t_materialize_delete; -SELECT name, rows, has_lightweight_delete FROM system.parts WHERE database = currentDatabase() AND table = 't_materialize_delete' AND active; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_materialize_delete' AND active; + +SELECT 'Lighweight deleted'; + +DELETE FROM t_materialize_delete WHERE id % 7 = 4; + +SELECT count(), sum(v) FROM t_materialize_delete; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_materialize_delete' AND active; + +SELECT 'Mask applied in partition'; + +ALTER TABLE t_materialize_delete APPLY DELETED MASK IN PARTITION 5; + +SELECT count(), sum(v) FROM t_materialize_delete; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_materialize_delete' AND active; DROP TABLE t_materialize_delete; diff --git a/tests/queries/0_stateless/02932_lwd_and_mutations.reference b/tests/queries/0_stateless/02932_lwd_and_mutations.reference new file mode 100644 index 00000000000..dc0d3536b8f --- /dev/null +++ b/tests/queries/0_stateless/02932_lwd_and_mutations.reference @@ -0,0 +1,14 @@ +900 0 [1,2,3,4,5,6,7,8,9] +1 1000 1 +800 200 [2,3,4,5,6,7,8,9] +1 800 0 +700 150 [3,4,5,6,7,8,9] +1 800 1 +600 300 [4,5,6,7,8,9] +1 600 0 +400 200 [6,7,8,9] +1 500 1 +200 100 [8,9] +1 300 1 +200 100 [8,9] +1 200 0 diff --git a/tests/queries/0_stateless/02932_lwd_and_mutations.sql b/tests/queries/0_stateless/02932_lwd_and_mutations.sql new file mode 100644 index 00000000000..a68aca91764 --- /dev/null +++ b/tests/queries/0_stateless/02932_lwd_and_mutations.sql @@ -0,0 +1,43 @@ +DROP TABLE IF EXISTS t_lwd_mutations; + +CREATE TABLE t_lwd_mutations(id UInt64, v UInt64) ENGINE = MergeTree ORDER BY id; +INSERT INTO t_lwd_mutations SELECT number, 0 FROM numbers(1000); + +SET mutations_sync = 2; + +DELETE FROM t_lwd_mutations WHERE id % 10 = 0; + +SELECT count(), sum(v), arraySort(groupUniqArray(id % 10)) FROM t_lwd_mutations; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_lwd_mutations' AND active; + +ALTER TABLE t_lwd_mutations UPDATE v = 1 WHERE id % 4 = 0, DELETE WHERE id % 10 = 1; + +SELECT count(), sum(v), arraySort(groupUniqArray(id % 10)) FROM t_lwd_mutations; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_lwd_mutations' AND active; + +DELETE FROM t_lwd_mutations WHERE id % 10 = 2; + +SELECT count(), sum(v), arraySort(groupUniqArray(id % 10)) FROM t_lwd_mutations; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_lwd_mutations' AND active; + +ALTER TABLE t_lwd_mutations UPDATE v = 1 WHERE id % 4 = 1, DELETE WHERE id % 10 = 3; + +SELECT count(), sum(v), arraySort(groupUniqArray(id % 10)) FROM t_lwd_mutations; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_lwd_mutations' AND active; + +ALTER TABLE t_lwd_mutations UPDATE _row_exists = 0 WHERE id % 10 = 4, DELETE WHERE id % 10 = 5; + +SELECT count(), sum(v), arraySort(groupUniqArray(id % 10)) FROM t_lwd_mutations; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_lwd_mutations' AND active; + +ALTER TABLE t_lwd_mutations DELETE WHERE id % 10 = 6, UPDATE _row_exists = 0 WHERE id % 10 = 7; + +SELECT count(), sum(v), arraySort(groupUniqArray(id % 10)) FROM t_lwd_mutations; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_lwd_mutations' AND active; + +ALTER TABLE t_lwd_mutations APPLY DELETED MASK; + +SELECT count(), sum(v), arraySort(groupUniqArray(id % 10)) FROM t_lwd_mutations; +SELECT count(), sum(rows), sum(has_lightweight_delete) FROM system.parts WHERE database = currentDatabase() AND table = 't_lwd_mutations' AND active; + +DROP TABLE IF EXISTS t_lwd_mutations; From 87dc575b47859b45e4c19abf12f03965e9ea60f8 Mon Sep 17 00:00:00 2001 From: Nikita Mikhaylov Date: Tue, 5 Dec 2023 15:24:29 +0100 Subject: [PATCH 022/253] Better --- tests/queries/0_stateless/02888_obsolete_settings.reference | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/queries/0_stateless/02888_obsolete_settings.reference b/tests/queries/0_stateless/02888_obsolete_settings.reference index 0ef6404eabb..a3988e4cd9c 100644 --- a/tests/queries/0_stateless/02888_obsolete_settings.reference +++ b/tests/queries/0_stateless/02888_obsolete_settings.reference @@ -62,7 +62,11 @@ merge_tree_enable_clear_old_broken_detached min_bytes_for_compact_part min_relative_delay_to_yield_leadership min_rows_for_compact_part +replicated_fetches_http_connection_timeout +replicated_fetches_http_receive_timeout +replicated_fetches_http_send_timeout replicated_max_parallel_fetches +replicated_max_parallel_fetches_for_host replicated_max_parallel_fetches_for_table replicated_max_parallel_sends replicated_max_parallel_sends_for_table From eb990d863df624dca621d05d14c9e8285ce7abb0 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 5 Dec 2023 15:15:46 +0000 Subject: [PATCH 023/253] fix tests --- src/Storages/MergeTree/MutateTask.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index d9f4eeb04ef..d5222312c80 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -1536,7 +1536,8 @@ private: for (auto & command_for_interpreter : ctx->for_interpreter) { - if (command_for_interpreter.type == MutationCommand::DELETE) + if (command_for_interpreter.type == MutationCommand::DELETE + || command_for_interpreter.type == MutationCommand::APPLY_DELETED_MASK) { has_delete = true; break; From b9f281f6721b73545a8cb4347d4a8af0f446dd9d Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Tue, 5 Dec 2023 19:24:47 +0000 Subject: [PATCH 024/253] Docs: Remove mention of MV table engine --- .../en/engines/table-engines/special/materializedview.md | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 docs/en/engines/table-engines/special/materializedview.md diff --git a/docs/en/engines/table-engines/special/materializedview.md b/docs/en/engines/table-engines/special/materializedview.md deleted file mode 100644 index d5f3b364d4e..00000000000 --- a/docs/en/engines/table-engines/special/materializedview.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -slug: /en/engines/table-engines/special/materializedview -sidebar_position: 100 -sidebar_label: MaterializedView ---- - -# MaterializedView Table Engine - -Used for implementing materialized views (for more information, see [CREATE VIEW](../../../sql-reference/statements/create/view.md#materialized)). For storing data, it uses a different engine that was specified when creating the view. When reading from a table, it just uses that engine. From 1276c3ab80645d31cd93ffd13af8d3213c7a2a03 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 6 Dec 2023 08:47:59 +0000 Subject: [PATCH 025/253] Try to fix broken links --- docs/en/engines/table-engines/index.md | 1 - .../mergetree-family/custom-partitioning-key.md | 2 +- docs/en/operations/system-tables/tables.md | 2 +- docs/en/sql-reference/statements/optimize.md | 2 +- docs/en/sql-reference/statements/select/order-by.md | 5 +++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/en/engines/table-engines/index.md b/docs/en/engines/table-engines/index.md index b024820024a..5e81eacc937 100644 --- a/docs/en/engines/table-engines/index.md +++ b/docs/en/engines/table-engines/index.md @@ -67,7 +67,6 @@ Engines in the family: Engines in the family: - [Distributed](../../engines/table-engines/special/distributed.md#distributed) -- [MaterializedView](../../engines/table-engines/special/materializedview.md#materializedview) - [Dictionary](../../engines/table-engines/special/dictionary.md#dictionary) - [Merge](../../engines/table-engines/special/merge.md#merge) - [File](../../engines/table-engines/special/file.md#file) diff --git a/docs/en/engines/table-engines/mergetree-family/custom-partitioning-key.md b/docs/en/engines/table-engines/mergetree-family/custom-partitioning-key.md index 97d37e476ae..23d98d4b20e 100644 --- a/docs/en/engines/table-engines/mergetree-family/custom-partitioning-key.md +++ b/docs/en/engines/table-engines/mergetree-family/custom-partitioning-key.md @@ -12,7 +12,7 @@ In most cases you do not need a partition key, and in most other cases you do no You should never use too granular of partitioning. Don't partition your data by client identifiers or names. Instead, make a client identifier or name the first column in the ORDER BY expression. ::: -Partitioning is available for the [MergeTree](../../../engines/table-engines/mergetree-family/mergetree.md) family tables (including [replicated](../../../engines/table-engines/mergetree-family/replication.md) tables). [Materialized views](../../../engines/table-engines/special/materializedview.md#materializedview) based on MergeTree tables support partitioning, as well. +Partitioning is available for the [MergeTree family tables](../../../engines/table-engines/mergetree-family/mergetree.md), including [replicated tables](../../../engines/table-engines/mergetree-family/replication.md) and [materialized views](../../../sql-reference/statements/create/view.md#materialized-view). A partition is a logical combination of records in a table by a specified criterion. You can set a partition by an arbitrary criterion, such as by month, by day, or by event type. Each partition is stored separately to simplify manipulations of this data. When accessing the data, ClickHouse uses the smallest subset of partitions possible. Partitions improve performance for queries containing a partitioning key because ClickHouse will filter for that partition before selecting the parts and granules within the partition. diff --git a/docs/en/operations/system-tables/tables.md b/docs/en/operations/system-tables/tables.md index e4461e14236..01558f4fbd9 100644 --- a/docs/en/operations/system-tables/tables.md +++ b/docs/en/operations/system-tables/tables.md @@ -29,7 +29,7 @@ Columns: - `dependencies_database` ([Array](../../sql-reference/data-types/array.md)([String](../../sql-reference/data-types/string.md))) - Database dependencies. -- `dependencies_table` ([Array](../../sql-reference/data-types/array.md)([String](../../sql-reference/data-types/string.md))) - Table dependencies ([MaterializedView](../../engines/table-engines/special/materializedview.md) tables based on the current table). +- `dependencies_table` ([Array](../../sql-reference/data-types/array.md)([String](../../sql-reference/data-types/string.md))) - Table dependencies ([materialized views](../../sql-reference/statements/create/view.md#materialized-view) the current table). - `create_table_query` ([String](../../sql-reference/data-types/string.md)) - The query that was used to create the table. diff --git a/docs/en/sql-reference/statements/optimize.md b/docs/en/sql-reference/statements/optimize.md index 07b5a196096..b5fc0a23745 100644 --- a/docs/en/sql-reference/statements/optimize.md +++ b/docs/en/sql-reference/statements/optimize.md @@ -17,7 +17,7 @@ This query tries to initialize an unscheduled merge of data parts for tables. No OPTIMIZE TABLE [db.]name [ON CLUSTER cluster] [PARTITION partition | PARTITION ID 'partition_id'] [FINAL] [DEDUPLICATE [BY expression]] ``` -The `OPTIMIZE` query is supported for [MergeTree](../../engines/table-engines/mergetree-family/mergetree.md) family, the [MaterializedView](../../engines/table-engines/special/materializedview.md) and the [Buffer](../../engines/table-engines/special/buffer.md) engines. Other table engines aren’t supported. +The `OPTIMIZE` query is supported for [MergeTree](../../engines/table-engines/mergetree-family/mergetree.md) family (including [materialized views](../../sql-reference/statements/create/view.md#materialized-view)) and the [Buffer](../../engines/table-engines/special/buffer.md) engines. Other table engines aren’t supported. When `OPTIMIZE` is used with the [ReplicatedMergeTree](../../engines/table-engines/mergetree-family/replication.md) family of table engines, ClickHouse creates a task for merging and waits for execution on all replicas (if the [alter_sync](../../operations/settings/settings.md#alter-sync) setting is set to `2`) or on current replica (if the [alter_sync](../../operations/settings/settings.md#alter-sync) setting is set to `1`). diff --git a/docs/en/sql-reference/statements/select/order-by.md b/docs/en/sql-reference/statements/select/order-by.md index 53bdc9041a1..b96ebff265d 100644 --- a/docs/en/sql-reference/statements/select/order-by.md +++ b/docs/en/sql-reference/statements/select/order-by.md @@ -265,8 +265,9 @@ Consider disabling `optimize_read_in_order` manually, when running queries that Optimization is supported in the following table engines: -- [MergeTree](../../../engines/table-engines/mergetree-family/mergetree.md) -- [Merge](../../../engines/table-engines/special/merge.md), [Buffer](../../../engines/table-engines/special/buffer.md), and [MaterializedView](../../../engines/table-engines/special/materializedview.md) table engines over `MergeTree`-engine tables +- [MergeTree](../../../engines/table-engines/mergetree-family/mergetree.md) (including [materialized views](../../../sql-reference/statements/create/view.md#materialized-view)), +- [Merge](../../../engines/table-engines/special/merge.md), +- [Buffer](../../../engines/table-engines/special/buffer.md) In `MaterializedView`-engine tables the optimization works with views like `SELECT ... FROM merge_tree_table ORDER BY pk`. But it is not supported in the queries like `SELECT ... FROM view ORDER BY pk` if the view query does not have the `ORDER BY` clause. From cb2bc710bc9560a07652a895b1c6644d46ad85fe Mon Sep 17 00:00:00 2001 From: Chen Lixiang Date: Thu, 7 Dec 2023 21:23:23 +0800 Subject: [PATCH 026/253] fix --- .../0_stateless/00753_system_columns_and_system_tables_long.sql | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql b/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql index fd9ba586580..c21f5c12b2f 100644 --- a/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql +++ b/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql @@ -68,8 +68,6 @@ FORMAT PrettyCompactNoEscapes; DROP TABLE IF EXISTS check_system_tables; - - SELECT 'Check total_bytes/total_rows for TinyLog'; CREATE TABLE check_system_tables (key UInt8) ENGINE = TinyLog(); SELECT total_bytes, total_rows FROM system.tables WHERE name = 'check_system_tables' AND database = currentDatabase(); From 89d4e639673505231c82776fea2e202c49b90940 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 7 Dec 2023 16:17:10 +0000 Subject: [PATCH 027/253] fix some tests --- ...731_long_merge_tree_select_opened_files.sh | 2 +- .../00877_memory_limit_for_new_delete.sql | 1 + .../02096_totals_global_in_bug.sql | 3 +- .../0_stateless/02163_shard_num.reference | 12 +- tests/queries/0_stateless/02163_shard_num.sql | 10 +- .../02480_max_map_null_totals.reference | 96 +++--- .../0_stateless/02480_max_map_null_totals.sql | 54 ++-- .../02496_remove_redundant_sorting.reference | 68 ++-- .../02496_remove_redundant_sorting.sh | 12 +- ...emove_redundant_sorting_analyzer.reference | 76 +++-- .../02500_remove_redundant_distinct.reference | 290 +++++++++-------- .../02500_remove_redundant_distinct.sh | 26 +- ...move_redundant_distinct_analyzer.reference | 294 ++++++++++-------- 13 files changed, 529 insertions(+), 415 deletions(-) diff --git a/tests/queries/0_stateless/00731_long_merge_tree_select_opened_files.sh b/tests/queries/0_stateless/00731_long_merge_tree_select_opened_files.sh index 11396dd34eb..1bb4dbd34de 100755 --- a/tests/queries/0_stateless/00731_long_merge_tree_select_opened_files.sh +++ b/tests/queries/0_stateless/00731_long_merge_tree_select_opened_files.sh @@ -8,7 +8,7 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh -settings="--log_queries=1 --log_query_threads=1 --log_profile_events=1 --log_query_settings=1 --allow_deprecated_syntax_for_merge_tree=1" +settings="--log_queries=1 --log_query_threads=1 --log_profile_events=1 --log_query_settings=1 --allow_deprecated_syntax_for_merge_tree=1 --max_bytes_before_external_group_by 0 --max_bytes_before_external_sort 0" # Test insert logging on each block and checkPacket() method diff --git a/tests/queries/0_stateless/00877_memory_limit_for_new_delete.sql b/tests/queries/0_stateless/00877_memory_limit_for_new_delete.sql index 3864293751f..8eb9d83b730 100644 --- a/tests/queries/0_stateless/00877_memory_limit_for_new_delete.sql +++ b/tests/queries/0_stateless/00877_memory_limit_for_new_delete.sql @@ -2,6 +2,7 @@ -- Tag no-msan: memory limits don't work correctly under msan because it replaces malloc/free SET max_memory_usage = 1000000000; +SET max_bytes_before_external_group_by = 0; SELECT sum(ignore(*)) FROM ( SELECT number, argMax(number, (number, toFixedString(toString(number), 1024))) diff --git a/tests/queries/0_stateless/02096_totals_global_in_bug.sql b/tests/queries/0_stateless/02096_totals_global_in_bug.sql index ac4f2b9d2ba..27ca26cf141 100644 --- a/tests/queries/0_stateless/02096_totals_global_in_bug.sql +++ b/tests/queries/0_stateless/02096_totals_global_in_bug.sql @@ -1,2 +1 @@ -select sum(number) from remote('127.0.0.{2,3}', numbers(2)) where number global in (select sum(number) from numbers(2) group by number with totals) group by number with totals - +select sum(number) from remote('127.0.0.{2,3}', numbers(2)) where number global in (select sum(number) from numbers(2) group by number with totals) group by number with totals order by number; diff --git a/tests/queries/0_stateless/02163_shard_num.reference b/tests/queries/0_stateless/02163_shard_num.reference index 77eea7c95b9..d79b95024f6 100644 --- a/tests/queries/0_stateless/02163_shard_num.reference +++ b/tests/queries/0_stateless/02163_shard_num.reference @@ -1,18 +1,18 @@ -- { echoOn } -SELECT shardNum() AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY _shard_num; -2 1 +SELECT shardNum() AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY _shard_num ORDER BY _shard_num; 1 1 -SELECT shardNum() AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY shard_num; 2 1 +SELECT shardNum() AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY shard_num ORDER BY shard_num; 1 1 -SELECT _shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY _shard_num; 2 1 +SELECT _shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY _shard_num ORDER BY _shard_num; 1 1 -SELECT _shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY shard_num; 2 1 +SELECT _shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY shard_num ORDER BY shard_num; 1 1 -SELECT a._shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) a GROUP BY shard_num; 2 1 +SELECT a._shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) a GROUP BY shard_num ORDER BY shard_num; 1 1 +2 1 SELECT _shard_num FROM remote('127.1', system.one) AS a INNER JOIN (SELECT _shard_num FROM system.one) AS b USING (dummy); -- { serverError UNSUPPORTED_METHOD, UNKNOWN_IDENTIFIER } diff --git a/tests/queries/0_stateless/02163_shard_num.sql b/tests/queries/0_stateless/02163_shard_num.sql index cc87140ebaf..d3b4a95c6a8 100644 --- a/tests/queries/0_stateless/02163_shard_num.sql +++ b/tests/queries/0_stateless/02163_shard_num.sql @@ -1,10 +1,10 @@ -- { echoOn } -SELECT shardNum() AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY _shard_num; -SELECT shardNum() AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY shard_num; -SELECT _shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY _shard_num; -SELECT _shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY shard_num; -SELECT a._shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) a GROUP BY shard_num; +SELECT shardNum() AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY _shard_num ORDER BY _shard_num; +SELECT shardNum() AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY shard_num ORDER BY shard_num; +SELECT _shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY _shard_num ORDER BY _shard_num; +SELECT _shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) GROUP BY shard_num ORDER BY shard_num; +SELECT a._shard_num AS shard_num, sum(1) as rows FROM remote('127.{1,2}', system, one) a GROUP BY shard_num ORDER BY shard_num; SELECT _shard_num FROM remote('127.1', system.one) AS a INNER JOIN (SELECT _shard_num FROM system.one) AS b USING (dummy); -- { serverError UNSUPPORTED_METHOD, UNKNOWN_IDENTIFIER } -- { echoOff } diff --git a/tests/queries/0_stateless/02480_max_map_null_totals.reference b/tests/queries/0_stateless/02480_max_map_null_totals.reference index 5cc9b5a495f..2fb87a76157 100644 --- a/tests/queries/0_stateless/02480_max_map_null_totals.reference +++ b/tests/queries/0_stateless/02480_max_map_null_totals.reference @@ -1,119 +1,119 @@ ([-1,0],[0,0]) -([1,2],[0,2]) ([0,1],[0,1]) +([1,2],[0,2]) ([-1,0,1,2],[0,0,0,2]) ([-1,0],[0,0]) -([1,2],[0,2]) -([0,1],[0,1]) ([-1,0,1,2],[0,0,0,2]) -([-1,0],[0,0]) -([1,2],[0,2]) ([0,1],[0,1]) +([1,2],[0,2]) +([-1,0],[0,0]) ([-1,0,1,2],[0,0,0,2]) -([-1,0],[0,0]) -([1,2],[0,2]) ([0,1],[0,1]) +([1,2],[0,2]) +([-1,0],[0,0]) +([0,1],[0,1]) +([1,2],[0,2]) ([-1,0,1,2],[0,0,0,2]) ([-1,0],[0,0]) -([1,2],[0,2]) -([0,1],[0,1]) ([-1,0,1,2],[0,0,0,2]) +([0,1],[0,1]) +([1,2],[0,2]) ([-1,0],[0,0]) -([1,2],[0,2]) -([0,1],[0,1]) ([-1,0,1,2],[0,0,0,2]) +([0,1],[0,1]) +([1,2],[0,2]) ([0],[0]) -([2],[2]) ([1],[1]) +([2],[2]) ([0,2],[0,2]) ([0],[0]) -([2],[2]) -([1],[1]) ([0,2],[0,2]) +([1],[1]) +([2],[2]) ([0],[0]) -([2],[2]) -([1],[1]) ([0,2],[0,2]) +([1],[1]) +([2],[2]) - ([-1,0],[0,0]) -([1,2],[0,2]) ([0,1],[0,1]) +([1,2],[0,2]) ([-1,0,1,2],[0,0,0,2]) ([-1,0],[0,0]) -([1,2],[0,2]) -([0,1],[0,1]) ([-1,0,1,2],[0,0,0,2]) -([-1,0],[0,0]) -([1,2],[0,2]) ([0,1],[0,1]) +([1,2],[0,2]) +([-1,0],[0,0]) ([-1,0,1,2],[0,0,0,2]) -([-1,0],[0,0]) -([1,2],[0,2]) ([0,1],[0,1]) +([1,2],[0,2]) +([-1,0],[0,0]) +([0,1],[0,1]) +([1,2],[0,2]) ([-1,0,1,2],[0,0,0,2]) ([-1,0],[0,0]) -([1,2],[0,2]) -([0,1],[0,1]) ([-1,0,1,2],[0,0,0,2]) +([0,1],[0,1]) +([1,2],[0,2]) ([-1,0],[0,0]) -([1,2],[0,2]) -([0,1],[0,1]) ([-1,0,1,2],[0,0,0,2]) +([0,1],[0,1]) +([1,2],[0,2]) ([0],[0]) -([2],[2]) ([1],[1]) +([2],[2]) ([0,2],[0,2]) ([0],[0]) -([2],[2]) -([1],[1]) ([0,2],[0,2]) +([1],[1]) +([2],[2]) ([0],[0]) -([2],[2]) -([1],[1]) ([0,2],[0,2]) +([1],[1]) +([2],[2]) - ([-1,0],[0,0]) -([1,2],[0,2]) ([0,1],[0,1]) +([1,2],[0,2]) ([-1,0,1,2],[0,0,0,2]) ([-1,0],[0,0]) -([1,2],[0,2]) -([0,1],[0,1]) ([-1,0,1,2],[0,0,0,2]) -([-1,0],[0,0]) -([1,2],[0,2]) ([0,1],[0,1]) +([1,2],[0,2]) +([-1,0],[0,0]) ([-1,0,1,2],[0,0,0,2]) -([-1,0],[0,0]) -([1,2],[0,2]) ([0,1],[0,1]) +([1,2],[0,2]) +([-1,0],[0,0]) +([0,1],[0,1]) +([1,2],[0,2]) ([-1,0,1,2],[0,0,0,2]) ([-1,0],[0,0]) -([1,2],[0,2]) -([0,1],[0,1]) ([-1,0,1,2],[0,0,0,2]) +([0,1],[0,1]) +([1,2],[0,2]) ([-1,0],[0,0]) -([1,2],[0,2]) -([0,1],[0,1]) ([-1,0,1,2],[0,0,0,2]) +([0,1],[0,1]) +([1,2],[0,2]) ([0],[0]) -([2],[2]) ([1],[1]) +([2],[2]) ([0,2],[0,2]) ([0],[0]) -([2],[2]) -([1],[1]) ([0,2],[0,2]) +([1],[1]) +([2],[2]) ([0],[0]) -([2],[2]) -([1],[1]) ([0,2],[0,2]) +([1],[1]) +([2],[2]) diff --git a/tests/queries/0_stateless/02480_max_map_null_totals.sql b/tests/queries/0_stateless/02480_max_map_null_totals.sql index 81e2a5c4243..be2c566ddc1 100644 --- a/tests/queries/0_stateless/02480_max_map_null_totals.sql +++ b/tests/queries/0_stateless/02480_max_map_null_totals.sql @@ -1,39 +1,39 @@ -SELECT maxMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS; -SELECT maxMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP; -SELECT maxMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH CUBE; +SELECT maxMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS ORDER BY number; +SELECT maxMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP ORDER BY number; +SELECT maxMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH CUBE ORDER BY number; -SELECT minMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS; -SELECT minMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP; -SELECT minMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH CUBE; +SELECT minMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS ORDER BY number; +SELECT minMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP ORDER BY number; +SELECT minMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH CUBE ORDER BY number; -SELECT sumMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS; -SELECT sumMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP; -SELECT sumMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH CUBE; +SELECT sumMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS ORDER BY number; +SELECT sumMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP ORDER BY number; +SELECT sumMap([number % 3, number % 4 - 1], [number, NULL]) FROM numbers(3) GROUP BY number WITH CUBE ORDER BY number; SELECT '-'; -SELECT maxMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS; -SELECT maxMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP; -SELECT maxMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH CUBE; +SELECT maxMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS ORDER BY number; +SELECT maxMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP ORDER BY number; +SELECT maxMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH CUBE ORDER BY number; -SELECT minMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS; -SELECT minMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP; -SELECT minMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH CUBE; +SELECT minMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS ORDER BY number; +SELECT minMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP ORDER BY number; +SELECT minMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH CUBE ORDER BY number; -SELECT sumMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS; -SELECT sumMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP; -SELECT sumMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH CUBE; +SELECT sumMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS ORDER BY number; +SELECT sumMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP ORDER BY number; +SELECT sumMap([number % 3, number % 4 - 1], [number :: Float64, NULL]) FROM numbers(3) GROUP BY number WITH CUBE ORDER BY number; SELECT '-'; -SELECT maxMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS; -SELECT maxMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP; -SELECT maxMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH CUBE; +SELECT maxMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS ORDER BY number; +SELECT maxMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP ORDER BY number; +SELECT maxMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH CUBE ORDER BY number; -SELECT minMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS; -SELECT minMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP; -SELECT minMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH CUBE; +SELECT minMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS ORDER BY number; +SELECT minMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP ORDER BY number; +SELECT minMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH CUBE ORDER BY number; -SELECT sumMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS; -SELECT sumMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP; -SELECT sumMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH CUBE; +SELECT sumMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH TOTALS ORDER BY number; +SELECT sumMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH ROLLUP ORDER BY number; +SELECT sumMap([number % 3, number % 4 - 1], [number :: UInt256, NULL]) FROM numbers(3) GROUP BY number WITH CUBE ORDER BY number; diff --git a/tests/queries/0_stateless/02496_remove_redundant_sorting.reference b/tests/queries/0_stateless/02496_remove_redundant_sorting.reference index b318157835d..b38cf176008 100644 --- a/tests/queries/0_stateless/02496_remove_redundant_sorting.reference +++ b/tests/queries/0_stateless/02496_remove_redundant_sorting.reference @@ -113,27 +113,26 @@ FROM ) ORDER BY number DESC ) AS t2 +ORDER BY number -- explain -Expression ((Projection + Before ORDER BY)) - Join (JOIN FillRightFirst) - Expression ((Before JOIN + Projection)) - Sorting (Sorting for ORDER BY) - Expression ((Before ORDER BY + (Projection + Before ORDER BY))) +Expression (Projection) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Join (JOIN FillRightFirst) + Expression ((Before JOIN + (Projection + (Before ORDER BY + (Projection + Before ORDER BY))))) ReadFromSystemNumbers - Expression ((Joined actions + (Rename joined columns + Projection))) - Sorting (Sorting for ORDER BY) - Expression ((Before ORDER BY + (Projection + Before ORDER BY))) + Expression ((Joined actions + (Rename joined columns + (Projection + (Before ORDER BY + (Projection + Before ORDER BY)))))) ReadFromSystemNumbers -- execute -0 2 -0 1 0 0 -1 2 -1 1 +0 1 +0 2 1 0 -2 2 -2 1 +1 1 +1 2 2 0 +2 1 +2 2 -- CROSS JOIN with subqueries, ORDER BY in main query -> all ORDER BY clauses will be removed in subqueries -- query SELECT * @@ -193,15 +192,18 @@ FROM ORDER BY number DESC ) GROUP BY number +ORDER BY number -- explain -Expression ((Projection + Before ORDER BY)) - Aggregating - Expression ((Before GROUP BY + (Projection + (Before ORDER BY + (Projection + Before ORDER BY))))) - ReadFromSystemNumbers +Expression (Projection) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Aggregating + Expression ((Before GROUP BY + (Projection + (Before ORDER BY + (Projection + Before ORDER BY))))) + ReadFromSystemNumbers -- execute 0 -2 1 +2 -- GROUP BY with aggregation function which depends on order -> keep ORDER BY in first subquery, and eliminate in second subquery -- query SELECT any(number) @@ -217,15 +219,18 @@ FROM ORDER BY number DESC ) GROUP BY number +ORDER BY number -- explain -Expression ((Projection + Before ORDER BY)) - Aggregating - Expression ((Before GROUP BY + (Projection + (Before ORDER BY + (Projection + Before ORDER BY))))) - ReadFromSystemNumbers +Expression (Projection) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Aggregating + Expression ((Before GROUP BY + (Projection + (Before ORDER BY + (Projection + Before ORDER BY))))) + ReadFromSystemNumbers -- execute 0 -2 1 +2 -- query with aggregation function but w/o GROUP BY -> remove sorting -- query SELECT sum(number) @@ -315,15 +320,18 @@ FROM GROUP BY number ) WHERE a > 0 +ORDER BY a -- explain -Expression ((Projection + (Before ORDER BY + ))) - Aggregating - Filter - Filter (( + (Before GROUP BY + (Projection + (Before ORDER BY + (Projection + Before ORDER BY)))))) - ReadFromSystemNumbers +Expression (Projection) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + )) + Aggregating + Filter + Filter (( + (Before GROUP BY + (Projection + (Before ORDER BY + (Projection + Before ORDER BY)))))) + ReadFromSystemNumbers -- execute -2 1 +2 -- GROUP BY in most inner query makes execution parallelized, and removing inner sorting steps will keep it that way. But need to correctly update data streams sorting properties after removing sorting steps -- query SELECT * diff --git a/tests/queries/0_stateless/02496_remove_redundant_sorting.sh b/tests/queries/0_stateless/02496_remove_redundant_sorting.sh index 8b529c26d93..d35892432a5 100755 --- a/tests/queries/0_stateless/02496_remove_redundant_sorting.sh +++ b/tests/queries/0_stateless/02496_remove_redundant_sorting.sh @@ -96,7 +96,8 @@ FROM ORDER BY number ASC ) ORDER BY number DESC -) AS t2" +) AS t2 +ORDER BY number" run_query "$query" echo "-- CROSS JOIN with subqueries, ORDER BY in main query -> all ORDER BY clauses will be removed in subqueries" @@ -138,7 +139,8 @@ FROM ) ORDER BY number DESC ) -GROUP BY number" +GROUP BY number +ORDER BY number" run_query "$query" echo "-- GROUP BY with aggregation function which depends on order -> keep ORDER BY in first subquery, and eliminate in second subquery" @@ -154,7 +156,8 @@ FROM ) ORDER BY number DESC ) -GROUP BY number" +GROUP BY number +ORDER BY number" run_query "$query" echo "-- query with aggregation function but w/o GROUP BY -> remove sorting" @@ -218,7 +221,8 @@ FROM ) GROUP BY number ) -WHERE a > 0" +WHERE a > 0 +ORDER BY a" run_query "$query" echo "-- GROUP BY in most inner query makes execution parallelized, and removing inner sorting steps will keep it that way. But need to correctly update data streams sorting properties after removing sorting steps" diff --git a/tests/queries/0_stateless/02496_remove_redundant_sorting_analyzer.reference b/tests/queries/0_stateless/02496_remove_redundant_sorting_analyzer.reference index ee2099c62ba..b2ac9e4533b 100644 --- a/tests/queries/0_stateless/02496_remove_redundant_sorting_analyzer.reference +++ b/tests/queries/0_stateless/02496_remove_redundant_sorting_analyzer.reference @@ -113,27 +113,26 @@ FROM ) ORDER BY number DESC ) AS t2 +ORDER BY number -- explain -Expression ((Project names + (Projection + DROP unused columns after JOIN))) - Join (JOIN FillRightFirst) - Expression ((Change column names to column identifiers + Project names)) - Sorting (Sorting for ORDER BY) - Expression ((Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + Change column names to column identifiers))))))) +Expression (Project names) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + (Projection + DROP unused columns after JOIN))) + Join (JOIN FillRightFirst) + Expression ((Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + Change column names to column identifiers))))))))) ReadFromSystemNumbers - Expression ((Change column names to column identifiers + Project names)) - Sorting (Sorting for ORDER BY) - Expression ((Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + Change column names to column identifiers))))))) + Expression ((Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + Change column names to column identifiers))))))))) ReadFromSystemNumbers -- execute -0 2 -0 1 0 0 -1 2 -1 1 +0 1 +0 2 1 0 -2 2 -2 1 +1 1 +1 2 2 0 +2 1 +2 2 -- CROSS JOIN with subqueries, ORDER BY in main query -> all ORDER BY clauses will be removed in subqueries -- query SELECT * @@ -193,15 +192,18 @@ FROM ORDER BY number DESC ) GROUP BY number +ORDER BY number -- explain -Expression ((Project names + Projection)) - Aggregating - Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + Change column names to column identifiers)))))))))) - ReadFromSystemNumbers +Expression (Project names) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + Projection)) + Aggregating + Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + Change column names to column identifiers)))))))))) + ReadFromSystemNumbers -- execute 0 -2 1 +2 -- GROUP BY with aggregation function which depends on order -> keep ORDER BY in first subquery, and eliminate in second subquery -- query SELECT any(number) @@ -217,17 +219,20 @@ FROM ORDER BY number DESC ) GROUP BY number +ORDER BY number -- explain -Expression ((Project names + Projection)) - Aggregating - Expression ((Before GROUP BY + (Change column names to column identifiers + Project names))) - Sorting (Sorting for ORDER BY) - Expression ((Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + Change column names to column identifiers))))))) - ReadFromSystemNumbers +Expression (Project names) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + Projection)) + Aggregating + Expression ((Before GROUP BY + (Change column names to column identifiers + Project names))) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + Change column names to column identifiers))))))) + ReadFromSystemNumbers -- execute 0 -2 1 +2 -- query with aggregation function but w/o GROUP BY -> remove sorting -- query SELECT sum(number) @@ -319,17 +324,20 @@ FROM GROUP BY number ) WHERE a > 0 +ORDER BY a -- explain -Expression ((Project names + Projection)) - Filter ((WHERE + (Change column names to column identifiers + (Project names + Projection)))) - Aggregating - Expression ((Before GROUP BY + (Change column names to column identifiers + Project names))) - Sorting (Sorting for ORDER BY) - Expression ((Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + Change column names to column identifiers))))))) - ReadFromSystemNumbers +Expression (Project names) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + Projection)) + Filter ((WHERE + (Change column names to column identifiers + (Project names + Projection)))) + Aggregating + Expression ((Before GROUP BY + (Change column names to column identifiers + Project names))) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Before ORDER BY + (Projection + Change column names to column identifiers))))))) + ReadFromSystemNumbers -- execute -2 1 +2 -- GROUP BY in most inner query makes execution parallelized, and removing inner sorting steps will keep it that way. But need to correctly update data streams sorting properties after removing sorting steps -- query SELECT * diff --git a/tests/queries/0_stateless/02500_remove_redundant_distinct.reference b/tests/queries/0_stateless/02500_remove_redundant_distinct.reference index 3f580763dba..5348d407097 100644 --- a/tests/queries/0_stateless/02500_remove_redundant_distinct.reference +++ b/tests/queries/0_stateless/02500_remove_redundant_distinct.reference @@ -73,22 +73,24 @@ FROM SELECT DISTINCT number AS n FROM numbers(2) ) as y +ORDER BY n -- explain Expression (Projection) Distinct - Distinct (Preliminary DISTINCT) - Expression (Before ORDER BY) - Join (JOIN FillRightFirst) - Expression ((Before JOIN + Projection)) - Distinct - Distinct (Preliminary DISTINCT) - Expression (Before ORDER BY) - ReadFromSystemNumbers - Expression ((Joined actions + (Rename joined columns + Projection))) - Distinct - Distinct (Preliminary DISTINCT) - Expression (Before ORDER BY) - ReadFromSystemNumbers + Sorting (Sorting for ORDER BY) + Distinct (Preliminary DISTINCT) + Expression (Before ORDER BY) + Join (JOIN FillRightFirst) + Expression ((Before JOIN + Projection)) + Distinct + Distinct (Preliminary DISTINCT) + Expression (Before ORDER BY) + ReadFromSystemNumbers + Expression ((Joined actions + (Rename joined columns + Projection))) + Distinct + Distinct (Preliminary DISTINCT) + Expression (Before ORDER BY) + ReadFromSystemNumbers -- execute 0 0 0 1 @@ -106,12 +108,15 @@ FROM FROM numbers(3) ) ) +ORDER BY a, b -- explain -Expression ((Projection + (Before ORDER BY + (Projection + (Before ORDER BY + Projection))))) - Distinct - Distinct (Preliminary DISTINCT) - Expression (Before ORDER BY) - ReadFromSystemNumbers +Expression (Projection) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + (Projection + (Before ORDER BY + Projection)))) + Distinct + Distinct (Preliminary DISTINCT) + Expression (Before ORDER BY) + ReadFromSystemNumbers -- execute 0 0 1 2 @@ -128,12 +133,15 @@ FROM FROM numbers(3) ) ) +ORDER BY a, b -- explain -Expression ((Projection + (Before ORDER BY + (Projection + (Before ORDER BY + Projection))))) - Distinct - Distinct (Preliminary DISTINCT) - Expression (Before ORDER BY) - ReadFromSystemNumbers +Expression (Projection) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + (Projection + (Before ORDER BY + Projection)))) + Distinct + Distinct (Preliminary DISTINCT) + Expression (Before ORDER BY) + ReadFromSystemNumbers -- execute 2 0 0 2 1 2 @@ -147,17 +155,19 @@ FROM FROM VALUES('Hello', 'World', 'Goodbye') ) AS words ARRAY JOIN [0, 1] AS arr +ORDER BY arr -- explain Expression (Projection) Distinct - Distinct (Preliminary DISTINCT) - Expression (Before ORDER BY) - ArrayJoin (ARRAY JOIN) - Expression ((Before ARRAY JOIN + Projection)) - Distinct - Distinct (Preliminary DISTINCT) - Expression (Before ORDER BY) - ReadFromStorage (Values) + Sorting (Sorting for ORDER BY) + Distinct (Preliminary DISTINCT) + Expression (Before ORDER BY) + ArrayJoin (ARRAY JOIN) + Expression ((Before ARRAY JOIN + Projection)) + Distinct + Distinct (Preliminary DISTINCT) + Expression (Before ORDER BY) + ReadFromStorage (Values) -- execute Hello World @@ -194,16 +204,18 @@ FROM SELECT DISTINCT ['Istanbul', 'Berlin', 'Bensheim'] AS cities ) WHERE arrayJoin(cities) IN ['Berlin', 'Bensheim'] +ORDER BY cities -- explain Expression (( + Projection)) Distinct - Distinct (Preliminary DISTINCT) - Expression (Before ORDER BY) - Filter ((WHERE + Projection)) - Distinct - Distinct (Preliminary DISTINCT) - Expression (Before ORDER BY) - ReadFromStorage (SystemOne) + Sorting (Sorting for ORDER BY) + Distinct (Preliminary DISTINCT) + Expression (Before ORDER BY) + Filter ((WHERE + Projection)) + Distinct + Distinct (Preliminary DISTINCT) + Expression (Before ORDER BY) + ReadFromStorage (SystemOne) -- execute ['Istanbul','Berlin','Bensheim'] -- GROUP BY before DISTINCT with on the same columns => remove DISTINCT @@ -222,20 +234,23 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a + ORDER BY a ) -- explain -Expression ((Projection + (Before ORDER BY + (Projection + Before ORDER BY)))) - Aggregating - Expression ((Before GROUP BY + (Projection + Before ORDER BY))) - Join (JOIN FillRightFirst) - Expression (Before JOIN) - ReadFromSystemNumbers - Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) - ReadFromSystemNumbers +Expression ((Projection + (Before ORDER BY + Projection))) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Aggregating + Expression ((Before GROUP BY + (Projection + Before ORDER BY))) + Join (JOIN FillRightFirst) + Expression (Before JOIN) + ReadFromSystemNumbers + Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) + ReadFromSystemNumbers -- execute 0 -2 1 +2 -- GROUP BY before DISTINCT with on different columns => do _not_ remove DISTINCT -- query SELECT DISTINCT c @@ -252,19 +267,22 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a + ORDER BY a ) -- explain Expression (Projection) Distinct Distinct (Preliminary DISTINCT) - Expression ((Before ORDER BY + (Projection + Before ORDER BY))) - Aggregating - Expression ((Before GROUP BY + (Projection + Before ORDER BY))) - Join (JOIN FillRightFirst) - Expression (Before JOIN) - ReadFromSystemNumbers - Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) - ReadFromSystemNumbers + Expression ((Before ORDER BY + Projection)) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Aggregating + Expression ((Before GROUP BY + (Projection + Before ORDER BY))) + Join (JOIN FillRightFirst) + Expression (Before JOIN) + ReadFromSystemNumbers + Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) + ReadFromSystemNumbers -- execute 12 -- GROUP BY WITH ROLLUP before DISTINCT with on different columns => do _not_ remove DISTINCT @@ -283,20 +301,23 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH ROLLUP + ORDER BY a ) -- explain Expression (Projection) Distinct Distinct (Preliminary DISTINCT) - Expression ((Before ORDER BY + (Projection + Before ORDER BY))) - Rollup - Aggregating - Expression ((Before GROUP BY + (Projection + Before ORDER BY))) - Join (JOIN FillRightFirst) - Expression (Before JOIN) - ReadFromSystemNumbers - Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) - ReadFromSystemNumbers + Expression ((Before ORDER BY + Projection)) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Rollup + Aggregating + Expression ((Before GROUP BY + (Projection + Before ORDER BY))) + Join (JOIN FillRightFirst) + Expression (Before JOIN) + ReadFromSystemNumbers + Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) + ReadFromSystemNumbers -- execute 12 36 @@ -316,22 +337,25 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH ROLLUP + ORDER BY a ) -- explain -Expression ((Projection + (Before ORDER BY + (Projection + Before ORDER BY)))) - Rollup - Aggregating - Expression ((Before GROUP BY + (Projection + Before ORDER BY))) - Join (JOIN FillRightFirst) - Expression (Before JOIN) - ReadFromSystemNumbers - Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) - ReadFromSystemNumbers +Expression ((Projection + (Before ORDER BY + Projection))) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Rollup + Aggregating + Expression ((Before GROUP BY + (Projection + Before ORDER BY))) + Join (JOIN FillRightFirst) + Expression (Before JOIN) + ReadFromSystemNumbers + Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) + ReadFromSystemNumbers -- execute 0 -2 -1 0 +1 +2 -- GROUP BY WITH CUBE before DISTINCT with on different columns => do _not_ remove DISTINCT -- query SELECT DISTINCT c @@ -348,20 +372,23 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH CUBE + ORDER BY a ) -- explain Expression (Projection) Distinct Distinct (Preliminary DISTINCT) - Expression ((Before ORDER BY + (Projection + Before ORDER BY))) - Cube - Aggregating - Expression ((Before GROUP BY + (Projection + Before ORDER BY))) - Join (JOIN FillRightFirst) - Expression (Before JOIN) - ReadFromSystemNumbers - Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) - ReadFromSystemNumbers + Expression ((Before ORDER BY + Projection)) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Cube + Aggregating + Expression ((Before GROUP BY + (Projection + Before ORDER BY))) + Join (JOIN FillRightFirst) + Expression (Before JOIN) + ReadFromSystemNumbers + Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) + ReadFromSystemNumbers -- execute 12 36 @@ -381,22 +408,25 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH CUBE + ORDER BY a ) -- explain -Expression ((Projection + (Before ORDER BY + (Projection + Before ORDER BY)))) - Cube - Aggregating - Expression ((Before GROUP BY + (Projection + Before ORDER BY))) - Join (JOIN FillRightFirst) - Expression (Before JOIN) - ReadFromSystemNumbers - Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) - ReadFromSystemNumbers +Expression ((Projection + (Before ORDER BY + Projection))) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Cube + Aggregating + Expression ((Before GROUP BY + (Projection + Before ORDER BY))) + Join (JOIN FillRightFirst) + Expression (Before JOIN) + ReadFromSystemNumbers + Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) + ReadFromSystemNumbers -- execute 0 -2 -1 0 +1 +2 -- GROUP BY WITH TOTALS before DISTINCT with on different columns => do _not_ remove DISTINCT -- query SELECT DISTINCT c @@ -413,20 +443,23 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH TOTALS + ORDER BY a ) -- explain Expression (Projection) Distinct Distinct (Preliminary DISTINCT) - Expression ((Before ORDER BY + (Projection + Before ORDER BY))) - TotalsHaving - Aggregating - Expression ((Before GROUP BY + (Projection + Before ORDER BY))) - Join (JOIN FillRightFirst) - Expression (Before JOIN) - ReadFromSystemNumbers - Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) - ReadFromSystemNumbers + Expression ((Before ORDER BY + Projection)) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + TotalsHaving + Aggregating + Expression ((Before GROUP BY + (Projection + Before ORDER BY))) + Join (JOIN FillRightFirst) + Expression (Before JOIN) + ReadFromSystemNumbers + Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) + ReadFromSystemNumbers -- execute 12 @@ -447,21 +480,24 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH TOTALS + ORDER BY a ) -- explain -Expression ((Projection + (Before ORDER BY + (Projection + Before ORDER BY)))) - TotalsHaving - Aggregating - Expression ((Before GROUP BY + (Projection + Before ORDER BY))) - Join (JOIN FillRightFirst) - Expression (Before JOIN) - ReadFromSystemNumbers - Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) - ReadFromSystemNumbers +Expression ((Projection + (Before ORDER BY + Projection))) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + TotalsHaving + Aggregating + Expression ((Before GROUP BY + (Projection + Before ORDER BY))) + Join (JOIN FillRightFirst) + Expression (Before JOIN) + ReadFromSystemNumbers + Expression ((Joined actions + (Rename joined columns + (Projection + Before ORDER BY)))) + ReadFromSystemNumbers -- execute 0 -2 1 +2 0 -- DISTINCT COUNT() with GROUP BY => do _not_ remove DISTINCT @@ -488,21 +524,23 @@ FROM SELECT DISTINCT number FROM numbers(2) ) +ORDER BY number -- explain Expression (Projection) Distinct - Distinct (Preliminary DISTINCT) - Union - Expression ((Before ORDER BY + Projection)) - Distinct - Distinct (Preliminary DISTINCT) - Expression (Before ORDER BY) - ReadFromSystemNumbers - Expression (( + Projection)) - Distinct - Distinct (Preliminary DISTINCT) - Expression (Before ORDER BY) - ReadFromSystemNumbers + Sorting (Sorting for ORDER BY) + Distinct (Preliminary DISTINCT) + Union + Expression ((Before ORDER BY + Projection)) + Distinct + Distinct (Preliminary DISTINCT) + Expression (Before ORDER BY) + ReadFromSystemNumbers + Expression (( + Projection)) + Distinct + Distinct (Preliminary DISTINCT) + Expression (Before ORDER BY) + ReadFromSystemNumbers -- execute 0 1 diff --git a/tests/queries/0_stateless/02500_remove_redundant_distinct.sh b/tests/queries/0_stateless/02500_remove_redundant_distinct.sh index f07cdca4b5a..f83fcff07c1 100755 --- a/tests/queries/0_stateless/02500_remove_redundant_distinct.sh +++ b/tests/queries/0_stateless/02500_remove_redundant_distinct.sh @@ -59,7 +59,8 @@ FROM ( SELECT DISTINCT number AS n FROM numbers(2) -) as y" +) as y +ORDER BY n" run_query "$query" echo "-- DISTINCT duplicates with several columns" @@ -72,7 +73,8 @@ FROM SELECT DISTINCT number as a, 2*number as b FROM numbers(3) ) -)" +) +ORDER BY a, b" run_query "$query" echo "-- DISTINCT duplicates with constant columns" @@ -85,7 +87,8 @@ FROM SELECT DISTINCT 1, number as a, 2*number as b FROM numbers(3) ) -)" +) +ORDER BY a, b" run_query "$query" echo "-- ARRAY JOIN: do _not_ remove outer DISTINCT because new rows are generated between inner and outer DISTINCTs" @@ -95,7 +98,8 @@ FROM SELECT DISTINCT * FROM VALUES('Hello', 'World', 'Goodbye') ) AS words -ARRAY JOIN [0, 1] AS arr" +ARRAY JOIN [0, 1] AS arr +ORDER BY arr" run_query "$query" echo "-- WITH FILL: do _not_ remove outer DISTINCT because new rows are generated between inner and outer DISTINCTs" @@ -114,7 +118,8 @@ FROM ( SELECT DISTINCT ['Istanbul', 'Berlin', 'Bensheim'] AS cities ) -WHERE arrayJoin(cities) IN ['Berlin', 'Bensheim']" +WHERE arrayJoin(cities) IN ['Berlin', 'Bensheim'] +ORDER BY cities" run_query "$query" echo "-- GROUP BY before DISTINCT with on the same columns => remove DISTINCT" @@ -132,6 +137,7 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a + ORDER BY a )" run_query "$query" @@ -150,6 +156,7 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a + ORDER BY a )" run_query "$query" @@ -168,6 +175,7 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH ROLLUP + ORDER BY a )" run_query "$query" @@ -186,6 +194,7 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH ROLLUP + ORDER BY a )" run_query "$query" @@ -204,6 +213,7 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH CUBE + ORDER BY a )" run_query "$query" @@ -222,6 +232,7 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH CUBE + ORDER BY a )" run_query "$query" @@ -240,6 +251,7 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH TOTALS + ORDER BY a )" run_query "$query" @@ -258,6 +270,7 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH TOTALS + ORDER BY a )" run_query "$query" @@ -274,5 +287,6 @@ FROM UNION ALL SELECT DISTINCT number FROM numbers(2) -)" +) +ORDER BY number" run_query "$query" diff --git a/tests/queries/0_stateless/02500_remove_redundant_distinct_analyzer.reference b/tests/queries/0_stateless/02500_remove_redundant_distinct_analyzer.reference index a5f2c3e5ca3..798191db7e4 100644 --- a/tests/queries/0_stateless/02500_remove_redundant_distinct_analyzer.reference +++ b/tests/queries/0_stateless/02500_remove_redundant_distinct_analyzer.reference @@ -74,22 +74,25 @@ FROM SELECT DISTINCT number AS n FROM numbers(2) ) as y +ORDER BY n -- explain Expression (Project names) Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Expression ((Projection + DROP unused columns after JOIN)) - Join (JOIN FillRightFirst) - Expression ((Change column names to column identifiers + Project names)) - Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Expression ((Projection + Change column names to column identifiers)) - ReadFromSystemNumbers - Expression ((Change column names to column identifiers + Project names)) - Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Expression ((Projection + Change column names to column identifiers)) - ReadFromSystemNumbers + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Distinct (Preliminary DISTINCT) + Expression ((Projection + DROP unused columns after JOIN)) + Join (JOIN FillRightFirst) + Expression ((Change column names to column identifiers + Project names)) + Distinct (DISTINCT) + Distinct (Preliminary DISTINCT) + Expression ((Projection + Change column names to column identifiers)) + ReadFromSystemNumbers + Expression ((Change column names to column identifiers + Project names)) + Distinct (DISTINCT) + Distinct (Preliminary DISTINCT) + Expression ((Projection + Change column names to column identifiers)) + ReadFromSystemNumbers -- execute 0 0 0 1 @@ -107,12 +110,15 @@ FROM FROM numbers(3) ) ) +ORDER BY a, b -- explain -Expression ((Project names + (Projection + (Change column names to column identifiers + (Project names + (Projection + (Change column names to column identifiers + Project names))))))) - Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Expression ((Projection + Change column names to column identifiers)) - ReadFromSystemNumbers +Expression (Project names) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Projection + (Change column names to column identifiers + Project names))))))) + Distinct (DISTINCT) + Distinct (Preliminary DISTINCT) + Expression ((Projection + Change column names to column identifiers)) + ReadFromSystemNumbers -- execute 0 0 1 2 @@ -129,12 +135,15 @@ FROM FROM numbers(3) ) ) +ORDER BY a, b -- explain -Expression ((Project names + (Projection + (Change column names to column identifiers + (Project names + (Projection + (Change column names to column identifiers + Project names))))))) - Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Expression ((Projection + Change column names to column identifiers)) - ReadFromSystemNumbers +Expression (Project names) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + (Projection + (Change column names to column identifiers + (Project names + (Projection + (Change column names to column identifiers + Project names))))))) + Distinct (DISTINCT) + Distinct (Preliminary DISTINCT) + Expression ((Projection + Change column names to column identifiers)) + ReadFromSystemNumbers -- execute 2 0 0 2 1 2 @@ -148,17 +157,20 @@ FROM FROM VALUES('Hello', 'World', 'Goodbye') ) AS words ARRAY JOIN [0, 1] AS arr +ORDER BY arr -- explain Expression (Project names) Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Expression (Projection) - ArrayJoin (ARRAY JOIN) - Expression ((DROP unused columns before ARRAY JOIN + (ARRAY JOIN actions + (Change column names to column identifiers + Project names)))) - Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Expression ((Projection + Change column names to column identifiers)) - ReadFromStorage (Values) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Distinct (Preliminary DISTINCT) + Expression (Projection) + ArrayJoin (ARRAY JOIN) + Expression ((DROP unused columns before ARRAY JOIN + (ARRAY JOIN actions + (Change column names to column identifiers + Project names)))) + Distinct (DISTINCT) + Distinct (Preliminary DISTINCT) + Expression ((Projection + Change column names to column identifiers)) + ReadFromStorage (Values) -- execute Hello World @@ -196,16 +208,19 @@ FROM SELECT DISTINCT ['Istanbul', 'Berlin', 'Bensheim'] AS cities ) WHERE arrayJoin(cities) IN ['Berlin', 'Bensheim'] +ORDER BY cities -- explain Expression (Project names) Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Expression (Projection) - Filter ((WHERE + (Change column names to column identifiers + Project names))) - Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Expression ((Projection + Change column names to column identifiers)) - ReadFromStorage (SystemOne) + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Distinct (Preliminary DISTINCT) + Expression (Projection) + Filter ((WHERE + (Change column names to column identifiers + Project names))) + Distinct (DISTINCT) + Distinct (Preliminary DISTINCT) + Expression ((Projection + Change column names to column identifiers)) + ReadFromStorage (SystemOne) -- execute ['Istanbul','Berlin','Bensheim'] -- GROUP BY before DISTINCT with on the same columns => remove DISTINCT @@ -224,20 +239,23 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a + ORDER BY a ) -- explain -Expression ((Project names + (Projection + (Change column names to column identifiers + (Project names + Projection))))) - Aggregating - Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) - Join (JOIN FillRightFirst) - Expression (Change column names to column identifiers) - ReadFromSystemNumbers - Expression (Change column names to column identifiers) - ReadFromSystemNumbers +Expression ((Project names + (Projection + (Change column names to column identifiers + Project names)))) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + Projection)) + Aggregating + Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) + Join (JOIN FillRightFirst) + Expression (Change column names to column identifiers) + ReadFromSystemNumbers + Expression (Change column names to column identifiers) + ReadFromSystemNumbers -- execute 0 -2 1 +2 -- GROUP BY before DISTINCT with on different columns => do _not_ remove DISTINCT -- query SELECT DISTINCT c @@ -254,19 +272,22 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a + ORDER BY a ) -- explain Expression (Project names) Distinct (DISTINCT) Distinct (Preliminary DISTINCT) - Expression ((Projection + (Change column names to column identifiers + (Project names + Projection)))) - Aggregating - Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) - Join (JOIN FillRightFirst) - Expression (Change column names to column identifiers) - ReadFromSystemNumbers - Expression (Change column names to column identifiers) - ReadFromSystemNumbers + Expression ((Projection + (Change column names to column identifiers + Project names))) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + Projection)) + Aggregating + Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) + Join (JOIN FillRightFirst) + Expression (Change column names to column identifiers) + ReadFromSystemNumbers + Expression (Change column names to column identifiers) + ReadFromSystemNumbers -- execute 12 -- GROUP BY WITH ROLLUP before DISTINCT with on different columns => do _not_ remove DISTINCT @@ -285,20 +306,23 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH ROLLUP + ORDER BY a ) -- explain Expression (Project names) Distinct (DISTINCT) Distinct (Preliminary DISTINCT) - Expression ((Projection + (Change column names to column identifiers + (Project names + Projection)))) - Rollup - Aggregating - Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) - Join (JOIN FillRightFirst) - Expression (Change column names to column identifiers) - ReadFromSystemNumbers - Expression (Change column names to column identifiers) - ReadFromSystemNumbers + Expression ((Projection + (Change column names to column identifiers + Project names))) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + Projection)) + Rollup + Aggregating + Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) + Join (JOIN FillRightFirst) + Expression (Change column names to column identifiers) + ReadFromSystemNumbers + Expression (Change column names to column identifiers) + ReadFromSystemNumbers -- execute 12 36 @@ -318,22 +342,25 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH ROLLUP + ORDER BY a ) -- explain -Expression ((Project names + (Projection + (Change column names to column identifiers + (Project names + Projection))))) - Rollup - Aggregating - Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) - Join (JOIN FillRightFirst) - Expression (Change column names to column identifiers) - ReadFromSystemNumbers - Expression (Change column names to column identifiers) - ReadFromSystemNumbers +Expression ((Project names + (Projection + (Change column names to column identifiers + Project names)))) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + Projection)) + Rollup + Aggregating + Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) + Join (JOIN FillRightFirst) + Expression (Change column names to column identifiers) + ReadFromSystemNumbers + Expression (Change column names to column identifiers) + ReadFromSystemNumbers -- execute 0 -2 -1 0 +1 +2 -- GROUP BY WITH CUBE before DISTINCT with on different columns => do _not_ remove DISTINCT -- query SELECT DISTINCT c @@ -350,20 +377,23 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH CUBE + ORDER BY a ) -- explain Expression (Project names) Distinct (DISTINCT) Distinct (Preliminary DISTINCT) - Expression ((Projection + (Change column names to column identifiers + (Project names + Projection)))) - Cube - Aggregating - Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) - Join (JOIN FillRightFirst) - Expression (Change column names to column identifiers) - ReadFromSystemNumbers - Expression (Change column names to column identifiers) - ReadFromSystemNumbers + Expression ((Projection + (Change column names to column identifiers + Project names))) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + Projection)) + Cube + Aggregating + Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) + Join (JOIN FillRightFirst) + Expression (Change column names to column identifiers) + ReadFromSystemNumbers + Expression (Change column names to column identifiers) + ReadFromSystemNumbers -- execute 12 36 @@ -383,22 +413,25 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH CUBE + ORDER BY a ) -- explain -Expression ((Project names + (Projection + (Change column names to column identifiers + (Project names + Projection))))) - Cube - Aggregating - Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) - Join (JOIN FillRightFirst) - Expression (Change column names to column identifiers) - ReadFromSystemNumbers - Expression (Change column names to column identifiers) - ReadFromSystemNumbers +Expression ((Project names + (Projection + (Change column names to column identifiers + Project names)))) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + Projection)) + Cube + Aggregating + Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) + Join (JOIN FillRightFirst) + Expression (Change column names to column identifiers) + ReadFromSystemNumbers + Expression (Change column names to column identifiers) + ReadFromSystemNumbers -- execute 0 -2 -1 0 +1 +2 -- GROUP BY WITH TOTALS before DISTINCT with on different columns => do _not_ remove DISTINCT -- query SELECT DISTINCT c @@ -415,20 +448,23 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH TOTALS + ORDER BY a ) -- explain Expression (Project names) Distinct (DISTINCT) Distinct (Preliminary DISTINCT) - Expression ((Projection + (Change column names to column identifiers + (Project names + Projection)))) - TotalsHaving - Aggregating - Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) - Join (JOIN FillRightFirst) - Expression (Change column names to column identifiers) - ReadFromSystemNumbers - Expression (Change column names to column identifiers) - ReadFromSystemNumbers + Expression ((Projection + (Change column names to column identifiers + Project names))) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + Projection)) + TotalsHaving + Aggregating + Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) + Join (JOIN FillRightFirst) + Expression (Change column names to column identifiers) + ReadFromSystemNumbers + Expression (Change column names to column identifiers) + ReadFromSystemNumbers -- execute 12 @@ -449,21 +485,24 @@ FROM FROM numbers(3) AS x, numbers(3, 3) AS y ) GROUP BY a WITH TOTALS + ORDER BY a ) -- explain -Expression ((Project names + (Projection + (Change column names to column identifiers + (Project names + Projection))))) - TotalsHaving - Aggregating - Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) - Join (JOIN FillRightFirst) - Expression (Change column names to column identifiers) - ReadFromSystemNumbers - Expression (Change column names to column identifiers) - ReadFromSystemNumbers +Expression ((Project names + (Projection + (Change column names to column identifiers + Project names)))) + Sorting (Sorting for ORDER BY) + Expression ((Before ORDER BY + Projection)) + TotalsHaving + Aggregating + Expression ((Before GROUP BY + (Change column names to column identifiers + (Project names + (Projection + DROP unused columns after JOIN))))) + Join (JOIN FillRightFirst) + Expression (Change column names to column identifiers) + ReadFromSystemNumbers + Expression (Change column names to column identifiers) + ReadFromSystemNumbers -- execute 0 -2 1 +2 0 -- DISTINCT COUNT() with GROUP BY => do _not_ remove DISTINCT @@ -490,21 +529,24 @@ FROM SELECT DISTINCT number FROM numbers(2) ) +ORDER BY number -- explain Expression (Project names) Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Union - Expression ((Projection + (Change column names to column identifiers + Project names))) - Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Expression ((Projection + Change column names to column identifiers)) - ReadFromSystemNumbers - Expression (( + ( + Project names))) - Distinct (DISTINCT) - Distinct (Preliminary DISTINCT) - Expression ((Projection + Change column names to column identifiers)) - ReadFromSystemNumbers + Sorting (Sorting for ORDER BY) + Expression (Before ORDER BY) + Distinct (Preliminary DISTINCT) + Union + Expression ((Projection + (Change column names to column identifiers + Project names))) + Distinct (DISTINCT) + Distinct (Preliminary DISTINCT) + Expression ((Projection + Change column names to column identifiers)) + ReadFromSystemNumbers + Expression (( + ( + Project names))) + Distinct (DISTINCT) + Distinct (Preliminary DISTINCT) + Expression ((Projection + Change column names to column identifiers)) + ReadFromSystemNumbers -- execute 0 1 From e5f849bdae77eeb8e4521553516f4e39a2faf4cd Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 8 Dec 2023 15:26:17 +0000 Subject: [PATCH 028/253] fix some tests --- tests/clickhouse-test | 4 ++-- tests/queries/0_stateless/00155_long_merges.sh | 14 +++++++++++--- tests/queries/0_stateless/00947_ml_test.sql | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 2ca22577083..8d813fbb904 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -608,10 +608,10 @@ class SettingsRandomizer: "merge_tree_coarse_index_granularity": lambda: random.randint(2, 32), "optimize_distinct_in_order": lambda: random.randint(0, 1), "max_bytes_before_external_sort": threshold_generator( - 0.2, 0.5, 1, 10 * 1024 * 1024 * 1024 + 1.0, 0.5, 1, 10 * 1024 * 1024 * 1024 ), "max_bytes_before_external_group_by": threshold_generator( - 0.2, 0.5, 1, 10 * 1024 * 1024 * 1024 + 1.0, 0.5, 1, 10 * 1024 * 1024 * 1024 ), "max_bytes_before_remerge_sort": lambda: random.randint(1, 3000000000), "optimize_sorting_by_input_stream_properties": lambda: random.randint(0, 1), diff --git a/tests/queries/0_stateless/00155_long_merges.sh b/tests/queries/0_stateless/00155_long_merges.sh index 9ed0f2c6de1..8ecca0aeb42 100755 --- a/tests/queries/0_stateless/00155_long_merges.sh +++ b/tests/queries/0_stateless/00155_long_merges.sh @@ -34,32 +34,40 @@ function test { SETTINGS="--min_insert_block_size_rows=0 --min_insert_block_size_bytes=0 --max_block_size=65505" + $CLICKHOUSE_CLIENT --query="SYSTEM STOP MERGES summing_00155" $CLICKHOUSE_CLIENT $SETTINGS --query="INSERT INTO summing_00155 (x) SELECT number AS x FROM system.numbers LIMIT $1" $CLICKHOUSE_CLIENT $SETTINGS --query="INSERT INTO summing_00155 (x) SELECT number AS x FROM system.numbers LIMIT $2" + $CLICKHOUSE_CLIENT --query="SYSTEM STOP MERGES collapsing_00155" $CLICKHOUSE_CLIENT $SETTINGS --query="INSERT INTO collapsing_00155 (x) SELECT number AS x FROM system.numbers LIMIT $1" $CLICKHOUSE_CLIENT $SETTINGS --query="INSERT INTO collapsing_00155 (x) SELECT number AS x FROM system.numbers LIMIT $2" + $CLICKHOUSE_CLIENT --query="SYSTEM STOP MERGES aggregating_00155" $CLICKHOUSE_CLIENT $SETTINGS --query="INSERT INTO aggregating_00155 (d, x, s) SELECT today() AS d, number AS x, sumState(materialize(toUInt64(1))) AS s FROM (SELECT number FROM system.numbers LIMIT $1) GROUP BY number" $CLICKHOUSE_CLIENT $SETTINGS --query="INSERT INTO aggregating_00155 (d, x, s) SELECT today() AS d, number AS x, sumState(materialize(toUInt64(1))) AS s FROM (SELECT number FROM system.numbers LIMIT $2) GROUP BY number" + $CLICKHOUSE_CLIENT --query="SYSTEM STOP MERGES replacing_00155" $CLICKHOUSE_CLIENT $SETTINGS --query="INSERT INTO replacing_00155 (x, v) SELECT number AS x, toUInt64(number % 3 == 0) FROM system.numbers LIMIT $1" $CLICKHOUSE_CLIENT $SETTINGS --query="INSERT INTO replacing_00155 (x, v) SELECT number AS x, toUInt64(number % 3 == 1) FROM system.numbers LIMIT $2" $CLICKHOUSE_CLIENT --query="SELECT count() = $SUM, sum(s) = $SUM FROM summing_00155" + $CLICKHOUSE_CLIENT --query="SYSTEM START MERGES summing_00155" $CLICKHOUSE_CLIENT --query="OPTIMIZE TABLE summing_00155" $CLICKHOUSE_CLIENT --query="SELECT count() = $MAX, sum(s) = $SUM FROM summing_00155" echo $CLICKHOUSE_CLIENT --query="SELECT count() = $SUM, sum(s) = $SUM FROM collapsing_00155" - $CLICKHOUSE_CLIENT --query="OPTIMIZE TABLE collapsing_00155" --server_logs_file='/dev/null'; + $CLICKHOUSE_CLIENT --query="SYSTEM START MERGES collapsing_00155" + $CLICKHOUSE_CLIENT --query="OPTIMIZE TABLE collapsing_00155 FINAL" --server_logs_file='/dev/null'; $CLICKHOUSE_CLIENT --query="SELECT count() = $MAX, sum(s) = $MAX FROM collapsing_00155" echo $CLICKHOUSE_CLIENT --query="SELECT count() = $SUM, sumMerge(s) = $SUM FROM aggregating_00155" - $CLICKHOUSE_CLIENT --query="OPTIMIZE TABLE aggregating_00155" + $CLICKHOUSE_CLIENT --query="SYSTEM START MERGES aggregating_00155" + $CLICKHOUSE_CLIENT --query="OPTIMIZE TABLE aggregating_00155 FINAL" $CLICKHOUSE_CLIENT --query="SELECT count() = $MAX, sumMerge(s) = $SUM FROM aggregating_00155" echo $CLICKHOUSE_CLIENT --query="SELECT count() = $SUM, sum(s) = $SUM FROM replacing_00155" - $CLICKHOUSE_CLIENT --query="OPTIMIZE TABLE replacing_00155" + $CLICKHOUSE_CLIENT --query="SYSTEM START MERGES replacing_00155" + $CLICKHOUSE_CLIENT --query="OPTIMIZE TABLE replacing_00155 FINAL" $CLICKHOUSE_CLIENT --query="SELECT count() = $MAX, sum(s) = $MAX FROM replacing_00155" $CLICKHOUSE_CLIENT --query="SELECT count() = sum(v) FROM replacing_00155 where x % 3 == 0 and x < $1" $CLICKHOUSE_CLIENT --query="SELECT count() = sum(v) FROM replacing_00155 where x % 3 == 1 and x < $2" diff --git a/tests/queries/0_stateless/00947_ml_test.sql b/tests/queries/0_stateless/00947_ml_test.sql index 94e4f3b4626..72000103a44 100644 --- a/tests/queries/0_stateless/00947_ml_test.sql +++ b/tests/queries/0_stateless/00947_ml_test.sql @@ -40,10 +40,10 @@ INSERT INTO grouptest VALUES (1, 1.732, 3.653, 11.422), (1, 2.150, 2.103, 7.609), (1, 0.061, 3.310, 7.052), (1, 1.030, 3.671, 10.075), (1, 1.879, 0.578, 2.492), (1, 0.922, 2.552, 6.499), (1, 1.145, -0.095, -0.993), (1, 1.920, 0.373, 1.959), (1, 0.458, 0.094, -1.801), (1, -0.118, 3.273, 6.582), (1, 2.667, 1.472, 6.752), (1, -0.387, -0.529, -5.360), (1, 2.219, 1.790, 6.810), (1, -0.754, 2.139, 1.908), (1, -0.446, -0.668, -5.896), (1, 1.729, 0.914, 3.199), (1, 2.908, -0.420, 1.556), (1, 1.645, 3.581, 11.034), (1, 0.358, -0.950, -5.136), (1, -0.467, 2.339, 3.084), (1, 3.629, 2.959, 13.135), (1, 2.393, 0.926, 4.563), (1, -0.945, 0.281, -4.047), (1, 3.688, -0.570, 2.667), (1, 3.016, 1.775, 8.356), (1, 2.571, 0.139, 2.559), (1, 2.999, 0.956, 5.866), (1, 1.754, -0.809, -1.920), (1, 3.943, 0.382, 6.030), (1, -0.970, 2.315, 2.004), (1, 1.503, 0.790, 2.376), (1, -0.775, 2.563, 3.139), (1, 1.211, 0.113, -0.240), (1, 3.058, 0.977, 6.048), (1, 2.729, 1.634, 7.360), (1, 0.307, 2.759, 5.893), (1, 3.272, 0.181, 4.089), (1, 1.192, 1.963, 5.273), (1, 0.931, 1.447, 3.203), (1, 3.835, 3.447, 15.011), (1, 0.709, 0.008, -1.559), (1, 3.155, -0.676, 1.283), (1, 2.342, 1.047, 4.824), (1, 2.059, 1.262, 4.903), (1, 2.797, 0.855, 5.159), (1, 0.387, 0.645, -0.292), (1, 1.418, 0.408, 1.060), (1, 2.719, -0.826, -0.039), (1, 2.735, 3.736, 13.678), (1, 0.205, 0.777, -0.260), (1, 3.117, 2.063, 9.424), (1, 0.601, 0.178, -1.263), (1, 0.064, 0.157, -2.401), (1, 3.104, -0.455, 1.842), (1, -0.253, 0.672, -1.490), (1, 2.592, -0.408, 0.961), (1, -0.909, 1.314, -0.878), (1, 0.625, 2.594, 6.031), (1, 2.749, -0.210, 1.869), (1, -0.469, 1.532, 0.657), (1, 1.954, 1.827, 6.388), (1, -0.528, 1.136, -0.647), (1, 0.802, -0.583, -3.146), (1, -0.176, 1.584, 1.400), (1, -0.705, -0.785, -6.766), (1, 1.660, 2.365, 7.416), (1, 2.278, 3.977, 13.485), (1, 2.846, 3.845, 14.229), (1, 3.588, -0.401, 2.974), (1, 3.525, 3.831, 15.542), (1, 0.191, 3.312, 7.318), (1, 2.615, -0.287, 1.370), (1, 2.701, -0.446, 1.064), (1, 2.065, -0.556, -0.538), (1, 2.572, 3.618, 12.997), (1, 3.743, -0.708, 2.362), (1, 3.734, 2.319, 11.425), (1, 3.768, 2.777, 12.866), (1, 3.203, 0.958, 6.280), (1, 1.512, 2.635, 7.927), (1, 2.194, 2.323, 8.356), (1, -0.726, 2.729, 3.735), (1, 0.020, 1.704, 2.152), (1, 2.173, 2.856, 9.912), (1, 3.124, 1.705, 8.364), (1, -0.834, 2.142, 1.759), (1, -0.702, 3.024, 4.666), (1, 1.393, 0.583, 1.535), (1, 2.136, 3.770, 12.581), (1, -0.445, 0.991, -0.917), (1, 0.244, -0.835, -5.016), (1, 2.789, 0.691, 4.652), (1, 0.246, 2.661, 5.475), (1, 3.793, 2.671, 12.601), (1, 1.645, -0.973, -2.627), (1, 2.405, 1.842, 7.336), (1, 3.221, 3.109, 12.769), (1, -0.638, 3.220, 5.385), (1, 1.836, 3.025, 9.748), (1, -0.660, 1.818, 1.133), (1, 0.901, 0.981, 1.744), (1, -0.236, 3.087, 5.789), (1, 1.744, 3.864, 12.078), (1, -0.166, 3.186, 6.226), (1, 3.536, -0.090, 3.803), (1, 3.284, 2.026, 9.648), (1, 1.327, 2.822, 8.119), (1, -0.709, 0.105, -4.104), (1, 0.509, -0.989, -4.949), (1, 0.180, -0.934, -5.440), (1, 3.522, 1.374, 8.168), (1, 1.497, -0.764, -2.297), (1, 1.696, 2.364, 7.482), (1, -0.202, -0.032, -3.500), (1, 3.109, -0.138, 2.804), (1, -0.238, 2.992, 5.501), (1, 1.639, 1.634, 5.181), (1, 1.919, 0.341, 1.859), (1, -0.563, 1.750, 1.124), (1, 0.886, 3.589, 9.539), (1, 3.619, 3.020, 13.299), (1, 1.703, -0.493, -1.073), (1, 2.364, 3.764, 13.022), (1, 1.820, 1.854, 6.201), (1, 1.437, -0.765, -2.421), (1, 1.396, 0.959, 2.668), (1, 2.608, 2.032, 8.312), (1, 0.333, -0.040, -2.455), (1, 3.441, 0.824, 6.355), (1, 1.303, 2.767, 7.908), (1, 1.359, 2.404, 6.932), (1, 0.674, 0.241, -0.930), (1, 2.708, -0.077, 2.183), (1, 3.821, 3.215, 14.287), (1, 3.316, 1.591, 8.404), (1, -0.848, 1.145, -1.259), (1, 3.455, 3.081, 13.153), (1, 2.568, 0.259, 2.914), (1, 2.866, 2.636, 10.642), (1, 2.776, -0.309, 1.626), (1, 2.087, 0.619, 3.031), (1, 1.682, 1.201, 3.967), (1, 3.800, 2.600, 12.399), (1, 3.344, -0.780, 1.347), (1, 1.053, -0.817, -3.346), (1, 0.805, 3.085, 7.865), (1, 0.173, 0.069, -2.449), (1, 2.018, 1.309, 4.964), (1, 3.713, 3.804, 15.838), (1, 3.805, -0.063, 4.421), (1, 3.587, 2.854, 12.738), (1, 2.426, -0.179, 1.315), (1, 0.535, 0.572, -0.213), (1, -0.558, 0.142, -3.690), (1, -0.875, 2.700, 3.349), (1, 2.405, 3.933, 13.610), (1, 1.633, 1.222, 3.934), (1, 0.049, 2.853, 5.657), (1, 1.146, 0.907, 2.015), (1, 0.300, 0.219, -1.744), (1, 2.226, 2.526, 9.029), (1, 2.545, -0.762, -0.198), (1, 2.553, 3.956, 13.974), (1, -0.898, 2.836, 3.713), (1, 3.796, -0.202, 3.985), (1, -0.810, 2.963, 4.268), (1, 0.511, 2.104, 4.334), (1, 3.527, 3.741, 15.275), (1, -0.921, 3.094, 4.440), (1, 0.856, 3.108, 8.036), (1, 0.815, 0.565, 0.323), (1, 3.717, 0.693, 6.512), (1, 3.052, 3.558, 13.778), (1, 2.942, 3.034, 11.986), (1, 0.765, 3.177, 8.061), (1, 3.175, -0.525, 1.776), (1, 0.309, 1.006, 0.638), (1, 1.922, 0.835, 3.349), (1, 3.678, 3.314, 14.297), (1, 2.840, -0.486, 1.221), (1, 1.195, 3.396, 9.578), (1, -0.157, 3.122, 6.053), (1, 2.404, 1.434, 6.110), (1, 3.108, 2.210, 9.845), (1, 2.289, 1.188, 5.142), (1, -0.319, -0.044, -3.769), (1, -0.625, 3.701, 6.854), (1, 2.269, -0.276, 0.710), (1, 0.777, 1.963, 4.442), (1, 0.411, 1.893, 3.501), (1, 1.173, 0.461, 0.728), (1, 1.767, 3.077, 9.765), (1, 0.853, 3.076, 7.933), (1, -0.013, 3.149, 6.421), (1, 3.841, 1.526, 9.260), (1, -0.950, 0.277, -4.070), (1, -0.644, -0.747, -6.527), (1, -0.923, 1.733, 0.353), (1, 0.044, 3.037, 6.201), (1, 2.074, 2.494, 8.631), (1, 0.016, 0.961, -0.085), (1, -0.780, -0.448, -5.904), (1, 0.170, 1.936, 3.148), (1, -0.420, 3.730, 7.349), (1, -0.630, 1.504, 0.254), (1, -0.006, 0.045, -2.879), (1, 1.101, -0.985, -3.753), (1, 1.618, 0.555, 1.900), (1, -0.336, 1.408, 0.552), (1, 1.086, 3.284, 9.024), (1, -0.815, 2.032, 1.466), (1, 3.144, -0.380, 2.148), (1, 2.326, 2.077, 7.883), (1, -0.571, 0.964, -1.251), (1, 2.416, 1.255, 5.595), (1, 3.964, 1.379, 9.065), (1, 3.897, 1.553, 9.455), (1, 1.806, 2.667, 8.611), (1, 0.323, 3.809, 9.073), (1, 0.501, 3.256, 7.769), (1, -0.679, 3.539, 6.259), (1, 2.825, 3.856, 14.219), (1, 0.288, -0.536, -4.032), (1, 3.009, 0.725, 5.193), (1, -0.763, 1.140, -1.105), (1, 1.124, 3.807, 10.670), (1, 2.478, 0.204, 2.570), (1, 2.825, 2.639, 10.566), (1, 1.878, -0.883, -1.892), (1, 3.380, 2.942, 12.587), (1, 2.202, 1.739, 6.621), (1, -0.711, -0.680, -6.463), (1, -0.266, 1.827, 1.951), (1, -0.846, 1.003, -1.683), (1, 3.201, 0.132, 3.798), (1, 2.797, 0.085, 2.849), (1, 1.632, 3.269, 10.072), (1, 2.410, 2.727, 10.003), (1, -0.624, 0.853, -1.690), (1, 1.314, 3.268, 9.433), (1, -0.395, 0.450, -2.440), (1, 0.992, 3.168, 8.489), (1, 3.355, 2.106, 10.028), (1, 0.509, -0.888, -4.647), (1, 1.007, 0.797, 1.405), (1, 0.045, 0.211, -2.278), (1, -0.911, 1.093, -1.544), (1, 2.409, 0.273, 2.637), (1, 2.640, 3.540, 12.899), (1, 2.668, -0.433, 1.038), (1, -0.014, 0.341, -2.005), (1, -0.525, -0.344, -5.083), (1, 2.278, 3.517, 12.105), (1, 3.712, 0.901, 7.128), (1, -0.689, 2.842, 4.149), (1, -0.467, 1.263, -0.147), (1, 0.963, -0.653, -3.034), (1, 2.559, 2.590, 9.889), (1, 1.566, 1.393, 4.312), (1, -1.000, 1.809, 0.429), (1, -0.297, 3.221, 6.070), (1, 2.199, 3.820, 12.856), (1, 3.096, 3.251, 12.944), (1, 1.479, 1.835, 5.461), (1, 0.276, 0.773, -0.130), (1, 0.607, 1.382, 2.360), (1, 1.169, -0.108, -0.985), (1, 3.429, 0.475, 5.282), (1, 2.626, 0.104, 2.563), (1, 1.156, 3.512, 9.850), (1, 3.947, 0.796, 7.282), (1, -0.462, 2.425, 3.351), (1, 3.957, 0.366, 6.014), (1, 3.763, -0.330, 3.536), (1, 0.667, 3.361, 8.417), (1, -0.583, 0.892, -1.492), (1, -0.505, 1.344, 0.021), (1, -0.474, 2.714, 4.195), (1, 3.455, 0.014, 3.950), (1, 1.016, 1.828, 4.516), (1, 1.845, 0.193, 1.269), (1, -0.529, 3.930, 7.731), (1, 2.636, 0.045, 2.408), (1, 3.757, -0.918, 1.760), (1, -0.808, 1.160, -1.137), (1, 0.744, 1.435, 2.793), (1, 3.457, 3.566, 14.613), (1, 1.061, 3.140, 8.544), (1, 3.733, 3.368, 14.570), (1, -0.969, 0.879, -2.301), (1, 3.940, 3.136, 14.287), (1, -0.730, 2.107, 1.860), (1, 3.699, 2.820, 12.858), (1, 2.197, -0.636, -0.514), (1, 0.775, -0.979, -4.387), (1, 2.019, 2.828, 9.521), (1, 1.415, 0.113, 0.170), (1, 1.567, 3.410, 10.363), (1, 0.984, -0.960, -3.913), (1, 1.809, 2.487, 8.079), (1, 1.550, 1.130, 3.489), (1, -0.770, 3.027, 4.542), (1, -0.358, 3.326, 6.262), (1, 3.140, 0.096, 3.567), (1, -0.685, 2.213, 2.270), (1, 0.916, 0.692, 0.907), (1, 1.526, 1.159, 3.527), (1, 2.675, -0.568, 0.645), (1, 1.740, 3.019, 9.538), (1, 1.223, 2.088, 5.709), (1, 1.572, -0.125, -0.230), (1, 3.641, 0.362, 5.369), (1, 2.944, 3.897, 14.578), (1, 2.775, 2.461, 9.932), (1, -0.200, 2.492, 4.076), (1, 0.065, 2.055, 3.296), (1, 2.375, -0.639, -0.167), (1, -0.133, 1.138, 0.149), (1, -0.385, 0.163, -3.281), (1, 2.200, 0.863, 3.989), (1, -0.470, 3.492, 6.536), (1, -0.916, -0.547, -6.472), (1, 0.634, 0.927, 1.049), (1, 2.930, 2.655, 10.825), (1, 3.094, 2.802, 11.596), (1, 0.457, 0.539, -0.470), (1, 1.277, 2.229, 6.240), (1, -0.157, 1.270, 0.496), (1, 3.320, 0.640, 5.559), (1, 2.836, 1.067, 5.872), (1, 0.921, -0.716, -3.307), (1, 3.886, 1.487, 9.233), (1, 0.306, -0.142, -2.815), (1, 3.727, -0.410, 3.225), (1, 1.268, -0.801, -2.866), (1, 2.302, 2.493, 9.084), (1, 0.331, 0.373, -1.220), (1, 3.224, -0.857, 0.879), (1, 1.328, 2.786, 8.014), (1, 3.639, 1.601, 9.081), (1, 3.201, -0.484, 1.949), (1, 3.447, -0.734, 1.692), (1, 2.773, -0.143, 2.117), (1, 1.517, -0.493, -1.445), (1, 1.778, -0.428, -0.728), (1, 3.989, 0.099, 5.274), (1, 1.126, 3.985, 11.206), (1, 0.348, 0.756, -0.035), (1, 2.399, 2.576, 9.525), (1, 0.866, 1.800, 4.132), (1, 3.612, 1.598, 9.017), (1, 0.495, 2.239, 4.707), (1, 2.442, 3.712, 13.019), (1, 0.238, -0.844, -5.057), (1, 1.404, 3.095, 9.093), (1, 2.842, 2.044, 8.816), (1, 0.622, 0.322, -0.791), (1, -0.561, 1.242, -0.395), (1, 0.679, 3.822, 9.823), (1, 1.875, 3.526, 11.327), (1, 3.587, 1.050, 7.324), (1, 1.467, 0.588, 1.699), (1, 3.180, 1.571, 8.074), (1, 1.402, 0.430, 1.093), (1, 1.834, 2.209, 7.294), (1, 3.542, -0.259, 3.306), (1, -0.517, 0.174, -3.513), (1, 3.549, 2.210, 10.729), (1, 2.260, 3.393, 11.699), (1, 0.036, 1.893, 2.751), (1, 0.680, 2.815, 6.804), (1, 0.219, 0.368, -1.459), (1, -0.519, 3.987, 7.924), (1, 0.974, 0.761, 1.231), (1, 0.107, 0.620, -0.927), (1, 1.513, 1.910, 5.755), (1, 3.114, 0.894, 5.910), (1, 3.061, 3.052, 12.276), (1, 2.556, 3.779, 13.448), (1, 1.964, 2.692, 9.002), (1, 3.894, -0.032, 4.690), (1, -0.693, 0.910, -1.655), (1, 2.692, 2.908, 11.108), (1, -0.824, 1.190, -1.078), (1, 3.621, 0.918, 6.997), (1, 3.190, 2.442, 10.707), (1, 1.424, -0.546, -1.791), (1, 2.061, -0.427, -0.158), (1, 1.532, 3.158, 9.540), (1, 0.648, 3.557, 8.967), (1, 2.511, 1.665, 7.017), (1, 1.903, -0.168, 0.302), (1, -0.186, -0.718, -5.528), (1, 2.421, 3.896, 13.531), (1, 3.063, 1.841, 8.650), (1, 0.636, 1.699, 3.367), (1, 1.555, 0.688, 2.174), (1, -0.412, 0.454, -2.462), (1, 1.645, 3.207, 9.911), (1, 3.396, 3.766, 15.090), (1, 0.375, -0.256, -3.017), (1, 3.636, 0.732, 6.469), (1, 2.503, 3.133, 11.405), (1, -0.253, 0.693, -1.429), (1, 3.178, 3.110, 12.686), (1, 3.282, -0.725, 1.388), (1, -0.297, 1.222, 0.070), (1, 1.872, 3.211, 10.377), (1, 3.471, 1.446, 8.278), (1, 2.891, 0.197, 3.374), (1, -0.896, 2.198, 1.802), (1, 1.178, -0.717, -2.796), (1, 0.650, 3.371, 8.412), (1, 0.447, 3.248, 7.637), (1, 1.616, -0.109, -0.097), (1, 1.837, 1.092, 3.951), (1, 0.767, 1.384, 2.684), (1, 3.466, -0.600, 2.133), (1, -0.800, -0.734, -6.802), (1, -0.534, 0.068, -3.865), (1, 3.416, -0.459, 2.455), (1, 0.800, -0.132, -1.795), (1, 2.150, 1.190, 4.869), (1, 0.830, 1.220, 2.319), (1, 2.656, 2.587, 10.072), (1, 0.375, -0.219, -2.906), (1, 0.582, -0.637, -3.749), (1, 0.588, -0.723, -3.992), (1, 3.875, 2.126, 11.127), (1, -0.476, 1.909, 1.775), (1, 0.963, 3.597, 9.716), (1, -0.888, 3.933, 7.021), (1, 1.711, -0.868, -2.184), (1, 3.244, 1.990, 9.460), (1, -0.057, 1.537, 1.497), (1, -0.015, 3.511, 7.504), (1, 0.280, 0.582, -0.695), (1, 2.402, 2.731, 9.998), (1, 2.053, 2.253, 7.865), (1, 1.955, 0.172, 1.424), (1, 3.746, 0.872, 7.107), (1, -0.157, 2.381, 3.829), (1, 3.548, -0.918, 1.340), (1, 2.449, 3.195, 11.482), (1, 1.582, 1.055, 3.329), (1, 1.908, -0.839, -1.700), (1, 2.341, 3.137, 11.091), (1, -0.043, 3.873, 8.532), (1, 0.528, -0.752, -4.198), (1, -0.940, 0.261, -4.098), (1, 2.609, 3.531, 12.812), (1, 2.439, 2.486, 9.336), (1, -0.659, -0.150, -4.768), (1, 2.131, 1.973, 7.181), (1, 0.253, 0.304, -1.583), (1, -0.169, 2.273, 3.480), (1, 1.855, 3.974, 12.631), (1, 0.092, 1.160, 0.666), (1, 3.990, 0.402, 6.187), (1, -0.455, 0.932, -1.113), (1, 2.365, 1.152, 5.185), (1, -0.058, 1.244, 0.618), (1, 0.674, 0.481, -0.209), (1, 3.002, 0.246, 3.743), (1, 1.804, 3.765, 11.902), (1, 3.567, -0.752, 1.876), (1, 0.098, 2.257, 3.968), (1, 0.130, -0.889, -5.409), (1, 0.633, 1.891, 3.940), (1, 0.421, 2.533, 5.440), (1, 2.252, 1.853, 7.063), (1, 3.191, -0.980, 0.443), (1, -0.776, 3.241, 5.171), (1, 0.509, 1.737, 3.229), (1, 3.583, 1.274, 7.986), (1, 1.101, 2.896, 7.891), (1, 3.072, -0.008, 3.120), (1, 2.945, -0.295, 2.006), (1, 3.621, -0.161, 3.760), (1, 1.399, 3.759, 11.075), (1, 3.783, -0.866, 1.968), (1, -0.241, 2.902, 5.225), (1, 1.323, 1.934, 5.449), (1, 1.449, 2.855, 8.464), (1, 0.088, 1.526, 1.753), (1, -1.000, 2.161, 1.485), (1, -0.214, 3.358, 6.647), (1, -0.384, 3.230, 5.921), (1, 3.146, 1.228, 6.975), (1, 1.917, 0.860, 3.415), (1, 1.982, 1.735, 6.167), (1, 1.404, 1.851, 5.360), (1, 2.428, -0.674, -0.166), (1, 2.081, -0.505, -0.352), (1, 0.914, -0.543, -2.802), (1, -0.029, -0.482, -4.506), (1, 0.671, 0.184, -1.105), (1, 1.641, -0.524, -1.292), (1, 1.005, 0.361, 0.094), (1, -0.493, 3.582, 6.760), (2, 3.876, 2.563, 21.500), (2, 0.159, -0.309, 7.986), (2, -0.496, 0.417, 12.998), (2, -0.164, -0.512, 7.092), (2, 0.632, 3.200, 28.571), (2, 3.772, 0.493, 9.188), (2, 2.430, -0.797, 2.789), (2, 3.872, -0.775, 1.475), (2, -0.031, -0.256, 8.495), (2, 2.726, 3.000, 25.271), (2, 1.116, -0.269, 7.269), (2, 0.551, 3.402, 29.860), (2, 0.820, 2.500, 24.179), (2, 1.153, -0.453, 6.131), (2, -0.717, -0.360, 8.556), (2, 0.532, 0.531, 12.654), (2, 2.096, 0.981, 13.791), (2, 0.146, -0.433, 7.259), (2, 1.000, 1.075, 15.452), (2, 2.963, -0.090, 6.495), (2, 1.047, 2.052, 21.267), (2, 0.882, 1.778, 19.785), (2, 1.380, 2.702, 24.832), (2, 1.853, 0.401, 10.554), (2, 2.004, 1.770, 18.618), (2, 3.377, 0.772, 11.253), (2, 1.227, -0.169, 7.759), (2, 0.428, 2.052, 21.885), (2, 0.070, 3.648, 31.816), (2, 0.128, -0.938, 4.244), (2, 2.061, 0.753, 12.454), (2, 1.207, -0.301, 6.989), (2, -0.168, 3.765, 32.757), (2, 3.450, 1.801, 17.353), (2, -0.483, 3.344, 30.547), (2, 1.847, 1.884, 19.455), (2, 3.241, 2.369, 20.975), (2, 0.628, 3.590, 30.912), (2, 2.183, 1.741, 18.263), (2, 0.774, 2.638, 25.057), (2, 3.292, 2.867, 23.912), (2, 0.056, 2.651, 25.850), (2, -0.506, 0.300, 12.308), (2, 0.524, 1.182, 16.570), (2, -0.267, 2.563, 25.647), (2, 3.953, -0.334, 4.040), (2, 2.507, 2.319, 21.408), (2, -0.770, 1.017, 16.875), (2, 0.481, 1.591, 19.062), (2, 3.243, 1.060, 13.114), (2, 2.178, -0.325, 5.873), (2, 2.510, 1.235, 14.900), (2, 2.684, 2.370, 21.535), (2, 3.466, 3.656, 28.469), (2, 2.994, 3.960, 30.764), (2, -0.363, 3.592, 31.917), (2, 1.738, 0.074, 8.708), (2, 1.462, 3.727, 30.902), (2, 0.059, 0.180, 11.021), (2, 2.980, 2.317, 20.925), (2, 1.248, 0.965, 14.545), (2, 0.776, -0.229, 7.850), (2, -0.562, 2.839, 27.598), (2, 3.581, 0.244, 7.883), (2, -0.958, 0.901, 16.362), (2, 3.257, 0.364, 8.925), (2, 1.478, 1.718, 18.827), (2, -0.121, -0.436, 7.507), (2, 0.966, 1.444, 17.697), (2, 3.631, 3.463, 27.144), (2, 0.174, -0.663, 5.848), (2, 2.783, 0.124, 7.959), (2, 1.106, -0.936, 3.276), (2, 0.186, -0.942, 4.162), (2, 3.513, 2.456, 21.222), (2, 0.339, 2.316, 23.558), (2, 0.566, 2.515, 24.523), (2, -0.134, 0.746, 14.607), (2, 1.554, 0.106, 9.084), (2, -0.846, 2.748, 27.337), (2, 3.934, 0.564, 9.451), (2, 2.840, -0.966, 1.366), (2, 1.379, 0.307, 10.463), (2, 1.065, -0.780, 4.253), (2, 3.324, 2.145, 19.546), (2, 0.974, -0.543, 5.767), (2, 2.469, 3.976, 31.385), (2, -0.434, 3.689, 32.570), (2, 0.261, 0.481, 12.624), (2, 3.786, 2.605, 21.843), (2, -0.460, -0.536, 7.243), (2, 2.576, 2.880, 24.702), (2, -0.501, 3.551, 31.810), (2, 2.946, 3.263, 26.633), (2, 2.959, -0.813, 2.162), (2, -0.749, 0.490, 13.686), (2, 2.821, 0.335, 9.187), (2, 3.964, 0.272, 7.667), (2, 0.808, -0.700, 4.994), (2, 0.415, 2.183, 22.682), (2, 2.551, 3.785, 30.156), (2, 0.821, 1.120, 15.897), (2, 1.714, 3.019, 26.400), (2, 2.265, 1.950, 19.438), (2, 1.493, 3.317, 28.409), (2, -0.445, 2.282, 24.134), (2, -0.508, 2.508, 25.553), (2, 1.017, -0.621, 5.255), (2, 1.053, 2.246, 22.422), (2, 0.441, 1.637, 19.382), (2, 3.657, 1.246, 13.816), (2, 0.756, 0.808, 14.095), (2, 1.849, 1.599, 17.742), (2, 1.782, -0.000, 8.215), (2, 1.136, 3.940, 32.506), (2, 2.814, 3.288, 26.916), (2, 3.180, 3.198, 26.008), (2, 0.728, -0.054, 8.946), (2, 0.801, 0.775, 13.852), (2, 1.399, -0.546, 5.322), (2, 1.415, 1.753, 19.103), (2, 2.860, 1.796, 17.913), (2, 0.712, 2.902, 26.699), (2, -0.389, 3.093, 28.945), (2, 3.661, 3.666, 28.333), (2, 3.944, 0.996, 12.030), (2, 1.655, 1.385, 16.657), (2, 0.122, -0.662, 5.906), (2, 3.667, 2.763, 22.912), (2, 2.606, 0.630, 11.172), (2, -0.291, 1.492, 19.242), (2, -0.787, 1.223, 18.125), (2, 2.405, 0.325, 9.545), (2, 3.129, -0.412, 4.398), (2, 0.588, 3.964, 33.194), (2, -0.177, 3.636, 31.993), (2, 2.079, 3.280, 27.603), (2, 3.055, 3.958, 30.692), (2, -0.164, 3.188, 29.292), (2, 3.803, 3.151, 25.105), (2, 3.123, -0.891, 1.531), (2, 3.070, -0.824, 1.988), (2, 3.103, -0.931, 1.309), (2, 0.589, 3.353, 29.529), (2, 1.095, 1.973, 20.744), (2, -0.557, 0.370, 12.775), (2, 1.223, 0.307, 10.620), (2, 3.255, -0.768, 2.136), (2, 0.508, 2.157, 22.435), (2, 0.373, 0.319, 11.544), (2, 1.240, 1.736, 19.177), (2, 1.846, 0.970, 13.972), (2, 3.352, -0.534, 3.445), (2, -0.352, -0.290, 8.610), (2, 0.281, 0.193, 10.880), (2, 3.450, -0.059, 6.193), (2, 0.310, 2.575, 25.140), (2, 1.791, 1.127, 14.970), (2, 1.992, 2.347, 22.087), (2, -0.288, 2.881, 27.576), (2, 3.464, 3.664, 28.518), (2, 0.573, 2.789, 26.159), (2, 2.265, 1.583, 17.233), (2, 3.203, 0.730, 11.177), (2, 3.345, 1.368, 14.862), (2, 0.891, 3.690, 31.248), (2, 2.252, -0.311, 5.884), (2, -0.087, 0.804, 14.912), (2, 0.153, 2.510, 24.905), (2, 3.533, -0.965, 0.675), (2, 2.035, 1.953, 19.683), (2, 0.316, 2.448, 24.373), (2, 2.199, 3.858, 30.946), (2, -0.519, 3.647, 32.399), (2, 0.867, 1.961, 20.901), (2, 2.739, 2.268, 20.866), (2, 2.462, -0.664, 3.551), (2, 1.372, 3.419, 29.144), (2, -0.628, 2.723, 26.968), (2, 3.989, -0.225, 4.659), (2, 0.166, 3.190, 28.976), (2, 1.681, 2.937, 25.943), (2, 2.979, 2.263, 20.600), (2, 3.896, -0.419, 3.590), (2, 3.861, 2.224, 19.485), (2, -0.087, -0.861, 4.918), (2, 1.182, 1.886, 20.133), (2, 3.622, 2.320, 20.301), (2, 3.560, 0.008, 6.491), (2, 3.082, -0.605, 3.285), (2, 1.777, 1.324, 16.169), (2, 2.269, 2.436, 22.348), (2, 0.019, 3.074, 28.423), (2, -0.560, 3.868, 33.765), (2, 1.568, 2.886, 25.749), (2, 2.045, 0.222, 9.286), (2, 1.391, 0.352, 10.723), (2, 0.172, 1.908, 21.276), (2, 1.173, -0.726, 4.474), (2, 1.642, 2.576, 23.814), (2, 3.346, 1.377, 14.918), (2, 0.120, 0.411, 12.344), (2, 3.913, 0.820, 11.008), (2, 1.054, 3.732, 31.340), (2, 2.284, 0.108, 8.362), (2, 2.266, 0.066, 8.131), (2, 3.204, 1.156, 13.735), (2, 3.243, 2.032, 18.947), (2, 3.052, -0.121, 6.221), (2, 1.131, 2.189, 22.000), (2, 2.958, 0.658, 10.990), (2, 1.717, 3.708, 30.530), (2, 2.417, 2.070, 20.004), (2, 2.175, 0.881, 13.110), (2, 0.333, 3.494, 30.629), (2, 3.598, 3.940, 30.044), (2, 3.683, -0.110, 5.660), (2, 2.555, 1.196, 14.620), (2, 1.511, 0.453, 11.206), (2, 0.903, 1.390, 17.439), (2, -0.897, 3.303, 30.716), (2, 0.245, 2.129, 22.527), (2, 1.370, 2.715, 24.923), (2, 1.822, -0.917, 2.676), (2, 2.690, -0.109, 6.657), (2, 0.206, 1.561, 19.162), (2, 3.905, 2.710, 22.357), (2, -0.438, 3.207, 29.678), (2, 0.898, 3.445, 29.772), (2, 1.838, 2.871, 25.385), (2, 0.116, 1.401, 18.292), (2, -0.408, 2.375, 24.656), (2, 1.681, 3.338, 28.349), (2, 1.177, -0.318, 6.914), (2, 1.004, 0.626, 12.753), (2, 2.840, 2.589, 22.691), (2, 1.258, 3.993, 32.700), (2, 2.016, 3.489, 28.920), (2, -0.728, 0.164, 11.713), (2, 0.193, 1.479, 18.682), (2, 2.647, -0.969, 1.541), (2, 3.837, 2.602, 21.773), (2, 0.541, 0.205, 10.690), (2, 0.026, 2.756, 26.511), (2, 0.924, 0.909, 14.530), (2, 0.974, -0.074, 8.581), (2, 0.081, 0.005, 9.948), (2, 1.331, 2.942, 26.320), (2, 2.498, 3.405, 27.934), (2, 3.741, 1.554, 15.581), (2, 3.502, -0.089, 5.964), (2, 3.069, 1.768, 17.539), (2, 3.115, -0.008, 6.839), (2, 3.237, -0.503, 3.745), (2, 0.768, -0.135, 8.420), (2, 0.410, 3.974, 33.437), (2, 0.238, -0.700, 5.564), (2, 3.619, 0.350, 8.482), (2, 3.563, 3.059, 24.788), (2, 2.916, 3.101, 25.691), (2, 0.144, 3.282, 29.549), (2, 1.288, 2.642, 24.565), (2, -0.859, 0.229, 12.234), (2, 1.507, -0.711, 4.229), (2, -0.634, 2.608, 26.281), (2, 2.054, -0.834, 2.942), (2, 0.453, 1.072, 15.980), (2, 3.914, 1.159, 13.039), (2, 0.254, 1.835, 20.758), (2, 1.577, 0.428, 10.991), (2, 1.990, 3.569, 29.421), (2, 1.584, 1.803, 19.234), (2, 0.835, 3.603, 30.785), (2, 0.900, 3.033, 27.296), (2, 1.180, 0.280, 10.499), (2, 2.400, 2.802, 24.409), (2, 0.924, 2.462, 23.851), (2, 2.138, 0.722, 12.192), (2, -0.253, -0.809, 5.401), (2, 3.570, -0.116, 5.733), (2, 0.201, -0.182, 8.708), (2, 2.457, 0.454, 10.267), (2, -0.053, 0.443, 12.709), (2, 2.108, 2.069, 20.309), (2, -0.964, -0.441, 8.318), (2, 1.802, 0.403, 10.614), (2, 3.704, 3.902, 29.711), (2, 1.904, 2.418, 22.603), (2, 2.965, 3.429, 27.606), (2, -0.801, -0.072, 10.370), (2, 3.009, 0.491, 9.937), (2, 2.781, 1.026, 13.376), (2, -0.421, 0.744, 14.883), (2, 3.639, -0.148, 5.476), (2, 0.584, 2.041, 21.663), (2, 1.547, -0.391, 6.107), (2, -0.204, 0.727, 14.564), (2, 0.372, 0.464, 12.410), (2, 1.185, 1.732, 19.207), (2, 3.574, 0.755, 10.954), (2, 2.164, 1.425, 16.385), (2, 1.895, 1.374, 16.351), (2, 2.352, 2.188, 20.779), (2, 0.187, 0.677, 13.874), (2, -0.589, 3.686, 32.703), (2, 3.081, 0.414, 9.403), (2, 3.341, 3.246, 26.137), (2, 0.617, -0.201, 8.174), (2, 1.518, 3.833, 31.481), (2, 2.613, -0.350, 5.286), (2, 3.426, 0.751, 11.082), (2, 2.726, 3.586, 28.787), (2, 2.834, -0.219, 5.855), (2, 1.038, 3.607, 30.605), (2, 0.479, 1.226, 16.874), (2, 1.729, 0.297, 10.053), (2, 0.050, 1.815, 20.841), (2, -0.554, 3.538, 31.782), (2, 2.773, 0.973, 13.064), (2, -0.239, 3.425, 30.786), (2, 3.611, 3.700, 28.590), (2, 1.418, 3.625, 30.332), (2, 1.599, 1.626, 18.156), (2, 1.841, 1.518, 17.269), (2, 1.119, 1.996, 20.856), (2, 2.810, 2.293, 20.947), (2, 1.174, 2.062, 21.198), (2, -0.326, -0.279, 8.655), (2, -0.365, 0.816, 15.259), (2, 1.296, -0.095, 8.132), (2, -0.263, 0.511, 13.327), (2, 1.757, 3.012, 26.314), (2, 1.849, 1.065, 14.539), (2, 1.651, 2.244, 21.814), (2, 3.942, 1.026, 12.214), (2, 2.314, 1.944, 19.353), (2, 3.055, -0.002, 6.930), (2, 0.402, 1.350, 17.698), (2, 0.004, 2.288, 23.724), (2, 3.265, 2.962, 24.509), (2, 1.044, -0.684, 4.850), (2, -0.280, 2.278, 23.948), (2, 1.216, 0.726, 13.142), (2, 3.181, 3.518, 27.925), (2, 3.199, -0.124, 6.055), (2, 0.510, -0.622, 5.755), (2, 2.920, 1.067, 13.484), (2, 2.573, 1.844, 18.492), (2, 1.155, 3.505, 29.878), (2, 2.033, 1.756, 18.502), (2, 1.312, 0.114, 9.373), (2, -0.823, 3.339, 30.854), (2, 0.287, 3.891, 33.060), (2, -0.621, -0.210, 9.363), (2, 3.734, 1.574, 15.712), (2, -0.932, 0.772, 15.561), (2, -0.719, 1.604, 20.345), (2, -0.555, 0.773, 15.190), (2, -0.744, 3.934, 34.348), (2, 1.671, -0.425, 5.778), (2, 2.754, 2.690, 23.385), (2, 1.826, 2.185, 21.283), (2, 1.970, 0.021, 8.159), (2, 2.882, 3.494, 28.081), (2, 1.668, -0.030, 8.150), (2, 0.472, 2.184, 22.633), (2, 1.656, 3.393, 28.701), (2, -0.069, 2.331, 24.057), (2, 0.075, 1.341, 17.973), (2, 1.836, 0.565, 11.554), (2, -0.235, 0.520, 13.357), (2, 3.620, 3.169, 25.393), (2, 0.401, -0.062, 9.224), (2, 1.503, 1.667, 18.501), (2, 3.727, 1.149, 13.166), (2, 2.777, -0.081, 6.737), (2, 3.914, -0.234, 4.680), (2, 1.765, 0.750, 12.737), (2, 1.746, 1.818, 19.161), (2, 0.019, 2.819, 26.893), (2, 1.068, 1.917, 20.434), (2, 3.035, 3.158, 25.915), (2, 2.012, 0.724, 12.330), (2, 2.597, 2.264, 20.986), (2, 3.428, 3.239, 26.005), (2, -0.016, -0.529, 6.842), (2, 1.314, 0.735, 13.095), (2, 2.832, -0.567, 3.768), (2, -0.296, 2.641, 26.141), (2, 2.863, 3.889, 30.470), (2, 2.849, 3.997, 31.130), (2, 1.660, 1.813, 19.216), (2, 2.798, 0.977, 13.062), (2, 3.935, 0.549, 9.359), (2, 1.002, 3.557, 30.342), (2, 3.052, 2.207, 20.193), (2, 3.455, 0.458, 9.294), (2, 3.312, 2.138, 19.515), (2, 0.292, 0.058, 10.056), (2, 0.050, -0.211, 8.682), (2, -0.215, 1.108, 16.866), (2, -0.169, 0.647, 14.048), (2, 2.546, 0.876, 12.709), (2, -0.911, -0.209, 9.659), (2, 0.950, 2.894, 26.413), (2, -0.512, -0.167, 9.508), (2, 1.821, -0.747, 3.696), (2, 2.257, 3.945, 31.415), (2, 2.398, -0.586, 4.087), (2, 3.051, 0.815, 11.836), (2, 3.399, 2.131, 19.389), (2, 2.982, 1.549, 16.314), (2, -0.790, -0.329, 8.819), (2, 3.797, 0.327, 8.167), (2, 1.838, 0.290, 9.902), (2, 1.906, 1.782, 18.785), (2, 1.330, -0.208, 7.422), (2, -0.217, 0.854, 15.344), (2, 3.310, 1.582, 16.180), (2, 2.965, 0.917, 12.537), (2, 3.558, -0.164, 5.460), (2, -0.841, 2.060, 23.203), (2, 2.892, 2.621, 22.834), (2, -0.011, -0.198, 8.821), (2, -0.430, 2.999, 28.424), (2, -0.584, 0.894, 15.946), (2, 0.033, 1.310, 17.829), (2, 3.044, 0.410, 9.418), (2, 3.932, 0.295, 7.836), (2, 0.394, 1.315, 17.494), (2, 1.424, -0.167, 7.573), (2, 1.676, 1.118, 15.031), (2, 1.821, 0.714, 12.462), (2, 2.688, 1.497, 16.292), (2, 3.960, 2.344, 20.103), (2, -0.787, -0.161, 9.819), (2, 3.538, 3.651, 28.366), (2, -0.338, 0.458, 13.088), (2, -0.146, 3.162, 29.120), (2, 3.124, 3.352, 26.989), (2, -0.189, 3.685, 32.301), (2, 0.396, 1.004, 15.626), (2, -0.171, 2.114, 22.858), (2, 3.736, 0.732, 10.659), (2, 1.259, 2.564, 24.127), (2, -0.263, 2.426, 24.820), (2, 1.558, -0.858, 3.292), (2, 2.882, 1.110, 13.776), (2, 0.039, 1.284, 17.666), (2, 3.074, 2.379, 21.201), (2, -0.523, 0.303, 12.344), (2, 0.363, 1.082, 16.132), (2, 2.925, 2.187, 20.195), (2, 0.595, -0.335, 7.397), (2, 0.062, -0.232, 8.544), (2, 0.877, 2.155, 22.050), (2, -0.256, 2.922, 27.788), (2, 1.813, 3.161, 27.152), (2, 2.177, 2.532, 23.016), (2, -0.051, 0.035, 10.263), (2, 2.688, 3.599, 28.906), (2, 2.539, -0.076, 7.008), (2, 2.563, 1.467, 16.240), (2, -0.755, 2.276, 24.410), (2, 3.092, 0.660, 10.868), (2, 2.403, 2.693, 23.756), (2, -0.170, 2.178, 23.239), (2, 2.672, -0.603, 3.712), (2, -0.077, -0.493, 7.116), (2, 1.997, 1.934, 19.608), (2, 1.913, -0.792, 3.335), (2, 0.171, -0.329, 7.857), (2, 2.488, 0.171, 8.540), (2, -0.514, 0.331, 12.500), (2, -0.201, 2.484, 25.103), (2, 2.436, 0.032, 7.759), (2, -0.094, 2.530, 25.275), (2, 2.186, 2.591, 23.358), (2, 3.171, -0.766, 2.231), (2, 2.410, 0.183, 8.687), (2, -0.699, -0.329, 8.728), (2, 3.285, 2.252, 20.228), (2, 1.928, -0.059, 7.720), (2, 3.460, 0.399, 8.931), (2, 2.542, 0.224, 8.801), (2, 2.902, 2.101, 19.702), (2, 3.808, 2.528, 21.358), (2, 0.330, 0.642, 13.522), (2, -0.088, 1.286, 17.804), (2, 3.025, 2.354, 21.100), (2, 3.306, 2.049, 18.986), (2, 1.477, 1.720, 18.845), (2, 2.676, 3.601, 28.931), (2, 1.577, 0.170, 9.443), (2, 1.362, 3.534, 29.843), (2, 2.616, 3.106, 26.018), (2, 3.773, 0.378, 8.496), (2, -0.125, 2.057, 22.465), (2, 3.174, 1.382, 15.120), (2, 0.844, 2.058, 21.503); SELECT ANS[1] > -1.1 AND ANS[1] < -0.9 AND ANS[2] > 5.9 AND ANS[2] < 6.1 AND ANS[3] > 9.9 AND ANS[3] < 10.1 FROM -(SELECT stochasticLinearRegression(0.05, 0, 1, 'SGD')(target, p1, p2) AS ANS FROM grouptest GROUP BY user_id LIMIT 0, 1); +(SELECT stochasticLinearRegression(0.05, 0, 1, 'SGD')(target, p1, p2) AS ANS FROM grouptest GROUP BY user_id ORDER BY user_id LIMIT 1, 1); SELECT ANS[1] > 1.9 AND ANS[1] < 2.1 AND ANS[2] > 2.9 AND ANS[2] < 3.1 AND ANS[3] > -3.1 AND ANS[3] < -2.9 FROM -(SELECT stochasticLinearRegression(0.05, 0, 1, 'SGD')(target, p1, p2) AS ANS FROM grouptest GROUP BY user_id LIMIT 1, 1); +(SELECT stochasticLinearRegression(0.05, 0, 1, 'SGD')(target, p1, p2) AS ANS FROM grouptest GROUP BY user_id ORDER BY user_id LIMIT 0, 1); DROP TABLE defaults; DROP TABLE model; From a57f5a0983e0d0d7c31196b53aad05f0c8a2a653 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 8 Dec 2023 16:15:33 +0100 Subject: [PATCH 029/253] Cover optimizations for thread_id/thread_name in system.stack_trace separately Signed-off-by: Azat Khuzhin --- ..._system_stacktrace_optimizations.reference | 5 +++++ .../02940_system_stacktrace_optimizations.sh | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/queries/0_stateless/02940_system_stacktrace_optimizations.reference create mode 100755 tests/queries/0_stateless/02940_system_stacktrace_optimizations.sh diff --git a/tests/queries/0_stateless/02940_system_stacktrace_optimizations.reference b/tests/queries/0_stateless/02940_system_stacktrace_optimizations.reference new file mode 100644 index 00000000000..f08b8ee767b --- /dev/null +++ b/tests/queries/0_stateless/02940_system_stacktrace_optimizations.reference @@ -0,0 +1,5 @@ +thread = 0 +thread != 0 +Send signal to +thread_name = 'foo' +Send signal to 0 threads (total) diff --git a/tests/queries/0_stateless/02940_system_stacktrace_optimizations.sh b/tests/queries/0_stateless/02940_system_stacktrace_optimizations.sh new file mode 100755 index 00000000000..0e23bb6c42b --- /dev/null +++ b/tests/queries/0_stateless/02940_system_stacktrace_optimizations.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Tags: no-parallel + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +# NOTE: due to grep "Cannot obtain a stack trace for thread {}' will be ignored automatically, which is the intention. + +# no message at all +echo "thread = 0" +$CLICKHOUSE_CLIENT --allow_repeated_settings --send_logs_level=test -nm -q "select * from system.stack_trace where thread_id = 0" |& grep -F -o 'Send signal to' + +# send messages to some threads +echo "thread != 0" +$CLICKHOUSE_CLIENT --allow_repeated_settings --send_logs_level=test -nm -q "select * from system.stack_trace where thread_id != 0 format Null" |& grep -F -o 'Send signal to' | grep -v 'Send signal to 0 threads (total)' + +# there is no thread with comm="foo", so no signals will be sent +echo "thread_name = 'foo'" +$CLICKHOUSE_CLIENT --allow_repeated_settings --send_logs_level=test -nm -q "select * from system.stack_trace where thread_name = 'foo' format Null" |& grep -F -o 'Send signal to 0 threads (total)' From f876bea0500e3d150609b2cd79878dd04d36b2dd Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 8 Dec 2023 14:58:44 +0100 Subject: [PATCH 030/253] Add support for system.stack_trace filtering optimizations for analyzer Signed-off-by: Azat Khuzhin --- .../System/StorageSystemStackTrace.cpp | 84 ++++++++++++++++--- src/Storages/System/StorageSystemStackTrace.h | 7 +- 2 files changed, 75 insertions(+), 16 deletions(-) diff --git a/src/Storages/System/StorageSystemStackTrace.cpp b/src/Storages/System/StorageSystemStackTrace.cpp index 477b784952e..efd425faa97 100644 --- a/src/Storages/System/StorageSystemStackTrace.cpp +++ b/src/Storages/System/StorageSystemStackTrace.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -24,9 +25,14 @@ #include #include #include +#include #include +#include #include +#include +#include #include +#include #include @@ -161,7 +167,7 @@ bool wait(int timeout_ms) } using ThreadIdToName = std::unordered_map>; -ThreadIdToName getFilteredThreadNames(ASTPtr query, ContextPtr context, const PaddedPODArray & thread_ids, Poco::Logger * log) +ThreadIdToName getFilteredThreadNames(const ActionsDAG::Node * predicate, ContextPtr context, const PaddedPODArray & thread_ids, Poco::Logger * log) { ThreadIdToName tid_to_name; MutableColumnPtr all_thread_names = ColumnString::create(); @@ -192,7 +198,7 @@ ThreadIdToName getFilteredThreadNames(ASTPtr query, ContextPtr context, const Pa LOG_TEST(log, "Read {} thread names for {} threads, took {} ms", tid_to_name.size(), thread_ids.size(), watch.elapsedMilliseconds()); Block block { ColumnWithTypeAndName(std::move(all_thread_names), std::make_shared(), "thread_name") }; - VirtualColumnUtils::filterBlockWithQuery(query, block, context); + VirtualColumnUtils::filterBlockWithPredicate(predicate, block, context); ColumnPtr thread_names = std::move(block.getByPosition(0).column); std::unordered_set filtered_thread_names; @@ -217,14 +223,16 @@ ThreadIdToName getFilteredThreadNames(ASTPtr query, ContextPtr context, const Pa /// We must wait for every thread one by one sequentially, /// because there is a limit on number of queued signals in OS and otherwise signals may get lost. /// Also, non-RT signals are not delivered if previous signal is handled right now (by default; but we use RT signals). -class StorageSystemStackTraceSource : public ISource +class StackTraceSource : public ISource { public: - StorageSystemStackTraceSource(const Names & column_names, Block header_, const ASTPtr query_, ContextPtr context_, UInt64 max_block_size_, Poco::Logger * log_) + StackTraceSource(const Names & column_names, Block header_, ASTPtr && query_, ActionsDAGPtr && filter_dag_, ContextPtr context_, UInt64 max_block_size_, Poco::Logger * log_) : ISource(header_) , context(context_) , header(std::move(header_)) - , query(query_) + , query(std::move(query_)) + , filter_dag(std::move(filter_dag_)) + , predicate(filter_dag ? filter_dag->getOutputs().at(0) : nullptr) , max_block_size(max_block_size_) , pipe_read_timeout_ms(static_cast(context->getSettingsRef().storage_system_stack_trace_pipe_read_timeout_ms.totalMilliseconds())) , log(log_) @@ -259,7 +267,7 @@ protected: ThreadIdToName thread_names; if (read_thread_names) - thread_names = getFilteredThreadNames(query, context, thread_ids_data, log); + thread_names = getFilteredThreadNames(predicate, context, thread_ids_data, log); for (UInt64 tid : thread_ids_data) { @@ -343,6 +351,8 @@ private: ContextPtr context; Block header; const ASTPtr query; + const ActionsDAGPtr filter_dag; + const ActionsDAG::Node * predicate; const size_t max_block_size; const int pipe_read_timeout_ms; @@ -372,11 +382,55 @@ private: } Block block { ColumnWithTypeAndName(std::move(all_thread_ids), std::make_shared(), "thread_id") }; - VirtualColumnUtils::filterBlockWithQuery(query, block, context); + VirtualColumnUtils::filterBlockWithPredicate(predicate, block, context); + return block.getByPosition(0).column; } }; +class ReadFromSystemStackTrace : public SourceStepWithFilter +{ +public: + std::string getName() const override { return "ReadFromSystemStackTrace"; } + + void initializePipeline(QueryPipelineBuilder & pipeline, const BuildQueryPipelineSettings &) override + { + auto filter_actions_dag = ActionsDAG::buildFilterActionsDAG(filter_nodes.nodes, {}, context); + Pipe pipe(std::make_shared( + column_names, + getOutputStream().header, + std::move(query), + std::move(filter_actions_dag), + context, + max_block_size, + log)); + pipeline.init(std::move(pipe)); + } + + ReadFromSystemStackTrace( + const Names & column_names_, + Block sample_block, + ASTPtr && query_, + ContextPtr context_, + size_t max_block_size_, + Poco::Logger * log_) + : SourceStepWithFilter(DataStream{.header = std::move(sample_block)}) + , column_names(column_names_) + , query(query_) + , context(std::move(context_)) + , max_block_size(max_block_size_) + , log(log_) + { + } + +private: + Names column_names; + ASTPtr query; + ContextPtr context; + size_t max_block_size; + Poco::Logger * log; +}; + } @@ -412,23 +466,27 @@ StorageSystemStackTrace::StorageSystemStackTrace(const StorageID & table_id_) } -Pipe StorageSystemStackTrace::read( +void StorageSystemStackTrace::read( + QueryPlan & query_plan, const Names & column_names, const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, - const size_t max_block_size, - const size_t /*num_streams*/) + size_t max_block_size, + size_t /*num_streams*/) { storage_snapshot->check(column_names); - return Pipe(std::make_shared( + Block sample_block = storage_snapshot->metadata->getSampleBlock(); + + auto reading = std::make_unique( column_names, - storage_snapshot->metadata->getSampleBlock(), + sample_block, query_info.query->clone(), context, max_block_size, - log)); + log); + query_plan.addStep(std::move(reading)); } } diff --git a/src/Storages/System/StorageSystemStackTrace.h b/src/Storages/System/StorageSystemStackTrace.h index 9f15499ce90..18216cea1bd 100644 --- a/src/Storages/System/StorageSystemStackTrace.h +++ b/src/Storages/System/StorageSystemStackTrace.h @@ -25,14 +25,15 @@ public: String getName() const override { return "SystemStackTrace"; } - Pipe read( + void read( + QueryPlan & query_plan, const Names & column_names, const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, - QueryProcessingStage::Enum processed_stage, + QueryProcessingStage::Enum /*processed_stage*/, size_t max_block_size, - size_t num_streams) override; + size_t /*num_streams*/) override; bool isSystemStorage() const override { return true; } From 5251548753d144d3454f617cfb632efad6d5fd09 Mon Sep 17 00:00:00 2001 From: Nikita Mikhaylov Date: Fri, 8 Dec 2023 17:28:26 +0000 Subject: [PATCH 031/253] Fix tests and build --- .../WriteBufferFromHTTPServerResponse.cpp | 2 +- src/Server/PrometheusRequestHandler.cpp | 8 +- .../__init__.py | 0 .../configs/remote_servers.xml | 40 ----- .../test.py | 157 ------------------ 5 files changed, 7 insertions(+), 200 deletions(-) delete mode 100644 tests/integration/test_max_http_connections_for_replication/__init__.py delete mode 100644 tests/integration/test_max_http_connections_for_replication/configs/remote_servers.xml delete mode 100644 tests/integration/test_max_http_connections_for_replication/test.py diff --git a/src/Server/HTTP/WriteBufferFromHTTPServerResponse.cpp b/src/Server/HTTP/WriteBufferFromHTTPServerResponse.cpp index ac722656899..1a12c09a8c7 100644 --- a/src/Server/HTTP/WriteBufferFromHTTPServerResponse.cpp +++ b/src/Server/HTTP/WriteBufferFromHTTPServerResponse.cpp @@ -136,7 +136,7 @@ void WriteBufferFromHTTPServerResponse::nextImpl() WriteBufferFromHTTPServerResponse::WriteBufferFromHTTPServerResponse( HTTPServerResponse & response_, bool is_http_method_head_, - size_t keep_alive_timeout_, + UInt64 keep_alive_timeout_, bool compress_, CompressionMethod compression_method_) : BufferWithOwnMemory(DBMS_DEFAULT_BUFFER_SIZE) diff --git a/src/Server/PrometheusRequestHandler.cpp b/src/Server/PrometheusRequestHandler.cpp index b871d1b0ddc..127ed843cb6 100644 --- a/src/Server/PrometheusRequestHandler.cpp +++ b/src/Server/PrometheusRequestHandler.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -18,7 +17,12 @@ void PrometheusRequestHandler::handleRequest(HTTPServerRequest & request, HTTPSe { try { - const auto keep_alive_timeout = server.context()->getServerSettings().keep_alive_timeout.totalSeconds(); + /// Raw config reference is used here to avoid dependency on Context and ServerSettings. + /// This is painful, because this class is also used in a build with CLICKHOUSE_KEEPER_STANDALONE_BUILD=1 + /// And there ordinary Context is replaced with a tiny clone. + const auto & config = server.config(); + unsigned keep_alive_timeout = config.getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT); + setResponseDefaultHeaders(response, keep_alive_timeout); response.setContentType("text/plain; version=0.0.4; charset=UTF-8"); diff --git a/tests/integration/test_max_http_connections_for_replication/__init__.py b/tests/integration/test_max_http_connections_for_replication/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/integration/test_max_http_connections_for_replication/configs/remote_servers.xml b/tests/integration/test_max_http_connections_for_replication/configs/remote_servers.xml deleted file mode 100644 index e62425fe1bb..00000000000 --- a/tests/integration/test_max_http_connections_for_replication/configs/remote_servers.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - true - - test - node1 - 9000 - - - test - node2 - 9000 - - - - - - true - - test - node3 - 9000 - - - test - node4 - 9000 - - - test - node5 - 9000 - - - - - - diff --git a/tests/integration/test_max_http_connections_for_replication/test.py b/tests/integration/test_max_http_connections_for_replication/test.py deleted file mode 100644 index bcb779ee913..00000000000 --- a/tests/integration/test_max_http_connections_for_replication/test.py +++ /dev/null @@ -1,157 +0,0 @@ -import time -from multiprocessing.dummy import Pool - -import pytest -from helpers.cluster import ClickHouseCluster -from helpers.test_tools import assert_eq_with_retry - - -def _fill_nodes(nodes, shard, connections_count): - for node in nodes: - node.query( - """ - CREATE DATABASE test; - - CREATE TABLE test_table(date Date, id UInt32, dummy UInt32) - ENGINE = ReplicatedMergeTree('/clickhouse/tables/test{shard}/replicated', '{replica}') - PARTITION BY date - ORDER BY id - SETTINGS - replicated_max_parallel_fetches_for_host={connections}, - index_granularity=8192; - """.format( - shard=shard, replica=node.name, connections=connections_count - ) - ) - - -cluster = ClickHouseCluster(__file__) -node1 = cluster.add_instance( - "node1", - user_configs=[], - main_configs=["configs/remote_servers.xml"], - with_zookeeper=True, -) -node2 = cluster.add_instance( - "node2", - user_configs=[], - main_configs=["configs/remote_servers.xml"], - with_zookeeper=True, -) - - -@pytest.fixture(scope="module") -def start_small_cluster(): - try: - cluster.start() - - _fill_nodes([node1, node2], 1, 1) - - yield cluster - - finally: - cluster.shutdown() - - -def test_single_endpoint_connections_count(start_small_cluster): - node1.query("TRUNCATE TABLE test_table") - node2.query("SYSTEM SYNC REPLICA test_table") - - def task(count): - print(("Inserting ten times from {}".format(count))) - for i in range(count, count + 10): - node1.query("insert into test_table values ('2017-06-16', {}, 0)".format(i)) - - p = Pool(10) - p.map(task, range(0, 100, 10)) - - assert_eq_with_retry(node1, "select count() from test_table", "100") - assert_eq_with_retry(node2, "select count() from test_table", "100") - - assert ( - node2.query( - "SELECT value FROM system.events where event='CreatedHTTPConnections'" - ) - == "1\n" - ) - - -def test_keepalive_timeout(start_small_cluster): - node1.query("TRUNCATE TABLE test_table") - node2.query("SYSTEM SYNC REPLICA test_table") - - node1.query("insert into test_table values ('2017-06-16', 777, 0)") - assert_eq_with_retry(node2, "select count() from test_table", str(1)) - # Server keepAliveTimeout is 3 seconds, default client session timeout is 8 - # lets sleep in that interval - time.sleep(4) - - node1.query("insert into test_table values ('2017-06-16', 888, 0)") - - time.sleep(3) - - assert_eq_with_retry(node2, "select count() from test_table", str(2)) - - assert not node2.contains_in_log( - "No message received" - ), "Found 'No message received' in clickhouse-server.log" - - -node3 = cluster.add_instance( - "node3", - user_configs=[], - main_configs=["configs/remote_servers.xml"], - with_zookeeper=True, -) -node4 = cluster.add_instance( - "node4", - user_configs=[], - main_configs=["configs/remote_servers.xml"], - with_zookeeper=True, -) -node5 = cluster.add_instance( - "node5", - user_configs=[], - main_configs=["configs/remote_servers.xml"], - with_zookeeper=True, -) - - -@pytest.fixture(scope="module") -def start_big_cluster(): - try: - cluster.start() - - _fill_nodes([node3, node4, node5], 2, 2) - - yield cluster - - finally: - cluster.shutdown() - - -def test_multiple_endpoint_connections_count(start_big_cluster): - def task(count): - print(("Inserting ten times from {}".format(count))) - if (count / 10) % 2 == 1: - node = node3 - else: - node = node4 - - for i in range(count, count + 10): - node.query("insert into test_table values ('2017-06-16', {}, 0)".format(i)) - - p = Pool(10) - p.map(task, range(0, 100, 10)) - - assert_eq_with_retry(node3, "select count() from test_table", "100") - assert_eq_with_retry(node4, "select count() from test_table", "100") - assert_eq_with_retry(node5, "select count() from test_table", "100") - - # Two per each host or sometimes less, if fetches are not performed in parallel. But not more. - assert ( - node5.query( - "SELECT value FROM system.events where event='CreatedHTTPConnections'" - ) - <= "4\n" - ) From 951f971d73b73be590c9ce25e7d52db41737d4c0 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 8 Dec 2023 19:20:48 +0000 Subject: [PATCH 032/253] fix some tests --- .../00166_functions_of_aggregation_states.sql | 3 +++ .../01012_reset_running_accumulate.sql | 3 +++ ...emental_streaming_from_2_src_with_feedback.sql | 3 +++ .../01088_array_slice_of_aggregate_functions.sql | 2 +- .../0_stateless/01472_many_rows_in_totals.sql | 3 +++ .../0_stateless/01799_long_uniq_theta_sketch.sql | 3 +++ .../01913_exact_rows_before_limit_full.reference | 2 +- .../01913_exact_rows_before_limit_full.sql | 10 +++++----- tests/queries/0_stateless/02366_kql_extend.sql | 7 +++++-- .../queries/0_stateless/02366_kql_makeseries.sql | 15 +++++++++------ 10 files changed, 36 insertions(+), 15 deletions(-) diff --git a/tests/queries/0_stateless/00166_functions_of_aggregation_states.sql b/tests/queries/0_stateless/00166_functions_of_aggregation_states.sql index b73a04e19b9..0a5a84bbb46 100644 --- a/tests/queries/0_stateless/00166_functions_of_aggregation_states.sql +++ b/tests/queries/0_stateless/00166_functions_of_aggregation_states.sql @@ -1 +1,4 @@ +-- Disable external aggregation because the state is reset for each new block of data in 'runningAccumulate' function. +SET max_bytes_before_external_group_by = 0; + SELECT k, finalizeAggregation(sum_state), runningAccumulate(sum_state) FROM (SELECT intDiv(number, 50000) AS k, sumState(number) AS sum_state FROM (SELECT number FROM system.numbers LIMIT 1000000) GROUP BY k ORDER BY k); diff --git a/tests/queries/0_stateless/01012_reset_running_accumulate.sql b/tests/queries/0_stateless/01012_reset_running_accumulate.sql index b9336b2f50c..c2c5bf6f87d 100644 --- a/tests/queries/0_stateless/01012_reset_running_accumulate.sql +++ b/tests/queries/0_stateless/01012_reset_running_accumulate.sql @@ -1,3 +1,6 @@ +-- Disable external aggregation because the state is reset for each new block of data in 'runningAccumulate' function. +SET max_bytes_before_external_group_by = 0; + SELECT grouping, item, runningAccumulate(state, grouping) diff --git a/tests/queries/0_stateless/01064_incremental_streaming_from_2_src_with_feedback.sql b/tests/queries/0_stateless/01064_incremental_streaming_from_2_src_with_feedback.sql index 3d75fb0ccc9..ae90dc3cc72 100644 --- a/tests/queries/0_stateless/01064_incremental_streaming_from_2_src_with_feedback.sql +++ b/tests/queries/0_stateless/01064_incremental_streaming_from_2_src_with_feedback.sql @@ -1,5 +1,8 @@ SET joined_subquery_requires_alias = 0; SET max_threads = 1; +-- It affects number of read rows and max_rows_to_read. +SET max_bytes_before_external_sort = 0; +SET max_bytes_before_external_group_by = 0; -- incremental streaming usecase -- that has sense only if data filling order has guarantees of chronological order diff --git a/tests/queries/0_stateless/01088_array_slice_of_aggregate_functions.sql b/tests/queries/0_stateless/01088_array_slice_of_aggregate_functions.sql index ba525f30228..c8466b57051 100644 --- a/tests/queries/0_stateless/01088_array_slice_of_aggregate_functions.sql +++ b/tests/queries/0_stateless/01088_array_slice_of_aggregate_functions.sql @@ -1 +1 @@ -select arraySlice(groupArray(x),1,1) as y from (select uniqState(number) as x from numbers(10) group by number); +select arraySlice(groupArray(x), 1, 1) as y from (select uniqState(number) as x from numbers(10) group by number order by number); diff --git a/tests/queries/0_stateless/01472_many_rows_in_totals.sql b/tests/queries/0_stateless/01472_many_rows_in_totals.sql index d79d189a28d..bea8c255f21 100644 --- a/tests/queries/0_stateless/01472_many_rows_in_totals.sql +++ b/tests/queries/0_stateless/01472_many_rows_in_totals.sql @@ -1,4 +1,7 @@ +-- Disable external aggregation because it may produce several blocks instead of one. +set max_bytes_before_external_group_by = 0; set output_format_write_statistics = 0; + select g, s from (select g, sum(number) as s from numbers(4) group by bitAnd(number, 1) as g with totals order by g) array join [1, 2] as a format Pretty; select '--'; diff --git a/tests/queries/0_stateless/01799_long_uniq_theta_sketch.sql b/tests/queries/0_stateless/01799_long_uniq_theta_sketch.sql index 37f0c31ab10..475f156af93 100644 --- a/tests/queries/0_stateless/01799_long_uniq_theta_sketch.sql +++ b/tests/queries/0_stateless/01799_long_uniq_theta_sketch.sql @@ -1,5 +1,8 @@ -- Tags: long, no-fasttest +-- The result slightly differs but it's ok since `uniqueTheta` is an approximate function. +set group_by_two_level_threshold_bytes = 0; + SELECT 'uniqTheta'; SELECT Y, uniqTheta(X) FROM (SELECT number AS X, (3*X*X - 7*X + 11) % 37 AS Y FROM system.numbers LIMIT 15) GROUP BY Y ORDER BY Y; diff --git a/tests/queries/0_stateless/01913_exact_rows_before_limit_full.reference b/tests/queries/0_stateless/01913_exact_rows_before_limit_full.reference index a0f4560ca1c..95ce4d6428d 100644 --- a/tests/queries/0_stateless/01913_exact_rows_before_limit_full.reference +++ b/tests/queries/0_stateless/01913_exact_rows_before_limit_full.reference @@ -45,7 +45,7 @@ "data": [ - [12] + [10] ], "rows": 1, diff --git a/tests/queries/0_stateless/01913_exact_rows_before_limit_full.sql b/tests/queries/0_stateless/01913_exact_rows_before_limit_full.sql index 84f97090169..07e54fb2ec2 100644 --- a/tests/queries/0_stateless/01913_exact_rows_before_limit_full.sql +++ b/tests/queries/0_stateless/01913_exact_rows_before_limit_full.sql @@ -10,20 +10,20 @@ set exact_rows_before_limit = 1, output_format_write_statistics = 0, max_block_s select * from test limit 1 FORMAT JSONCompact; -select * from test where i < 10 group by i limit 1 FORMAT JSONCompact; +select * from test where i < 10 group by i order by i limit 1 FORMAT JSONCompact; -select * from test group by i having i in (10, 11, 12) limit 1 FORMAT JSONCompact; +select * from test group by i having i in (10, 11, 12) order by i limit 1 FORMAT JSONCompact; select * from test where i < 20 order by i limit 1 FORMAT JSONCompact; set prefer_localhost_replica = 0; -select * from cluster(test_cluster_two_shards, currentDatabase(), test) where i < 30 limit 1 FORMAT JSONCompact; +select * from cluster(test_cluster_two_shards, currentDatabase(), test) where i < 30 order by i limit 1 FORMAT JSONCompact; select * from cluster(test_cluster_two_shards, currentDatabase(), test) where i < 20 order by i limit 1 FORMAT JSONCompact; set prefer_localhost_replica = 1; -select * from cluster(test_cluster_two_shards, currentDatabase(), test) where i < 30 limit 1 FORMAT JSONCompact; +select * from cluster(test_cluster_two_shards, currentDatabase(), test) where i < 30 order by i limit 1 FORMAT JSONCompact; select * from cluster(test_cluster_two_shards, currentDatabase(), test) where i < 20 order by i limit 1 FORMAT JSONCompact; -select * from (select * from cluster(test_cluster_two_shards, currentDatabase(), test) where i < 10) limit 1 FORMAT JSONCompact; +select * from (select * from cluster(test_cluster_two_shards, currentDatabase(), test) where i < 10) order by i limit 1 FORMAT JSONCompact; drop table if exists test; diff --git a/tests/queries/0_stateless/02366_kql_extend.sql b/tests/queries/0_stateless/02366_kql_extend.sql index 3de489b0815..0a3c1f3dcd4 100644 --- a/tests/queries/0_stateless/02366_kql_extend.sql +++ b/tests/queries/0_stateless/02366_kql_extend.sql @@ -12,16 +12,19 @@ -- 'Costco','Snargaluff',200,'2016-09-10', -- ] + DROP TABLE IF EXISTS Ledger; CREATE TABLE Ledger -( +( Supplier Nullable(String), Fruit String , Price Float64, - Purchase Date + Purchase Date ) ENGINE = Memory; INSERT INTO Ledger VALUES ('Aldi','Apple',4,'2016-09-10'), ('Costco','Apple',2,'2016-09-11'), ('Aldi','Apple',6,'2016-09-10'), ('Costco','Snargaluff',100,'2016-09-12'), ('Aldi','Apple',7,'2016-09-12'), ('Aldi','Snargaluff',400,'2016-09-11'),('Costco','Snargaluff',104,'2016-09-12'),('Aldi','Apple',5,'2016-09-12'),('Aldi','Snargaluff',600,'2016-09-11'),('Costco','Snargaluff',200,'2016-09-10'); +-- This test requies sorting after some of aggregations but I don't know KQL, sorry +set max_bytes_before_external_group_by = 0; set dialect = 'kusto'; print '-- extend #1 --'; diff --git a/tests/queries/0_stateless/02366_kql_makeseries.sql b/tests/queries/0_stateless/02366_kql_makeseries.sql index ecf2ef43cc4..c9ca91c0be0 100644 --- a/tests/queries/0_stateless/02366_kql_makeseries.sql +++ b/tests/queries/0_stateless/02366_kql_makeseries.sql @@ -14,31 +14,34 @@ -- ]; DROP TABLE IF EXISTS make_series_test_table; CREATE TABLE make_series_test_table -( +( Supplier Nullable(String), Fruit String , Price Float64, - Purchase Date + Purchase Date ) ENGINE = Memory; INSERT INTO make_series_test_table VALUES ('Aldi','Apple',4,'2016-09-10'), ('Costco','Apple',2,'2016-09-11'), ('Aldi','Apple',6,'2016-09-10'), ('Costco','Snargaluff',100,'2016-09-12'), ('Aldi','Apple',7,'2016-09-12'), ('Aldi','Snargaluff',400,'2016-09-11'),('Costco','Snargaluff',104,'2016-09-12'),('Aldi','Apple',5,'2016-09-12'),('Aldi','Snargaluff',600,'2016-09-11'),('Costco','Snargaluff',200,'2016-09-10'); DROP TABLE IF EXISTS make_series_test_table2; CREATE TABLE make_series_test_table2 -( +( Supplier Nullable(String), Fruit String , Price Int32, - Purchase Int32 + Purchase Int32 ) ENGINE = Memory; INSERT INTO make_series_test_table2 VALUES ('Aldi','Apple',4,10),('Costco','Apple',2,11),('Aldi','Apple',6,10),('Costco','Snargaluff',100,12),('Aldi','Apple',7,12),('Aldi','Snargaluff',400,11),('Costco','Snargaluff',104,12),('Aldi','Apple',5,12),('Aldi','Snargaluff',600,11),('Costco','Snargaluff',200,10); DROP TABLE IF EXISTS make_series_test_table3; CREATE TABLE make_series_test_table3 -( +( timestamp datetime, metric Float64, ) ENGINE = Memory; INSERT INTO make_series_test_table3 VALUES (parseDateTimeBestEffort('2016-12-31T06:00', 'UTC'), 50), (parseDateTimeBestEffort('2017-01-01', 'UTC'), 4), (parseDateTimeBestEffort('2017-01-02', 'UTC'), 3), (parseDateTimeBestEffort('2017-01-03', 'UTC'), 4), (parseDateTimeBestEffort('2017-01-03T03:00', 'UTC'), 6), (parseDateTimeBestEffort('2017-01-05', 'UTC'), 8), (parseDateTimeBestEffort('2017-01-05T13:40', 'UTC'), 13), (parseDateTimeBestEffort('2017-01-06', 'UTC'), 4), (parseDateTimeBestEffort('2017-01-07', 'UTC'), 3), (parseDateTimeBestEffort('2017-01-08', 'UTC'), 8), (parseDateTimeBestEffort('2017-01-08T21:00', 'UTC'), 8), (parseDateTimeBestEffort('2017-01-09', 'UTC'), 2), (parseDateTimeBestEffort('2017-01-09T12:00', 'UTC'), 11), (parseDateTimeBestEffort('2017-01-10T05:00', 'UTC'), 5); +-- This test requies sorting after some of aggregations but I don't know KQL, sorry +set max_bytes_before_external_group_by = 0; set dialect = 'kusto'; + print '-- from to'; make_series_test_table | make-series PriceAvg = avg(Price) default=0 on Purchase from datetime(2016-09-10) to datetime(2016-09-13) step 1d by Supplier, Fruit | order by Supplier, Fruit; print '-- from'; @@ -68,7 +71,7 @@ make_series_test_table2 | make-series PriceAvg=avg(Price) default=0 on Purchase print '-- without by'; make_series_test_table2 | make-series PriceAvg=avg(Price) default=0 on Purchase step 2.0; -make_series_test_table3 | make-series avg(metric) default=0 on timestamp from datetime(2017-01-01) to datetime(2017-01-10) step 1d +make_series_test_table3 | make-series avg(metric) default=0 on timestamp from datetime(2017-01-01) to datetime(2017-01-10) step 1d -- print '-- summarize --' -- make_series_test_table | summarize count() by format_datetime(bin(Purchase, 1d), 'yy-MM-dd'); From 7cd770e9a73c14ea078607b360f66506d639a269 Mon Sep 17 00:00:00 2001 From: Chen Lixiang Date: Sat, 9 Dec 2023 20:09:41 +0800 Subject: [PATCH 033/253] fix doc and comments --- docs/en/operations/system-tables/tables.md | 2 +- src/Storages/IStorage.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/operations/system-tables/tables.md b/docs/en/operations/system-tables/tables.md index 231be6404a3..364692a934c 100644 --- a/docs/en/operations/system-tables/tables.md +++ b/docs/en/operations/system-tables/tables.md @@ -57,7 +57,7 @@ Columns: - If the table stores data on disk, returns used space on disk (i.e. compressed). - If the table stores data in memory, returns approximated number of used bytes in memory. -- `total_bytes_uncompressed` ([Nullable](../../sql-reference/data-types/nullable.md)([UInt64](../../sql-reference/data-types/int-uint.md))) - Total number of uncompressed bytes, if it is possible to quickly determine exact number of bytes from checksums for the table on storage, otherwise `NULL` (does not includes any underlying storage). +- `total_bytes_uncompressed` ([Nullable](../../sql-reference/data-types/nullable.md)([UInt64](../../sql-reference/data-types/int-uint.md))) - Total number of uncompressed bytes, if it's possible to quickly determine the exact number of bytes from the part checksums for the table on storage, otherwise `NULL` (does not take underlying storage (if any) into account). - `lifetime_rows` ([Nullable](../../sql-reference/data-types/nullable.md)([UInt64](../../sql-reference/data-types/int-uint.md))) - Total number of rows INSERTed since server start (only for `Buffer` tables). diff --git a/src/Storages/IStorage.h b/src/Storages/IStorage.h index ecdf3231997..c50de2ad6bc 100644 --- a/src/Storages/IStorage.h +++ b/src/Storages/IStorage.h @@ -699,7 +699,7 @@ public: /// Used for: /// - For total_bytes_uncompressed column in system.tables /// - /// Does not takes underlying Storage (if any) into account + /// Does not take underlying Storage (if any) into account virtual std::optional totalBytesUncompressed(const Settings &) const { return {}; } /// Number of rows INSERTed since server start. From 30b09f4287323c03a39d0e74c0b11c0603ed2813 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 11 Dec 2023 12:15:42 +0000 Subject: [PATCH 034/253] fix some tests --- .../queries/0_stateless/00109_shard_totals_after_having.sql | 3 +++ tests/queries/0_stateless/00119_storage_join.sql | 2 +- .../00184_shard_distributed_group_by_no_merge.reference | 2 +- .../00184_shard_distributed_group_by_no_merge.sql | 2 +- tests/queries/0_stateless/00273_quantiles.sql | 3 +++ .../00410_aggregation_combinators_with_arenas.sql | 4 ++++ tests/queries/0_stateless/00808_not_optimize_predicate.sql | 6 ++++-- .../01244_optimize_distributed_group_by_sharding_key.sql | 3 +++ tests/queries/0_stateless/01563_distributed_query_finish.sh | 2 +- tests/queries/0_stateless/01799_long_uniq_theta_sketch.sql | 2 +- .../0_stateless/02496_remove_redundant_sorting.reference | 2 +- tests/queries/0_stateless/02496_remove_redundant_sorting.sh | 2 +- .../02496_remove_redundant_sorting_analyzer.reference | 2 +- .../0_stateless/02500_remove_redundant_distinct.reference | 6 +++--- .../queries/0_stateless/02500_remove_redundant_distinct.sh | 4 ++-- .../02500_remove_redundant_distinct_analyzer.reference | 6 +++--- tests/queries/0_stateless/02567_and_consistency.sql | 4 ++-- 17 files changed, 35 insertions(+), 20 deletions(-) diff --git a/tests/queries/0_stateless/00109_shard_totals_after_having.sql b/tests/queries/0_stateless/00109_shard_totals_after_having.sql index b17accc0dae..dce265e0552 100644 --- a/tests/queries/0_stateless/00109_shard_totals_after_having.sql +++ b/tests/queries/0_stateless/00109_shard_totals_after_having.sql @@ -4,6 +4,9 @@ SET max_rows_to_group_by = 100000; SET max_block_size = 100001; SET group_by_overflow_mode = 'any'; +-- Settings 'max_rows_to_group_by' and 'max_bytes_before_external_group_by' are mutually exclusive. +SET max_bytes_before_external_group_by = 0; + DROP TABLE IF EXISTS numbers500k; CREATE TABLE numbers500k (number UInt32) ENGINE = TinyLog; diff --git a/tests/queries/0_stateless/00119_storage_join.sql b/tests/queries/0_stateless/00119_storage_join.sql index 2569a64d2c3..cd255cdfe24 100644 --- a/tests/queries/0_stateless/00119_storage_join.sql +++ b/tests/queries/0_stateless/00119_storage_join.sql @@ -12,7 +12,7 @@ SELECT x, s, k FROM (SELECT number AS k FROM system.numbers LIMIT 10) js1 ANY LE SELECT 1, x, 2, s, 3, k, 4 FROM (SELECT number AS k FROM system.numbers LIMIT 10) js1 ANY LEFT JOIN t2 USING k; SELECT t1.k, t1.s, t2.x -FROM ( SELECT number AS k, 'a' AS s FROM numbers(2) GROUP BY number WITH TOTALS ) AS t1 +FROM ( SELECT number AS k, 'a' AS s FROM numbers(2) GROUP BY number WITH TOTALS ORDER BY number) AS t1 ANY LEFT JOIN t2 AS t2 USING(k); DROP TABLE t2; diff --git a/tests/queries/0_stateless/00184_shard_distributed_group_by_no_merge.reference b/tests/queries/0_stateless/00184_shard_distributed_group_by_no_merge.reference index b2b0b43e490..72828aae5a9 100644 --- a/tests/queries/0_stateless/00184_shard_distributed_group_by_no_merge.reference +++ b/tests/queries/0_stateless/00184_shard_distributed_group_by_no_merge.reference @@ -36,9 +36,9 @@ GROUP BY ORDER BY 1 GROUP BY w/ ALIAS 0 -1 0 1 +1 ORDER BY w/ ALIAS 0 func(aggregate function) GROUP BY diff --git a/tests/queries/0_stateless/00184_shard_distributed_group_by_no_merge.sql b/tests/queries/0_stateless/00184_shard_distributed_group_by_no_merge.sql index 422f4a010f1..1bd6cbe8948 100644 --- a/tests/queries/0_stateless/00184_shard_distributed_group_by_no_merge.sql +++ b/tests/queries/0_stateless/00184_shard_distributed_group_by_no_merge.sql @@ -34,7 +34,7 @@ SELECT uniq(number) u FROM remote('127.0.0.{2,3}', currentDatabase(), data_00184 -- cover possible tricky issues SELECT 'GROUP BY w/ ALIAS'; -SELECT n FROM remote('127.0.0.{2,3}', currentDatabase(), data_00184) GROUP BY number AS n SETTINGS distributed_group_by_no_merge=2; +SELECT n FROM remote('127.0.0.{2,3}', currentDatabase(), data_00184) GROUP BY number AS n ORDER BY n SETTINGS distributed_group_by_no_merge=2; SELECT 'ORDER BY w/ ALIAS'; SELECT n FROM remote('127.0.0.{2,3}', currentDatabase(), data_00184) ORDER BY number AS n LIMIT 1 SETTINGS distributed_group_by_no_merge=2; diff --git a/tests/queries/0_stateless/00273_quantiles.sql b/tests/queries/0_stateless/00273_quantiles.sql index 9fef1f63057..f5b739b8be1 100644 --- a/tests/queries/0_stateless/00273_quantiles.sql +++ b/tests/queries/0_stateless/00273_quantiles.sql @@ -8,4 +8,7 @@ SELECT quantilesExact(0, 0.001, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0 SELECT quantilesTDigest(0, 0.001, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99, 0.999, 1)(x) FROM (SELECT number AS x FROM system.numbers LIMIT 1001); SELECT quantilesDeterministic(0, 0.001, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99, 0.999, 1)(x, x) FROM (SELECT number AS x FROM system.numbers LIMIT 1001); +-- The result slightly differs but it's ok since `quantilesDeterministic` is an approximate function. +SET max_bytes_before_external_group_by = 0; + SELECT round(1000000 / (number + 1)) AS k, count() AS c, arrayMap(x -> round(x, 6), quantilesDeterministic(0.1, 0.5, 0.9)(number, intHash64(number))) AS q1, quantilesExact(0.1, 0.5, 0.9)(number) AS q2 FROM (SELECT number FROM system.numbers LIMIT 1000000) GROUP BY k ORDER BY k; diff --git a/tests/queries/0_stateless/00410_aggregation_combinators_with_arenas.sql b/tests/queries/0_stateless/00410_aggregation_combinators_with_arenas.sql index 67f5cc54afd..a3abbb9fd58 100644 --- a/tests/queries/0_stateless/00410_aggregation_combinators_with_arenas.sql +++ b/tests/queries/0_stateless/00410_aggregation_combinators_with_arenas.sql @@ -7,4 +7,8 @@ DROP TABLE IF EXISTS arena; SELECT length(arrayReduce('groupUniqArray', [[1, 2], [1], emptyArrayUInt8(), [1], [1, 2]])); SELECT min(x), max(x) FROM (SELECT length(arrayReduce('groupUniqArray', [hex(number), hex(number+1), hex(number)])) AS x FROM system.numbers LIMIT 100000); + +-- Disable external aggregation because the state is reset for each new block of data in 'runningAccumulate' function. +SET max_bytes_before_external_group_by = 0; + SELECT sum(length(runningAccumulate(x))) FROM (SELECT groupUniqArrayState(toString(number % 10)) AS x, number FROM (SELECT * FROM system.numbers LIMIT 11) GROUP BY number ORDER BY number); diff --git a/tests/queries/0_stateless/00808_not_optimize_predicate.sql b/tests/queries/0_stateless/00808_not_optimize_predicate.sql index ba8f5eb5753..d0dda14e026 100644 --- a/tests/queries/0_stateless/00808_not_optimize_predicate.sql +++ b/tests/queries/0_stateless/00808_not_optimize_predicate.sql @@ -48,7 +48,8 @@ SELECT intDiv(number, 25) AS n, avgState(number) AS s FROM numbers(2500) -GROUP BY n; +GROUP BY n +ORDER BY n; SET force_primary_key = 1, enable_optimize_predicate_expression = 1; @@ -60,7 +61,8 @@ FROM finalizeAggregation(s) FROM test_00808_push_down_with_finalizeAggregation ) -WHERE (n >= 2) AND (n <= 5); +WHERE (n >= 2) AND (n <= 5) +ORDER BY n; EXPLAIN SYNTAX SELECT * FROM diff --git a/tests/queries/0_stateless/01244_optimize_distributed_group_by_sharding_key.sql b/tests/queries/0_stateless/01244_optimize_distributed_group_by_sharding_key.sql index 291910ed43f..0d24b238d64 100644 --- a/tests/queries/0_stateless/01244_optimize_distributed_group_by_sharding_key.sql +++ b/tests/queries/0_stateless/01244_optimize_distributed_group_by_sharding_key.sql @@ -4,6 +4,9 @@ set optimize_distributed_group_by_sharding_key=1; +-- Some queries in this test require sorting after aggregation. +set max_bytes_before_external_group_by = 0; + drop table if exists dist_01247; drop table if exists data_01247; diff --git a/tests/queries/0_stateless/01563_distributed_query_finish.sh b/tests/queries/0_stateless/01563_distributed_query_finish.sh index b49042ead9d..0019c714e40 100755 --- a/tests/queries/0_stateless/01563_distributed_query_finish.sh +++ b/tests/queries/0_stateless/01563_distributed_query_finish.sh @@ -28,7 +28,7 @@ opts=( "--prefer_localhost_replica=0" ) $CLICKHOUSE_CLIENT "${opts[@]}" --format CSV -nm < all ORDER BY clauses will be removed in subqueries" diff --git a/tests/queries/0_stateless/02496_remove_redundant_sorting_analyzer.reference b/tests/queries/0_stateless/02496_remove_redundant_sorting_analyzer.reference index b2ac9e4533b..16d3327b9c2 100644 --- a/tests/queries/0_stateless/02496_remove_redundant_sorting_analyzer.reference +++ b/tests/queries/0_stateless/02496_remove_redundant_sorting_analyzer.reference @@ -113,7 +113,7 @@ FROM ) ORDER BY number DESC ) AS t2 -ORDER BY number +ORDER BY t1.number, t2.number -- explain Expression (Project names) Sorting (Sorting for ORDER BY) diff --git a/tests/queries/0_stateless/02500_remove_redundant_distinct.reference b/tests/queries/0_stateless/02500_remove_redundant_distinct.reference index 5348d407097..d7623cd5541 100644 --- a/tests/queries/0_stateless/02500_remove_redundant_distinct.reference +++ b/tests/queries/0_stateless/02500_remove_redundant_distinct.reference @@ -73,7 +73,7 @@ FROM SELECT DISTINCT number AS n FROM numbers(2) ) as y -ORDER BY n +ORDER BY x.n, y.n -- explain Expression (Projection) Distinct @@ -155,7 +155,7 @@ FROM FROM VALUES('Hello', 'World', 'Goodbye') ) AS words ARRAY JOIN [0, 1] AS arr -ORDER BY arr +ORDER BY c1, arr -- explain Expression (Projection) Distinct @@ -169,9 +169,9 @@ Expression (Projection) Expression (Before ORDER BY) ReadFromStorage (Values) -- execute +Goodbye Hello World -Goodbye -- WITH FILL: do _not_ remove outer DISTINCT because new rows are generated between inner and outer DISTINCTs -- query SELECT DISTINCT * diff --git a/tests/queries/0_stateless/02500_remove_redundant_distinct.sh b/tests/queries/0_stateless/02500_remove_redundant_distinct.sh index f83fcff07c1..c4f0994cd13 100755 --- a/tests/queries/0_stateless/02500_remove_redundant_distinct.sh +++ b/tests/queries/0_stateless/02500_remove_redundant_distinct.sh @@ -60,7 +60,7 @@ FROM SELECT DISTINCT number AS n FROM numbers(2) ) as y -ORDER BY n" +ORDER BY x.n, y.n" run_query "$query" echo "-- DISTINCT duplicates with several columns" @@ -99,7 +99,7 @@ FROM FROM VALUES('Hello', 'World', 'Goodbye') ) AS words ARRAY JOIN [0, 1] AS arr -ORDER BY arr" +ORDER BY c1, arr" run_query "$query" echo "-- WITH FILL: do _not_ remove outer DISTINCT because new rows are generated between inner and outer DISTINCTs" diff --git a/tests/queries/0_stateless/02500_remove_redundant_distinct_analyzer.reference b/tests/queries/0_stateless/02500_remove_redundant_distinct_analyzer.reference index 798191db7e4..b79f6310166 100644 --- a/tests/queries/0_stateless/02500_remove_redundant_distinct_analyzer.reference +++ b/tests/queries/0_stateless/02500_remove_redundant_distinct_analyzer.reference @@ -74,7 +74,7 @@ FROM SELECT DISTINCT number AS n FROM numbers(2) ) as y -ORDER BY n +ORDER BY x.n, y.n -- explain Expression (Project names) Distinct (DISTINCT) @@ -157,7 +157,7 @@ FROM FROM VALUES('Hello', 'World', 'Goodbye') ) AS words ARRAY JOIN [0, 1] AS arr -ORDER BY arr +ORDER BY c1, arr -- explain Expression (Project names) Distinct (DISTINCT) @@ -172,9 +172,9 @@ Expression (Project names) Expression ((Projection + Change column names to column identifiers)) ReadFromStorage (Values) -- execute +Goodbye Hello World -Goodbye -- WITH FILL: do _not_ remove outer DISTINCT because new rows are generated between inner and outer DISTINCTs -- query SELECT DISTINCT * diff --git a/tests/queries/0_stateless/02567_and_consistency.sql b/tests/queries/0_stateless/02567_and_consistency.sql index 8ad06bd68cb..ca0c0e8ab77 100644 --- a/tests/queries/0_stateless/02567_and_consistency.sql +++ b/tests/queries/0_stateless/02567_and_consistency.sql @@ -76,10 +76,10 @@ GROUP BY g; select '='; SELECT MAX(left.c0), min2(left.c0, -(-left.c0) * (radians(left.c0) - radians(left.c0))) as g, (((-1925024212 IS NOT NULL) IS NOT NULL) != radians(tan(1216286224))) AND cos(lcm(MAX(left.c0), -1966575216) OR (MAX(left.c0) * 1180517420)) as h, not h, h is null FROM t2 AS left - GROUP BY g HAVING h SETTINGS enable_optimize_predicate_expression = 0; + GROUP BY g HAVING h ORDER BY g DESC SETTINGS enable_optimize_predicate_expression = 0; select '='; SELECT MAX(left.c0), min2(left.c0, -(-left.c0) * (radians(left.c0) - radians(left.c0))) as g, (((-1925024212 IS NOT NULL) IS NOT NULL) != radians(tan(1216286224))) AND cos(lcm(MAX(left.c0), -1966575216) OR (MAX(left.c0) * 1180517420)) as h, not h, h is null FROM t2 AS left - GROUP BY g HAVING h SETTINGS enable_optimize_predicate_expression = 1; + GROUP BY g HAVING h ORDER BY g DESC SETTINGS enable_optimize_predicate_expression = 1; DROP TABLE IF EXISTS t2; From 01c10e10857efcdd649433b968360699442a1918 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 11 Dec 2023 17:08:01 +0000 Subject: [PATCH 035/253] fix some tests --- .../00133_long_shard_memory_tracker_and_exception_safety.sh | 2 +- tests/queries/0_stateless/00953_moving_functions.sql | 4 ++++ tests/queries/0_stateless/01193_metadata_loading.sh | 2 +- .../01513_optimize_aggregation_in_order_memory_long.sql | 1 + .../0_stateless/01514_distributed_cancel_query_on_error.sh | 1 + tests/queries/0_stateless/01591_window_functions.sql | 4 ++++ tests/queries/0_stateless/02352_rwlock.sh | 4 ++-- 7 files changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/queries/0_stateless/00133_long_shard_memory_tracker_and_exception_safety.sh b/tests/queries/0_stateless/00133_long_shard_memory_tracker_and_exception_safety.sh index 389a2cd9684..e6e82e8c976 100755 --- a/tests/queries/0_stateless/00133_long_shard_memory_tracker_and_exception_safety.sh +++ b/tests/queries/0_stateless/00133_long_shard_memory_tracker_and_exception_safety.sh @@ -15,7 +15,7 @@ if [ -n "$DBMS_TESTS_UNDER_VALGRIND" ]; then STEP_MULTIPLIER=1000 fi -for i in $(seq 1000000 $((20000 * $STEP_MULTIPLIER)) 10000000 && seq 10100000 $((100000 * $STEP_MULTIPLIER)) 50000000); do +for i in $(seq 1000000 $((20000 * $STEP_MULTIPLIER)) 10000000 && seq 10100000 $((100000 * $STEP_MULTIPLIER)) 100000000); do $CLICKHOUSE_CLIENT --max_memory_usage="$i" --query=" SELECT intDiv(number, 5) AS k, max(toString(number)) FROM remote('127.0.0.{2,3}', ${CLICKHOUSE_DATABASE}.numbers_100k) GROUP BY k ORDER BY k LIMIT 1; " 2> /dev/null; diff --git a/tests/queries/0_stateless/00953_moving_functions.sql b/tests/queries/0_stateless/00953_moving_functions.sql index daaceeeb3ac..b9046158a16 100644 --- a/tests/queries/0_stateless/00953_moving_functions.sql +++ b/tests/queries/0_stateless/00953_moving_functions.sql @@ -24,6 +24,10 @@ INSERT INTO moving_sum_num SELECT * FROM moving_sum_num ORDER BY k,dt FORMAT TabSeparatedWithNames; +-- Result of function 'groupArrayMovingSum' depends on the order of merging +-- aggregate states which is implementation defined in external aggregation. +SET max_bytes_before_external_group_by = 0; + SELECT k, groupArrayMovingSum(v) FROM (SELECT * FROM moving_sum_num ORDER BY k, dt) GROUP BY k ORDER BY k FORMAT TabSeparatedWithNamesAndTypes; SELECT k, groupArrayMovingSum(3)(v) FROM (SELECT * FROM moving_sum_num ORDER BY k, dt) GROUP BY k ORDER BY k FORMAT TabSeparatedWithNamesAndTypes; diff --git a/tests/queries/0_stateless/01193_metadata_loading.sh b/tests/queries/0_stateless/01193_metadata_loading.sh index 50425eae018..c25cdf4e970 100755 --- a/tests/queries/0_stateless/01193_metadata_loading.sh +++ b/tests/queries/0_stateless/01193_metadata_loading.sh @@ -29,7 +29,7 @@ create_tables() { groupArray( create1 || toString(number) || create2 || engines[1 + number % length(engines)] || ';\n' || insert1 || toString(number) || insert2 - ), ';\n') FROM numbers($tables) FORMAT TSVRaw;" | $CLICKHOUSE_CLIENT -nm + ), ';\n') FROM numbers($tables) SETTINGS max_bytes_before_external_group_by = 0 FORMAT TSVRaw;" | $CLICKHOUSE_CLIENT -nm } $CLICKHOUSE_CLIENT -q "CREATE DATABASE $db" diff --git a/tests/queries/0_stateless/01513_optimize_aggregation_in_order_memory_long.sql b/tests/queries/0_stateless/01513_optimize_aggregation_in_order_memory_long.sql index 3d57518d0f4..b107af07194 100644 --- a/tests/queries/0_stateless/01513_optimize_aggregation_in_order_memory_long.sql +++ b/tests/queries/0_stateless/01513_optimize_aggregation_in_order_memory_long.sql @@ -12,6 +12,7 @@ optimize table data_01513 final; set max_memory_usage='500M'; set max_threads=1; set max_block_size=500; +set max_bytes_before_external_group_by=0; select key, groupArray(repeat('a', 200)), count() from data_01513 group by key format Null settings optimize_aggregation_in_order=0; -- { serverError 241 } select key, groupArray(repeat('a', 200)), count() from data_01513 group by key format Null settings optimize_aggregation_in_order=1; diff --git a/tests/queries/0_stateless/01514_distributed_cancel_query_on_error.sh b/tests/queries/0_stateless/01514_distributed_cancel_query_on_error.sh index 99025890cb3..edf3683ccba 100755 --- a/tests/queries/0_stateless/01514_distributed_cancel_query_on_error.sh +++ b/tests/queries/0_stateless/01514_distributed_cancel_query_on_error.sh @@ -15,6 +15,7 @@ opts=( "--max_block_size=50" "--max_threads=1" "--max_distributed_connections=2" + "--max_bytes_before_external_group_by=0" ) ${CLICKHOUSE_CLIENT} "${opts[@]}" -q "SELECT groupArray(repeat('a', if(_shard_num == 2, 100000, 1))), number%100000 k from remote('127.{2,3}', system.numbers) GROUP BY k LIMIT 10e6" |& { # the query should fail earlier on 127.3 and 127.2 should not even go to the memory limit exceeded error. diff --git a/tests/queries/0_stateless/01591_window_functions.sql b/tests/queries/0_stateless/01591_window_functions.sql index 07e323b3c40..952a66616a9 100644 --- a/tests/queries/0_stateless/01591_window_functions.sql +++ b/tests/queries/0_stateless/01591_window_functions.sql @@ -2,6 +2,10 @@ SET allow_experimental_analyzer = 1; +-- Too slow +SET max_bytes_before_external_sort = 0; +SET max_bytes_before_external_group_by = 0; + -- { echo } -- just something basic diff --git a/tests/queries/0_stateless/02352_rwlock.sh b/tests/queries/0_stateless/02352_rwlock.sh index 7de2c7089b8..08551794c2e 100755 --- a/tests/queries/0_stateless/02352_rwlock.sh +++ b/tests/queries/0_stateless/02352_rwlock.sh @@ -21,7 +21,7 @@ function wait_query_by_id_started() # wait for query to be started while [ "$($CLICKHOUSE_CLIENT "$@" -q "select count() from system.processes where query_id = '$query_id'")" -ne 1 ]; do if [ "$( - $CLICKHOUSE_CLIENT -nm -q " + $CLICKHOUSE_CLIENT --max_bytes_before_external_group_by 0 -nm -q " system flush logs; select count() from system.query_log @@ -56,7 +56,7 @@ while :; do insert_query_id="insert-$(random_str 10)" # 20 seconds sleep - $CLICKHOUSE_CLIENT --function_sleep_max_microseconds_per_block 20000000 --query_id "$insert_query_id" -q "INSERT INTO ${CLICKHOUSE_DATABASE}_ordinary.data_02352 SELECT sleepEachRow(1) FROM numbers(20) GROUP BY number" & + $CLICKHOUSE_CLIENT --function_sleep_max_microseconds_per_block 20000000 --max_bytes_before_external_group_by 0 --query_id "$insert_query_id" -q "INSERT INTO ${CLICKHOUSE_DATABASE}_ordinary.data_02352 SELECT sleepEachRow(1) FROM numbers(20) GROUP BY number" & if ! wait_query_by_id_started "$insert_query_id"; then wait continue From cd65e6b60c798e6074fac914fe8c6ec09c60fa5c Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 11 Dec 2023 21:05:29 +0000 Subject: [PATCH 036/253] fix some tests --- tests/queries/0_stateless/01134_max_rows_to_group_by.sql | 3 +++ tests/queries/0_stateless/01710_aggregate_projections.sh | 2 ++ .../0_stateless/02428_combinators_with_over_statement.sql | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01134_max_rows_to_group_by.sql b/tests/queries/0_stateless/01134_max_rows_to_group_by.sql index bfbc499e1c3..f9ea37cb65a 100644 --- a/tests/queries/0_stateless/01134_max_rows_to_group_by.sql +++ b/tests/queries/0_stateless/01134_max_rows_to_group_by.sql @@ -2,6 +2,9 @@ SET max_block_size = 1; SET max_rows_to_group_by = 10; SET group_by_overflow_mode = 'throw'; +-- Settings 'max_rows_to_group_by' and 'max_bytes_before_external_group_by' are mutually exclusive. +SET max_bytes_before_external_group_by = 0; + SELECT 'test1', number FROM system.numbers GROUP BY number; -- { serverError 158 } SET group_by_overflow_mode = 'break'; diff --git a/tests/queries/0_stateless/01710_aggregate_projections.sh b/tests/queries/0_stateless/01710_aggregate_projections.sh index 326a564a208..7ea40365937 100755 --- a/tests/queries/0_stateless/01710_aggregate_projections.sh +++ b/tests/queries/0_stateless/01710_aggregate_projections.sh @@ -4,6 +4,8 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh +# Number of read rows depends on max_bytes_before_external_group_by. +CLICKHOUSE_CLIENT="$CLICKHOUSE_CLIENT --max_bytes_before_external_group_by 0" $CLICKHOUSE_CLIENT -q "CREATE TABLE test_agg_proj (x Int32, y Int32, PROJECTION x_plus_y (SELECT sum(x - y), argMax(x, y) group by x + y)) ENGINE = MergeTree ORDER BY tuple() settings index_granularity = 1" $CLICKHOUSE_CLIENT -q "insert into test_agg_proj select intDiv(number, 2), -intDiv(number,3) - 1 from numbers(100)" diff --git a/tests/queries/0_stateless/02428_combinators_with_over_statement.sql b/tests/queries/0_stateless/02428_combinators_with_over_statement.sql index b42066cdf52..7946b997b00 100644 --- a/tests/queries/0_stateless/02428_combinators_with_over_statement.sql +++ b/tests/queries/0_stateless/02428_combinators_with_over_statement.sql @@ -1,6 +1,6 @@ drop table if exists test; create table test (x AggregateFunction(uniq, UInt64), y Int64) engine=Memory; -insert into test select uniqState(number) as x, number as y from numbers(10) group by number; +insert into test select uniqState(number) as x, number as y from numbers(10) group by number order by x, y; select uniqStateMap(map(1, x)) OVER (PARTITION BY y) from test; select uniqStateForEach([x]) OVER (PARTITION BY y) from test; select uniqStateResample(30, 75, 30)([x], 30) OVER (PARTITION BY y) from test; From e34c13b9d9449c1b2fd6691834e7b448c4674727 Mon Sep 17 00:00:00 2001 From: Zhuo Qiu Date: Wed, 22 Nov 2023 17:09:54 +0800 Subject: [PATCH 037/253] Consider lightweight deleted rows when selecting parts to merge --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 96 +++++++++++++++++++ src/Storages/MergeTree/IMergeTreeDataPart.h | 14 +++ .../MergeTree/MergeFromLogEntryTask.cpp | 2 +- .../MergeTree/MergeTreeDataMergerMutator.cpp | 11 ++- .../MergeTree/MergeTreeDataMergerMutator.h | 2 +- src/Storages/MergeTree/MergeTreeSettings.h | 1 + .../MergeTree/MutateFromLogEntryTask.cpp | 2 +- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 2 +- src/Storages/StorageMergeTree.cpp | 4 +- .../02942_consider_lwd_when_merge.reference | 3 + .../02942_consider_lwd_when_merge.sql | 23 +++++ 11 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 tests/queries/0_stateless/02942_consider_lwd_when_merge.reference create mode 100644 tests/queries/0_stateless/02942_consider_lwd_when_merge.sql diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 38ecd8f4067..d5e1cf25188 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -593,6 +593,23 @@ UInt64 IMergeTreeDataPart::getMarksCount() const return index_granularity.getMarksCount(); } +UInt64 IMergeTreeDataPart::getExistingBytesOnDisk() const +{ + if (!supportLightweightDeleteMutate() || !hasLightweightDelete() || !rows_count + || !storage.getSettings()->exclude_deleted_rows_for_part_size_in_merge) + return bytes_on_disk; + + /// Uninitialized existing_rows_count + /// (if existing_rows_count equals rows_count, it means that previously we failed to read existing_rows_count) + if (existing_rows_count > rows_count) + readExistingRowsCount(); + + if (existing_rows_count < rows_count) + return bytes_on_disk * existing_rows_count / rows_count; + else /// Load failed + return bytes_on_disk; +} + size_t IMergeTreeDataPart::getFileSizeOrZero(const String & file_name) const { auto checksum = checksums.files.find(file_name); @@ -1285,6 +1302,85 @@ void IMergeTreeDataPart::loadRowsCount() } } +void IMergeTreeDataPart::readExistingRowsCount() const +{ + if (!supportLightweightDeleteMutate() || !hasLightweightDelete() || !storage.getSettings()->exclude_deleted_rows_for_part_size_in_merge + || existing_rows_count < rows_count || !getMarksCount()) + return; + + std::lock_guard lock(existing_rows_count_mutex); + + /// Already read by another thread + if (existing_rows_count < rows_count) + return; + + NamesAndTypesList cols; + cols.push_back(LightweightDeleteDescription::FILTER_COLUMN); + + StorageMetadataPtr metadata_ptr = storage.getInMemoryMetadataPtr(); + StorageSnapshotPtr storage_snapshot_ptr = std::make_shared(storage, metadata_ptr); + + MergeTreeReaderPtr reader = getReader( + cols, + storage_snapshot_ptr, + MarkRanges{MarkRange(0, getMarksCount())}, + nullptr, + storage.getContext()->getMarkCache().get(), + std::make_shared(), + MergeTreeReaderSettings{}, + ValueSizeMap{}, + ReadBufferFromFileBase::ProfileCallback{}); + + if (!reader) + { + LOG_WARNING(storage.log, "Create reader failed while reading existing rows count"); + existing_rows_count = rows_count; + return; + } + + size_t current_mark = 0; + const size_t total_mark = getMarksCount(); + + bool continue_reading = false; + size_t current_row = 0; + size_t existing_count = 0; + + while (current_row < rows_count) + { + size_t rows_to_read = index_granularity.getMarkRows(current_mark); + continue_reading = (current_mark != 0); + + Columns result; + result.resize(1); + + size_t rows_read = reader->readRows(current_mark, total_mark, continue_reading, rows_to_read, result); + if (!rows_read) + { + LOG_WARNING(storage.log, "Part {} has lightweight delete, but _row_exists column not found", name); + existing_rows_count = rows_count; + return; + } + + current_row += rows_read; + current_mark += (rows_to_read == rows_read); + + const ColumnUInt8 * row_exists_col = typeid_cast(result[0].get()); + if (!row_exists_col) + { + LOG_WARNING(storage.log, "Part {} _row_exists column type is not UInt8", name); + existing_rows_count = rows_count; + return; + } + + for (UInt8 row_exists : row_exists_col->getData()) + if (row_exists) + existing_count++; + } + + existing_rows_count = existing_count; + LOG_DEBUG(storage.log, "Part {} existing_rows_count = {}", name, existing_rows_count); +} + void IMergeTreeDataPart::appendFilesOfRowsCount(Strings & files) { files.push_back("count.txt"); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 06e0712646a..349f58da7c1 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -229,6 +229,13 @@ public: size_t rows_count = 0; + /// Existing rows count (excluding lightweight deleted rows) + /// UINT64_MAX -> uninitialized + /// 0 -> all rows were deleted + /// if reading failed, it will be set to rows_count + mutable size_t existing_rows_count = UINT64_MAX; + mutable std::mutex existing_rows_count_mutex; + time_t modification_time = 0; /// When the part is removed from the working set. Changes once. mutable std::atomic remove_time { std::numeric_limits::max() }; @@ -372,6 +379,10 @@ public: UInt64 getBytesOnDisk() const { return bytes_on_disk; } void setBytesOnDisk(UInt64 bytes_on_disk_) { bytes_on_disk = bytes_on_disk_; } + /// Returns estimated size of existing rows if setting exclude_deleted_rows_for_part_size_in_merge is true + /// Otherwise returns bytes_on_disk + UInt64 getExistingBytesOnDisk() const; + size_t getFileSizeOrZero(const String & file_name) const; auto getFilesChecksums() const { return checksums.files; } @@ -498,6 +509,9 @@ public: /// True if here is lightweight deleted mask file in part. bool hasLightweightDelete() const { return columns.contains(LightweightDeleteDescription::FILTER_COLUMN.name); } + /// Read existing rows count from _row_exists column + void readExistingRowsCount() const; + void writeChecksums(const MergeTreeDataPartChecksums & checksums_, const WriteSettings & settings); /// Checks the consistency of this data part. diff --git a/src/Storages/MergeTree/MergeFromLogEntryTask.cpp b/src/Storages/MergeTree/MergeFromLogEntryTask.cpp index 3d8bc62b5cc..975cffbed9f 100644 --- a/src/Storages/MergeTree/MergeFromLogEntryTask.cpp +++ b/src/Storages/MergeTree/MergeFromLogEntryTask.cpp @@ -160,7 +160,7 @@ ReplicatedMergeMutateTaskBase::PrepareResult MergeFromLogEntryTask::prepare() } /// Start to make the main work - size_t estimated_space_for_merge = MergeTreeDataMergerMutator::estimateNeededDiskSpace(parts); + size_t estimated_space_for_merge = MergeTreeDataMergerMutator::estimateNeededDiskSpace(parts, true); /// Can throw an exception while reserving space. IMergeTreeDataPart::TTLInfos ttl_infos; diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index f78b383e173..042a6cce24c 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -405,7 +405,7 @@ MergeTreeDataMergerMutator::MergeSelectingInfo MergeTreeDataMergerMutator::getPo } IMergeSelector::Part part_info; - part_info.size = part->getBytesOnDisk(); + part_info.size = part->getExistingBytesOnDisk(); part_info.age = res.current_time - part->modification_time; part_info.level = part->info.level; part_info.data = ∂ @@ -611,7 +611,7 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectAllPartsToMergeWithinParti return SelectPartsDecision::CANNOT_SELECT; } - sum_bytes += (*it)->getBytesOnDisk(); + sum_bytes += (*it)->getExistingBytesOnDisk(); prev_it = it; ++it; @@ -793,7 +793,7 @@ MergeTreeData::DataPartPtr MergeTreeDataMergerMutator::renameMergedTemporaryPart } -size_t MergeTreeDataMergerMutator::estimateNeededDiskSpace(const MergeTreeData::DataPartsVector & source_parts) +size_t MergeTreeDataMergerMutator::estimateNeededDiskSpace(const MergeTreeData::DataPartsVector & source_parts, const bool & is_merge) { size_t res = 0; time_t current_time = std::time(nullptr); @@ -804,7 +804,10 @@ size_t MergeTreeDataMergerMutator::estimateNeededDiskSpace(const MergeTreeData:: if (part_max_ttl && part_max_ttl <= current_time) continue; - res += part->getBytesOnDisk(); + if (is_merge && part->storage.getSettings()->exclude_deleted_rows_for_part_size_in_merge) + res += part->getExistingBytesOnDisk(); + else + res += part->getBytesOnDisk(); } return static_cast(res * DISK_USAGE_COEFFICIENT_TO_RESERVE); diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.h b/src/Storages/MergeTree/MergeTreeDataMergerMutator.h index 6eab0ee0c37..4cc9ea170f3 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.h +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.h @@ -193,7 +193,7 @@ public: /// The approximate amount of disk space needed for merge or mutation. With a surplus. - static size_t estimateNeededDiskSpace(const MergeTreeData::DataPartsVector & source_parts); + static size_t estimateNeededDiskSpace(const MergeTreeData::DataPartsVector & source_parts, const bool & is_merge); private: /** Select all parts belonging to the same partition. diff --git a/src/Storages/MergeTree/MergeTreeSettings.h b/src/Storages/MergeTree/MergeTreeSettings.h index 5bb712ea786..d9b996b36ca 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -73,6 +73,7 @@ struct Settings; M(UInt64, number_of_mutations_to_throw, 1000, "If table has at least that many unfinished mutations, throw 'Too many mutations' exception. Disabled if set to 0", 0) \ M(UInt64, min_delay_to_mutate_ms, 10, "Min delay of mutating MergeTree table in milliseconds, if there are a lot of unfinished mutations", 0) \ M(UInt64, max_delay_to_mutate_ms, 1000, "Max delay of mutating MergeTree table in milliseconds, if there are a lot of unfinished mutations", 0) \ + M(Bool, exclude_deleted_rows_for_part_size_in_merge, false, "If true, estimated size (excluding lightweight deleted rows) will be used as source part size when selecting parts to merge", 0) \ \ /** Inserts settings. */ \ M(UInt64, parts_to_delay_insert, 1000, "If table contains at least that many active parts in single partition, artificially slow down insert into table. Disabled if set to 0", 0) \ diff --git a/src/Storages/MergeTree/MutateFromLogEntryTask.cpp b/src/Storages/MergeTree/MutateFromLogEntryTask.cpp index a9ff687fe4d..620b0e34c6a 100644 --- a/src/Storages/MergeTree/MutateFromLogEntryTask.cpp +++ b/src/Storages/MergeTree/MutateFromLogEntryTask.cpp @@ -49,7 +49,7 @@ ReplicatedMergeMutateTaskBase::PrepareResult MutateFromLogEntryTask::prepare() } /// TODO - some better heuristic? - size_t estimated_space_for_result = MergeTreeDataMergerMutator::estimateNeededDiskSpace({source_part}); + size_t estimated_space_for_result = MergeTreeDataMergerMutator::estimateNeededDiskSpace({source_part}, false); if (entry.create_time + storage_settings_ptr->prefer_fetch_merged_part_time_threshold.totalSeconds() <= time(nullptr) && estimated_space_for_result >= storage_settings_ptr->prefer_fetch_merged_part_size_threshold) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index bb74c4dd7bb..c45abb282a0 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1349,7 +1349,7 @@ bool ReplicatedMergeTreeQueue::shouldExecuteLogEntry( if (auto part_in_memory = asInMemoryPart(part)) sum_parts_size_in_bytes += part_in_memory->block.bytes(); else - sum_parts_size_in_bytes += part->getBytesOnDisk(); + sum_parts_size_in_bytes += part->getExistingBytesOnDisk(); } } diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index e9a0dd5fbf3..d1192bbfbd9 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1062,7 +1062,7 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMerge( if (isTTLMergeType(future_part->merge_type)) getContext()->getMergeList().bookMergeWithTTL(); - merging_tagger = std::make_unique(future_part, MergeTreeDataMergerMutator::estimateNeededDiskSpace(future_part->parts), *this, metadata_snapshot, false); + merging_tagger = std::make_unique(future_part, MergeTreeDataMergerMutator::estimateNeededDiskSpace(future_part->parts, true), *this, metadata_snapshot, false); return std::make_shared(future_part, std::move(merging_tagger), std::make_shared()); } @@ -1279,7 +1279,7 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMutate( future_part->name = part->getNewName(new_part_info); future_part->part_format = part->getFormat(); - tagger = std::make_unique(future_part, MergeTreeDataMergerMutator::estimateNeededDiskSpace({part}), *this, metadata_snapshot, true); + tagger = std::make_unique(future_part, MergeTreeDataMergerMutator::estimateNeededDiskSpace({part}, false), *this, metadata_snapshot, true); return std::make_shared(future_part, std::move(tagger), commands, txn); } } diff --git a/tests/queries/0_stateless/02942_consider_lwd_when_merge.reference b/tests/queries/0_stateless/02942_consider_lwd_when_merge.reference new file mode 100644 index 00000000000..19920de3d3c --- /dev/null +++ b/tests/queries/0_stateless/02942_consider_lwd_when_merge.reference @@ -0,0 +1,3 @@ +2 +2 +1 diff --git a/tests/queries/0_stateless/02942_consider_lwd_when_merge.sql b/tests/queries/0_stateless/02942_consider_lwd_when_merge.sql new file mode 100644 index 00000000000..a65e8877020 --- /dev/null +++ b/tests/queries/0_stateless/02942_consider_lwd_when_merge.sql @@ -0,0 +1,23 @@ +DROP TABLE IF EXISTS lwd_merge; + +CREATE TABLE lwd_merge (id UInt64 CODEC(NONE)) + ENGINE = MergeTree ORDER BY id +SETTINGS max_bytes_to_merge_at_max_space_in_pool = 80000, exclude_deleted_rows_for_part_size_in_merge = 0; + +INSERT INTO lwd_merge SELECT number FROM numbers(10000); +INSERT INTO lwd_merge SELECT number FROM numbers(10000, 10000); + +OPTIMIZE TABLE lwd_merge; +SELECT count() FROM system.parts WHERE database = currentDatabase() AND table = 'lwd_merge' AND active = 1; + +DELETE FROM lwd_merge WHERE id % 10 > 0; + +OPTIMIZE TABLE lwd_merge; +SELECT count() FROM system.parts WHERE database = currentDatabase() AND table = 'lwd_merge' AND active = 1; + +ALTER TABLE lwd_merge MODIFY SETTING exclude_deleted_rows_for_part_size_in_merge = 1; + +OPTIMIZE TABLE lwd_merge; +SELECT count() FROM system.parts WHERE database = currentDatabase() AND table = 'lwd_merge' AND active = 1; + +DROP TABLE IF EXISTS lwd_merge; From 0b89fbbdd92af06045f021f84552996069208b82 Mon Sep 17 00:00:00 2001 From: Chen Lixiang Date: Tue, 12 Dec 2023 16:49:58 +0800 Subject: [PATCH 038/253] fix style issue --- src/Storages/IStorage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/IStorage.h b/src/Storages/IStorage.h index c50de2ad6bc..1693d0e073b 100644 --- a/src/Storages/IStorage.h +++ b/src/Storages/IStorage.h @@ -701,7 +701,7 @@ public: /// /// Does not take underlying Storage (if any) into account virtual std::optional totalBytesUncompressed(const Settings &) const { return {}; } - + /// Number of rows INSERTed since server start. /// /// Does not take the underlying Storage (if any) into account. From f805956661c5e5513eae0c825f3bbd980661d6ee Mon Sep 17 00:00:00 2001 From: Chen Lixiang Date: Tue, 12 Dec 2023 20:29:51 +0800 Subject: [PATCH 039/253] fix test --- .../queries/0_stateless/02117_show_create_table_system.reference | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/0_stateless/02117_show_create_table_system.reference b/tests/queries/0_stateless/02117_show_create_table_system.reference index 9ed905a0df8..0f0f91f5494 100644 --- a/tests/queries/0_stateless/02117_show_create_table_system.reference +++ b/tests/queries/0_stateless/02117_show_create_table_system.reference @@ -1086,6 +1086,7 @@ CREATE TABLE system.tables `storage_policy` String, `total_rows` Nullable(UInt64), `total_bytes` Nullable(UInt64), + `total_bytes_uncompressed` Nullable(UInt64), `parts` Nullable(UInt64), `active_parts` Nullable(UInt64), `total_marks` Nullable(UInt64), From 254bcade1ef203c7e333f78b4b2fe5350a5caf4b Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Tue, 12 Dec 2023 18:28:20 +0000 Subject: [PATCH 040/253] Forbid CREATE AS SELECT for database Replicated --- src/Interpreters/InterpreterCreateQuery.cpp | 9 +++++++++ ...cated_database_forbid_create_as_select.reference | 1 + ...3_replicated_database_forbid_create_as_select.sh | 13 +++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.reference create mode 100755 tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index 2b60b0b7b47..bb8878fc2d5 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -1252,6 +1252,15 @@ BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create) if (need_add_to_database) database = DatabaseCatalog::instance().tryGetDatabase(database_name); + if (database && database->getEngineName() == "Replicated" && create.select) + { + bool allow_create_select_for_replicated = create.isView() || create.is_create_empty; + if (!allow_create_select_for_replicated) + throw Exception( + ErrorCodes::SUPPORT_IS_DISABLED, + "CREATE AS SELECT is not supported with Replicated databases. Use separate CREATE and INSERT queries"); + } + if (need_add_to_database && database && database->shouldReplicateQuery(getContext(), query_ptr)) { chassert(!ddl_guard); diff --git a/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.reference b/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.reference new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.reference @@ -0,0 +1 @@ +1 diff --git a/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh b/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh new file mode 100755 index 00000000000..da0ca5183ce --- /dev/null +++ b/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Tags: replica + +# CREATE AS SELECT for Replicated database is broken (https://github.com/ClickHouse/ClickHouse/issues/35408). +# This should be fixed and this test should eventually be deleted. + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +${CLICKHOUSE_CLIENT} --allow_experimental_database_replicated=1 --query "CREATE DATABASE ${CLICKHOUSE_DATABASE}_db engine = Replicated('/clickhouse/databases/${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}/${CLICKHOUSE_DATABASE}_db', '{shard}', '{replica}')" +${CLICKHOUSE_CLIENT} --query "CREATE TABLE ${CLICKHOUSE_DATABASE}_db.test (id UInt64) ENGINE = MergeTree() ORDER BY id AS SELECT 1" |& grep -cm1 "SUPPORT_IS_DISABLED" +${CLICKHOUSE_CLIENT} --query "DROP DATABASE ${CLICKHOUSE_DATABASE}_db" From 2cd7762a1a859e4a08431d58d0f68ad069142b4e Mon Sep 17 00:00:00 2001 From: Chen Lixiang Date: Wed, 13 Dec 2023 13:35:02 +0800 Subject: [PATCH 041/253] fix long test issue --- .../00753_system_columns_and_system_tables_long.reference | 2 +- .../0_stateless/00753_system_columns_and_system_tables_long.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.reference b/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.reference index f834bcede27..dd5860ae491 100644 --- a/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.reference +++ b/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.reference @@ -53,4 +53,4 @@ Check total_bytes/total_rows for Join 1 100 Check total_uncompressed_bytes/total_bytes/total_rows for Materialized views 0 0 0 -117 397 1 +1 1 1 diff --git a/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql b/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql index c21f5c12b2f..51818228913 100644 --- a/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql +++ b/tests/queries/0_stateless/00753_system_columns_and_system_tables_long.sql @@ -155,6 +155,6 @@ SELECT 'Check total_uncompressed_bytes/total_bytes/total_rows for Materialized v CREATE MATERIALIZED VIEW check_system_tables_mv ENGINE = MergeTree() ORDER BY name2 AS SELECT name1, name2, name3 FROM check_system_tables; SELECT total_bytes_uncompressed, total_bytes, total_rows FROM system.tables WHERE name = 'check_system_tables_mv' AND database = currentDatabase(); INSERT INTO check_system_tables VALUES (1, 1, 1); -SELECT total_bytes_uncompressed, total_bytes, total_rows FROM system.tables WHERE name = 'check_system_tables_mv' AND database = currentDatabase(); +SELECT total_bytes_uncompressed > 0, total_bytes > 0, total_rows FROM system.tables WHERE name = 'check_system_tables_mv' AND database = currentDatabase(); DROP TABLE check_system_tables_mv; DROP TABLE check_system_tables; From 398499d25393026489721b747247122e4044a9e7 Mon Sep 17 00:00:00 2001 From: vdimir Date: Tue, 5 Dec 2023 18:29:18 +0000 Subject: [PATCH 042/253] Support SHARDS for HashedArrayDictionary --- docs/en/sql-reference/dictionaries/index.md | 8 +- src/Dictionaries/HashedArrayDictionary.cpp | 343 +++++++++++------- src/Dictionaries/HashedArrayDictionary.h | 112 ++++-- src/Dictionaries/HashedDictionary.h | 9 +- .../HashedDictionaryParallelLoader.h | 7 +- ...shed_array_dictionary_simple_key.reference | 66 ++++ ...hashed_array_dictionary_simple_key.sql.j2} | 19 +- ...hed_array_dictionary_complex_key.reference | 56 +++ ...ashed_array_dictionary_complex_key.sql.j2} | 12 +- ...ictionary_hierarchical_functions.reference | 35 ++ ..._dictionary_hierarchical_functions.sql.j2} | 6 +- ...dictionaries_nullable_parent_key.reference | 36 ++ ...l_dictionaries_nullable_parent_key.sql.j2} | 6 +- .../02760_dictionaries_memory.sql.j2 | 1 + 14 files changed, 530 insertions(+), 186 deletions(-) rename tests/queries/0_stateless/{02098_hashed_array_dictionary_simple_key.sql => 02098_hashed_array_dictionary_simple_key.sql.j2} (95%) rename tests/queries/0_stateless/{02099_hashed_array_dictionary_complex_key.sql => 02099_hashed_array_dictionary_complex_key.sql.j2} (96%) rename tests/queries/0_stateless/{02311_hashed_array_dictionary_hierarchical_functions.sql => 02311_hashed_array_dictionary_hierarchical_functions.sql.j2} (91%) rename tests/queries/0_stateless/{02316_hierarchical_dictionaries_nullable_parent_key.sql => 02316_hierarchical_dictionaries_nullable_parent_key.sql.j2} (97%) diff --git a/docs/en/sql-reference/dictionaries/index.md b/docs/en/sql-reference/dictionaries/index.md index 4f021b25809..9f86aaf2502 100644 --- a/docs/en/sql-reference/dictionaries/index.md +++ b/docs/en/sql-reference/dictionaries/index.md @@ -394,7 +394,7 @@ Configuration example: or ``` sql -LAYOUT(HASHED_ARRAY()) +LAYOUT(HASHED_ARRAY([SHARDS 1])) ``` ### complex_key_hashed_array @@ -412,7 +412,7 @@ Configuration example: or ``` sql -LAYOUT(COMPLEX_KEY_HASHED_ARRAY()) +LAYOUT(COMPLEX_KEY_HASHED_ARRAY([SHARDS 1])) ``` ### range_hashed {#range_hashed} @@ -2415,8 +2415,8 @@ clickhouse client \ --secure \ --password MY_PASSWORD \ --query " - INSERT INTO regexp_dictionary_source_table - SELECT * FROM input ('id UInt64, parent_id UInt64, regexp String, keys Array(String), values Array(String)') + INSERT INTO regexp_dictionary_source_table + SELECT * FROM input ('id UInt64, parent_id UInt64, regexp String, keys Array(String), values Array(String)') FORMAT CSV" < regexp_dict.csv ``` diff --git a/src/Dictionaries/HashedArrayDictionary.cpp b/src/Dictionaries/HashedArrayDictionary.cpp index 21016025d96..4c9ff8abe80 100644 --- a/src/Dictionaries/HashedArrayDictionary.cpp +++ b/src/Dictionaries/HashedArrayDictionary.cpp @@ -20,17 +20,19 @@ namespace ErrorCodes { extern const int BAD_ARGUMENTS; extern const int DICTIONARY_IS_EMPTY; + extern const int LOGICAL_ERROR; extern const int UNSUPPORTED_METHOD; } -template -HashedArrayDictionary::HashedArrayDictionary( +template +HashedArrayDictionary::HashedArrayDictionary( const StorageID & dict_id_, const DictionaryStructure & dict_struct_, DictionarySourcePtr source_ptr_, const HashedArrayDictionaryStorageConfiguration & configuration_, BlockPtr update_field_loaded_block_) : IDictionary(dict_id_) + , log(&Poco::Logger::get("HashedArrayDictionary")) , dict_struct(dict_struct_) , source_ptr(std::move(source_ptr_)) , configuration(configuration_) @@ -42,8 +44,8 @@ HashedArrayDictionary::HashedArrayDictionary( calculateBytesAllocated(); } -template -ColumnPtr HashedArrayDictionary::getColumn( +template +ColumnPtr HashedArrayDictionary::getColumn( const std::string & attribute_name, const DataTypePtr & result_type, const Columns & key_columns, @@ -67,8 +69,8 @@ ColumnPtr HashedArrayDictionary::getColumn( return getAttributeColumn(attribute, dictionary_attribute, keys_size, default_values_column, extractor); } -template -Columns HashedArrayDictionary::getColumns( +template +Columns HashedArrayDictionary::getColumns( const Strings & attribute_names, const DataTypes & result_types, const Columns & key_columns, @@ -83,7 +85,7 @@ Columns HashedArrayDictionary::getColumns( const size_t keys_size = extractor.getKeysSize(); - PaddedPODArray key_index_to_element_index; + KeyIndexToElementIndex key_index_to_element_index; /** Optimization for multiple attributes. * For each key save element index in key_index_to_element_index array. @@ -92,7 +94,6 @@ Columns HashedArrayDictionary::getColumns( */ if (attribute_names.size() > 1) { - const auto & key_attribute_container = key_attribute.container; size_t keys_found = 0; key_index_to_element_index.resize(keys_size); @@ -100,15 +101,23 @@ Columns HashedArrayDictionary::getColumns( for (size_t key_index = 0; key_index < keys_size; ++key_index) { auto key = extractor.extractCurrentKey(); + auto shard = getShard(key); + const auto & key_attribute_container = key_attribute.containers[shard]; auto it = key_attribute_container.find(key); if (it == key_attribute_container.end()) { - key_index_to_element_index[key_index] = -1; + if constexpr (sharded) + key_index_to_element_index[key_index] = std::make_pair(-1, shard); + else + key_index_to_element_index[key_index] = -1; } else { - key_index_to_element_index[key_index] = it->getMapped(); + if constexpr (sharded) + key_index_to_element_index[key_index] = std::make_pair(it->getMapped(), shard); + else + key_index_to_element_index[key_index] = it->getMapped(); ++keys_found; } @@ -147,8 +156,8 @@ Columns HashedArrayDictionary::getColumns( return result_columns; } -template -ColumnUInt8::Ptr HashedArrayDictionary::hasKeys(const Columns & key_columns, const DataTypes & key_types) const +template +ColumnUInt8::Ptr HashedArrayDictionary::hasKeys(const Columns & key_columns, const DataTypes & key_types) const { if (dictionary_key_type == DictionaryKeyType::Complex) dict_struct.validateKeyTypes(key_types); @@ -166,8 +175,10 @@ ColumnUInt8::Ptr HashedArrayDictionary::hasKeys(const Colum for (size_t requested_key_index = 0; requested_key_index < keys_size; ++requested_key_index) { auto requested_key = extractor.extractCurrentKey(); + auto shard = getShard(requested_key); + const auto & key_attribute_container = key_attribute.containers[shard]; - out[requested_key_index] = key_attribute.container.find(requested_key) != key_attribute.container.end(); + out[requested_key_index] = key_attribute_container.find(requested_key) != key_attribute_container.end(); keys_found += out[requested_key_index]; extractor.rollbackCurrentKey(); @@ -179,8 +190,8 @@ ColumnUInt8::Ptr HashedArrayDictionary::hasKeys(const Colum return result; } -template -ColumnPtr HashedArrayDictionary::getHierarchy(ColumnPtr key_column [[maybe_unused]], const DataTypePtr &) const +template +ColumnPtr HashedArrayDictionary::getHierarchy(ColumnPtr key_column [[maybe_unused]], const DataTypePtr &) const { if constexpr (dictionary_key_type == DictionaryKeyType::Simple) { @@ -197,16 +208,20 @@ ColumnPtr HashedArrayDictionary::getHierarchy(ColumnPtr key if (!dictionary_attribute.null_value.isNull()) null_value = dictionary_attribute.null_value.get(); - const auto & key_attribute_container = key_attribute.container; - const AttributeContainerType & parent_keys_container = std::get>(hierarchical_attribute.container); - auto is_key_valid_func = [&](auto & key) { return key_attribute_container.find(key) != key_attribute_container.end(); }; + auto is_key_valid_func = [&, this](auto & key) + { + const auto & key_attribute_container = key_attribute.containers[getShard(key)]; + return key_attribute_container.find(key) != key_attribute_container.end(); + }; size_t keys_found = 0; - auto get_parent_func = [&](auto & hierarchy_key) + auto get_parent_func = [&, this](auto & hierarchy_key) { std::optional result; + auto shard = getShard(hierarchy_key); + const auto & key_attribute_container = key_attribute.containers[shard]; auto it = key_attribute_container.find(hierarchy_key); @@ -215,8 +230,9 @@ ColumnPtr HashedArrayDictionary::getHierarchy(ColumnPtr key size_t key_index = it->getMapped(); - if (unlikely(hierarchical_attribute.is_index_null) && (*hierarchical_attribute.is_index_null)[key_index]) + if (unlikely(hierarchical_attribute.is_index_null) && (*hierarchical_attribute.is_index_null)[shard][key_index]) return result; + const auto & parent_keys_container = std::get>(hierarchical_attribute.containers)[shard]; UInt64 parent_key = parent_keys_container[key_index]; if (null_value && *null_value == parent_key) @@ -241,8 +257,8 @@ ColumnPtr HashedArrayDictionary::getHierarchy(ColumnPtr key } } -template -ColumnUInt8::Ptr HashedArrayDictionary::isInHierarchy( +template +ColumnUInt8::Ptr HashedArrayDictionary::isInHierarchy( ColumnPtr key_column [[maybe_unused]], ColumnPtr in_key_column [[maybe_unused]], const DataTypePtr &) const @@ -265,16 +281,20 @@ ColumnUInt8::Ptr HashedArrayDictionary::isInHierarchy( if (!dictionary_attribute.null_value.isNull()) null_value = dictionary_attribute.null_value.get(); - const auto & key_attribute_container = key_attribute.container; - const AttributeContainerType & parent_keys_container = std::get>(hierarchical_attribute.container); - auto is_key_valid_func = [&](auto & key) { return key_attribute_container.find(key) != key_attribute_container.end(); }; + auto is_key_valid_func = [&](auto & key) + { + const auto & key_attribute_container = key_attribute.containers[getShard(key)]; + return key_attribute_container.find(key) != key_attribute_container.end(); + }; size_t keys_found = 0; auto get_parent_func = [&](auto & hierarchy_key) { std::optional result; + auto shard = getShard(hierarchy_key); + const auto & key_attribute_container = key_attribute.containers[shard]; auto it = key_attribute_container.find(hierarchy_key); @@ -283,9 +303,10 @@ ColumnUInt8::Ptr HashedArrayDictionary::isInHierarchy( size_t key_index = it->getMapped(); - if (unlikely(hierarchical_attribute.is_index_null) && (*hierarchical_attribute.is_index_null)[key_index]) + if (unlikely(hierarchical_attribute.is_index_null) && (*hierarchical_attribute.is_index_null)[shard][key_index]) return result; + const auto & parent_keys_container = std::get>(hierarchical_attribute.containers)[shard]; UInt64 parent_key = parent_keys_container[key_index]; if (null_value && *null_value == parent_key) return result; @@ -309,8 +330,8 @@ ColumnUInt8::Ptr HashedArrayDictionary::isInHierarchy( } } -template -DictionaryHierarchicalParentToChildIndexPtr HashedArrayDictionary::getHierarchicalIndex() const +template +DictionaryHierarchicalParentToChildIndexPtr HashedArrayDictionary::getHierarchicalIndex() const { if constexpr (dictionary_key_type == DictionaryKeyType::Simple) { @@ -318,33 +339,35 @@ DictionaryHierarchicalParentToChildIndexPtr HashedArrayDictionary & parent_keys_container = std::get>(hierarchical_attribute.container); - - const auto & key_attribute_container = key_attribute.container; - - HashMap index_to_key; - index_to_key.reserve(key_attribute.container.size()); - - for (auto & [key, value] : key_attribute_container) - index_to_key[value] = key; DictionaryHierarchicalParentToChildIndex::ParentToChildIndex parent_to_child; - parent_to_child.reserve(index_to_key.size()); - - size_t parent_keys_container_size = parent_keys_container.size(); - for (size_t i = 0; i < parent_keys_container_size; ++i) + for (size_t shard = 0; shard < configuration.shards; ++shard) { - if (unlikely(hierarchical_attribute.is_index_null) && (*hierarchical_attribute.is_index_null)[i]) - continue; + HashMap index_to_key; + index_to_key.reserve(element_counts[shard]); - const auto * it = index_to_key.find(i); - if (it == index_to_key.end()) - continue; + for (auto & [key, value] : key_attribute.containers[shard]) + index_to_key[value] = key; - auto child_key = it->getMapped(); - auto parent_key = parent_keys_container[i]; - parent_to_child[parent_key].emplace_back(child_key); + parent_to_child.reserve(parent_to_child.size() + index_to_key.size()); + + const auto & hierarchical_attribute = attributes[hierarchical_attribute_index]; + const auto & parent_keys_container = std::get>(hierarchical_attribute.containers)[shard]; + + size_t parent_keys_container_size = parent_keys_container.size(); + for (size_t i = 0; i < parent_keys_container_size; ++i) + { + if (unlikely(hierarchical_attribute.is_index_null) && (*hierarchical_attribute.is_index_null)[shard][i]) + continue; + + const auto * it = index_to_key.find(i); + if (it == index_to_key.end()) + continue; + + auto child_key = it->getMapped(); + auto parent_key = parent_keys_container[i]; + parent_to_child[parent_key].emplace_back(child_key); + } } return std::make_shared(parent_to_child); @@ -355,8 +378,8 @@ DictionaryHierarchicalParentToChildIndexPtr HashedArrayDictionary -ColumnPtr HashedArrayDictionary::getDescendants( +template +ColumnPtr HashedArrayDictionary::getDescendants( ColumnPtr key_column [[maybe_unused]], const DataTypePtr &, size_t level [[maybe_unused]], @@ -381,8 +404,8 @@ ColumnPtr HashedArrayDictionary::getDescendants( } } -template -void HashedArrayDictionary::createAttributes() +template +void HashedArrayDictionary::createAttributes() { const auto size = dict_struct.attributes.size(); attributes.reserve(size); @@ -395,17 +418,24 @@ void HashedArrayDictionary::createAttributes() using AttributeType = typename Type::AttributeType; using ValueType = DictionaryValueType; - auto is_index_null = dictionary_attribute.is_nullable ? std::make_optional>() : std::optional>{}; - Attribute attribute{dictionary_attribute.underlying_type, AttributeContainerType(), std::move(is_index_null)}; + auto is_index_null = dictionary_attribute.is_nullable ? std::make_optional>(configuration.shards) : std::nullopt; + Attribute attribute{dictionary_attribute.underlying_type, AttributeContainerShardsType(configuration.shards), std::move(is_index_null)}; attributes.emplace_back(std::move(attribute)); }; callOnDictionaryAttributeType(dictionary_attribute.underlying_type, type_call); } + + key_attribute.containers.resize(configuration.shards); + element_counts.resize(configuration.shards); + + string_arenas.resize(configuration.shards); + for (auto & arena : string_arenas) + arena = std::make_unique(); } -template -void HashedArrayDictionary::updateData() +template +void HashedArrayDictionary::updateData() { if (!update_field_loaded_block || update_field_loaded_block->rows() == 0) { @@ -445,13 +475,17 @@ void HashedArrayDictionary::updateData() if (update_field_loaded_block) { resize(update_field_loaded_block->rows()); - blockToAttributes(*update_field_loaded_block.get()); + DictionaryKeysArenaHolder arena_holder; + blockToAttributes(*update_field_loaded_block.get(), arena_holder, /* shard = */ 0); } } -template -void HashedArrayDictionary::blockToAttributes(const Block & block [[maybe_unused]]) +template +void HashedArrayDictionary::blockToAttributes(const Block & block, DictionaryKeysArenaHolder & arena_holder, size_t shard) { + if (unlikely(shard >= configuration.shards)) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Shard number {} is out of range: 0..{}", shard, configuration.shards - 1); + size_t skip_keys_size_offset = dict_struct.getKeysSize(); Columns key_columns; @@ -461,7 +495,6 @@ void HashedArrayDictionary::blockToAttributes(const Block & for (size_t i = 0; i < skip_keys_size_offset; ++i) key_columns.emplace_back(block.safeGetByPosition(i).column); - DictionaryKeysArenaHolder arena_holder; DictionaryKeysExtractor keys_extractor(key_columns, arena_holder.getComplexKeyArena()); const size_t keys_size = keys_extractor.getKeysSize(); @@ -471,18 +504,18 @@ void HashedArrayDictionary::blockToAttributes(const Block & { auto key = keys_extractor.extractCurrentKey(); - auto it = key_attribute.container.find(key); + auto it = key_attribute.containers[shard].find(key); - if (it != key_attribute.container.end()) + if (it != key_attribute.containers[shard].end()) { keys_extractor.rollbackCurrentKey(); continue; } if constexpr (std::is_same_v) - key = copyStringInArena(string_arena, key); + key = copyStringInArena(*string_arenas[shard], key); - key_attribute.container.insert({key, element_count}); + key_attribute.containers[shard].insert({key, element_counts[shard]}); for (size_t attribute_index = 0; attribute_index < attributes.size(); ++attribute_index) { @@ -498,16 +531,16 @@ void HashedArrayDictionary::blockToAttributes(const Block & using AttributeType = typename Type::AttributeType; using AttributeValueType = DictionaryValueType; - auto & attribute_container = std::get>(attribute.container); + auto & attribute_container = std::get>(attribute.containers)[shard]; attribute_container.emplace_back(); if (attribute_is_nullable) { - attribute.is_index_null->emplace_back(); + (*attribute.is_index_null)[shard].emplace_back(); if (column_value_to_insert.isNull()) { - (*attribute.is_index_null).back() = true; + (*attribute.is_index_null)[shard].back() = true; return; } } @@ -515,7 +548,7 @@ void HashedArrayDictionary::blockToAttributes(const Block & if constexpr (std::is_same_v) { String & value_to_insert = column_value_to_insert.get(); - StringRef string_in_arena_reference = copyStringInArena(string_arena, value_to_insert); + StringRef string_in_arena_reference = copyStringInArena(*string_arenas[shard], value_to_insert); attribute_container.back() = string_in_arena_reference; } else @@ -528,23 +561,29 @@ void HashedArrayDictionary::blockToAttributes(const Block & callOnDictionaryAttributeType(attribute.type, type_call); } - ++element_count; + ++element_counts[shard]; + ++total_element_count; keys_extractor.rollbackCurrentKey(); } } -template -void HashedArrayDictionary::resize(size_t total_rows) +template +void HashedArrayDictionary::resize(size_t total_rows) { if (unlikely(!total_rows)) return; - key_attribute.container.reserve(total_rows); + /// In multi shards configuration it is pointless. + if constexpr (sharded) + return; + + for (auto & container : key_attribute.containers) + container.reserve(total_rows); } -template +template template -ColumnPtr HashedArrayDictionary::getAttributeColumn( +ColumnPtr HashedArrayDictionary::getAttributeColumn( const Attribute & attribute, const DictionaryAttribute & dictionary_attribute, size_t keys_size, @@ -638,16 +677,14 @@ ColumnPtr HashedArrayDictionary::getAttributeColumn( return result; } -template +template template -void HashedArrayDictionary::getItemsImpl( +void HashedArrayDictionary::getItemsImpl( const Attribute & attribute, DictionaryKeysExtractor & keys_extractor, ValueSetter && set_value [[maybe_unused]], DefaultValueExtractor & default_value_extractor) const { - const auto & key_attribute_container = key_attribute.container; - const auto & attribute_container = std::get>(attribute.container); const size_t keys_size = keys_extractor.getKeysSize(); size_t keys_found = 0; @@ -655,6 +692,9 @@ void HashedArrayDictionary::getItemsImpl( for (size_t key_index = 0; key_index < keys_size; ++key_index) { auto key = keys_extractor.extractCurrentKey(); + auto shard = getShard(key); + const auto & key_attribute_container = key_attribute.containers[shard]; + const auto & attribute_container = std::get>(attribute.containers)[shard]; const auto it = key_attribute_container.find(key); @@ -665,7 +705,7 @@ void HashedArrayDictionary::getItemsImpl( const auto & element = attribute_container[element_index]; if constexpr (is_nullable) - set_value(key_index, element, (*attribute.is_index_null)[element_index]); + set_value(key_index, element, (*attribute.is_index_null)[shard][element_index]); else set_value(key_index, element, false); @@ -686,28 +726,39 @@ void HashedArrayDictionary::getItemsImpl( found_count.fetch_add(keys_found, std::memory_order_relaxed); } -template +template template -void HashedArrayDictionary::getItemsImpl( +void HashedArrayDictionary::getItemsImpl( const Attribute & attribute, - const PaddedPODArray & key_index_to_element_index, + const KeyIndexToElementIndex & key_index_to_element_index, ValueSetter && set_value, DefaultValueExtractor & default_value_extractor) const { - const auto & attribute_container = std::get>(attribute.container); const size_t keys_size = key_index_to_element_index.size(); + size_t shard = 0; for (size_t key_index = 0; key_index < keys_size; ++key_index) { - bool key_exists = key_index_to_element_index[key_index] != -1; - - if (key_exists) + ssize_t element_index; + if constexpr (sharded) { - size_t element_index = static_cast(key_index_to_element_index[key_index]); - const auto & element = attribute_container[element_index]; + element_index = key_index_to_element_index[key_index].first; + shard = key_index_to_element_index[key_index].second; + } + else + { + element_index = key_index_to_element_index[key_index]; + } + + if (element_index != -1) + { + const auto & attribute_container = std::get>(attribute.containers)[shard]; + + size_t found_element_index = static_cast(element_index); + const auto & element = attribute_container[found_element_index]; if constexpr (is_nullable) - set_value(key_index, element, (*attribute.is_index_null)[element_index]); + set_value(key_index, element, (*attribute.is_index_null)[shard][found_element_index]); else set_value(key_index, element, false); } @@ -721,13 +772,17 @@ void HashedArrayDictionary::getItemsImpl( } } -template -void HashedArrayDictionary::loadData() +template +void HashedArrayDictionary::loadData() { if (!source_ptr->hasUpdateField()) { - QueryPipeline pipeline; - pipeline = QueryPipeline(source_ptr->loadAll()); + + std::optional parallel_loader; + if constexpr (sharded) + parallel_loader.emplace(*this); + + QueryPipeline pipeline(source_ptr->loadAll()); DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor); UInt64 pull_time_microseconds = 0; @@ -751,10 +806,22 @@ void HashedArrayDictionary::loadData() Stopwatch watch_process; resize(total_rows); - blockToAttributes(block); + + if (parallel_loader) + { + parallel_loader->addBlock(block); + } + else + { + DictionaryKeysArenaHolder arena_holder; + blockToAttributes(block, arena_holder, /* shard = */ 0); + } process_time_microseconds += watch_process.elapsedMicroseconds(); } + if (parallel_loader) + parallel_loader->finish(); + LOG_DEBUG(&Poco::Logger::get("HashedArrayDictionary"), "Finished {}reading {} blocks with {} rows from pipeline in {:.2f} sec and inserted into hashtable in {:.2f} sec", configuration.use_async_executor ? "asynchronous " : "", @@ -765,14 +832,14 @@ void HashedArrayDictionary::loadData() updateData(); } - if (configuration.require_nonempty && 0 == element_count) + if (configuration.require_nonempty && 0 == total_element_count) throw Exception(ErrorCodes::DICTIONARY_IS_EMPTY, "{}: dictionary source is empty and 'require_nonempty' property is set.", getFullName()); } -template -void HashedArrayDictionary::buildHierarchyParentToChildIndexIfNeeded() +template +void HashedArrayDictionary::buildHierarchyParentToChildIndexIfNeeded() { if (!dict_struct.hierarchical_attribute_index) return; @@ -781,12 +848,13 @@ void HashedArrayDictionary::buildHierarchyParentToChildInde hierarchical_index = getHierarchicalIndex(); } -template -void HashedArrayDictionary::calculateBytesAllocated() +template +void HashedArrayDictionary::calculateBytesAllocated() { bytes_allocated += attributes.size() * sizeof(attributes.front()); - bytes_allocated += key_attribute.container.size(); + for (const auto & container : key_attribute.containers) + bytes_allocated += container.size(); for (auto & attribute : attributes) { @@ -796,26 +864,29 @@ void HashedArrayDictionary::calculateBytesAllocated() using AttributeType = typename Type::AttributeType; using ValueType = DictionaryValueType; - const auto & container = std::get>(attribute.container); - bytes_allocated += sizeof(AttributeContainerType); - - if constexpr (std::is_same_v) + for (const auto & container : std::get>(attribute.containers)) { - /// It is not accurate calculations - bytes_allocated += sizeof(Array) * container.size(); - } - else - { - bytes_allocated += container.allocated_bytes(); - } + bytes_allocated += sizeof(AttributeContainerType); - bucket_count = container.capacity(); + if constexpr (std::is_same_v) + { + /// It is not accurate calculations + bytes_allocated += sizeof(Array) * container.size(); + } + else + { + bytes_allocated += container.allocated_bytes(); + } + + bucket_count = container.capacity(); + } }; callOnDictionaryAttributeType(attribute.type, type_call); if (attribute.is_index_null.has_value()) - bytes_allocated += (*attribute.is_index_null).size(); + for (const auto & container : attribute.is_index_null.value()) + bytes_allocated += container.size(); } if (update_field_loaded_block) @@ -826,18 +897,19 @@ void HashedArrayDictionary::calculateBytesAllocated() hierarchical_index_bytes_allocated = hierarchical_index->getSizeInBytes(); bytes_allocated += hierarchical_index_bytes_allocated; } - - bytes_allocated += string_arena.allocatedBytes(); + for (const auto & string_arena : string_arenas) + bytes_allocated += string_arena->allocatedBytes(); } -template -Pipe HashedArrayDictionary::read(const Names & column_names, size_t max_block_size, size_t num_streams) const +template +Pipe HashedArrayDictionary::read(const Names & column_names, size_t max_block_size, size_t num_streams) const { PaddedPODArray keys; - keys.reserve(key_attribute.container.size()); + keys.reserve(total_element_count); - for (auto & [key, _] : key_attribute.container) - keys.emplace_back(key); + for (const auto & container : key_attribute.containers) + for (auto & [key, _] : container) + keys.emplace_back(key); ColumnsWithTypeAndName key_columns; @@ -858,8 +930,10 @@ Pipe HashedArrayDictionary::read(const Names & column_names return result; } -template class HashedArrayDictionary; -template class HashedArrayDictionary; +template class HashedArrayDictionary; +template class HashedArrayDictionary; +template class HashedArrayDictionary; +template class HashedArrayDictionary; void registerDictionaryArrayHashed(DictionaryFactory & factory) { @@ -886,7 +960,14 @@ void registerDictionaryArrayHashed(DictionaryFactory & factory) const DictionaryLifetime dict_lifetime{config, config_prefix + ".lifetime"}; const bool require_nonempty = config.getBool(config_prefix + ".require_nonempty", false); - HashedArrayDictionaryStorageConfiguration configuration{require_nonempty, dict_lifetime}; + std::string dictionary_layout_name = dictionary_key_type == DictionaryKeyType::Simple ? "hashed_array" : "complex_key_hashed_array"; + std::string dictionary_layout_prefix = ".layout." + dictionary_layout_name; + + Int64 shards = config.getInt(config_prefix + dictionary_layout_prefix + ".shards", 1); + if (shards <= 0 || 128 < shards) + throw Exception(ErrorCodes::BAD_ARGUMENTS,"{}: SHARDS parameter should be within [1, 128]", full_name); + + HashedArrayDictionaryStorageConfiguration configuration{require_nonempty, dict_lifetime, static_cast(shards)}; ContextMutablePtr context = copyContextAndApplySettingsFromDictionaryConfig(global_context, config, config_prefix); const auto & settings = context->getSettingsRef(); @@ -895,9 +976,17 @@ void registerDictionaryArrayHashed(DictionaryFactory & factory) configuration.use_async_executor = clickhouse_source && clickhouse_source->isLocal() && settings.dictionary_use_async_executor; if (dictionary_key_type == DictionaryKeyType::Simple) - return std::make_unique>(dict_id, dict_struct, std::move(source_ptr), configuration); + { + if (shards > 1) + return std::make_unique>(dict_id, dict_struct, std::move(source_ptr), configuration); + return std::make_unique>(dict_id, dict_struct, std::move(source_ptr), configuration); + } else - return std::make_unique>(dict_id, dict_struct, std::move(source_ptr), configuration); + { + if (shards > 1) + return std::make_unique>(dict_id, dict_struct, std::move(source_ptr), configuration); + return std::make_unique>(dict_id, dict_struct, std::move(source_ptr), configuration); + } }; factory.registerLayout("hashed_array", diff --git a/src/Dictionaries/HashedArrayDictionary.h b/src/Dictionaries/HashedArrayDictionary.h index 3b9446e4e8f..606008ce921 100644 --- a/src/Dictionaries/HashedArrayDictionary.h +++ b/src/Dictionaries/HashedArrayDictionary.h @@ -13,6 +13,7 @@ #include #include #include +#include /** This dictionary stores all attributes in arrays. * Key is stored in hash table and value is index into attribute array. @@ -25,12 +26,17 @@ struct HashedArrayDictionaryStorageConfiguration { const bool require_nonempty; const DictionaryLifetime lifetime; + size_t shards = 1; + size_t shard_load_queue_backlog = 10000; bool use_async_executor = false; }; -template +template class HashedArrayDictionary final : public IDictionary { + using DictionaryParallelLoaderType = HashedDictionaryImpl::HashedDictionaryParallelLoader>; + friend class HashedDictionaryImpl::HashedDictionaryParallelLoader>; + public: using KeyType = std::conditional_t; @@ -63,13 +69,13 @@ public: double getHitRate() const override { return 1.0; } - size_t getElementCount() const override { return element_count; } + size_t getElementCount() const override { return total_element_count; } - double getLoadFactor() const override { return static_cast(element_count) / bucket_count; } + double getLoadFactor() const override { return static_cast(total_element_count) / bucket_count; } std::shared_ptr clone() const override { - return std::make_shared>(getDictionaryID(), dict_struct, source_ptr->clone(), configuration, update_field_loaded_block); + return std::make_shared>(getDictionaryID(), dict_struct, source_ptr->clone(), configuration, update_field_loaded_block); } DictionarySourcePtr getSource() const override { return source_ptr; } @@ -132,50 +138,54 @@ private: template using AttributeContainerType = std::conditional_t, std::vector, PaddedPODArray>; + template + using AttributeContainerShardsType = std::vector>; + struct Attribute final { AttributeUnderlyingType type; std::variant< - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType, - AttributeContainerType> - container; + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType, + AttributeContainerShardsType> + containers; - std::optional> is_index_null; + /// One container per shard + using RowsMask = std::vector; + std::optional> is_index_null; }; struct KeyAttribute final { - - KeyContainerType container; - + /// One container per shard + std::vector containers; }; void createAttributes(); - void blockToAttributes(const Block & block); + void blockToAttributes(const Block & block, DictionaryKeysArenaHolder & arena_holder, size_t shard); void updateData(); @@ -185,6 +195,22 @@ private: void calculateBytesAllocated(); + UInt64 getShard(UInt64 key) const + { + if constexpr (!sharded) + return 0; + /// NOTE: function here should not match with the DefaultHash<> since + /// it used for the HashMap/sparse_hash_map. + return intHashCRC32(key) % configuration.shards; + } + + UInt64 getShard(StringRef key) const + { + if constexpr (!sharded) + return 0; + return StringRefHash()(key) % configuration.shards; + } + template ColumnPtr getAttributeColumn( const Attribute & attribute, @@ -200,10 +226,13 @@ private: ValueSetter && set_value, DefaultValueExtractor & default_value_extractor) const; + + using KeyIndexToElementIndex = std::conditional_t>, PaddedPODArray>; + template void getItemsImpl( const Attribute & attribute, - const PaddedPODArray & key_index_to_element_index, + const KeyIndexToElementIndex & key_index_to_element_index, ValueSetter && set_value, DefaultValueExtractor & default_value_extractor) const; @@ -215,6 +244,8 @@ private: void resize(size_t total_rows); + Poco::Logger * log; + const DictionaryStructure dict_struct; const DictionarySourcePtr source_ptr; const HashedArrayDictionaryStorageConfiguration configuration; @@ -225,17 +256,20 @@ private: size_t bytes_allocated = 0; size_t hierarchical_index_bytes_allocated = 0; - size_t element_count = 0; + std::atomic total_element_count = 0; + std::vector element_counts; size_t bucket_count = 0; mutable std::atomic query_count{0}; mutable std::atomic found_count{0}; BlockPtr update_field_loaded_block; - Arena string_arena; + std::vector> string_arenas; DictionaryHierarchicalParentToChildIndexPtr hierarchical_index; }; -extern template class HashedArrayDictionary; -extern template class HashedArrayDictionary; +extern template class HashedArrayDictionary; +extern template class HashedArrayDictionary; +extern template class HashedArrayDictionary; +extern template class HashedArrayDictionary; } diff --git a/src/Dictionaries/HashedDictionary.h b/src/Dictionaries/HashedDictionary.h index 376637189dd..8009ffab80a 100644 --- a/src/Dictionaries/HashedDictionary.h +++ b/src/Dictionaries/HashedDictionary.h @@ -71,7 +71,8 @@ struct HashedDictionaryConfiguration template class HashedDictionary final : public IDictionary { - friend class HashedDictionaryParallelLoader; + using DictionaryParallelLoaderType = HashedDictionaryParallelLoader>; + friend class HashedDictionaryParallelLoader>; public: using KeyType = std::conditional_t; @@ -987,7 +988,7 @@ void HashedDictionary::getItemsImpl( auto key = keys_extractor.extractCurrentKey(); auto shard = getShard(key); - const auto & container = attribute_containers[getShard(key)]; + const auto & container = attribute_containers[shard]; const auto it = container.find(key); if (it != container.end()) @@ -1020,11 +1021,11 @@ void HashedDictionary::loadData() { if (!source_ptr->hasUpdateField()) { - std::optional> parallel_loader; + std::optional parallel_loader; if constexpr (sharded) parallel_loader.emplace(*this); - QueryPipeline pipeline = QueryPipeline(source_ptr->loadAll()); + QueryPipeline pipeline(source_ptr->loadAll()); DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor); Block block; diff --git a/src/Dictionaries/HashedDictionaryParallelLoader.h b/src/Dictionaries/HashedDictionaryParallelLoader.h index b52158c7fcb..907a987555e 100644 --- a/src/Dictionaries/HashedDictionaryParallelLoader.h +++ b/src/Dictionaries/HashedDictionaryParallelLoader.h @@ -38,13 +38,12 @@ namespace DB::HashedDictionaryImpl { /// Implementation parallel dictionary load for SHARDS -template +template class HashedDictionaryParallelLoader : public boost::noncopyable { - using HashedDictionary = HashedDictionary; public: - explicit HashedDictionaryParallelLoader(HashedDictionary & dictionary_) + explicit HashedDictionaryParallelLoader(DictionaryType & dictionary_) : dictionary(dictionary_) , shards(dictionary.configuration.shards) , pool(CurrentMetrics::HashedDictionaryThreads, CurrentMetrics::HashedDictionaryThreadsActive, CurrentMetrics::HashedDictionaryThreadsScheduled, shards) @@ -118,7 +117,7 @@ public: } private: - HashedDictionary & dictionary; + DictionaryType & dictionary; const size_t shards; ThreadPool pool; std::vector>> shards_queues; diff --git a/tests/queries/0_stateless/02098_hashed_array_dictionary_simple_key.reference b/tests/queries/0_stateless/02098_hashed_array_dictionary_simple_key.reference index 6e88bbad146..41b9ab687f8 100644 --- a/tests/queries/0_stateless/02098_hashed_array_dictionary_simple_key.reference +++ b/tests/queries/0_stateless/02098_hashed_array_dictionary_simple_key.reference @@ -26,6 +26,62 @@ select all values as input stream 0 value_0 value_second_0 1 value_1 value_second_1 2 value_2 value_second_2 +Dictionary hashed_array_dictionary_simple_key_simple_attributes +dictGet existing value +value_0 value_second_0 +value_1 value_second_1 +value_2 value_second_2 +dictGet with non existing value +value_0 value_second_0 +value_1 value_second_1 +value_2 value_second_2 +value_first_default value_second_default +dictGetOrDefault existing value +value_0 value_second_0 +value_1 value_second_1 +value_2 value_second_2 +dictGetOrDefault non existing value +value_0 value_second_0 +value_1 value_second_1 +value_2 value_second_2 +default default +dictHas +1 +1 +1 +0 +select all values as input stream +0 value_0 value_second_0 +1 value_1 value_second_1 +2 value_2 value_second_2 +Dictionary hashed_array_dictionary_simple_key_complex_attributes +dictGet existing value +value_0 value_second_0 +value_1 \N +value_2 value_second_2 +dictGet with non existing value +value_0 value_second_0 +value_1 \N +value_2 value_second_2 +value_first_default value_second_default +dictGetOrDefault existing value +value_0 value_second_0 +value_1 \N +value_2 value_second_2 +dictGetOrDefault non existing value +value_0 value_second_0 +value_1 \N +value_2 value_second_2 +default default +dictHas +1 +1 +1 +0 +select all values as input stream +0 value_0 value_second_0 +1 value_1 \N +2 value_2 value_second_2 Dictionary hashed_array_dictionary_simple_key_complex_attributes dictGet existing value value_0 value_second_0 @@ -64,3 +120,13 @@ dictGet dictGetHierarchy [1] [4,2,1] +Dictionary hashed_array_dictionary_simple_key_hierarchy +dictGet +0 +0 +1 +1 +2 +dictGetHierarchy +[1] +[4,2,1] diff --git a/tests/queries/0_stateless/02098_hashed_array_dictionary_simple_key.sql b/tests/queries/0_stateless/02098_hashed_array_dictionary_simple_key.sql.j2 similarity index 95% rename from tests/queries/0_stateless/02098_hashed_array_dictionary_simple_key.sql rename to tests/queries/0_stateless/02098_hashed_array_dictionary_simple_key.sql.j2 index 7d952223705..e5d8ad36c6d 100644 --- a/tests/queries/0_stateless/02098_hashed_array_dictionary_simple_key.sql +++ b/tests/queries/0_stateless/02098_hashed_array_dictionary_simple_key.sql.j2 @@ -11,6 +11,8 @@ INSERT INTO simple_key_simple_attributes_source_table VALUES(0, 'value_0', 'valu INSERT INTO simple_key_simple_attributes_source_table VALUES(1, 'value_1', 'value_second_1'); INSERT INTO simple_key_simple_attributes_source_table VALUES(2, 'value_2', 'value_second_2'); +{% for dictionary_config in ['', 'SHARDS 16'] -%} + DROP DICTIONARY IF EXISTS hashed_array_dictionary_simple_key_simple_attributes; CREATE DICTIONARY hashed_array_dictionary_simple_key_simple_attributes ( @@ -20,7 +22,7 @@ CREATE DICTIONARY hashed_array_dictionary_simple_key_simple_attributes ) PRIMARY KEY id SOURCE(CLICKHOUSE(TABLE 'simple_key_simple_attributes_source_table')) -LAYOUT(HASHED_ARRAY()) +LAYOUT(HASHED_ARRAY({{ dictionary_config }})) LIFETIME(MIN 1 MAX 1000) SETTINGS(dictionary_use_async_executor=1, max_threads=8); @@ -43,6 +45,7 @@ SELECT 'select all values as input stream'; SELECT * FROM hashed_array_dictionary_simple_key_simple_attributes ORDER BY id; DROP DICTIONARY hashed_array_dictionary_simple_key_simple_attributes; +{% endfor %} DROP TABLE simple_key_simple_attributes_source_table; @@ -59,6 +62,8 @@ INSERT INTO simple_key_complex_attributes_source_table VALUES(0, 'value_0', 'val INSERT INTO simple_key_complex_attributes_source_table VALUES(1, 'value_1', NULL); INSERT INTO simple_key_complex_attributes_source_table VALUES(2, 'value_2', 'value_second_2'); +{% for dictionary_config in ['', 'SHARDS 16'] -%} + DROP DICTIONARY IF EXISTS hashed_array_dictionary_simple_key_complex_attributes; CREATE DICTIONARY hashed_array_dictionary_simple_key_complex_attributes ( @@ -68,7 +73,7 @@ CREATE DICTIONARY hashed_array_dictionary_simple_key_complex_attributes ) PRIMARY KEY id SOURCE(CLICKHOUSE(TABLE 'simple_key_complex_attributes_source_table')) -LAYOUT(HASHED_ARRAY()) +LAYOUT(HASHED_ARRAY({{ dictionary_config }})) LIFETIME(MIN 1 MAX 1000); SELECT 'Dictionary hashed_array_dictionary_simple_key_complex_attributes'; @@ -90,6 +95,9 @@ SELECT 'select all values as input stream'; SELECT * FROM hashed_array_dictionary_simple_key_complex_attributes ORDER BY id; DROP DICTIONARY hashed_array_dictionary_simple_key_complex_attributes; + +{% endfor %} + DROP TABLE simple_key_complex_attributes_source_table; DROP TABLE IF EXISTS simple_key_hierarchy_table; @@ -104,6 +112,8 @@ INSERT INTO simple_key_hierarchy_table VALUES (2, 1); INSERT INTO simple_key_hierarchy_table VALUES (3, 1); INSERT INTO simple_key_hierarchy_table VALUES (4, 2); +{% for dictionary_config in ['', 'SHARDS 16'] -%} + DROP DICTIONARY IF EXISTS hashed_array_dictionary_simple_key_hierarchy; CREATE DICTIONARY hashed_array_dictionary_simple_key_hierarchy ( @@ -112,7 +122,7 @@ CREATE DICTIONARY hashed_array_dictionary_simple_key_hierarchy ) PRIMARY KEY id SOURCE(CLICKHOUSE(HOST 'localhost' PORT tcpPort() USER 'default' TABLE 'simple_key_hierarchy_table')) -LAYOUT(HASHED_ARRAY()) +LAYOUT(HASHED_ARRAY({{ dictionary_config }})) LIFETIME(MIN 1 MAX 1000); SELECT 'Dictionary hashed_array_dictionary_simple_key_hierarchy'; @@ -122,5 +132,8 @@ SELECT 'dictGetHierarchy'; SELECT dictGetHierarchy('hashed_array_dictionary_simple_key_hierarchy', toUInt64(1)); SELECT dictGetHierarchy('hashed_array_dictionary_simple_key_hierarchy', toUInt64(4)); +{% endfor %} + DROP DICTIONARY hashed_array_dictionary_simple_key_hierarchy; + DROP TABLE simple_key_hierarchy_table; diff --git a/tests/queries/0_stateless/02099_hashed_array_dictionary_complex_key.reference b/tests/queries/0_stateless/02099_hashed_array_dictionary_complex_key.reference index ec32fa72b4e..13a7548b86f 100644 --- a/tests/queries/0_stateless/02099_hashed_array_dictionary_complex_key.reference +++ b/tests/queries/0_stateless/02099_hashed_array_dictionary_complex_key.reference @@ -26,6 +26,62 @@ select all values as input stream 0 id_key_0 value_0 value_second_0 1 id_key_1 value_1 value_second_1 2 id_key_2 value_2 value_second_2 +Dictionary hashed_array_dictionary_complex_key_simple_attributes +dictGet existing value +value_0 value_second_0 +value_1 value_second_1 +value_2 value_second_2 +dictGet with non existing value +value_0 value_second_0 +value_1 value_second_1 +value_2 value_second_2 +value_first_default value_second_default +dictGetOrDefault existing value +value_0 value_second_0 +value_1 value_second_1 +value_2 value_second_2 +dictGetOrDefault non existing value +value_0 value_second_0 +value_1 value_second_1 +value_2 value_second_2 +default default +dictHas +1 +1 +1 +0 +select all values as input stream +0 id_key_0 value_0 value_second_0 +1 id_key_1 value_1 value_second_1 +2 id_key_2 value_2 value_second_2 +Dictionary hashed_array_dictionary_complex_key_complex_attributes +dictGet existing value +value_0 value_second_0 +value_1 \N +value_2 value_second_2 +dictGet with non existing value +value_0 value_second_0 +value_1 \N +value_2 value_second_2 +value_first_default value_second_default +dictGetOrDefault existing value +value_0 value_second_0 +value_1 \N +value_2 value_second_2 +dictGetOrDefault non existing value +value_0 value_second_0 +value_1 \N +value_2 value_second_2 +default default +dictHas +1 +1 +1 +0 +select all values as input stream +0 id_key_0 value_0 value_second_0 +1 id_key_1 value_1 \N +2 id_key_2 value_2 value_second_2 Dictionary hashed_array_dictionary_complex_key_complex_attributes dictGet existing value value_0 value_second_0 diff --git a/tests/queries/0_stateless/02099_hashed_array_dictionary_complex_key.sql b/tests/queries/0_stateless/02099_hashed_array_dictionary_complex_key.sql.j2 similarity index 96% rename from tests/queries/0_stateless/02099_hashed_array_dictionary_complex_key.sql rename to tests/queries/0_stateless/02099_hashed_array_dictionary_complex_key.sql.j2 index 4d2a825c8af..56f9b264a62 100644 --- a/tests/queries/0_stateless/02099_hashed_array_dictionary_complex_key.sql +++ b/tests/queries/0_stateless/02099_hashed_array_dictionary_complex_key.sql.j2 @@ -12,6 +12,8 @@ INSERT INTO complex_key_simple_attributes_source_table VALUES(0, 'id_key_0', 'va INSERT INTO complex_key_simple_attributes_source_table VALUES(1, 'id_key_1', 'value_1', 'value_second_1'); INSERT INTO complex_key_simple_attributes_source_table VALUES(2, 'id_key_2', 'value_2', 'value_second_2'); +{% for dictionary_config in ['', 'SHARDS 16'] -%} + DROP DICTIONARY IF EXISTS hashed_array_dictionary_complex_key_simple_attributes; CREATE DICTIONARY hashed_array_dictionary_complex_key_simple_attributes ( @@ -23,7 +25,7 @@ CREATE DICTIONARY hashed_array_dictionary_complex_key_simple_attributes PRIMARY KEY id, id_key SOURCE(CLICKHOUSE(TABLE 'complex_key_simple_attributes_source_table')) LIFETIME(MIN 1 MAX 1000) -LAYOUT(COMPLEX_KEY_HASHED_ARRAY()); +LAYOUT(COMPLEX_KEY_HASHED_ARRAY({{ dictionary_config }})); SELECT 'Dictionary hashed_array_dictionary_complex_key_simple_attributes'; SELECT 'dictGet existing value'; @@ -45,6 +47,8 @@ SELECT * FROM hashed_array_dictionary_complex_key_simple_attributes ORDER BY (id DROP DICTIONARY hashed_array_dictionary_complex_key_simple_attributes; +{% endfor %} + DROP TABLE complex_key_simple_attributes_source_table; DROP TABLE IF EXISTS complex_key_complex_attributes_source_table; @@ -61,6 +65,8 @@ INSERT INTO complex_key_complex_attributes_source_table VALUES(0, 'id_key_0', 'v INSERT INTO complex_key_complex_attributes_source_table VALUES(1, 'id_key_1', 'value_1', NULL); INSERT INTO complex_key_complex_attributes_source_table VALUES(2, 'id_key_2', 'value_2', 'value_second_2'); +{% for dictionary_config in ['', 'SHARDS 16'] -%} + DROP DICTIONARY IF EXISTS hashed_array_dictionary_complex_key_complex_attributes; CREATE DICTIONARY hashed_array_dictionary_complex_key_complex_attributes ( @@ -73,7 +79,7 @@ CREATE DICTIONARY hashed_array_dictionary_complex_key_complex_attributes PRIMARY KEY id, id_key SOURCE(CLICKHOUSE(TABLE 'complex_key_complex_attributes_source_table')) LIFETIME(MIN 1 MAX 1000) -LAYOUT(COMPLEX_KEY_HASHED_ARRAY()); +LAYOUT(COMPLEX_KEY_HASHED_ARRAY({{ dictionary_config }})); SELECT 'Dictionary hashed_array_dictionary_complex_key_complex_attributes'; SELECT 'dictGet existing value'; @@ -93,5 +99,7 @@ SELECT dictHas('hashed_array_dictionary_complex_key_complex_attributes', (number SELECT 'select all values as input stream'; SELECT * FROM hashed_array_dictionary_complex_key_complex_attributes ORDER BY (id, id_key); +{% endfor %} + DROP DICTIONARY hashed_array_dictionary_complex_key_complex_attributes; DROP TABLE complex_key_complex_attributes_source_table; diff --git a/tests/queries/0_stateless/02311_hashed_array_dictionary_hierarchical_functions.reference b/tests/queries/0_stateless/02311_hashed_array_dictionary_hierarchical_functions.reference index 7f4ba0901b6..0b0b4175e1f 100644 --- a/tests/queries/0_stateless/02311_hashed_array_dictionary_hierarchical_functions.reference +++ b/tests/queries/0_stateless/02311_hashed_array_dictionary_hierarchical_functions.reference @@ -33,3 +33,38 @@ Get descendants at first level [] [] [] +Get hierarchy +[] +[1] +[2,1] +[3,1] +[4,2,1] +[] +Get is in hierarchy +0 +1 +1 +1 +1 +0 +Get children +[1] +[2,3] +[4] +[] +[] +[] +Get all descendants +[1,2,3,4] +[2,3,4] +[4] +[] +[] +[] +Get descendants at first level +[1] +[2,3] +[4] +[] +[] +[] diff --git a/tests/queries/0_stateless/02311_hashed_array_dictionary_hierarchical_functions.sql b/tests/queries/0_stateless/02311_hashed_array_dictionary_hierarchical_functions.sql.j2 similarity index 91% rename from tests/queries/0_stateless/02311_hashed_array_dictionary_hierarchical_functions.sql rename to tests/queries/0_stateless/02311_hashed_array_dictionary_hierarchical_functions.sql.j2 index a775f0e5cbf..bc13bcfdb09 100644 --- a/tests/queries/0_stateless/02311_hashed_array_dictionary_hierarchical_functions.sql +++ b/tests/queries/0_stateless/02311_hashed_array_dictionary_hierarchical_functions.sql.j2 @@ -7,6 +7,8 @@ CREATE TABLE hierarchy_source_table INSERT INTO hierarchy_source_table VALUES (1, 0), (2, 1), (3, 1), (4, 2); +{% for dictionary_config in ['', 'SHARDS 16'] -%} + DROP DICTIONARY IF EXISTS hierarchy_hashed_array_dictionary; CREATE DICTIONARY hierarchy_hashed_array_dictionary ( @@ -15,7 +17,7 @@ CREATE DICTIONARY hierarchy_hashed_array_dictionary ) PRIMARY KEY id SOURCE(CLICKHOUSE(TABLE 'hierarchy_source_table')) -LAYOUT(HASHED_ARRAY()) +LAYOUT(HASHED_ARRAY({{ dictionary_config }})) LIFETIME(MIN 1 MAX 1000); SELECT 'Get hierarchy'; @@ -29,6 +31,8 @@ SELECT dictGetDescendants('hierarchy_hashed_array_dictionary', number) FROM syst SELECT 'Get descendants at first level'; SELECT dictGetDescendants('hierarchy_hashed_array_dictionary', number, 1) FROM system.numbers LIMIT 6; +{% endfor %} + DROP DICTIONARY hierarchy_hashed_array_dictionary; DROP TABLE hierarchy_source_table; diff --git a/tests/queries/0_stateless/02316_hierarchical_dictionaries_nullable_parent_key.reference b/tests/queries/0_stateless/02316_hierarchical_dictionaries_nullable_parent_key.reference index 60d9fb16c5f..ab6a247219b 100644 --- a/tests/queries/0_stateless/02316_hierarchical_dictionaries_nullable_parent_key.reference +++ b/tests/queries/0_stateless/02316_hierarchical_dictionaries_nullable_parent_key.reference @@ -106,6 +106,42 @@ Get descendants at first level [] [] [] +HashedArray dictionary +Get hierarchy +[0] +[1,0] +[2,1,0] +[3] +[4,2,1,0] +[] +Get is in hierarchy +1 +1 +1 +1 +1 +0 +Get children +[1] +[2] +[4] +[] +[] +[] +Get all descendants +[1,2,4] +[2,4] +[4] +[] +[] +[] +Get descendants at first level +[1] +[2] +[4] +[] +[] +[] Cache dictionary Get hierarchy [0] diff --git a/tests/queries/0_stateless/02316_hierarchical_dictionaries_nullable_parent_key.sql b/tests/queries/0_stateless/02316_hierarchical_dictionaries_nullable_parent_key.sql.j2 similarity index 97% rename from tests/queries/0_stateless/02316_hierarchical_dictionaries_nullable_parent_key.sql rename to tests/queries/0_stateless/02316_hierarchical_dictionaries_nullable_parent_key.sql.j2 index d477d58d398..b456495513e 100644 --- a/tests/queries/0_stateless/02316_hierarchical_dictionaries_nullable_parent_key.sql +++ b/tests/queries/0_stateless/02316_hierarchical_dictionaries_nullable_parent_key.sql.j2 @@ -56,7 +56,7 @@ SELECT 'Get descendants at first level'; SELECT dictGetDescendants('hierachical_hashed_dictionary', number, 1) FROM system.numbers LIMIT 6; DROP DICTIONARY hierachical_hashed_dictionary; - +{% for dictionary_config in ['', 'SHARDS 16'] -%} DROP DICTIONARY IF EXISTS hierachical_hashed_array_dictionary; CREATE DICTIONARY hierachical_hashed_array_dictionary ( @@ -64,7 +64,7 @@ CREATE DICTIONARY hierachical_hashed_array_dictionary parent_id Nullable(UInt64) HIERARCHICAL ) PRIMARY KEY id SOURCE(CLICKHOUSE(TABLE 'test_hierarhical_table')) -LAYOUT(HASHED_ARRAY()) +LAYOUT(HASHED_ARRAY({{ dictionary_config }})) LIFETIME(0); SELECT 'HashedArray dictionary'; @@ -82,6 +82,8 @@ SELECT dictGetDescendants('hierachical_hashed_array_dictionary', number, 1) FROM DROP DICTIONARY hierachical_hashed_array_dictionary; +{% endfor %} + DROP DICTIONARY IF EXISTS hierachical_cache_dictionary; CREATE DICTIONARY hierachical_cache_dictionary ( diff --git a/tests/queries/0_stateless/02760_dictionaries_memory.sql.j2 b/tests/queries/0_stateless/02760_dictionaries_memory.sql.j2 index ea979506e07..67e8f098217 100644 --- a/tests/queries/0_stateless/02760_dictionaries_memory.sql.j2 +++ b/tests/queries/0_stateless/02760_dictionaries_memory.sql.j2 @@ -14,6 +14,7 @@ SET max_memory_usage='4Mi'; 'FLAT(INITIAL_ARRAY_SIZE 3_000_000 MAX_ARRAY_SIZE 3_000_000)', 'HASHED()', 'HASHED_ARRAY()', + 'HASHED_ARRAY(SHARDS 2)', 'SPARSE_HASHED()', 'SPARSE_HASHED(SHARDS 2 /* shards are special, they use threads */)', ] %} From 1960713176777cc75a082170bbb75ea3b4accdbc Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 13 Dec 2023 17:27:23 +0000 Subject: [PATCH 043/253] add docs --- .../statements/alter/apply-deleted-mask.md | 22 +++++++++++++++++++ .../sql-reference/statements/alter/index.md | 5 +++-- src/Interpreters/MutationsInterpreter.cpp | 6 ++--- 3 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 docs/en/sql-reference/statements/alter/apply-deleted-mask.md diff --git a/docs/en/sql-reference/statements/alter/apply-deleted-mask.md b/docs/en/sql-reference/statements/alter/apply-deleted-mask.md new file mode 100644 index 00000000000..7a11d66e739 --- /dev/null +++ b/docs/en/sql-reference/statements/alter/apply-deleted-mask.md @@ -0,0 +1,22 @@ +--- +slug: /en/sql-reference/statements/alter/apply-deleted-mask +sidebar_position: 46 +sidebar_label: APPLY DELETED MASK +--- + +# Apply mask of deleted rows + +``` sql +ALTER TABLE [db].name [ON CLUSTER cluster] APPLY DELETED MASK [IN PARTITION partition_id] +``` + +The command applies mask created by [lightweight delete](/docs/en/sql-reference/statements/delete) and forcefully removes rows marked as deleted from disk. This command is a heavyweight mutation and it semantically equals to query ```ALTER TABLE [db].name DELETE WHERE _row_exists = 0```. + +:::note +It only works for tables in the [`MergeTree`](../../../engines/table-engines/mergetree-family/mergetree.md) family (including [replicated](../../../engines/table-engines/mergetree-family/replication.md) tables). +::: + +**See also** + +- [Lightweight deletes](/docs/en/sql-reference/statements/delete) +- [Heavyweight deletes](/docs/en/sql-reference/statements/alter/delete.md) diff --git a/docs/en/sql-reference/statements/alter/index.md b/docs/en/sql-reference/statements/alter/index.md index d28542e0a43..dc6668c7983 100644 --- a/docs/en/sql-reference/statements/alter/index.md +++ b/docs/en/sql-reference/statements/alter/index.md @@ -17,8 +17,9 @@ Most `ALTER TABLE` queries modify table settings or data: - [CONSTRAINT](/docs/en/sql-reference/statements/alter/constraint.md) - [TTL](/docs/en/sql-reference/statements/alter/ttl.md) - [STATISTIC](/docs/en/sql-reference/statements/alter/statistic.md) +- [APPLY DELETED MASK](/docs/en/sql-reference/statements/alter/apply-deleted-mask.md) -:::note +:::note Most `ALTER TABLE` queries are supported only for [\*MergeTree](/docs/en/engines/table-engines/mergetree-family/index.md) tables, as well as [Merge](/docs/en/engines/table-engines/special/merge.md) and [Distributed](/docs/en/engines/table-engines/special/distributed.md). ::: @@ -59,7 +60,7 @@ For all `ALTER` queries, you can use the [alter_sync](/docs/en/operations/settin You can specify how long (in seconds) to wait for inactive replicas to execute all `ALTER` queries with the [replication_wait_for_inactive_replica_timeout](/docs/en/operations/settings/settings.md/#replication-wait-for-inactive-replica-timeout) setting. -:::note +:::note For all `ALTER` queries, if `alter_sync = 2` and some replicas are not active for more than the time, specified in the `replication_wait_for_inactive_replica_timeout` setting, then an exception `UNFINISHED` is thrown. ::: diff --git a/src/Interpreters/MutationsInterpreter.cpp b/src/Interpreters/MutationsInterpreter.cpp index a492ea266cf..bf50766c165 100644 --- a/src/Interpreters/MutationsInterpreter.cpp +++ b/src/Interpreters/MutationsInterpreter.cpp @@ -269,9 +269,9 @@ MutationCommand createCommandToApplyDeletedMask(const MutationCommand & command) std::make_shared(Field(0))); if (command.predicate) - alter_command->predicate = makeASTFunction("and", row_exists_predicate, command.predicate); - else - alter_command->predicate = row_exists_predicate; + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Mutation command APPLY DELETED MASK does not support WHERE clause"); + + alter_command->predicate = row_exists_predicate; auto mutation_command = MutationCommand::parse(alter_command.get()); if (!mutation_command) From 48fe75a70d8edac08e7fe43a392bf294555429af Mon Sep 17 00:00:00 2001 From: vdimir Date: Wed, 13 Dec 2023 17:43:15 +0000 Subject: [PATCH 044/253] Fix MergeJoin lowcard keys support --- src/Interpreters/JoinUtils.cpp | 21 ------------------- src/Interpreters/JoinUtils.h | 2 -- src/Interpreters/MergeJoin.cpp | 11 +--------- src/Interpreters/TableJoin.cpp | 9 +++++++- ...oin_with_totals_and_subquery_bug.reference | 20 ++++++++++++++++++ ..._join_with_totals_and_subquery_bug.sql.j2} | 8 +++++++ 6 files changed, 37 insertions(+), 34 deletions(-) rename tests/queries/0_stateless/{02516_join_with_totals_and_subquery_bug.sql => 02516_join_with_totals_and_subquery_bug.sql.j2} (94%) diff --git a/src/Interpreters/JoinUtils.cpp b/src/Interpreters/JoinUtils.cpp index 949a97d5748..ff98bd35d02 100644 --- a/src/Interpreters/JoinUtils.cpp +++ b/src/Interpreters/JoinUtils.cpp @@ -345,27 +345,6 @@ ColumnRawPtrs getRawPointers(const Columns & columns) return ptrs; } -void convertToFullColumnsInplace(Block & block) -{ - for (size_t i = 0; i < block.columns(); ++i) - { - auto & col = block.getByPosition(i); - col.column = recursiveRemoveLowCardinality(recursiveRemoveSparse(col.column)); - col.type = recursiveRemoveLowCardinality(col.type); - } -} - -void convertToFullColumnsInplace(Block & block, const Names & names, bool change_type) -{ - for (const String & column_name : names) - { - auto & col = block.getByName(column_name); - col.column = recursiveRemoveLowCardinality(recursiveRemoveSparse(col.column)); - if (change_type) - col.type = recursiveRemoveLowCardinality(col.type); - } -} - void restoreLowCardinalityInplace(Block & block, const Names & lowcard_keys) { for (const auto & column_name : lowcard_keys) diff --git a/src/Interpreters/JoinUtils.h b/src/Interpreters/JoinUtils.h index a88fca02bd8..ff48f34d82c 100644 --- a/src/Interpreters/JoinUtils.h +++ b/src/Interpreters/JoinUtils.h @@ -71,8 +71,6 @@ ColumnPtr materializeColumn(const Block & block, const String & name); Columns materializeColumns(const Block & block, const Names & names); ColumnRawPtrs materializeColumnsInplace(Block & block, const Names & names); ColumnRawPtrs getRawPointers(const Columns & columns); -void convertToFullColumnsInplace(Block & block); -void convertToFullColumnsInplace(Block & block, const Names & names, bool change_type = true); void restoreLowCardinalityInplace(Block & block, const Names & lowcard_keys); ColumnRawPtrs extractKeysForJoin(const Block & block_keys, const Names & key_names_right); diff --git a/src/Interpreters/MergeJoin.cpp b/src/Interpreters/MergeJoin.cpp index 30c62386ca3..36c1a5699d4 100644 --- a/src/Interpreters/MergeJoin.cpp +++ b/src/Interpreters/MergeJoin.cpp @@ -537,9 +537,6 @@ MergeJoin::MergeJoin(std::shared_ptr table_join_, const Block & right lowcard_right_keys.push_back(right_key); } - JoinCommon::convertToFullColumnsInplace(right_table_keys); - JoinCommon::convertToFullColumnsInplace(right_sample_block, key_names_right); - for (const auto & column : right_table_keys) if (required_right_keys.contains(column.name)) right_columns_to_add.insert(ColumnWithTypeAndName{nullptr, column.type, column.name}); @@ -662,9 +659,7 @@ bool MergeJoin::saveRightBlock(Block && block) Block MergeJoin::modifyRightBlock(const Block & src_block) const { - Block block = materializeBlock(src_block); - JoinCommon::convertToFullColumnsInplace(block, table_join->getOnlyClause().key_names_right); - return block; + return materializeBlock(src_block); } bool MergeJoin::addBlockToJoin(const Block & src_block, bool) @@ -705,8 +700,6 @@ void MergeJoin::joinBlock(Block & block, ExtraBlockPtr & not_processed) lowcard_keys.push_back(column_name); } - JoinCommon::convertToFullColumnsInplace(block, key_names_left, false); - sortBlock(block, left_sort_description); } @@ -739,8 +732,6 @@ void MergeJoin::joinBlock(Block & block, ExtraBlockPtr & not_processed) if (needConditionJoinColumn()) block.erase(deriveTempName(mask_column_name_left, JoinTableSide::Left)); - - JoinCommon::restoreLowCardinalityInplace(block, lowcard_keys); } template diff --git a/src/Interpreters/TableJoin.cpp b/src/Interpreters/TableJoin.cpp index fa289b82aaf..a069cf1b1f0 100644 --- a/src/Interpreters/TableJoin.cpp +++ b/src/Interpreters/TableJoin.cpp @@ -558,7 +558,14 @@ TableJoin::createConvertingActions( */ NameToNameMap left_column_rename; NameToNameMap right_column_rename; - inferJoinKeyCommonType(left_sample_columns, right_sample_columns, !isSpecialStorage(), isEnabledAlgorithm(JoinAlgorithm::FULL_SORTING_MERGE)); + + /// FullSortingMerge and PartialMerge join algorithms doen't support joining keys with different types + /// (e.g. String and LowCardinality(String)) + bool require_strict_keys_match = isEnabledAlgorithm(JoinAlgorithm::FULL_SORTING_MERGE) + || isEnabledAlgorithm(JoinAlgorithm::PARTIAL_MERGE) + || isEnabledAlgorithm(JoinAlgorithm::PREFER_PARTIAL_MERGE) + || isEnabledAlgorithm(JoinAlgorithm::AUTO); + inferJoinKeyCommonType(left_sample_columns, right_sample_columns, !isSpecialStorage(), require_strict_keys_match); if (!left_type_map.empty() || !right_type_map.empty()) { left_dag = applyKeyConvertToTable(left_sample_columns, left_type_map, JoinTableSide::Left, left_column_rename); diff --git a/tests/queries/0_stateless/02516_join_with_totals_and_subquery_bug.reference b/tests/queries/0_stateless/02516_join_with_totals_and_subquery_bug.reference index 83571fd9005..86e7e2a6a49 100644 --- a/tests/queries/0_stateless/02516_join_with_totals_and_subquery_bug.reference +++ b/tests/queries/0_stateless/02516_join_with_totals_and_subquery_bug.reference @@ -29,3 +29,23 @@ ['1'] [] 0 [] [] 3 +--- +[] 0 ['2'] +['0'] 2 ['0'] +['0'] 2 ['0'] +['1'] 1 [] + +[] 3 [] +--- +[] 0 ['2'] 1 +['0'] 2 ['0'] 2 +['1'] 1 [] 0 + +[] 3 [] 3 +--- +[] ['2'] 1 +['0'] ['0'] 2 +['0'] ['0'] 2 +['1'] [] 0 + +[] [] 3 diff --git a/tests/queries/0_stateless/02516_join_with_totals_and_subquery_bug.sql b/tests/queries/0_stateless/02516_join_with_totals_and_subquery_bug.sql.j2 similarity index 94% rename from tests/queries/0_stateless/02516_join_with_totals_and_subquery_bug.sql rename to tests/queries/0_stateless/02516_join_with_totals_and_subquery_bug.sql.j2 index d39efb0b193..09447dfce65 100644 --- a/tests/queries/0_stateless/02516_join_with_totals_and_subquery_bug.sql +++ b/tests/queries/0_stateless/02516_join_with_totals_and_subquery_bug.sql.j2 @@ -70,6 +70,12 @@ ALL LEFT JOIN ) AS js2 USING (a) ORDER BY b ASC NULLS FIRST; + + +{% for join_algorithm in ['default', 'partial_merge'] -%} + +SET join_algorithm = '{{ join_algorithm }}'; + SELECT '---'; SELECT * @@ -112,3 +118,5 @@ FULL JOIN ( ON l.item_id = r.item_id ORDER BY 1,2,3 ; + +{% endfor %} From 4ff6d47304c21d001d5c8349104a39011edf1d5a Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 14 Dec 2023 14:35:58 +0000 Subject: [PATCH 045/253] fix some tests --- .../00133_long_shard_memory_tracker_and_exception_safety.sh | 4 ++-- .../01291_distributed_low_cardinality_memory_efficient.sql | 5 +++++ .../0_stateless/02178_column_function_insert_from.sql | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/00133_long_shard_memory_tracker_and_exception_safety.sh b/tests/queries/0_stateless/00133_long_shard_memory_tracker_and_exception_safety.sh index e6e82e8c976..a42fd58190a 100755 --- a/tests/queries/0_stateless/00133_long_shard_memory_tracker_and_exception_safety.sh +++ b/tests/queries/0_stateless/00133_long_shard_memory_tracker_and_exception_safety.sh @@ -15,8 +15,8 @@ if [ -n "$DBMS_TESTS_UNDER_VALGRIND" ]; then STEP_MULTIPLIER=1000 fi -for i in $(seq 1000000 $((20000 * $STEP_MULTIPLIER)) 10000000 && seq 10100000 $((100000 * $STEP_MULTIPLIER)) 100000000); do - $CLICKHOUSE_CLIENT --max_memory_usage="$i" --query=" +for i in $(seq 1000000 $((20000 * $STEP_MULTIPLIER)) 10000000 && seq 10100000 $((100000 * $STEP_MULTIPLIER)) 50000000); do + $CLICKHOUSE_CLIENT --max_memory_usage="$i" --max_bytes_before_external_group_by 0 --query=" SELECT intDiv(number, 5) AS k, max(toString(number)) FROM remote('127.0.0.{2,3}', ${CLICKHOUSE_DATABASE}.numbers_100k) GROUP BY k ORDER BY k LIMIT 1; " 2> /dev/null; CODE=$?; diff --git a/tests/queries/0_stateless/01291_distributed_low_cardinality_memory_efficient.sql b/tests/queries/0_stateless/01291_distributed_low_cardinality_memory_efficient.sql index 267f5585705..3697a167989 100644 --- a/tests/queries/0_stateless/01291_distributed_low_cardinality_memory_efficient.sql +++ b/tests/queries/0_stateless/01291_distributed_low_cardinality_memory_efficient.sql @@ -6,7 +6,12 @@ DROP TABLE IF EXISTS dist; create table data (key String) Engine=Memory(); create table dist (key LowCardinality(String)) engine=Distributed(test_cluster_two_shards, currentDatabase(), data); insert into data values ('foo'); + set distributed_aggregation_memory_efficient=1; + +-- There is an obscure bug in rare corner case. +set max_bytes_before_external_group_by = 0; + select * from dist group by key; DROP TABLE data; diff --git a/tests/queries/0_stateless/02178_column_function_insert_from.sql b/tests/queries/0_stateless/02178_column_function_insert_from.sql index 13d1ebb4788..dc7c134b6f9 100644 --- a/tests/queries/0_stateless/02178_column_function_insert_from.sql +++ b/tests/queries/0_stateless/02178_column_function_insert_from.sql @@ -8,6 +8,9 @@ INSERT INTO TESTTABLE values (0,'0',['1']), (1,'1',['1']); SET max_threads = 1; +-- There is a bug which is fixed in new analyzer. +SET max_bytes_before_external_group_by = 0; + SELECT attr, _id, arrayFilter(x -> (x IN (select '1')), attr_list) z FROM TESTTABLE ARRAY JOIN z AS attr ORDER BY _id LIMIT 3 BY attr; From 0473e39352237fe0108a243511c3e8142a7af15a Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 14 Dec 2023 15:08:29 +0000 Subject: [PATCH 046/253] lowcard in join output --- src/Interpreters/JoinUtils.cpp | 4 +- src/Interpreters/MergeJoin.cpp | 14 ++ src/Interpreters/TableJoin.cpp | 20 +-- src/Interpreters/TableJoin.h | 2 +- src/Planner/PlannerJoinTree.cpp | 57 ++++---- .../01049_join_low_card_bug_long.reference.j2 | 124 +++++++++--------- 6 files changed, 117 insertions(+), 104 deletions(-) diff --git a/src/Interpreters/JoinUtils.cpp b/src/Interpreters/JoinUtils.cpp index ff98bd35d02..511ecdd783e 100644 --- a/src/Interpreters/JoinUtils.cpp +++ b/src/Interpreters/JoinUtils.cpp @@ -474,8 +474,8 @@ void addDefaultValues(IColumn & column, const DataTypePtr & type, size_t count) bool typesEqualUpToNullability(DataTypePtr left_type, DataTypePtr right_type) { - DataTypePtr left_type_strict = removeNullable(recursiveRemoveLowCardinality(left_type)); - DataTypePtr right_type_strict = removeNullable(recursiveRemoveLowCardinality(right_type)); + DataTypePtr left_type_strict = removeNullable(left_type); + DataTypePtr right_type_strict = removeNullable(right_type); return left_type_strict->equals(*right_type_strict); } diff --git a/src/Interpreters/MergeJoin.cpp b/src/Interpreters/MergeJoin.cpp index 36c1a5699d4..f0427b5a6ca 100644 --- a/src/Interpreters/MergeJoin.cpp +++ b/src/Interpreters/MergeJoin.cpp @@ -138,6 +138,9 @@ Block extractMinMax(const Block & block, const Block & keys) } min_max.setColumns(std::move(columns)); + + for (auto & column : min_max) + column.column = column.column->convertToFullColumnIfLowCardinality(); return min_max; } @@ -224,6 +227,16 @@ public: MergeJoinCursor(const Block & block, const SortDescription & desc_) : impl(block, desc_) { + for (auto *& column : impl.sort_columns) + { + const auto * lowcard_column = typeid_cast(column); + if (lowcard_column) + { + auto & new_col = column_holder.emplace_back(lowcard_column->convertToFullColumn()); + column = new_col.get(); + } + } + /// SortCursorImpl can work with permutation, but MergeJoinCursor can't. if (impl.permutation) throw Exception(ErrorCodes::LOGICAL_ERROR, "Logical error: MergeJoinCursor doesn't support permutation"); @@ -287,6 +300,7 @@ public: private: SortCursorImpl impl; + Columns column_holder; bool has_left_nullable = false; bool has_right_nullable = false; diff --git a/src/Interpreters/TableJoin.cpp b/src/Interpreters/TableJoin.cpp index a069cf1b1f0..066bdc419ef 100644 --- a/src/Interpreters/TableJoin.cpp +++ b/src/Interpreters/TableJoin.cpp @@ -375,7 +375,7 @@ void TableJoin::addJoinedColumnsAndCorrectTypesImpl(TColumns & left_columns, boo * For `JOIN ON expr1 == expr2` we will infer common type later in makeTableJoin, * when part of plan built and types of expression will be known. */ - inferJoinKeyCommonType(left_columns, columns_from_joined_table, !isSpecialStorage(), isEnabledAlgorithm(JoinAlgorithm::FULL_SORTING_MERGE)); + inferJoinKeyCommonType(left_columns, columns_from_joined_table, !isSpecialStorage()); if (auto it = left_type_map.find(col.name); it != left_type_map.end()) { @@ -559,13 +559,7 @@ TableJoin::createConvertingActions( NameToNameMap left_column_rename; NameToNameMap right_column_rename; - /// FullSortingMerge and PartialMerge join algorithms doen't support joining keys with different types - /// (e.g. String and LowCardinality(String)) - bool require_strict_keys_match = isEnabledAlgorithm(JoinAlgorithm::FULL_SORTING_MERGE) - || isEnabledAlgorithm(JoinAlgorithm::PARTIAL_MERGE) - || isEnabledAlgorithm(JoinAlgorithm::PREFER_PARTIAL_MERGE) - || isEnabledAlgorithm(JoinAlgorithm::AUTO); - inferJoinKeyCommonType(left_sample_columns, right_sample_columns, !isSpecialStorage(), require_strict_keys_match); + inferJoinKeyCommonType(left_sample_columns, right_sample_columns, !isSpecialStorage()); if (!left_type_map.empty() || !right_type_map.empty()) { left_dag = applyKeyConvertToTable(left_sample_columns, left_type_map, JoinTableSide::Left, left_column_rename); @@ -619,8 +613,14 @@ TableJoin::createConvertingActions( } template -void TableJoin::inferJoinKeyCommonType(const LeftNamesAndTypes & left, const RightNamesAndTypes & right, bool allow_right, bool strict) +void TableJoin::inferJoinKeyCommonType(const LeftNamesAndTypes & left, const RightNamesAndTypes & right, bool allow_right) { + /// FullSortingMerge and PartialMerge join algorithms don't support joining keys with different types + /// (e.g. String and LowCardinality(String)) + bool require_strict_keys_match = isEnabledAlgorithm(JoinAlgorithm::FULL_SORTING_MERGE) + || isEnabledAlgorithm(JoinAlgorithm::PARTIAL_MERGE) + || isEnabledAlgorithm(JoinAlgorithm::PREFER_PARTIAL_MERGE) + || isEnabledAlgorithm(JoinAlgorithm::AUTO); if (!left_type_map.empty() || !right_type_map.empty()) return; @@ -652,7 +652,7 @@ void TableJoin::inferJoinKeyCommonType(const LeftNamesAndTypes & left, const Rig const auto & ltype = ltypeit->second; const auto & rtype = rtypeit->second; - bool type_equals = strict ? ltype->equals(*rtype) : JoinCommon::typesEqualUpToNullability(ltype, rtype); + bool type_equals = require_strict_keys_match ? ltype->equals(*rtype) : JoinCommon::typesEqualUpToNullability(ltype, rtype); if (type_equals) return true; diff --git a/src/Interpreters/TableJoin.h b/src/Interpreters/TableJoin.h index f97e6a74b8c..247835d9c53 100644 --- a/src/Interpreters/TableJoin.h +++ b/src/Interpreters/TableJoin.h @@ -218,7 +218,7 @@ private: /// Calculates common supertypes for corresponding join key columns. template - void inferJoinKeyCommonType(const LeftNamesAndTypes & left, const RightNamesAndTypes & right, bool allow_right, bool strict); + void inferJoinKeyCommonType(const LeftNamesAndTypes & left, const RightNamesAndTypes & right, bool allow_right); void deduplicateAndQualifyColumnNames(const NameSet & left_table_columns, const String & right_table_prefix); diff --git a/src/Planner/PlannerJoinTree.cpp b/src/Planner/PlannerJoinTree.cpp index abcf971b832..88a1c36dd0c 100644 --- a/src/Planner/PlannerJoinTree.cpp +++ b/src/Planner/PlannerJoinTree.cpp @@ -955,6 +955,29 @@ JoinTreeQueryPlan buildQueryPlanForTableExpression(QueryTreeNodePtr table_expres }; } +void joinCastPlanColumnsToNullable(QueryPlan & plan_to_add_cast, PlannerContextPtr & planner_context, const FunctionOverloadResolverPtr & to_nullable_function) +{ + auto cast_actions_dag = std::make_shared(plan_to_add_cast.getCurrentDataStream().header.getColumnsWithTypeAndName()); + + for (auto & output_node : cast_actions_dag->getOutputs()) + { + if (planner_context->getGlobalPlannerContext()->hasColumnIdentifier(output_node->result_name)) + { + DataTypePtr type_to_check = output_node->result_type; + if (const auto * type_to_check_low_cardinality = typeid_cast(type_to_check.get())) + type_to_check = type_to_check_low_cardinality->getDictionaryType(); + + if (type_to_check->canBeInsideNullable()) + output_node = &cast_actions_dag->addFunction(to_nullable_function, {output_node}, output_node->result_name); + } + } + + cast_actions_dag->projectInput(); + auto cast_join_columns_step = std::make_unique(plan_to_add_cast.getCurrentDataStream(), std::move(cast_actions_dag)); + cast_join_columns_step->setStepDescription("Cast JOIN columns to Nullable"); + plan_to_add_cast.addStep(std::move(cast_join_columns_step)); +} + JoinTreeQueryPlan buildQueryPlanForJoinNode(const QueryTreeNodePtr & join_table_expression, JoinTreeQueryPlan left_join_tree_query_plan, JoinTreeQueryPlan right_join_tree_query_plan, @@ -1068,45 +1091,21 @@ JoinTreeQueryPlan buildQueryPlanForJoinNode(const QueryTreeNodePtr & join_table_ const auto & query_context = planner_context->getQueryContext(); const auto & settings = query_context->getSettingsRef(); - auto to_nullable_function = FunctionFactory::instance().get("toNullable", query_context); - - auto join_cast_plan_columns_to_nullable = [&](QueryPlan & plan_to_add_cast) - { - auto cast_actions_dag = std::make_shared(plan_to_add_cast.getCurrentDataStream().header.getColumnsWithTypeAndName()); - - for (auto & output_node : cast_actions_dag->getOutputs()) - { - if (planner_context->getGlobalPlannerContext()->hasColumnIdentifier(output_node->result_name)) - { - DataTypePtr type_to_check = output_node->result_type; - if (const auto * type_to_check_low_cardinality = typeid_cast(type_to_check.get())) - type_to_check = type_to_check_low_cardinality->getDictionaryType(); - - if (type_to_check->canBeInsideNullable()) - output_node = &cast_actions_dag->addFunction(to_nullable_function, {output_node}, output_node->result_name); - } - } - - cast_actions_dag->projectInput(); - auto cast_join_columns_step = std::make_unique(plan_to_add_cast.getCurrentDataStream(), std::move(cast_actions_dag)); - cast_join_columns_step->setStepDescription("Cast JOIN columns to Nullable"); - plan_to_add_cast.addStep(std::move(cast_join_columns_step)); - }; - if (settings.join_use_nulls) { + auto to_nullable_function = FunctionFactory::instance().get("toNullable", query_context); if (isFull(join_kind)) { - join_cast_plan_columns_to_nullable(left_plan); - join_cast_plan_columns_to_nullable(right_plan); + joinCastPlanColumnsToNullable(left_plan, planner_context, to_nullable_function); + joinCastPlanColumnsToNullable(right_plan, planner_context, to_nullable_function); } else if (isLeft(join_kind)) { - join_cast_plan_columns_to_nullable(right_plan); + joinCastPlanColumnsToNullable(right_plan, planner_context, to_nullable_function); } else if (isRight(join_kind)) { - join_cast_plan_columns_to_nullable(left_plan); + joinCastPlanColumnsToNullable(left_plan, planner_context, to_nullable_function); } } diff --git a/tests/queries/0_stateless/01049_join_low_card_bug_long.reference.j2 b/tests/queries/0_stateless/01049_join_low_card_bug_long.reference.j2 index 2ebe5c373b2..373edb3b19d 100644 --- a/tests/queries/0_stateless/01049_join_low_card_bug_long.reference.j2 +++ b/tests/queries/0_stateless/01049_join_low_card_bug_long.reference.j2 @@ -37,30 +37,30 @@ SELECT lc, toTypeName(lc) FROM l_lc AS l RIGHT JOIN r USING (x) ORDER BY x, lc; str LowCardinality(String) LowCardinality(String) SELECT lc, toTypeName(lc) FROM l_lc AS l RIGHT JOIN r USING (lc) ORDER BY x, lc; -str LowCardinality(String) -str_r LowCardinality(String) +str String +str_r String SELECT lc, toTypeName(lc) FROM l_lc AS l FULL JOIN r USING (x) ORDER BY x, lc; str LowCardinality(String) LowCardinality(String) str_l LowCardinality(String) SELECT lc, toTypeName(lc) FROM l_lc AS l FULL JOIN r USING (lc) ORDER BY x, lc; -str LowCardinality(String) -str_r LowCardinality(String) -str_l LowCardinality(String) +str String +str_r String +str_l String SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN r USING (x) ORDER BY x, r.lc, l.lc; String String str str LowCardinality(String) LowCardinality(String) str str String String str_r str_r LowCardinality(String) LowCardinality(String) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN r USING (lc) ORDER BY x, r.lc, l.lc; -String String str str LowCardinality(String) LowCardinality(String) str str -String String str_r str_r LowCardinality(String) LowCardinality(String) +String String str str String String str str +String String str_r str_r String String SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN r USING (x) ORDER BY x, r.lc, l.lc; String String str str LowCardinality(String) LowCardinality(String) str str String String str_r str_r LowCardinality(String) LowCardinality(String) String String LowCardinality(String) LowCardinality(String) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN r USING (lc) ORDER BY x, r.lc, l.lc; -String String str str LowCardinality(String) LowCardinality(String) str str -String String str_r str_r LowCardinality(String) LowCardinality(String) -String String LowCardinality(String) LowCardinality(String) str_l str_l +String String str str String String str str +String String str_r str_r String String +String String String String str_l str_l -- SELECT lc, toTypeName(lc) FROM l RIGHT JOIN r USING (x) ORDER BY x, lc; @@ -81,46 +81,46 @@ SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), LowCardinality(String) LowCardinality(String) str str String String str str LowCardinality(String) LowCardinality(String) str_r str_r String String SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l RIGHT JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -LowCardinality(String) LowCardinality(String) str str String String str str -LowCardinality(String) LowCardinality(String) str_r str_r String String +String String str str String String str str +String String str_r str_r String String SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l FULL JOIN r_lc AS r USING (x) ORDER BY x, r.lc, l.lc; LowCardinality(String) LowCardinality(String) str str String String str str LowCardinality(String) LowCardinality(String) str_r str_r String String LowCardinality(String) LowCardinality(String) String String str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l FULL JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -LowCardinality(String) LowCardinality(String) str str String String str str -LowCardinality(String) LowCardinality(String) str_r str_r String String -LowCardinality(String) LowCardinality(String) String String str_l str_l +String String str str String String str str +String String str_r str_r String String +String String String String str_l str_l -- SELECT lc, toTypeName(lc) FROM l_lc RIGHT JOIN nr USING (x) ORDER BY x, lc; str LowCardinality(String) LowCardinality(String) SELECT lc, toTypeName(lc) FROM l_lc RIGHT JOIN nr USING (lc) ORDER BY x, lc; -str LowCardinality(String) -str_r LowCardinality(String) +str Nullable(String) +str_r Nullable(String) SELECT lc, toTypeName(lc) FROM l_lc FULL JOIN nr USING (x) ORDER BY x, lc; str LowCardinality(String) LowCardinality(String) str_l LowCardinality(String) SELECT lc, toTypeName(lc) FROM l_lc FULL JOIN nr USING (lc) ORDER BY x, lc; -str LowCardinality(String) -str_r LowCardinality(String) -str_l LowCardinality(String) +str Nullable(String) +str_r Nullable(String) +str_l Nullable(String) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN nr AS r USING (x) ORDER BY x, r.lc, l.lc; Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str_r str_r LowCardinality(String) LowCardinality(String) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN nr AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str str -Nullable(String) Nullable(String) str_r str_r LowCardinality(String) LowCardinality(String) +Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str +Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN nr AS r USING (x) ORDER BY x, r.lc, l.lc; Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str_r str_r LowCardinality(String) LowCardinality(String) Nullable(String) Nullable(String) \N \N LowCardinality(String) LowCardinality(String) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN nr AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str str -Nullable(String) Nullable(String) str_r str_r LowCardinality(String) LowCardinality(String) -Nullable(String) Nullable(String) \N \N LowCardinality(String) LowCardinality(String) str_l str_l +Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str +Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N +Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l -- SELECT lc, toTypeName(lc) FROM nl RIGHT JOIN r_lc USING (x) ORDER BY x, lc; @@ -141,16 +141,16 @@ SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l RIGHT JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str -LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N +Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str +Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l FULL JOIN r_lc AS r USING (x) ORDER BY x, r.lc, l.lc; LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N LowCardinality(String) LowCardinality(String) Nullable(String) Nullable(String) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l FULL JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str -LowCardinality(String) LowCardinality(String) Nullable(String) Nullable(String) str_l str_l -LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N +Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str +Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l +Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N SET join_use_nulls = 1; SELECT lc, toTypeName(lc) FROM l_lc AS l RIGHT JOIN r_lc AS r USING (x) ORDER BY x, lc; str LowCardinality(Nullable(String)) @@ -170,8 +170,8 @@ SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), LowCardinality(String) LowCardinality(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str LowCardinality(String) LowCardinality(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -LowCardinality(String) LowCardinality(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str -LowCardinality(String) LowCardinality(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N +LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str +LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN r_lc AS r USING (x) ORDER BY x, r.lc, l.lc; LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N @@ -186,30 +186,30 @@ SELECT lc, toTypeName(lc) FROM l_lc AS l RIGHT JOIN r USING (x) ORDER BY x, lc; str LowCardinality(Nullable(String)) \N LowCardinality(Nullable(String)) SELECT lc, toTypeName(lc) FROM l_lc AS l RIGHT JOIN r USING (lc) ORDER BY x, lc; -str LowCardinality(Nullable(String)) -str_r LowCardinality(Nullable(String)) +str Nullable(String) +str_r Nullable(String) SELECT lc, toTypeName(lc) FROM l_lc AS l FULL JOIN r USING (x) ORDER BY x, lc; str LowCardinality(Nullable(String)) \N LowCardinality(Nullable(String)) str_l LowCardinality(Nullable(String)) SELECT lc, toTypeName(lc) FROM l_lc AS l FULL JOIN r USING (lc) ORDER BY x, lc; -str LowCardinality(Nullable(String)) -str_l LowCardinality(Nullable(String)) -str_r LowCardinality(Nullable(String)) +str Nullable(String) +str_l Nullable(String) +str_r Nullable(String) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN r USING (x) ORDER BY x, r.lc, l.lc; String String str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str String String str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN r USING (lc) ORDER BY x, r.lc, l.lc; -String String str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str -String String str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N +String String str str Nullable(String) Nullable(String) str str +String String str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN r USING (x) ORDER BY x, r.lc, l.lc; Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str -Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_l str_l -Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N +Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str +Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l +Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N -- SELECT lc, toTypeName(lc) FROM l RIGHT JOIN r USING (x) ORDER BY x, lc; @@ -230,46 +230,46 @@ SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l RIGHT JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str -LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N +String String str str Nullable(String) Nullable(String) str str +String String str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l FULL JOIN r_lc AS r USING (x) ORDER BY x, r.lc, l.lc; LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l FULL JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str str -LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) str_l str_l -LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r Nullable(String) Nullable(String) \N \N +Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str +Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l +Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N -- SELECT lc, toTypeName(lc) FROM l_lc RIGHT JOIN nr USING (x) ORDER BY x, lc; str LowCardinality(Nullable(String)) \N LowCardinality(Nullable(String)) SELECT lc, toTypeName(lc) FROM l_lc RIGHT JOIN nr USING (lc) ORDER BY x, lc; -str LowCardinality(Nullable(String)) -str_r LowCardinality(Nullable(String)) +str Nullable(String) +str_r Nullable(String) SELECT lc, toTypeName(lc) FROM l_lc FULL JOIN nr USING (x) ORDER BY x, lc; str LowCardinality(Nullable(String)) \N LowCardinality(Nullable(String)) str_l LowCardinality(Nullable(String)) SELECT lc, toTypeName(lc) FROM l_lc FULL JOIN nr USING (lc) ORDER BY x, lc; -str LowCardinality(Nullable(String)) -str_l LowCardinality(Nullable(String)) -str_r LowCardinality(Nullable(String)) +str Nullable(String) +str_l Nullable(String) +str_r Nullable(String) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN nr AS r USING (x) ORDER BY x, r.lc, l.lc; Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN nr AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str -Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N +Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str +Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN nr AS r USING (x) ORDER BY x, r.lc, l.lc; Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN nr AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str -Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_l str_l -Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N +Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str +Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l +Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N -- SELECT lc, toTypeName(lc) FROM nl RIGHT JOIN r_lc USING (x) ORDER BY x, lc; @@ -290,16 +290,16 @@ SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l RIGHT JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str -LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N +Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str +Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l FULL JOIN r_lc AS r USING (x) ORDER BY x, r.lc, l.lc; LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l FULL JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str str -LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) str_l str_l -LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r Nullable(String) Nullable(String) \N \N +Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str +Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l +Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N {% endfor -%} SELECT '--'; -- From b4bef78e639a79335e35b49ba153e82de086bc1e Mon Sep 17 00:00:00 2001 From: joelynch Date: Thu, 14 Dec 2023 17:07:26 +0100 Subject: [PATCH 047/253] bugfix: addresses_expr ignored for psql named collections --- src/Databases/DatabaseFactory.cpp | 4 ++-- src/Storages/StoragePostgreSQL.cpp | 10 ++++++++-- src/Storages/StoragePostgreSQL.h | 2 +- .../configs/named_collections.xml | 3 +-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Databases/DatabaseFactory.cpp b/src/Databases/DatabaseFactory.cpp index 53d5245770e..a967ecf67c6 100644 --- a/src/Databases/DatabaseFactory.cpp +++ b/src/Databases/DatabaseFactory.cpp @@ -323,7 +323,7 @@ DatabasePtr DatabaseFactory::getImpl(const ASTCreateQuery & create, const String if (auto named_collection = tryGetNamedCollectionWithOverrides(engine_args, context)) { - configuration = StoragePostgreSQL::processNamedCollectionResult(*named_collection, false); + configuration = StoragePostgreSQL::processNamedCollectionResult(*named_collection, context, false); use_table_cache = named_collection->getOrDefault("use_table_cache", 0); } else @@ -386,7 +386,7 @@ DatabasePtr DatabaseFactory::getImpl(const ASTCreateQuery & create, const String if (auto named_collection = tryGetNamedCollectionWithOverrides(engine_args, context)) { - configuration = StoragePostgreSQL::processNamedCollectionResult(*named_collection, false); + configuration = StoragePostgreSQL::processNamedCollectionResult(*named_collection, context, false); } else { diff --git a/src/Storages/StoragePostgreSQL.cpp b/src/Storages/StoragePostgreSQL.cpp index 7961c44e844..a97104a5a68 100644 --- a/src/Storages/StoragePostgreSQL.cpp +++ b/src/Storages/StoragePostgreSQL.cpp @@ -456,7 +456,7 @@ SinkToStoragePtr StoragePostgreSQL::write( return std::make_shared(metadata_snapshot, pool->get(), remote_table_name, remote_table_schema, on_conflict); } -StoragePostgreSQL::Configuration StoragePostgreSQL::processNamedCollectionResult(const NamedCollection & named_collection, bool require_table) +StoragePostgreSQL::Configuration StoragePostgreSQL::processNamedCollectionResult(const NamedCollection & named_collection, ContextPtr context_, bool require_table) { StoragePostgreSQL::Configuration configuration; ValidateKeysMultiset required_arguments = {"user", "username", "password", "database", "db"}; @@ -473,6 +473,12 @@ StoragePostgreSQL::Configuration StoragePostgreSQL::processNamedCollectionResult configuration.port = static_cast(named_collection.get("port")); configuration.addresses = {std::make_pair(configuration.host, configuration.port)}; } + else + { + size_t max_addresses = context_->getSettingsRef().glob_expansion_max_elements; + configuration.addresses = parseRemoteDescriptionForExternalDatabase( + configuration.addresses_expr, max_addresses, 5432); + } configuration.username = named_collection.getAny({"username", "user"}); configuration.password = named_collection.get("password"); @@ -490,7 +496,7 @@ StoragePostgreSQL::Configuration StoragePostgreSQL::getConfiguration(ASTs engine StoragePostgreSQL::Configuration configuration; if (auto named_collection = tryGetNamedCollectionWithOverrides(engine_args, context)) { - configuration = StoragePostgreSQL::processNamedCollectionResult(*named_collection); + configuration = StoragePostgreSQL::processNamedCollectionResult(*named_collection, context); } else { diff --git a/src/Storages/StoragePostgreSQL.h b/src/Storages/StoragePostgreSQL.h index fb8b5a22df2..725a935aa46 100644 --- a/src/Storages/StoragePostgreSQL.h +++ b/src/Storages/StoragePostgreSQL.h @@ -65,7 +65,7 @@ public: static Configuration getConfiguration(ASTs engine_args, ContextPtr context); - static Configuration processNamedCollectionResult(const NamedCollection & named_collection, bool require_table = true); + static Configuration processNamedCollectionResult(const NamedCollection & named_collection, ContextPtr context_, bool require_table = true); static ColumnsDescription getTableStructureFromData( const postgres::PoolWithFailoverPtr & pool_, diff --git a/tests/integration/test_storage_postgresql/configs/named_collections.xml b/tests/integration/test_storage_postgresql/configs/named_collections.xml index 129225f36b9..ebe9f7ce9ce 100644 --- a/tests/integration/test_storage_postgresql/configs/named_collections.xml +++ b/tests/integration/test_storage_postgresql/configs/named_collections.xml @@ -16,8 +16,7 @@ postgres mysecretpassword - postgres1 - 1111 + postgres1:1111 postgres test_table
From 028cd8955bacfc7e6345cb500c61ff8f135a7352 Mon Sep 17 00:00:00 2001 From: zhanglistar Date: Fri, 15 Dec 2023 15:19:05 +0800 Subject: [PATCH 048/253] function if trival opt. --- src/Functions/if.cpp | 59 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/Functions/if.cpp b/src/Functions/if.cpp index eba1733c683..320210288df 100644 --- a/src/Functions/if.cpp +++ b/src/Functions/if.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -55,24 +56,48 @@ inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const Arr { size_t a_index = 0, b_index = 0; for (size_t i = 0; i < size; ++i) - res[i] = cond[i] ? static_cast(a[a_index++]) : static_cast(b[b_index++]); + { + if constexpr (std::is_integral_v) + { + res[i] = cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b[b_index]); + a_index += cond[i]; + b_index += !cond[i]; + } + else + res[i] = cond[i] ? static_cast(a[a_index++]) : static_cast(b[b_index++]); + } } else if (a_is_short) { size_t a_index = 0; for (size_t i = 0; i < size; ++i) - res[i] = cond[i] ? static_cast(a[a_index++]) : static_cast(b[i]); + if constexpr (std::is_integral_v) + { + res[i] = cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b[i]); + a_index += cond[i]; + } + else + res[i] = cond[i] ? static_cast(a[a_index++]) : static_cast(b[i]); } else if (b_is_short) { size_t b_index = 0; for (size_t i = 0; i < size; ++i) - res[i] = cond[i] ? static_cast(a[i]) : static_cast(b[b_index++]); + if constexpr (std::is_integral_v) + { + res[i] = cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b[b_index]); + b_index += !cond[i]; + } + else + res[i] = cond[i] ? static_cast(a[i]) : static_cast(b[b_index++]); } else { for (size_t i = 0; i < size; ++i) - res[i] = cond[i] ? static_cast(a[i]) : static_cast(b[i]); + if constexpr (std::is_integral_v) + res[i] = cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b[i]); + else + res[i] = cond[i] ? static_cast(a[i]) : static_cast(b[i]); } } @@ -85,12 +110,21 @@ inline void fillVectorConstant(const ArrayCond & cond, const ArrayA & a, B b, Ar { size_t a_index = 0; for (size_t i = 0; i < size; ++i) - res[i] = cond[i] ? static_cast(a[a_index++]) : static_cast(b); + if constexpr (std::is_integral_v) + { + res[i] = cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b); + a_index += cond[i]; + } + else + res[i] = cond[i] ? static_cast(a[a_index++]) : static_cast(b); } else { for (size_t i = 0; i < size; ++i) - res[i] = cond[i] ? static_cast(a[i]) : static_cast(b); + if constexpr (std::is_integral_v) + res[i] = cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b); + else + res[i] = cond[i] ? static_cast(a[i]) : static_cast(b); } } @@ -103,12 +137,21 @@ inline void fillConstantVector(const ArrayCond & cond, A a, const ArrayB & b, Ar { size_t b_index = 0; for (size_t i = 0; i < size; ++i) - res[i] = cond[i] ? static_cast(a) : static_cast(b[b_index++]); + if constexpr (std::is_integral_v) + { + res[i] = cond[i] * static_cast(a) + (!cond[i]) * static_cast(b_index); + b_index += !cond[i]; + } + else + res[i] = cond[i] ? static_cast(a) : static_cast(b[b_index++]); } else { for (size_t i = 0; i < size; ++i) - res[i] = cond[i] ? static_cast(a) : static_cast(b[i]); + if constexpr (std::is_integral_v) + res[i] = cond[i] * static_cast(a) + (!cond[i]) * static_cast(b[i]); + else + res[i] = cond[i] ? static_cast(a) : static_cast(b[i]); } } From 16c7c1e3080559f140503002da475a301d9b6857 Mon Sep 17 00:00:00 2001 From: vdimir Date: Fri, 15 Dec 2023 10:57:14 +0000 Subject: [PATCH 049/253] fix --- src/Interpreters/JoinUtils.cpp | 4 +- src/Interpreters/TableJoin.cpp | 6 +- .../01049_join_low_card_bug_long.reference.j2 | 124 +++++++++--------- 3 files changed, 66 insertions(+), 68 deletions(-) diff --git a/src/Interpreters/JoinUtils.cpp b/src/Interpreters/JoinUtils.cpp index 511ecdd783e..6bd202a1dd7 100644 --- a/src/Interpreters/JoinUtils.cpp +++ b/src/Interpreters/JoinUtils.cpp @@ -474,8 +474,8 @@ void addDefaultValues(IColumn & column, const DataTypePtr & type, size_t count) bool typesEqualUpToNullability(DataTypePtr left_type, DataTypePtr right_type) { - DataTypePtr left_type_strict = removeNullable(left_type); - DataTypePtr right_type_strict = removeNullable(right_type); + DataTypePtr left_type_strict = removeNullable(removeLowCardinality(left_type)); + DataTypePtr right_type_strict = removeNullable(removeLowCardinality(right_type)); return left_type_strict->equals(*right_type_strict); } diff --git a/src/Interpreters/TableJoin.cpp b/src/Interpreters/TableJoin.cpp index 066bdc419ef..5f3492f0871 100644 --- a/src/Interpreters/TableJoin.cpp +++ b/src/Interpreters/TableJoin.cpp @@ -34,6 +34,7 @@ #include #include +#include namespace DB { @@ -617,10 +618,7 @@ void TableJoin::inferJoinKeyCommonType(const LeftNamesAndTypes & left, const Rig { /// FullSortingMerge and PartialMerge join algorithms don't support joining keys with different types /// (e.g. String and LowCardinality(String)) - bool require_strict_keys_match = isEnabledAlgorithm(JoinAlgorithm::FULL_SORTING_MERGE) - || isEnabledAlgorithm(JoinAlgorithm::PARTIAL_MERGE) - || isEnabledAlgorithm(JoinAlgorithm::PREFER_PARTIAL_MERGE) - || isEnabledAlgorithm(JoinAlgorithm::AUTO); + bool require_strict_keys_match = isEnabledAlgorithm(JoinAlgorithm::FULL_SORTING_MERGE); if (!left_type_map.empty() || !right_type_map.empty()) return; diff --git a/tests/queries/0_stateless/01049_join_low_card_bug_long.reference.j2 b/tests/queries/0_stateless/01049_join_low_card_bug_long.reference.j2 index 373edb3b19d..2ebe5c373b2 100644 --- a/tests/queries/0_stateless/01049_join_low_card_bug_long.reference.j2 +++ b/tests/queries/0_stateless/01049_join_low_card_bug_long.reference.j2 @@ -37,30 +37,30 @@ SELECT lc, toTypeName(lc) FROM l_lc AS l RIGHT JOIN r USING (x) ORDER BY x, lc; str LowCardinality(String) LowCardinality(String) SELECT lc, toTypeName(lc) FROM l_lc AS l RIGHT JOIN r USING (lc) ORDER BY x, lc; -str String -str_r String +str LowCardinality(String) +str_r LowCardinality(String) SELECT lc, toTypeName(lc) FROM l_lc AS l FULL JOIN r USING (x) ORDER BY x, lc; str LowCardinality(String) LowCardinality(String) str_l LowCardinality(String) SELECT lc, toTypeName(lc) FROM l_lc AS l FULL JOIN r USING (lc) ORDER BY x, lc; -str String -str_r String -str_l String +str LowCardinality(String) +str_r LowCardinality(String) +str_l LowCardinality(String) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN r USING (x) ORDER BY x, r.lc, l.lc; String String str str LowCardinality(String) LowCardinality(String) str str String String str_r str_r LowCardinality(String) LowCardinality(String) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN r USING (lc) ORDER BY x, r.lc, l.lc; -String String str str String String str str -String String str_r str_r String String +String String str str LowCardinality(String) LowCardinality(String) str str +String String str_r str_r LowCardinality(String) LowCardinality(String) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN r USING (x) ORDER BY x, r.lc, l.lc; String String str str LowCardinality(String) LowCardinality(String) str str String String str_r str_r LowCardinality(String) LowCardinality(String) String String LowCardinality(String) LowCardinality(String) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN r USING (lc) ORDER BY x, r.lc, l.lc; -String String str str String String str str -String String str_r str_r String String -String String String String str_l str_l +String String str str LowCardinality(String) LowCardinality(String) str str +String String str_r str_r LowCardinality(String) LowCardinality(String) +String String LowCardinality(String) LowCardinality(String) str_l str_l -- SELECT lc, toTypeName(lc) FROM l RIGHT JOIN r USING (x) ORDER BY x, lc; @@ -81,46 +81,46 @@ SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), LowCardinality(String) LowCardinality(String) str str String String str str LowCardinality(String) LowCardinality(String) str_r str_r String String SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l RIGHT JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -String String str str String String str str -String String str_r str_r String String +LowCardinality(String) LowCardinality(String) str str String String str str +LowCardinality(String) LowCardinality(String) str_r str_r String String SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l FULL JOIN r_lc AS r USING (x) ORDER BY x, r.lc, l.lc; LowCardinality(String) LowCardinality(String) str str String String str str LowCardinality(String) LowCardinality(String) str_r str_r String String LowCardinality(String) LowCardinality(String) String String str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l FULL JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -String String str str String String str str -String String str_r str_r String String -String String String String str_l str_l +LowCardinality(String) LowCardinality(String) str str String String str str +LowCardinality(String) LowCardinality(String) str_r str_r String String +LowCardinality(String) LowCardinality(String) String String str_l str_l -- SELECT lc, toTypeName(lc) FROM l_lc RIGHT JOIN nr USING (x) ORDER BY x, lc; str LowCardinality(String) LowCardinality(String) SELECT lc, toTypeName(lc) FROM l_lc RIGHT JOIN nr USING (lc) ORDER BY x, lc; -str Nullable(String) -str_r Nullable(String) +str LowCardinality(String) +str_r LowCardinality(String) SELECT lc, toTypeName(lc) FROM l_lc FULL JOIN nr USING (x) ORDER BY x, lc; str LowCardinality(String) LowCardinality(String) str_l LowCardinality(String) SELECT lc, toTypeName(lc) FROM l_lc FULL JOIN nr USING (lc) ORDER BY x, lc; -str Nullable(String) -str_r Nullable(String) -str_l Nullable(String) +str LowCardinality(String) +str_r LowCardinality(String) +str_l LowCardinality(String) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN nr AS r USING (x) ORDER BY x, r.lc, l.lc; Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str_r str_r LowCardinality(String) LowCardinality(String) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN nr AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str -Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N +Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str str +Nullable(String) Nullable(String) str_r str_r LowCardinality(String) LowCardinality(String) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN nr AS r USING (x) ORDER BY x, r.lc, l.lc; Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str_r str_r LowCardinality(String) LowCardinality(String) Nullable(String) Nullable(String) \N \N LowCardinality(String) LowCardinality(String) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN nr AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str -Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N -Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l +Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str str +Nullable(String) Nullable(String) str_r str_r LowCardinality(String) LowCardinality(String) +Nullable(String) Nullable(String) \N \N LowCardinality(String) LowCardinality(String) str_l str_l -- SELECT lc, toTypeName(lc) FROM nl RIGHT JOIN r_lc USING (x) ORDER BY x, lc; @@ -141,16 +141,16 @@ SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l RIGHT JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str -Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N +LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str +LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l FULL JOIN r_lc AS r USING (x) ORDER BY x, r.lc, l.lc; LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N LowCardinality(String) LowCardinality(String) Nullable(String) Nullable(String) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l FULL JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str -Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l -Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N +LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str +LowCardinality(String) LowCardinality(String) Nullable(String) Nullable(String) str_l str_l +LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N SET join_use_nulls = 1; SELECT lc, toTypeName(lc) FROM l_lc AS l RIGHT JOIN r_lc AS r USING (x) ORDER BY x, lc; str LowCardinality(Nullable(String)) @@ -170,8 +170,8 @@ SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), LowCardinality(String) LowCardinality(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str LowCardinality(String) LowCardinality(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str -LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N +LowCardinality(String) LowCardinality(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str +LowCardinality(String) LowCardinality(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN r_lc AS r USING (x) ORDER BY x, r.lc, l.lc; LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N @@ -186,30 +186,30 @@ SELECT lc, toTypeName(lc) FROM l_lc AS l RIGHT JOIN r USING (x) ORDER BY x, lc; str LowCardinality(Nullable(String)) \N LowCardinality(Nullable(String)) SELECT lc, toTypeName(lc) FROM l_lc AS l RIGHT JOIN r USING (lc) ORDER BY x, lc; -str Nullable(String) -str_r Nullable(String) +str LowCardinality(Nullable(String)) +str_r LowCardinality(Nullable(String)) SELECT lc, toTypeName(lc) FROM l_lc AS l FULL JOIN r USING (x) ORDER BY x, lc; str LowCardinality(Nullable(String)) \N LowCardinality(Nullable(String)) str_l LowCardinality(Nullable(String)) SELECT lc, toTypeName(lc) FROM l_lc AS l FULL JOIN r USING (lc) ORDER BY x, lc; -str Nullable(String) -str_l Nullable(String) -str_r Nullable(String) +str LowCardinality(Nullable(String)) +str_l LowCardinality(Nullable(String)) +str_r LowCardinality(Nullable(String)) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN r USING (x) ORDER BY x, r.lc, l.lc; String String str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str String String str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN r USING (lc) ORDER BY x, r.lc, l.lc; -String String str str Nullable(String) Nullable(String) str str -String String str_r str_r Nullable(String) Nullable(String) \N \N +String String str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str +String String str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN r USING (x) ORDER BY x, r.lc, l.lc; Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str -Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l -Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N +Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str +Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_l str_l +Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N -- SELECT lc, toTypeName(lc) FROM l RIGHT JOIN r USING (x) ORDER BY x, lc; @@ -230,46 +230,46 @@ SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l RIGHT JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -String String str str Nullable(String) Nullable(String) str str -String String str_r str_r Nullable(String) Nullable(String) \N \N +LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str +LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l FULL JOIN r_lc AS r USING (x) ORDER BY x, r.lc, l.lc; LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l FULL JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str -Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l -Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N +LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str str +LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) str_l str_l +LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r Nullable(String) Nullable(String) \N \N -- SELECT lc, toTypeName(lc) FROM l_lc RIGHT JOIN nr USING (x) ORDER BY x, lc; str LowCardinality(Nullable(String)) \N LowCardinality(Nullable(String)) SELECT lc, toTypeName(lc) FROM l_lc RIGHT JOIN nr USING (lc) ORDER BY x, lc; -str Nullable(String) -str_r Nullable(String) +str LowCardinality(Nullable(String)) +str_r LowCardinality(Nullable(String)) SELECT lc, toTypeName(lc) FROM l_lc FULL JOIN nr USING (x) ORDER BY x, lc; str LowCardinality(Nullable(String)) \N LowCardinality(Nullable(String)) str_l LowCardinality(Nullable(String)) SELECT lc, toTypeName(lc) FROM l_lc FULL JOIN nr USING (lc) ORDER BY x, lc; -str Nullable(String) -str_l Nullable(String) -str_r Nullable(String) +str LowCardinality(Nullable(String)) +str_l LowCardinality(Nullable(String)) +str_r LowCardinality(Nullable(String)) SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN nr AS r USING (x) ORDER BY x, r.lc, l.lc; Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l RIGHT JOIN nr AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str -Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N +Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str +Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN nr AS r USING (x) ORDER BY x, r.lc, l.lc; Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM l_lc AS l FULL JOIN nr AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str -Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l -Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N +Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str +Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_l str_l +Nullable(String) Nullable(String) str_r str_r LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N -- SELECT lc, toTypeName(lc) FROM nl RIGHT JOIN r_lc USING (x) ORDER BY x, lc; @@ -290,16 +290,16 @@ SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l RIGHT JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str -Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N +LowCardinality(String) LowCardinality(String) str str Nullable(String) Nullable(String) str str +LowCardinality(String) LowCardinality(String) str_r str_r Nullable(String) Nullable(String) \N \N SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l FULL JOIN r_lc AS r USING (x) ORDER BY x, r.lc, l.lc; LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str str LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r Nullable(String) Nullable(String) \N \N LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) str_l str_l SELECT toTypeName(r.lc), toTypeName(materialize(r.lc)), r.lc, materialize(r.lc), toTypeName(l.lc), toTypeName(materialize(l.lc)), l.lc, materialize(l.lc) FROM nl AS l FULL JOIN r_lc AS r USING (lc) ORDER BY x, r.lc, l.lc; -Nullable(String) Nullable(String) str str Nullable(String) Nullable(String) str str -Nullable(String) Nullable(String) \N \N Nullable(String) Nullable(String) str_l str_l -Nullable(String) Nullable(String) str_r str_r Nullable(String) Nullable(String) \N \N +LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str str Nullable(String) Nullable(String) str str +LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) \N \N Nullable(String) Nullable(String) str_l str_l +LowCardinality(Nullable(String)) LowCardinality(Nullable(String)) str_r str_r Nullable(String) Nullable(String) \N \N {% endfor -%} SELECT '--'; -- From 0dcb3133c7591d81a9901c879699fd836dca7e08 Mon Sep 17 00:00:00 2001 From: kssenii Date: Fri, 15 Dec 2023 13:54:58 +0100 Subject: [PATCH 050/253] Allow to dynamically change fs cache size --- src/Interpreters/Cache/FileCache.cpp | 76 +++++++++++++----- src/Interpreters/Cache/FileCacheFactory.cpp | 32 +++++--- src/Interpreters/Cache/FileCacheFactory.h | 2 + src/Interpreters/Cache/IFileCachePriority.h | 10 ++- .../Cache/LRUFileCachePriority.cpp | 52 +++++++++++- src/Interpreters/Cache/LRUFileCachePriority.h | 2 + src/Interpreters/Cache/Metadata.cpp | 7 +- .../Cache/SLRUFileCachePriority.cpp | 22 +++++- .../Cache/SLRUFileCachePriority.h | 5 +- tests/config/config.d/storage_conf.xml | 11 +++ ...lly_change_filesystem_cache_size.reference | 20 +++++ ...ynamically_change_filesystem_cache_size.sh | 79 +++++++++++++++++++ 12 files changed, 272 insertions(+), 46 deletions(-) create mode 100644 tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference create mode 100755 tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh diff --git a/src/Interpreters/Cache/FileCache.cpp b/src/Interpreters/Cache/FileCache.cpp index b6a3a0dadc5..0e799df1615 100644 --- a/src/Interpreters/Cache/FileCache.cpp +++ b/src/Interpreters/Cache/FileCache.cpp @@ -707,7 +707,7 @@ KeyMetadata::iterator FileCache::addFileSegment( stash_records.emplace( stash_key, stash->queue->add(locked_key.getKeyMetadata(), offset, 0, *lock)); - if (stash->queue->getElementsCount(*lock) > stash->queue->getElementsLimit()) + if (stash->queue->getElementsCount(*lock) > stash->queue->getElementsLimit(*lock)) stash->queue->pop(*lock); result_state = FileSegment::State::DETACHED; @@ -748,7 +748,7 @@ bool FileCache::tryReserve(FileSegment & file_segment, const size_t size, FileCa LOG_TEST( log, "Trying to reserve space ({} bytes) for {}:{}, current usage {}/{}", size, file_segment.key(), file_segment.offset(), - main_priority->getSize(cache_lock), main_priority->getSizeLimit()); + main_priority->getSize(cache_lock), main_priority->getSizeLimit(cache_lock)); /// In case of per query cache limit (by default disabled), we add/remove entries from both /// (main_priority and query_priority) priority queues, but iterate entries in order of query_priority, @@ -760,7 +760,7 @@ bool FileCache::tryReserve(FileSegment & file_segment, const size_t size, FileCa { query_priority = &query_context->getPriority(); - const bool query_limit_exceeded = query_priority->getSize(cache_lock) + size > query_priority->getSizeLimit(); + const bool query_limit_exceeded = query_priority->getSize(cache_lock) + size > query_priority->getSizeLimit(cache_lock); if (query_limit_exceeded && !query_context->recacheOnFileCacheQueryLimitExceeded()) { LOG_TEST(log, "Query limit exceeded, space reservation failed, " @@ -771,7 +771,7 @@ bool FileCache::tryReserve(FileSegment & file_segment, const size_t size, FileCa LOG_TEST( log, "Using query limit, current usage: {}/{} (while reserving for {}:{})", - query_priority->getSize(cache_lock), query_priority->getSizeLimit(), + query_priority->getSize(cache_lock), query_priority->getSizeLimit(cache_lock), file_segment.key(), file_segment.offset()); } @@ -1057,9 +1057,11 @@ void FileCache::loadMetadataForKeys(const fs::path & keys_dir) bool limits_satisfied; IFileCachePriority::IteratorPtr cache_it; + size_t size_limit = 0; { auto lock = lockCache(); + size_limit = main_priority->getSizeLimit(lock); limits_satisfied = main_priority->canFit(size, lock); if (limits_satisfied) @@ -1109,7 +1111,7 @@ void FileCache::loadMetadataForKeys(const fs::path & keys_dir) log, "Cache capacity changed (max size: {}), " "cached file `{}` does not fit in cache anymore (size: {})", - main_priority->getSizeLimit(), offset_it->path().string(), size); + size_limit, offset_it->path().string(), size); fs::remove(offset_it->path()); } @@ -1215,7 +1217,8 @@ void FileCache::applySettingsIfPossible(const FileCacheSettings & new_settings, std::lock_guard lock(apply_settings_mutex); - if (metadata.setBackgroundDownloadQueueSizeLimit(new_settings.background_download_queue_size_limit)) + if (new_settings.background_download_queue_size_limit != actual_settings.background_download_queue_size_limit + && metadata.setBackgroundDownloadQueueSizeLimit(new_settings.background_download_queue_size_limit)) { LOG_INFO(log, "Changed background_download_queue_size from {} to {}", actual_settings.background_download_queue_size_limit, @@ -1224,24 +1227,57 @@ void FileCache::applySettingsIfPossible(const FileCacheSettings & new_settings, actual_settings.background_download_queue_size_limit = new_settings.background_download_queue_size_limit; } - bool updated; - try + if (new_settings.background_download_threads != actual_settings.background_download_threads) { - updated = metadata.setBackgroundDownloadThreads(new_settings.background_download_threads); - } - catch (...) - { - actual_settings.background_download_threads = metadata.getBackgroundDownloadThreads(); - throw; + bool updated = false; + try + { + updated = metadata.setBackgroundDownloadThreads(new_settings.background_download_threads); + } + catch (...) + { + actual_settings.background_download_threads = metadata.getBackgroundDownloadThreads(); + throw; + } + + if (updated) + { + LOG_INFO(log, "Changed background_download_threads from {} to {}", + actual_settings.background_download_threads, + new_settings.background_download_threads); + + actual_settings.background_download_threads = new_settings.background_download_threads; + } } - if (updated) - { - LOG_INFO(log, "Changed background_download_threads from {} to {}", - actual_settings.background_download_threads, - new_settings.background_download_threads); - actual_settings.background_download_threads = new_settings.background_download_threads; + if (new_settings.max_size != actual_settings.max_size + || new_settings.max_elements != actual_settings.max_elements) + { + auto cache_lock = lockCache(); + + bool updated = false; + try + { + updated = main_priority->modifySizeLimits( + new_settings.max_size, new_settings.max_elements, new_settings.slru_size_ratio, cache_lock); + } + catch (...) + { + actual_settings.max_size = main_priority->getSizeLimit(cache_lock); + actual_settings.max_elements = main_priority->getElementsLimit(cache_lock); + throw; + } + + if (updated) + { + LOG_INFO(log, "Changed max_size from {} to {}, max_elements from {} to {}", + actual_settings.max_size, new_settings.max_size, + actual_settings.max_elements, new_settings.max_elements); + + actual_settings.max_size = main_priority->getSizeLimit(cache_lock); + actual_settings.max_elements = main_priority->getElementsLimit(cache_lock); + } } } diff --git a/src/Interpreters/Cache/FileCacheFactory.cpp b/src/Interpreters/Cache/FileCacheFactory.cpp index 9ba96de26dc..84eafde9afd 100644 --- a/src/Interpreters/Cache/FileCacheFactory.cpp +++ b/src/Interpreters/Cache/FileCacheFactory.cpp @@ -25,6 +25,12 @@ FileCacheSettings FileCacheFactory::FileCacheData::getSettings() const return settings; } +void FileCacheFactory::FileCacheData::setSettings(const FileCacheSettings & new_settings) +{ + std::lock_guard lock(settings_mutex); + settings = new_settings; +} + FileCacheFactory & FileCacheFactory::instance() { static FileCacheFactory ret; @@ -100,21 +106,23 @@ void FileCacheFactory::updateSettingsFromConfig(const Poco::Util::AbstractConfig FileCacheSettings new_settings; new_settings.loadFromConfig(config, cache_info->config_path); - FileCacheSettings old_settings; - { - std::lock_guard lock(cache_info->settings_mutex); - if (new_settings == cache_info->settings) - continue; + FileCacheSettings old_settings = cache_info->getSettings(); + if (old_settings == new_settings) + continue; - old_settings = cache_info->settings; + try + { + cache_info->cache->applySettingsIfPossible(new_settings, old_settings); + } + catch (...) + { + /// Settings changes could be partially applied in case of exception, + /// make sure cache_info->settings show correct state of applied settings. + cache_info->setSettings(old_settings); + throw; } - cache_info->cache->applySettingsIfPossible(new_settings, old_settings); - - { - std::lock_guard lock(cache_info->settings_mutex); - cache_info->settings = old_settings; - } + cache_info->setSettings(old_settings); } } diff --git a/src/Interpreters/Cache/FileCacheFactory.h b/src/Interpreters/Cache/FileCacheFactory.h index 2148e520fd1..c60b247005b 100644 --- a/src/Interpreters/Cache/FileCacheFactory.h +++ b/src/Interpreters/Cache/FileCacheFactory.h @@ -24,6 +24,8 @@ public: FileCacheSettings getSettings() const; + void setSettings(const FileCacheSettings & new_settings); + const FileCachePtr cache; const std::string config_path; diff --git a/src/Interpreters/Cache/IFileCachePriority.h b/src/Interpreters/Cache/IFileCachePriority.h index 0f407a3082c..52de36849ae 100644 --- a/src/Interpreters/Cache/IFileCachePriority.h +++ b/src/Interpreters/Cache/IFileCachePriority.h @@ -55,9 +55,9 @@ public: virtual ~IFileCachePriority() = default; - size_t getElementsLimit() const { return max_elements; } + size_t getElementsLimit(const CacheGuard::Lock &) const { return max_elements; } - size_t getSizeLimit() const { return max_size; } + size_t getSizeLimit(const CacheGuard::Lock &) const { return max_size; } virtual size_t getSize(const CacheGuard::Lock &) const = 0; @@ -86,9 +86,11 @@ public: FinalizeEvictionFunc & finalize_eviction_func, const CacheGuard::Lock &) = 0; + virtual bool modifySizeLimits(size_t max_size_, size_t max_elements_, double size_ratio_, const CacheGuard::Lock &) = 0; + protected: - const size_t max_size = 0; - const size_t max_elements = 0; + size_t max_size = 0; + size_t max_elements = 0; }; } diff --git a/src/Interpreters/Cache/LRUFileCachePriority.cpp b/src/Interpreters/Cache/LRUFileCachePriority.cpp index a6abaea11c3..ba9d624ca9b 100644 --- a/src/Interpreters/Cache/LRUFileCachePriority.cpp +++ b/src/Interpreters/Cache/LRUFileCachePriority.cpp @@ -16,6 +16,9 @@ namespace ProfileEvents { extern const Event FilesystemCacheEvictionSkippedFileSegments; extern const Event FilesystemCacheEvictionTries; + extern const Event FilesystemCacheEvictMicroseconds; + extern const Event FilesystemCacheEvictedBytes; + extern const Event FilesystemCacheEvictedFileSegments; } namespace DB @@ -36,7 +39,7 @@ IFileCachePriority::IteratorPtr LRUFileCachePriority::add( /// NOLINT return std::make_shared(add(Entry(key_metadata->key, offset, size, key_metadata), lock)); } -LRUFileCachePriority::LRUIterator LRUFileCachePriority::add(Entry && entry, const CacheGuard::Lock &) +LRUFileCachePriority::LRUIterator LRUFileCachePriority::add(Entry && entry, const CacheGuard::Lock & lock) { if (entry.size == 0) { @@ -59,7 +62,7 @@ LRUFileCachePriority::LRUIterator LRUFileCachePriority::add(Entry && entry, cons } #endif - const auto & size_limit = getSizeLimit(); + const auto & size_limit = getSizeLimit(lock); if (size_limit && current_size + entry.size > size_limit) { throw Exception( @@ -288,6 +291,51 @@ std::vector LRUFileCachePriority::dump(FileCache & cache, const return res; } +bool LRUFileCachePriority::modifySizeLimits( + size_t max_size_, size_t max_elements_, double /* size_ratio_ */, const CacheGuard::Lock & lock) +{ + if (max_size == max_size_ && max_elements == max_elements_) + return false; /// Nothing to change. + + auto check_limits_satisfied = [&]() + { + return (max_size_ == 0 || current_size <= max_size_) + && (max_elements_ == 0 || current_elements_num <= max_elements_); + }; + + if (check_limits_satisfied()) + { + max_size = max_size_; + max_elements = max_elements_; + return true; + } + + auto iterate_func = [&](LockedKey & locked_key, const FileSegmentMetadataPtr & segment_metadata) + { + chassert(segment_metadata->file_segment->assertCorrectness()); + + if (!segment_metadata->releasable()) + return IterationResult::CONTINUE; + + auto segment = segment_metadata->file_segment; + locked_key.removeFileSegment(segment->offset(), segment->lock()); + + ProfileEvents::increment(ProfileEvents::FilesystemCacheEvictedFileSegments); + ProfileEvents::increment(ProfileEvents::FilesystemCacheEvictedBytes, segment->getDownloadedSize()); + return IterationResult::REMOVE_AND_CONTINUE; + }; + + auto timer = DB::CurrentThread::getProfileEvents().timer(ProfileEvents::FilesystemCacheEvictMicroseconds); + iterate( + [&](LockedKey & locked_key, const FileSegmentMetadataPtr & segment_metadata) + { return check_limits_satisfied() ? IterationResult::BREAK : iterate_func(locked_key, segment_metadata); }, + lock); + + max_size = max_size_; + max_elements = max_elements_; + return true; +} + void LRUFileCachePriority::LRUIterator::remove(const CacheGuard::Lock & lock) { assertValid(); diff --git a/src/Interpreters/Cache/LRUFileCachePriority.h b/src/Interpreters/Cache/LRUFileCachePriority.h index 5ff6c61eb4d..2450ecbff39 100644 --- a/src/Interpreters/Cache/LRUFileCachePriority.h +++ b/src/Interpreters/Cache/LRUFileCachePriority.h @@ -48,6 +48,8 @@ public: void pop(const CacheGuard::Lock & lock) { remove(queue.begin(), lock); } + bool modifySizeLimits(size_t max_size_, size_t max_elements_, double size_ratio_, const CacheGuard::Lock &) override; + private: void updateElementsCount(int64_t num); void updateSize(int64_t size); diff --git a/src/Interpreters/Cache/Metadata.cpp b/src/Interpreters/Cache/Metadata.cpp index 08c6151f1cb..f4aa7cc7ab1 100644 --- a/src/Interpreters/Cache/Metadata.cpp +++ b/src/Interpreters/Cache/Metadata.cpp @@ -681,7 +681,7 @@ void CacheMetadata::startup() download_threads.emplace_back(std::make_shared()); download_threads.back()->thread = std::make_unique([this, thread = download_threads.back()] { downloadThreadFunc(thread->stop_flag); }); } - cleanup_thread = std::make_unique(std::function{ [this]{ cleanupThreadFunc(); }}); + cleanup_thread = std::make_unique([this]{ cleanupThreadFunc(); }); } void CacheMetadata::shutdown() @@ -708,10 +708,10 @@ bool CacheMetadata::setBackgroundDownloadThreads(size_t threads_num) if (threads_num == download_threads_num) return false; + SCOPE_EXIT({ download_threads_num = download_threads.size(); }); + if (threads_num > download_threads_num) { - SCOPE_EXIT({ download_threads_num = download_threads.size(); }); - size_t add_threads = threads_num - download_threads_num; for (size_t i = 0; i < add_threads; ++i) { @@ -739,7 +739,6 @@ bool CacheMetadata::setBackgroundDownloadThreads(size_t threads_num) } download_queue->cv.notify_all(); - SCOPE_EXIT({ download_threads_num = download_threads.size(); }); for (size_t i = 0; i < remove_threads; ++i) { diff --git a/src/Interpreters/Cache/SLRUFileCachePriority.cpp b/src/Interpreters/Cache/SLRUFileCachePriority.cpp index 71b8d44d438..56af33b98f3 100644 --- a/src/Interpreters/Cache/SLRUFileCachePriority.cpp +++ b/src/Interpreters/Cache/SLRUFileCachePriority.cpp @@ -21,14 +21,15 @@ namespace SLRUFileCachePriority::SLRUFileCachePriority( size_t max_size_, size_t max_elements_, - double size_ratio) + double size_ratio_) : IFileCachePriority(max_size_, max_elements_) + , size_ratio(size_ratio_) , protected_queue(LRUFileCachePriority(getRatio(max_size_, size_ratio), getRatio(max_elements_, size_ratio))) , probationary_queue(LRUFileCachePriority(getRatio(max_size_, 1 - size_ratio), getRatio(max_elements_, 1 - size_ratio))) { LOG_DEBUG( log, "Using probationary queue size: {}, protected queue size: {}", - probationary_queue.getSizeLimit(), protected_queue.getSizeLimit()); + probationary_queue.max_size, protected_queue.max_elements); } size_t SLRUFileCachePriority::getSize(const CacheGuard::Lock & lock) const @@ -151,7 +152,7 @@ void SLRUFileCachePriority::increasePriority(SLRUIterator & iterator, const Cach /// Entry is in probationary queue. /// We need to move it to protected queue. const size_t size = iterator.getEntry().size; - if (size > protected_queue.getSizeLimit()) + if (size > protected_queue.getSizeLimit(lock)) { /// Entry size is bigger than the whole protected queue limit. /// This is only possible if protected_queue_size_limit is less than max_file_segment_size, @@ -235,6 +236,21 @@ void SLRUFileCachePriority::shuffle(const CacheGuard::Lock & lock) probationary_queue.shuffle(lock); } +bool SLRUFileCachePriority::modifySizeLimits( + size_t max_size_, size_t max_elements_, double size_ratio_, const CacheGuard::Lock & lock) +{ + if (max_size == max_size_ && max_elements == max_elements_ && size_ratio == size_ratio_) + return false; /// Nothing to change. + + protected_queue.modifySizeLimits(getRatio(max_size_, size_ratio_), getRatio(max_elements_, size_ratio_), 0, lock); + probationary_queue.modifySizeLimits(getRatio(max_size_, 1 - size_ratio_), getRatio(max_elements_, 1 - size_ratio_), 0, lock); + + max_size = max_size_; + max_elements = max_elements_; + size_ratio = size_ratio_; + return true; +} + SLRUFileCachePriority::SLRUIterator::SLRUIterator( SLRUFileCachePriority * cache_priority_, LRUFileCachePriority::LRUIterator && lru_iterator_, diff --git a/src/Interpreters/Cache/SLRUFileCachePriority.h b/src/Interpreters/Cache/SLRUFileCachePriority.h index 45fc7ad8333..373e37a8fc9 100644 --- a/src/Interpreters/Cache/SLRUFileCachePriority.h +++ b/src/Interpreters/Cache/SLRUFileCachePriority.h @@ -18,7 +18,7 @@ private: public: class SLRUIterator; - SLRUFileCachePriority(size_t max_size_, size_t max_elements_, double size_ratio); + SLRUFileCachePriority(size_t max_size_, size_t max_elements_, double size_ratio_); size_t getSize(const CacheGuard::Lock & lock) const override; @@ -45,7 +45,10 @@ public: std::vector dump(FileCache & cache, const CacheGuard::Lock &) override; + bool modifySizeLimits(size_t max_size_, size_t max_elements_, double size_ratio_, const CacheGuard::Lock &) override; + private: + double size_ratio; LRUFileCachePriority protected_queue; LRUFileCachePriority probationary_queue; Poco::Logger * log = &Poco::Logger::get("SLRUFileCachePriority"); diff --git a/tests/config/config.d/storage_conf.xml b/tests/config/config.d/storage_conf.xml index 18652826d83..72248f668a7 100644 --- a/tests/config/config.d/storage_conf.xml +++ b/tests/config/config.d/storage_conf.xml @@ -29,6 +29,17 @@ 0 0 + + cache + s3disk + s3_cache_02944/ + 100 + 100 + 10 + 10 + 100 + 0 + local_blob_storage diff --git a/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference new file mode 100644 index 00000000000..7fa32ec2b09 --- /dev/null +++ b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference @@ -0,0 +1,20 @@ +100 10 10 10 0 0 98 10 /var/lib/clickhouse/caches/s3_cache_02944/ 5 5000 0 1 +0 +10 +98 +set max_size from 100 to 10 +10 10 10 10 0 0 8 1 /var/lib/clickhouse/caches/s3_cache_02944/ 5 5000 0 1 +1 +8 +set max_size from 10 to 100 +100 10 10 10 0 0 8 1 /var/lib/clickhouse/caches/s3_cache_02944/ 5 5000 0 1 +10 +98 +set max_elements from 10 to 2 +100 2 10 10 0 0 18 2 /var/lib/clickhouse/caches/s3_cache_02944/ 5 5000 0 1 +2 +18 +set max_elements from 2 to 10 +100 10 10 10 0 0 18 2 /var/lib/clickhouse/caches/s3_cache_02944/ 5 5000 0 1 +10 +98 diff --git a/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh new file mode 100755 index 00000000000..021493eaa82 --- /dev/null +++ b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-parallel, no-s3-storage + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +disk_name="s3_cache_02944" + +$CLICKHOUSE_CLIENT --query "DESCRIBE FILESYSTEM CACHE '${disk_name}'" + +$CLICKHOUSE_CLIENT -nm --query " +DROP TABLE IF EXISTS test; +CREATE TABLE test (a String) engine=MergeTree() ORDER BY tuple() SETTINGS disk = '$disk_name'; +INSERT INTO test SELECT randomString(100); +SYSTEM DROP FILESYSTEM CACHE; +" + +$CLICKHOUSE_CLIENT --query "SELECT count() FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" + +$CLICKHOUSE_CLIENT --query "SELECT * FROM test FORMAT Null" + +$CLICKHOUSE_CLIENT --query "SELECT count() FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" +$CLICKHOUSE_CLIENT --query "SELECT sum(size) FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" + +config_path=/etc/clickhouse-server/config.d/storage_conf.xml +config_path_tmp=$config_path.tmp + +echo 'set max_size from 100 to 10' +cat $config_path \ +| sed "s|100<\/max_size>|10<\/max_size>|" \ +> $config_path_tmp +mv $config_path_tmp $config_path + +$CLICKHOUSE_CLIENT -nm --query "SYSTEM RELOAD CONFIG" +$CLICKHOUSE_CLIENT --query "DESCRIBE FILESYSTEM CACHE '${disk_name}'" + +$CLICKHOUSE_CLIENT --query "SELECT count() FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" +$CLICKHOUSE_CLIENT --query "SELECT sum(size) FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" + +echo 'set max_size from 10 to 100' +cat $config_path \ +| sed "s|10<\/max_size>|100<\/max_size>|" \ +> $config_path_tmp +mv $config_path_tmp $config_path + +$CLICKHOUSE_CLIENT -nm --query "SYSTEM RELOAD CONFIG" +$CLICKHOUSE_CLIENT --query "DESCRIBE FILESYSTEM CACHE '${disk_name}'" + +$CLICKHOUSE_CLIENT --query "SELECT * FROM test FORMAT Null" + +$CLICKHOUSE_CLIENT --query "SELECT count() FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" +$CLICKHOUSE_CLIENT --query "SELECT sum(size) FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" + +echo 'set max_elements from 10 to 2' +cat $config_path \ +| sed "s|10<\/max_elements>|2<\/max_elements>|" \ +> $config_path_tmp +mv $config_path_tmp $config_path + +$CLICKHOUSE_CLIENT -nm --query "SYSTEM RELOAD CONFIG" +$CLICKHOUSE_CLIENT --query "DESCRIBE FILESYSTEM CACHE '${disk_name}'" + +$CLICKHOUSE_CLIENT --query "SELECT count() FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" +$CLICKHOUSE_CLIENT --query "SELECT sum(size) FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" + +echo 'set max_elements from 2 to 10' +cat $config_path \ +| sed "s|2<\/max_elements>|10<\/max_elements>|" \ +> $config_path_tmp +mv $config_path_tmp $config_path + +$CLICKHOUSE_CLIENT -nm --query "SYSTEM RELOAD CONFIG" +$CLICKHOUSE_CLIENT --query "DESCRIBE FILESYSTEM CACHE '${disk_name}'" + +$CLICKHOUSE_CLIENT --query "SELECT * FROM test FORMAT Null" + +$CLICKHOUSE_CLIENT --query "SELECT count() FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" +$CLICKHOUSE_CLIENT --query "SELECT sum(size) FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" From e05cc51ef0a5ad61879919e37a5c9970f1e444f7 Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:17:30 +0100 Subject: [PATCH 051/253] Update storage_conf.xml --- tests/config/config.d/storage_conf.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/config/config.d/storage_conf.xml b/tests/config/config.d/storage_conf.xml index 72248f668a7..7a5caad9139 100644 --- a/tests/config/config.d/storage_conf.xml +++ b/tests/config/config.d/storage_conf.xml @@ -31,7 +31,7 @@ cache - s3disk + s3_disk s3_cache_02944/ 100 100 From e4a831be17f85e345c5c9cd8f00bd22b8e759692 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 15 Dec 2023 17:40:03 +0100 Subject: [PATCH 052/253] normalize function names in CREATE INDEX --- src/Interpreters/InterpreterCreateFunctionQuery.cpp | 2 ++ src/Interpreters/InterpreterCreateIndexQuery.cpp | 2 ++ .../02487_create_index_normalize_functions.reference | 0 .../0_stateless/02487_create_index_normalize_functions.sql | 6 ++++++ 4 files changed, 10 insertions(+) create mode 100644 tests/queries/0_stateless/02487_create_index_normalize_functions.reference create mode 100644 tests/queries/0_stateless/02487_create_index_normalize_functions.sql diff --git a/src/Interpreters/InterpreterCreateFunctionQuery.cpp b/src/Interpreters/InterpreterCreateFunctionQuery.cpp index 3e87f4fe440..a7b17b1cdcf 100644 --- a/src/Interpreters/InterpreterCreateFunctionQuery.cpp +++ b/src/Interpreters/InterpreterCreateFunctionQuery.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -19,6 +20,7 @@ namespace ErrorCodes BlockIO InterpreterCreateFunctionQuery::execute() { + FunctionNameNormalizer().visit(query_ptr.get()); const auto updated_query_ptr = removeOnClusterClauseIfNeeded(query_ptr, getContext()); ASTCreateFunctionQuery & create_function_query = updated_query_ptr->as(); diff --git a/src/Interpreters/InterpreterCreateIndexQuery.cpp b/src/Interpreters/InterpreterCreateIndexQuery.cpp index 3b47a002e50..ed29c82a0f0 100644 --- a/src/Interpreters/InterpreterCreateIndexQuery.cpp +++ b/src/Interpreters/InterpreterCreateIndexQuery.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ namespace ErrorCodes BlockIO InterpreterCreateIndexQuery::execute() { + FunctionNameNormalizer().visit(query_ptr.get()); auto current_context = getContext(); const auto & create_index = query_ptr->as(); diff --git a/tests/queries/0_stateless/02487_create_index_normalize_functions.reference b/tests/queries/0_stateless/02487_create_index_normalize_functions.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02487_create_index_normalize_functions.sql b/tests/queries/0_stateless/02487_create_index_normalize_functions.sql new file mode 100644 index 00000000000..2155f5d6665 --- /dev/null +++ b/tests/queries/0_stateless/02487_create_index_normalize_functions.sql @@ -0,0 +1,6 @@ + +create table rmt (n int, ts DateTime64(8, 'UTC')) engine=ReplicatedMergeTree('/test/02487/{database}/rmt', '1') order by n; +alter table rmt add index idx1 date(ts) TYPE MinMax GRANULARITY 1; +create index idx2 on rmt date(ts) TYPE MinMax GRANULARITY 1; +system restart replica rmt; +create table rmt2 (n int, ts DateTime64(8, 'UTC'), index idx1 date(ts) TYPE MinMax GRANULARITY 1, index idx2 date(ts) TYPE MinMax GRANULARITY 1) engine=ReplicatedMergeTree('/test/02487/{database}/rmt', '2') order by n; From efa5724b61d284bdc0455e402e748f52e0ba7996 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 16 Dec 2023 02:46:51 +0000 Subject: [PATCH 053/253] fix test --- tests/queries/0_stateless/02178_column_function_insert_from.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02178_column_function_insert_from.sql b/tests/queries/0_stateless/02178_column_function_insert_from.sql index dc7c134b6f9..fc692ec859c 100644 --- a/tests/queries/0_stateless/02178_column_function_insert_from.sql +++ b/tests/queries/0_stateless/02178_column_function_insert_from.sql @@ -9,7 +9,7 @@ INSERT INTO TESTTABLE values (0,'0',['1']), (1,'1',['1']); SET max_threads = 1; -- There is a bug which is fixed in new analyzer. -SET max_bytes_before_external_group_by = 0; +SET max_bytes_before_external_sort = 0; SELECT attr, _id, arrayFilter(x -> (x IN (select '1')), attr_list) z FROM TESTTABLE ARRAY JOIN z AS attr ORDER BY _id LIMIT 3 BY attr; From 01f5b205175530e7189901d6a87157e2fe8a07f3 Mon Sep 17 00:00:00 2001 From: zhongyuankai <872237106@qq.com> Date: Wed, 13 Dec 2023 15:42:43 +0800 Subject: [PATCH 054/253] Support ORDER BY ALL --- .../statements/select/order-by.md | 16 +++++++++ .../statements/select/order-by.md | 16 +++++++++ src/Interpreters/TreeRewriter.cpp | 20 +++++++++++ src/Parsers/ASTSelectQuery.cpp | 5 ++- src/Parsers/ASTSelectQuery.h | 1 + src/Parsers/ParserSelectQuery.cpp | 35 +++++++++++-------- .../0_stateless/02943_order_by_all.reference | 9 +++++ .../0_stateless/02943_order_by_all.sql | 15 ++++++++ 8 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 tests/queries/0_stateless/02943_order_by_all.reference create mode 100644 tests/queries/0_stateless/02943_order_by_all.sql diff --git a/docs/en/sql-reference/statements/select/order-by.md b/docs/en/sql-reference/statements/select/order-by.md index 53bdc9041a1..264d8ed323e 100644 --- a/docs/en/sql-reference/statements/select/order-by.md +++ b/docs/en/sql-reference/statements/select/order-by.md @@ -241,6 +241,22 @@ Result: └───┴─────────┘ ``` +## ORDER BY ALL + +`ORDER BY ALL` is sorts all selected columns in ascending order. + +For example: + +``` sql +SELECT a, b, c FROM t ORDER BY ALL +``` + +is the same as + +``` sql +SELECT a, b, c FROM t ORDER BY a, b, c +``` + ## Implementation Details Less RAM is used if a small enough [LIMIT](../../../sql-reference/statements/select/limit.md) is specified in addition to `ORDER BY`. Otherwise, the amount of memory spent is proportional to the volume of data for sorting. For distributed query processing, if [GROUP BY](../../../sql-reference/statements/select/group-by.md) is omitted, sorting is partially done on remote servers, and the results are merged on the requestor server. This means that for distributed sorting, the volume of data to sort can be greater than the amount of memory on a single server. diff --git a/docs/zh/sql-reference/statements/select/order-by.md b/docs/zh/sql-reference/statements/select/order-by.md index 01f702a4b1e..3286fc9f9e7 100644 --- a/docs/zh/sql-reference/statements/select/order-by.md +++ b/docs/zh/sql-reference/statements/select/order-by.md @@ -61,6 +61,22 @@ sidebar_label: ORDER BY 我们只建议使用 `COLLATE` 对于少量行的最终排序,因为排序与 `COLLATE` 比正常的按字节排序效率低。 +## ORDER BY ALL + +`ORDER BY ALL` 对所有选定的列进行升序排序。 + +示例: + +``` sql +SELECT a, b, c FROM t ORDER BY ALL +``` + +等同于: + +``` sql +SELECT a, b, c FROM t ORDER BY a, b, c +``` + ## 实现细节 {#implementation-details} 更少的RAM使用,如果一个足够小 [LIMIT](../../../sql-reference/statements/select/limit.md) 除了指定 `ORDER BY`. 否则,所花费的内存量与用于排序的数据量成正比。 对于分布式查询处理,如果 [GROUP BY](../../../sql-reference/statements/select/group-by.md) 省略排序,在远程服务器上部分完成排序,并将结果合并到请求者服务器上。 这意味着对于分布式排序,要排序的数据量可以大于单个服务器上的内存量。 diff --git a/src/Interpreters/TreeRewriter.cpp b/src/Interpreters/TreeRewriter.cpp index c63aae32090..62d3590f4e2 100644 --- a/src/Interpreters/TreeRewriter.cpp +++ b/src/Interpreters/TreeRewriter.cpp @@ -776,6 +776,22 @@ void expandGroupByAll(ASTSelectQuery * select_query) select_query->setExpression(ASTSelectQuery::Expression::GROUP_BY, group_expression_list); } +void expandOrderByAll(ASTSelectQuery * select_query) +{ + auto order_expression_list = std::make_shared(); + + for (const auto & expr : select_query->select()->children) + { + auto elem = std::make_shared(); + elem->direction = 1; + elem->nulls_direction = 1; + elem->children.push_back(expr); + order_expression_list->children.push_back(elem); + } + + select_query->setExpression(ASTSelectQuery::Expression::ORDER_BY, order_expression_list); +} + ASTs getAggregates(ASTPtr & query, const ASTSelectQuery & select_query) { /// There can not be aggregate functions inside the WHERE and PREWHERE. @@ -1292,6 +1308,10 @@ TreeRewriterResultPtr TreeRewriter::analyzeSelect( if (select_query->group_by_all) expandGroupByAll(select_query); + // expand ORDER BY ALL + if (select_query->order_by_all) + expandOrderByAll(select_query); + /// Remove unneeded columns according to 'required_result_columns'. /// Leave all selected columns in case of DISTINCT; columns that contain arrayJoin function inside. /// Must be after 'normalizeTree' (after expanding aliases, for aliases not get lost) diff --git a/src/Parsers/ASTSelectQuery.cpp b/src/Parsers/ASTSelectQuery.cpp index 7c96db006c4..d2c7de18c8a 100644 --- a/src/Parsers/ASTSelectQuery.cpp +++ b/src/Parsers/ASTSelectQuery.cpp @@ -144,7 +144,10 @@ void ASTSelectQuery::formatImpl(const FormatSettings & s, FormatState & state, F window()->as().formatImplMultiline(s, state, frame); } - if (orderBy()) + if (order_by_all) + s.ostr << (s.hilite ? hilite_keyword : "") << s.nl_or_ws << indent_str << "ORDER BY ALL" << (s.hilite ? hilite_none : ""); + + if (!order_by_all && orderBy()) { s.ostr << (s.hilite ? hilite_keyword : "") << s.nl_or_ws << indent_str << "ORDER BY" << (s.hilite ? hilite_none : ""); s.one_line diff --git a/src/Parsers/ASTSelectQuery.h b/src/Parsers/ASTSelectQuery.h index 57f45a8aacd..eb171dc00ee 100644 --- a/src/Parsers/ASTSelectQuery.h +++ b/src/Parsers/ASTSelectQuery.h @@ -87,6 +87,7 @@ public: bool group_by_with_cube = false; bool group_by_with_constant_keys = false; bool group_by_with_grouping_sets = false; + bool order_by_all = false; bool limit_with_ties = false; ASTPtr & refSelect() { return getExpression(Expression::SELECT); } diff --git a/src/Parsers/ParserSelectQuery.cpp b/src/Parsers/ParserSelectQuery.cpp index 341c1ef60b4..6c94eedc470 100644 --- a/src/Parsers/ParserSelectQuery.cpp +++ b/src/Parsers/ParserSelectQuery.cpp @@ -268,23 +268,30 @@ bool ParserSelectQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) /// ORDER BY expr ASC|DESC COLLATE 'locale' list if (s_order_by.ignore(pos, expected)) { - if (!order_list.parse(pos, order_expression_list, expected)) - return false; - - /// if any WITH FILL parse possible INTERPOLATE list - if (std::any_of(order_expression_list->children.begin(), order_expression_list->children.end(), - [](auto & child) { return child->template as()->with_fill; })) + if (s_all.ignore(pos, expected)) { - if (s_interpolate.ignore(pos, expected)) + select_query->order_by_all = true; + } + else + { + if (!order_list.parse(pos, order_expression_list, expected)) + return false; + + /// if any WITH FILL parse possible INTERPOLATE list + if (std::any_of(order_expression_list->children.begin(), order_expression_list->children.end(), + [](auto & child) { return child->template as()->with_fill; })) { - if (open_bracket.ignore(pos, expected)) + if (s_interpolate.ignore(pos, expected)) { - if (!interpolate_list.parse(pos, interpolate_expression_list, expected)) - return false; - if (!close_bracket.ignore(pos, expected)) - return false; - } else - interpolate_expression_list = std::make_shared(); + if (open_bracket.ignore(pos, expected)) + { + if (!interpolate_list.parse(pos, interpolate_expression_list, expected)) + return false; + if (!close_bracket.ignore(pos, expected)) + return false; + } else + interpolate_expression_list = std::make_shared(); + } } } } diff --git a/tests/queries/0_stateless/02943_order_by_all.reference b/tests/queries/0_stateless/02943_order_by_all.reference new file mode 100644 index 00000000000..f9dcebc156f --- /dev/null +++ b/tests/queries/0_stateless/02943_order_by_all.reference @@ -0,0 +1,9 @@ +abc1 3 2 +abc2 3 2 +abc3 2 3 +abc4 1 4 +abc1 1 1 +abc2 1 1 +abc3 1 1 +abc4 1 1 +abc 4 diff --git a/tests/queries/0_stateless/02943_order_by_all.sql b/tests/queries/0_stateless/02943_order_by_all.sql new file mode 100644 index 00000000000..1ddd8866533 --- /dev/null +++ b/tests/queries/0_stateless/02943_order_by_all.sql @@ -0,0 +1,15 @@ +DROP TABLE IF EXISTS order_by_all; + +CREATE TABLE order_by_all +( + a String, + b int, + c int +) +engine = Memory; + +insert into order_by_all values ('abc2', 3, 2), ('abc3', 2, 3), ('abc4', 1, 4), ('abc1', 3, 2); + +select a, b, c from order_by_all order by all; +select a, count(b), count(c) from order_by_all group by all order by all; +select substring(a, 1, 3), count(b) from order_by_all group by all order by all; From b0e17f8b999068818846553f1ea68c068ebf6b63 Mon Sep 17 00:00:00 2001 From: ubuntu <872237106@qq.com> Date: Fri, 15 Dec 2023 11:32:07 +0800 Subject: [PATCH 055/253] fix test --- .../0_stateless/02943_order_by_all.reference | 14 ++++++++------ tests/queries/0_stateless/02943_order_by_all.sql | 7 ++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/queries/0_stateless/02943_order_by_all.reference b/tests/queries/0_stateless/02943_order_by_all.reference index f9dcebc156f..9760e4732da 100644 --- a/tests/queries/0_stateless/02943_order_by_all.reference +++ b/tests/queries/0_stateless/02943_order_by_all.reference @@ -1,9 +1,11 @@ abc1 3 2 +abc2 1 1 abc2 3 2 abc3 2 3 -abc4 1 4 -abc1 1 1 -abc2 1 1 -abc3 1 1 -abc4 1 1 -abc 4 +1 abc1 1 +1 abc3 1 +2 abc2 2 +abc 1 1 +abc 2 3 +abc 3 2 +abc 3 2 diff --git a/tests/queries/0_stateless/02943_order_by_all.sql b/tests/queries/0_stateless/02943_order_by_all.sql index 1ddd8866533..1039203f4df 100644 --- a/tests/queries/0_stateless/02943_order_by_all.sql +++ b/tests/queries/0_stateless/02943_order_by_all.sql @@ -8,8 +8,9 @@ CREATE TABLE order_by_all ) engine = Memory; -insert into order_by_all values ('abc2', 3, 2), ('abc3', 2, 3), ('abc4', 1, 4), ('abc1', 3, 2); +insert into order_by_all values ('abc2', 3, 2), ('abc3', 2, 3), ('abc2', 1, 1), ('abc1', 3, 2); select a, b, c from order_by_all order by all; -select a, count(b), count(c) from order_by_all group by all order by all; -select substring(a, 1, 3), count(b) from order_by_all group by all order by all; +select count(b), a, count(c) from order_by_all group by all order by all; +select substring(a, 1, 3), b, c from order_by_all order by all; + From 2cfb543b33e0dc0854ac5026dda73574c0199617 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Fri, 15 Dec 2023 14:13:08 +0000 Subject: [PATCH 056/253] Doc updates --- .../statements/select/order-by.md | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/docs/en/sql-reference/statements/select/order-by.md b/docs/en/sql-reference/statements/select/order-by.md index 264d8ed323e..37e42f261fa 100644 --- a/docs/en/sql-reference/statements/select/order-by.md +++ b/docs/en/sql-reference/statements/select/order-by.md @@ -5,12 +5,20 @@ sidebar_label: ORDER BY # ORDER BY Clause -The `ORDER BY` clause contains a list of expressions, which can each be attributed with `DESC` (descending) or `ASC` (ascending) modifier which determine the sorting direction. If the direction is not specified, `ASC` is assumed, so it’s usually omitted. The sorting direction applies to a single expression, not to the entire list. Example: `ORDER BY Visits DESC, SearchPhrase`. Sorting is case-sensitive. +The `ORDER BY` clause contains -If you want to sort by column numbers instead of column names, enable the setting [enable_positional_arguments](../../../operations/settings/settings.md#enable-positional-arguments). +- a list of expressions, e.g. `ORDER BY visits, search_phrase`, +- a list of numbers referring to columns in the `SELECT` clause, e.g. `ORDER BY 2, 1`, or +- `ALL` which means all columns of the `SELECT` clause, e.g. `ORDER BY ALL`. -Rows that have identical values for the list of sorting expressions are output in an arbitrary order, which can also be non-deterministic (different each time). -If the ORDER BY clause is omitted, the order of the rows is also undefined, and may be non-deterministic as well. +To disable sorting by column numbers, set setting [enable_positional_arguments](../../../operations/settings/settings.md#enable-positional-arguments) = 0. + +Sort expressions or column numbers in `ORDER BY` can be attributed by a `DESC` (descending) or `ASC` (ascending) modifier which determine the sorting direction. +If no sort order is specified explicitly, `ASC` is used as default. +The sorting direction applies to a single expression, not to the entire list, e.g. `ORDER BY Visits DESC, SearchPhrase`. Sorting is performed case-sensitively. + +Rows with identical values for a sort expressions are returned in an arbitrary and non-deterministic order. +If the `ORDER BY` clause is omitted in a `SELECT` statement, the row order is also arbitrary and non-deterministic. ## Sorting of Special Values @@ -241,22 +249,6 @@ Result: └───┴─────────┘ ``` -## ORDER BY ALL - -`ORDER BY ALL` is sorts all selected columns in ascending order. - -For example: - -``` sql -SELECT a, b, c FROM t ORDER BY ALL -``` - -is the same as - -``` sql -SELECT a, b, c FROM t ORDER BY a, b, c -``` - ## Implementation Details Less RAM is used if a small enough [LIMIT](../../../sql-reference/statements/select/limit.md) is specified in addition to `ORDER BY`. Otherwise, the amount of memory spent is proportional to the volume of data for sorting. For distributed query processing, if [GROUP BY](../../../sql-reference/statements/select/group-by.md) is omitted, sorting is partially done on remote servers, and the results are merged on the requestor server. This means that for distributed sorting, the volume of data to sort can be greater than the amount of memory on a single server. From 5ccc5b4fd825887ba229395256739a79848dacf8 Mon Sep 17 00:00:00 2001 From: zhongyuankai <872237106@qq.com> Date: Sat, 16 Dec 2023 17:59:50 +0800 Subject: [PATCH 057/253] batter --- src/Core/Settings.h | 1 + src/Interpreters/TreeRewriter.cpp | 22 ++++++-- src/Parsers/ASTSelectQuery.cpp | 21 ++++++-- src/Parsers/ParserSelectQuery.cpp | 50 +++++++++++-------- .../0_stateless/02943_order_by_all.reference | 31 ++++++++---- .../0_stateless/02943_order_by_all.sql | 19 ++++--- 6 files changed, 99 insertions(+), 45 deletions(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 69efedf5d3e..9d09ff42395 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -845,6 +845,7 @@ class IColumn; M(UInt64, cache_warmer_threads, 4, "Only available in ClickHouse Cloud", 0) \ M(Int64, ignore_cold_parts_seconds, 0, "Only available in ClickHouse Cloud", 0) \ M(Int64, prefer_warmed_unmerged_parts_seconds, 0, "Only available in ClickHouse Cloud", 0) \ + M(Bool, enable_order_by_all, true, "Clause ORDER BY supports specifying ALL, sorts by all columns in the SELECT clause.", 0)\ // End of COMMON_SETTINGS // Please add settings related to formats into the FORMAT_FACTORY_SETTINGS, move obsolete settings to OBSOLETE_SETTINGS and obsolete format settings to OBSOLETE_FORMAT_SETTINGS. diff --git a/src/Interpreters/TreeRewriter.cpp b/src/Interpreters/TreeRewriter.cpp index 62d3590f4e2..27f0bf2502e 100644 --- a/src/Interpreters/TreeRewriter.cpp +++ b/src/Interpreters/TreeRewriter.cpp @@ -73,6 +73,7 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; extern const int UNKNOWN_IDENTIFIER; + extern const int UNEXPECTED_EXPRESSION; } namespace @@ -778,13 +779,28 @@ void expandGroupByAll(ASTSelectQuery * select_query) void expandOrderByAll(ASTSelectQuery * select_query) { + auto * all_elem = select_query->orderBy()->children[0]->as(); + if (!all_elem) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Select analyze for not order by asts."); + auto order_expression_list = std::make_shared(); for (const auto & expr : select_query->select()->children) { + if (auto * identifier = expr->as(); identifier) + if (Poco::toUpper(identifier->name()) == "ALL" || Poco::toUpper(identifier->alias) == "ALL") + throw Exception(ErrorCodes::UNEXPECTED_EXPRESSION, + "The column name (all/ALL) conflicts with `ORDER BY ALL`, try to disable setting `enable_order_by_all`."); + + if (auto * function = expr->as(); function) + if (Poco::toUpper(function->alias) == "ALL") + throw Exception(ErrorCodes::UNEXPECTED_EXPRESSION, + "The column name (all/ALL) conflicts with `ORDER BY ALL`, try to disable setting `enable_order_by_all`."); + auto elem = std::make_shared(); - elem->direction = 1; - elem->nulls_direction = 1; + elem->direction = all_elem->direction; + elem->nulls_direction = all_elem->nulls_direction; + elem->nulls_direction_was_explicitly_specified = all_elem->nulls_direction_was_explicitly_specified; elem->children.push_back(expr); order_expression_list->children.push_back(elem); } @@ -1309,7 +1325,7 @@ TreeRewriterResultPtr TreeRewriter::analyzeSelect( expandGroupByAll(select_query); // expand ORDER BY ALL - if (select_query->order_by_all) + if (settings.enable_order_by_all && select_query->order_by_all) expandOrderByAll(select_query); /// Remove unneeded columns according to 'required_result_columns'. diff --git a/src/Parsers/ASTSelectQuery.cpp b/src/Parsers/ASTSelectQuery.cpp index d2c7de18c8a..2115de1c124 100644 --- a/src/Parsers/ASTSelectQuery.cpp +++ b/src/Parsers/ASTSelectQuery.cpp @@ -144,9 +144,6 @@ void ASTSelectQuery::formatImpl(const FormatSettings & s, FormatState & state, F window()->as().formatImplMultiline(s, state, frame); } - if (order_by_all) - s.ostr << (s.hilite ? hilite_keyword : "") << s.nl_or_ws << indent_str << "ORDER BY ALL" << (s.hilite ? hilite_none : ""); - if (!order_by_all && orderBy()) { s.ostr << (s.hilite ? hilite_keyword : "") << s.nl_or_ws << indent_str << "ORDER BY" << (s.hilite ? hilite_none : ""); @@ -166,6 +163,24 @@ void ASTSelectQuery::formatImpl(const FormatSettings & s, FormatState & state, F } } + if (order_by_all) + { + s.ostr << (s.hilite ? hilite_keyword : "") << s.nl_or_ws << indent_str << "ORDER BY ALL" << (s.hilite ? hilite_none : ""); + + auto * elem = orderBy()->children[0]->as(); + s.ostr << (s.hilite ? hilite_keyword : "") + << (elem->direction == -1 ? " DESC" : " ASC") + << (s.hilite ? hilite_none : ""); + + if (elem->nulls_direction_was_explicitly_specified) + { + s.ostr << (s.hilite ? hilite_keyword : "") + << " NULLS " + << (elem->nulls_direction == elem->direction ? "LAST" : "FIRST") + << (s.hilite ? hilite_none : ""); + } + } + if (limitByLength()) { s.ostr << (s.hilite ? hilite_keyword : "") << s.nl_or_ws << indent_str << "LIMIT " << (s.hilite ? hilite_none : ""); diff --git a/src/Parsers/ParserSelectQuery.cpp b/src/Parsers/ParserSelectQuery.cpp index 6c94eedc470..911ed546293 100644 --- a/src/Parsers/ParserSelectQuery.cpp +++ b/src/Parsers/ParserSelectQuery.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace DB @@ -49,6 +50,13 @@ bool ParserSelectQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) ParserKeyword s_having("HAVING"); ParserKeyword s_window("WINDOW"); ParserKeyword s_order_by("ORDER BY"); + ParserKeyword ascending("ASCENDING"); + ParserKeyword descending("DESCENDING"); + ParserKeyword asc("ASC"); + ParserKeyword desc("DESC"); + ParserKeyword nulls("NULLS"); + ParserKeyword first("FIRST"); + ParserKeyword last("LAST"); ParserKeyword s_limit("LIMIT"); ParserKeyword s_settings("SETTINGS"); ParserKeyword s_by("BY"); @@ -268,32 +276,32 @@ bool ParserSelectQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) /// ORDER BY expr ASC|DESC COLLATE 'locale' list if (s_order_by.ignore(pos, expected)) { - if (s_all.ignore(pos, expected)) - { - select_query->order_by_all = true; - } - else - { - if (!order_list.parse(pos, order_expression_list, expected)) - return false; + if (!order_list.parse(pos, order_expression_list, expected)) + return false; - /// if any WITH FILL parse possible INTERPOLATE list - if (std::any_of(order_expression_list->children.begin(), order_expression_list->children.end(), - [](auto & child) { return child->template as()->with_fill; })) + /// if any WITH FILL parse possible INTERPOLATE list + if (std::any_of(order_expression_list->children.begin(), order_expression_list->children.end(), + [](auto & child) { return child->template as()->with_fill; })) + { + if (s_interpolate.ignore(pos, expected)) { - if (s_interpolate.ignore(pos, expected)) + if (open_bracket.ignore(pos, expected)) { - if (open_bracket.ignore(pos, expected)) - { - if (!interpolate_list.parse(pos, interpolate_expression_list, expected)) - return false; - if (!close_bracket.ignore(pos, expected)) - return false; - } else - interpolate_expression_list = std::make_shared(); - } + if (!interpolate_list.parse(pos, interpolate_expression_list, expected)) + return false; + if (!close_bracket.ignore(pos, expected)) + return false; + } else + interpolate_expression_list = std::make_shared(); } } + else if (order_expression_list->children.size() == 1) + { + /// ORDER BY ALL [ASC|DESC] [NULLS [FIRST|LAST]] + auto * identifier = order_expression_list->children[0]->as()->children[0]->as(); + if (Poco::toUpper(identifier->name()) == "ALL") + select_query->order_by_all = true; + } } /// This is needed for TOP expression, because it can also use WITH TIES. diff --git a/tests/queries/0_stateless/02943_order_by_all.reference b/tests/queries/0_stateless/02943_order_by_all.reference index 9760e4732da..1d573800619 100644 --- a/tests/queries/0_stateless/02943_order_by_all.reference +++ b/tests/queries/0_stateless/02943_order_by_all.reference @@ -1,11 +1,20 @@ -abc1 3 2 -abc2 1 1 -abc2 3 2 -abc3 2 3 -1 abc1 1 -1 abc3 1 -2 abc2 2 -abc 1 1 -abc 2 3 -abc 3 2 -abc 3 2 +A 3 +B 1 +B 3 +C \N +B 1 1 +A 3 2 +B 3 2 +C \N 3 +B 1 1 +B 3 2 +A 3 2 +C \N 3 +C \N +B 3 +B 1 +A 3 +\N C +1 B +3 A +3 B diff --git a/tests/queries/0_stateless/02943_order_by_all.sql b/tests/queries/0_stateless/02943_order_by_all.sql index 1039203f4df..856eff586a7 100644 --- a/tests/queries/0_stateless/02943_order_by_all.sql +++ b/tests/queries/0_stateless/02943_order_by_all.sql @@ -3,14 +3,19 @@ DROP TABLE IF EXISTS order_by_all; CREATE TABLE order_by_all ( a String, - b int, - c int + b Nullable(Int32), + all int, ) -engine = Memory; + engine = Memory; -insert into order_by_all values ('abc2', 3, 2), ('abc3', 2, 3), ('abc2', 1, 1), ('abc1', 3, 2); +INSERT INTO order_by_all VALUES ('B', 3, 2), ('C', NULL, 3), ('B', 1, 1), ('A', 3, 2); -select a, b, c from order_by_all order by all; -select count(b), a, count(c) from order_by_all group by all order by all; -select substring(a, 1, 3), b, c from order_by_all order by all; +SELECT a, b FROM order_by_all ORDER BY ALL; +SELECT a, b, all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b, all FROM order_by_all ORDER BY all, a; +SELECT a, b, all FROM order_by_all ORDER BY all settings enable_order_by_all = false; +SELECT a, b FROM order_by_all ORDER BY ALL DESC; +SELECT b, a FROM order_by_all ORDER BY ALL NULLS FIRST; + +DROP TABLE IF EXISTS order_by_all; From 5a3f0e7a5bf0fc97339428b4a29eac8d4c5c250d Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Sat, 16 Dec 2023 14:25:27 +0000 Subject: [PATCH 058/253] Forbid CREATE AS SELECT for Replicated storages only --- src/Interpreters/InterpreterCreateQuery.cpp | 10 +++++++++- ...2933_replicated_database_forbid_create_as_select.sh | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index bb8878fc2d5..d74076b5362 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -1254,7 +1254,15 @@ BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create) if (database && database->getEngineName() == "Replicated" && create.select) { - bool allow_create_select_for_replicated = create.isView() || create.is_create_empty; + bool is_storage_replicated = false; + if (create.storage && create.storage->engine) + { + const auto & storage_name = create.storage->engine->name; + if (storage_name.starts_with("Replicated") || storage_name.starts_with("Shared")) + is_storage_replicated = true; + } + + const bool allow_create_select_for_replicated = create.isView() || create.is_create_empty || !is_storage_replicated; if (!allow_create_select_for_replicated) throw Exception( ErrorCodes::SUPPORT_IS_DISABLED, diff --git a/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh b/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh index da0ca5183ce..4e44244fa9a 100755 --- a/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh +++ b/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh @@ -9,5 +9,8 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) . "$CURDIR"/../shell_config.sh ${CLICKHOUSE_CLIENT} --allow_experimental_database_replicated=1 --query "CREATE DATABASE ${CLICKHOUSE_DATABASE}_db engine = Replicated('/clickhouse/databases/${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}/${CLICKHOUSE_DATABASE}_db', '{shard}', '{replica}')" -${CLICKHOUSE_CLIENT} --query "CREATE TABLE ${CLICKHOUSE_DATABASE}_db.test (id UInt64) ENGINE = MergeTree() ORDER BY id AS SELECT 1" |& grep -cm1 "SUPPORT_IS_DISABLED" +# Non-replicated engines are allowed +${CLICKHOUSE_CLIENT} --distributed_ddl_output_mode=none --query "CREATE TABLE ${CLICKHOUSE_DATABASE}_db.test (id UInt64) ENGINE = MergeTree() ORDER BY id AS SELECT 1" +# Replicated storafes are forbidden +${CLICKHOUSE_CLIENT} --query "CREATE TABLE ${CLICKHOUSE_DATABASE}_db.test (id UInt64) ENGINE = ReplicatedMergeTree() ORDER BY id AS SELECT 1" |& grep -cm1 "SUPPORT_IS_DISABLED" ${CLICKHOUSE_CLIENT} --query "DROP DATABASE ${CLICKHOUSE_DATABASE}_db" From af2a46064734de2820f8efb42c0ddec0ef7df9a0 Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Sat, 16 Dec 2023 14:50:56 +0000 Subject: [PATCH 059/253] Fix style --- .../02933_replicated_database_forbid_create_as_select.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh b/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh index 4e44244fa9a..c295f5be43b 100755 --- a/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh +++ b/tests/queries/0_stateless/02933_replicated_database_forbid_create_as_select.sh @@ -12,5 +12,5 @@ ${CLICKHOUSE_CLIENT} --allow_experimental_database_replicated=1 --query "CREATE # Non-replicated engines are allowed ${CLICKHOUSE_CLIENT} --distributed_ddl_output_mode=none --query "CREATE TABLE ${CLICKHOUSE_DATABASE}_db.test (id UInt64) ENGINE = MergeTree() ORDER BY id AS SELECT 1" # Replicated storafes are forbidden -${CLICKHOUSE_CLIENT} --query "CREATE TABLE ${CLICKHOUSE_DATABASE}_db.test (id UInt64) ENGINE = ReplicatedMergeTree() ORDER BY id AS SELECT 1" |& grep -cm1 "SUPPORT_IS_DISABLED" +${CLICKHOUSE_CLIENT} --query "CREATE TABLE ${CLICKHOUSE_DATABASE}_db.test2 (id UInt64) ENGINE = ReplicatedMergeTree('/clickhouse/$CLICKHOUSE_TEST_ZOOKEEPER_PREFIX/test2', '1') ORDER BY id AS SELECT 1" |& grep -cm1 "SUPPORT_IS_DISABLED" ${CLICKHOUSE_CLIENT} --query "DROP DATABASE ${CLICKHOUSE_DATABASE}_db" From 0e8b26d84f68b3e8d515e02584c49f0ace853b24 Mon Sep 17 00:00:00 2001 From: ubuntu <872237106@qq.com> Date: Sun, 17 Dec 2023 11:21:46 +0800 Subject: [PATCH 060/253] fix test --- src/Parsers/ParserSelectQuery.cpp | 9 +--- .../0_stateless/02943_order_by_all.reference | 42 +++++++++++-------- .../0_stateless/02943_order_by_all.sql | 9 ++-- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/Parsers/ParserSelectQuery.cpp b/src/Parsers/ParserSelectQuery.cpp index 911ed546293..74321434262 100644 --- a/src/Parsers/ParserSelectQuery.cpp +++ b/src/Parsers/ParserSelectQuery.cpp @@ -50,13 +50,6 @@ bool ParserSelectQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) ParserKeyword s_having("HAVING"); ParserKeyword s_window("WINDOW"); ParserKeyword s_order_by("ORDER BY"); - ParserKeyword ascending("ASCENDING"); - ParserKeyword descending("DESCENDING"); - ParserKeyword asc("ASC"); - ParserKeyword desc("DESC"); - ParserKeyword nulls("NULLS"); - ParserKeyword first("FIRST"); - ParserKeyword last("LAST"); ParserKeyword s_limit("LIMIT"); ParserKeyword s_settings("SETTINGS"); ParserKeyword s_by("BY"); @@ -281,7 +274,7 @@ bool ParserSelectQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) /// if any WITH FILL parse possible INTERPOLATE list if (std::any_of(order_expression_list->children.begin(), order_expression_list->children.end(), - [](auto & child) { return child->template as()->with_fill; })) + [](auto & child) { return child->template as()->with_fill; })) { if (s_interpolate.ignore(pos, expected)) { diff --git a/tests/queries/0_stateless/02943_order_by_all.reference b/tests/queries/0_stateless/02943_order_by_all.reference index 1d573800619..17867ec7f18 100644 --- a/tests/queries/0_stateless/02943_order_by_all.reference +++ b/tests/queries/0_stateless/02943_order_by_all.reference @@ -1,20 +1,28 @@ -A 3 -B 1 +A 2 B 3 C \N -B 1 1 -A 3 2 -B 3 2 -C \N 3 -B 1 1 -B 3 2 -A 3 2 -C \N 3 -C \N -B 3 -B 1 -A 3 -\N C -1 B -3 A +D 1 +1 D +2 A +3 B +\N C +B 3 10 +D 1 20 +A 2 30 +C \N 40 +D 1 +A 2 +B 3 +C \N +B 3 10 +D 1 20 +A 2 30 +C \N 40 +D 1 +C \N +B 3 +A 2 +\N C +1 D +2 A 3 B diff --git a/tests/queries/0_stateless/02943_order_by_all.sql b/tests/queries/0_stateless/02943_order_by_all.sql index 856eff586a7..e1cfabed8cf 100644 --- a/tests/queries/0_stateless/02943_order_by_all.sql +++ b/tests/queries/0_stateless/02943_order_by_all.sql @@ -6,14 +6,17 @@ CREATE TABLE order_by_all b Nullable(Int32), all int, ) - engine = Memory; +engine = Memory; -INSERT INTO order_by_all VALUES ('B', 3, 2), ('C', NULL, 3), ('B', 1, 1), ('A', 3, 2); +INSERT INTO order_by_all VALUES ('B', 3, 10), ('C', NULL, 40), ('D', 1, 20), ('A', 2, 30); SELECT a, b FROM order_by_all ORDER BY ALL; +SELECT b, a FROM order_by_all ORDER BY ALL; SELECT a, b, all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } -SELECT a, b, all FROM order_by_all ORDER BY all, a; +SELECT a, b as all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } SELECT a, b, all FROM order_by_all ORDER BY all settings enable_order_by_all = false; +SELECT a, b as all FROM order_by_all ORDER BY all settings enable_order_by_all = false; +SELECT a, b, all FROM order_by_all ORDER BY all, a; SELECT a, b FROM order_by_all ORDER BY ALL DESC; SELECT b, a FROM order_by_all ORDER BY ALL NULLS FIRST; From c696c0bfe704f3007767b4e7533507a0859f606a Mon Sep 17 00:00:00 2001 From: Shani Elharrar Date: Thu, 14 Dec 2023 10:02:21 +0200 Subject: [PATCH 061/253] S3Common.AuthSettings: Allow passing SESSION_TOKEN to AWSCredentials This sets the infrastructure of loading session_token and passing it directly to all AWSCredentials instances that are created using the AuthSettings. The default SESSION_TOKEN is set to an empty string as documented in AWS SDK reference: https://sdk.amazonaws.com/cpp/api/0.12.9/d4/d27/class_aws_1_1_auth_1_1_a_w_s_credentials.html --- src/Coordination/KeeperSnapshotManagerS3.cpp | 2 +- src/IO/S3Common.cpp | 6 +++++- src/IO/S3Common.h | 1 + src/Storages/StorageS3.cpp | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Coordination/KeeperSnapshotManagerS3.cpp b/src/Coordination/KeeperSnapshotManagerS3.cpp index d76e310f2a3..a245ccc16df 100644 --- a/src/Coordination/KeeperSnapshotManagerS3.cpp +++ b/src/Coordination/KeeperSnapshotManagerS3.cpp @@ -76,7 +76,7 @@ void KeeperSnapshotManagerS3::updateS3Configuration(const Poco::Util::AbstractCo LOG_INFO(log, "S3 configuration was updated"); - auto credentials = Aws::Auth::AWSCredentials(auth_settings.access_key_id, auth_settings.secret_access_key); + auto credentials = Aws::Auth::AWSCredentials(auth_settings.access_key_id, auth_settings.secret_access_key, auth_settings.session_token); auto headers = auth_settings.headers; static constexpr size_t s3_max_redirects = 10; diff --git a/src/IO/S3Common.cpp b/src/IO/S3Common.cpp index ffd6b6d711f..96ad6413ef5 100644 --- a/src/IO/S3Common.cpp +++ b/src/IO/S3Common.cpp @@ -109,6 +109,8 @@ AuthSettings AuthSettings::loadFromConfig(const std::string & config_elem, const { auto access_key_id = config.getString(config_elem + ".access_key_id", ""); auto secret_access_key = config.getString(config_elem + ".secret_access_key", ""); + auto session_token = config.getString(config_elem + ".session_token", ""); + auto region = config.getString(config_elem + ".region", ""); auto server_side_encryption_customer_key_base64 = config.getString(config_elem + ".server_side_encryption_customer_key_base64", ""); @@ -133,7 +135,7 @@ AuthSettings AuthSettings::loadFromConfig(const std::string & config_elem, const return AuthSettings { - std::move(access_key_id), std::move(secret_access_key), + std::move(access_key_id), std::move(secret_access_key), std::move(session_token), std::move(region), std::move(server_side_encryption_customer_key_base64), std::move(sse_kms_config), @@ -155,6 +157,8 @@ void AuthSettings::updateFrom(const AuthSettings & from) access_key_id = from.access_key_id; if (!from.secret_access_key.empty()) secret_access_key = from.secret_access_key; + if (!from.session_token.empty()) + session_token = from.session_token; headers = from.headers; region = from.region; diff --git a/src/IO/S3Common.h b/src/IO/S3Common.h index 8c45c1c34a7..ebfc07a3976 100644 --- a/src/IO/S3Common.h +++ b/src/IO/S3Common.h @@ -80,6 +80,7 @@ struct AuthSettings std::string access_key_id; std::string secret_access_key; + std::string session_token; std::string region; std::string server_side_encryption_customer_key_base64; ServerSideEncryptionKMSConfig server_side_encryption_kms_config; diff --git a/src/Storages/StorageS3.cpp b/src/Storages/StorageS3.cpp index 556b4f5655b..e8f460525db 100644 --- a/src/Storages/StorageS3.cpp +++ b/src/Storages/StorageS3.cpp @@ -1460,7 +1460,7 @@ void StorageS3::Configuration::connect(ContextPtr context) client_configuration.requestTimeoutMs = request_settings.request_timeout_ms; - auto credentials = Aws::Auth::AWSCredentials(auth_settings.access_key_id, auth_settings.secret_access_key); + auto credentials = Aws::Auth::AWSCredentials(auth_settings.access_key_id, auth_settings.secret_access_key, auth_settings.session_token); client = S3::ClientFactory::instance().create( client_configuration, url.is_virtual_hosted_style, From 9033c96e29af74525b42647dc6df7489a3f107f1 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Sun, 17 Dec 2023 08:11:30 +0000 Subject: [PATCH 062/253] Some fixups --- docs/en/operations/settings/settings.md | 35 +++++++++++++++++ .../statements/select/order-by.md | 8 ++-- src/Core/Settings.h | 2 +- src/Interpreters/TreeRewriter.cpp | 8 ++-- .../0_stateless/02943_order_by_all.reference | 34 ++++++++++++----- .../0_stateless/02943_order_by_all.sql | 38 +++++++++++++++---- 6 files changed, 100 insertions(+), 25 deletions(-) diff --git a/docs/en/operations/settings/settings.md b/docs/en/operations/settings/settings.md index 5c509058cbb..7491699bf0d 100644 --- a/docs/en/operations/settings/settings.md +++ b/docs/en/operations/settings/settings.md @@ -4152,6 +4152,41 @@ Result: └─────┴─────┴───────┘ ``` +## enable_order_by_all {#enable-order-by-all} + +Enables or disables sorting by `ALL` columns, i.e. [ORDER BY](../../sql-reference/statements/select/order-by.md) + +Possible values: + +- 0 — Disable ORDER BY ALL. +- 1 — Enable ORDER BY ALL. + +Default value: `1`. + +**Example** + +Query: + +```sql +CREATE TABLE TAB(C1 Int, C2 Int, ALL Int) ENGINE=Memory(); + +INSERT INTO TAB VALUES (10, 20, 30), (20, 20, 10), (30, 10, 20); + +SELECT * FROM TAB ORDER BY ALL; -- returns an error that ALL is ambiguous + +SELECT * FROM TAB ORDER BY ALL SETTINGS enable_order_by_all; +``` + +Result: + +```text +┌─C1─┬─C2─┬─ALL─┐ +│ 20 │ 20 │ 10 │ +│ 30 │ 10 │ 20 │ +│ 10 │ 20 │ 30 │ +└────┴────┴─────┘ +``` + ## splitby_max_substrings_includes_remaining_string {#splitby_max_substrings_includes_remaining_string} Controls whether function [splitBy*()](../../sql-reference/functions/splitting-merging-functions.md) with argument `max_substrings` > 0 will include the remaining string in the last element of the result array. diff --git a/docs/en/sql-reference/statements/select/order-by.md b/docs/en/sql-reference/statements/select/order-by.md index 37e42f261fa..13b05e71161 100644 --- a/docs/en/sql-reference/statements/select/order-by.md +++ b/docs/en/sql-reference/statements/select/order-by.md @@ -12,10 +12,12 @@ The `ORDER BY` clause contains - `ALL` which means all columns of the `SELECT` clause, e.g. `ORDER BY ALL`. To disable sorting by column numbers, set setting [enable_positional_arguments](../../../operations/settings/settings.md#enable-positional-arguments) = 0. +To disable sorting by `ALL`, set setting [enable_order_by_all](../../../operations/settings/settings.md#enable-order-by-all) = 0. -Sort expressions or column numbers in `ORDER BY` can be attributed by a `DESC` (descending) or `ASC` (ascending) modifier which determine the sorting direction. -If no sort order is specified explicitly, `ASC` is used as default. -The sorting direction applies to a single expression, not to the entire list, e.g. `ORDER BY Visits DESC, SearchPhrase`. Sorting is performed case-sensitively. +The `ORDER BY` clause can be attributed by a `DESC` (descending) or `ASC` (ascending) modifier which determines the sorting direction. +Unless an explicit sort order is specified, `ASC` is used by default. +The sorting direction applies to a single expression, not to the entire list, e.g. `ORDER BY Visits DESC, SearchPhrase`. +Also, sorting is performed case-sensitively. Rows with identical values for a sort expressions are returned in an arbitrary and non-deterministic order. If the `ORDER BY` clause is omitted in a `SELECT` statement, the row order is also arbitrary and non-deterministic. diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 9d09ff42395..9c3dafd257d 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -845,7 +845,7 @@ class IColumn; M(UInt64, cache_warmer_threads, 4, "Only available in ClickHouse Cloud", 0) \ M(Int64, ignore_cold_parts_seconds, 0, "Only available in ClickHouse Cloud", 0) \ M(Int64, prefer_warmed_unmerged_parts_seconds, 0, "Only available in ClickHouse Cloud", 0) \ - M(Bool, enable_order_by_all, true, "Clause ORDER BY supports specifying ALL, sorts by all columns in the SELECT clause.", 0)\ + M(Bool, enable_order_by_all, true, "Enable sorting expression ORDER BY ALL.", 0)\ // End of COMMON_SETTINGS // Please add settings related to formats into the FORMAT_FACTORY_SETTINGS, move obsolete settings to OBSOLETE_SETTINGS and obsolete format settings to OBSOLETE_FORMAT_SETTINGS. diff --git a/src/Interpreters/TreeRewriter.cpp b/src/Interpreters/TreeRewriter.cpp index 27f0bf2502e..9cbf24091e3 100644 --- a/src/Interpreters/TreeRewriter.cpp +++ b/src/Interpreters/TreeRewriter.cpp @@ -787,15 +787,15 @@ void expandOrderByAll(ASTSelectQuery * select_query) for (const auto & expr : select_query->select()->children) { - if (auto * identifier = expr->as(); identifier) + if (auto * identifier = expr->as(); identifier != nullptr) if (Poco::toUpper(identifier->name()) == "ALL" || Poco::toUpper(identifier->alias) == "ALL") throw Exception(ErrorCodes::UNEXPECTED_EXPRESSION, - "The column name (all/ALL) conflicts with `ORDER BY ALL`, try to disable setting `enable_order_by_all`."); + "Cannot use ORDER BY ALL to sort a column with name 'all', please disable setting `enable_order_by_all` and try again"); - if (auto * function = expr->as(); function) + if (auto * function = expr->as(); function != nullptr) if (Poco::toUpper(function->alias) == "ALL") throw Exception(ErrorCodes::UNEXPECTED_EXPRESSION, - "The column name (all/ALL) conflicts with `ORDER BY ALL`, try to disable setting `enable_order_by_all`."); + "Cannot use ORDER BY ALL to sort a column with name 'all', please disable setting `enable_order_by_all` and try again"); auto elem = std::make_shared(); elem->direction = all_elem->direction; diff --git a/tests/queries/0_stateless/02943_order_by_all.reference b/tests/queries/0_stateless/02943_order_by_all.reference index 17867ec7f18..f60c7976ae6 100644 --- a/tests/queries/0_stateless/02943_order_by_all.reference +++ b/tests/queries/0_stateless/02943_order_by_all.reference @@ -1,3 +1,4 @@ +-- no modifiers A 2 B 3 C \N @@ -6,23 +7,38 @@ D 1 2 A 3 B \N C -B 3 10 -D 1 20 -A 2 30 -C \N 40 -D 1 +-- with ASC/DESC modifiers A 2 B 3 C \N -B 3 10 -D 1 20 -A 2 30 -C \N 40 +D 1 D 1 C \N B 3 A 2 +-- with NULLS FIRST/LAST modifiers \N C 1 D 2 A 3 B +1 D +2 A +3 B +\N C +-- what happens if some column "all" already exists? +B 3 10 +D 1 20 +A 2 30 +C \N 40 +D 1 +A 2 +B 3 +C \N +A 2 +B 3 +D 1 +\N +B 3 10 +D 1 20 +A 2 30 +C \N 40 diff --git a/tests/queries/0_stateless/02943_order_by_all.sql b/tests/queries/0_stateless/02943_order_by_all.sql index e1cfabed8cf..c1e358178d5 100644 --- a/tests/queries/0_stateless/02943_order_by_all.sql +++ b/tests/queries/0_stateless/02943_order_by_all.sql @@ -1,24 +1,46 @@ +-- Tests that sort expression ORDER BY ALL + DROP TABLE IF EXISTS order_by_all; CREATE TABLE order_by_all ( a String, b Nullable(Int32), - all int, + all UInt64, ) -engine = Memory; +ENGINE = Memory; INSERT INTO order_by_all VALUES ('B', 3, 10), ('C', NULL, 40), ('D', 1, 20), ('A', 2, 30); +SELECT '-- no modifiers'; SELECT a, b FROM order_by_all ORDER BY ALL; SELECT b, a FROM order_by_all ORDER BY ALL; -SELECT a, b, all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } -SELECT a, b as all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } -SELECT a, b, all FROM order_by_all ORDER BY all settings enable_order_by_all = false; -SELECT a, b as all FROM order_by_all ORDER BY all settings enable_order_by_all = false; -SELECT a, b, all FROM order_by_all ORDER BY all, a; + +SELECT '-- with ASC/DESC modifiers'; +SELECT a, b FROM order_by_all ORDER BY ALL ASC; SELECT a, b FROM order_by_all ORDER BY ALL DESC; + +SELECT '-- with NULLS FIRST/LAST modifiers'; SELECT b, a FROM order_by_all ORDER BY ALL NULLS FIRST; +SELECT b, a FROM order_by_all ORDER BY ALL NULLS LAST; -DROP TABLE IF EXISTS order_by_all; +SELECT '-- what happens if some column "all" already exists?'; +-- columns +SELECT a, b, all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b, all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b, all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; + +-- column aliases +SELECT a, b AS all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b AS all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b AS all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; + +-- expressions +SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } +SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } +SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; + +SELECT a, b, all FROM order_by_all ORDER BY all, a; + +DROP TABLE order_by_all; From 41da561e3a6d2af1cd9416e0d57c72cf02590f9d Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Sun, 17 Dec 2023 10:16:25 +0000 Subject: [PATCH 063/253] Fix crash --- src/Parsers/ParserSelectQuery.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Parsers/ParserSelectQuery.cpp b/src/Parsers/ParserSelectQuery.cpp index 74321434262..641e74b5f18 100644 --- a/src/Parsers/ParserSelectQuery.cpp +++ b/src/Parsers/ParserSelectQuery.cpp @@ -290,9 +290,9 @@ bool ParserSelectQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) } else if (order_expression_list->children.size() == 1) { - /// ORDER BY ALL [ASC|DESC] [NULLS [FIRST|LAST]] + /// ORDER BY ALL auto * identifier = order_expression_list->children[0]->as()->children[0]->as(); - if (Poco::toUpper(identifier->name()) == "ALL") + if (identifier != nullptr && Poco::toUpper(identifier->name()) == "ALL") select_query->order_by_all = true; } } From 19c8ac567f2e92c46d2a3ef86d9d293288143d28 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Sun, 17 Dec 2023 13:51:13 +0000 Subject: [PATCH 064/253] Implement punycodeEncode()/Decode() --- .gitmodules | 3 + contrib/CMakeLists.txt | 1 + contrib/idna | 1 + contrib/idna-cmake/CMakeLists.txt | 24 +++ .../functions/string-functions.md | 65 ++++++ src/Common/config.h.in | 1 + src/Functions/CMakeLists.txt | 4 + src/Functions/punycode.cpp | 199 ++++++++++++++++++ src/configure_config.cmake | 3 + .../0_stateless/02932_punycode.reference | 35 +++ tests/queries/0_stateless/02932_punycode.sql | 63 ++++++ 11 files changed, 399 insertions(+) create mode 160000 contrib/idna create mode 100644 contrib/idna-cmake/CMakeLists.txt create mode 100644 src/Functions/punycode.cpp create mode 100644 tests/queries/0_stateless/02932_punycode.reference create mode 100644 tests/queries/0_stateless/02932_punycode.sql diff --git a/.gitmodules b/.gitmodules index 53ef899dd99..3b9faea3cc1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -360,3 +360,6 @@ [submodule "contrib/sqids-cpp"] path = contrib/sqids-cpp url = https://github.com/sqids/sqids-cpp.git +[submodule "contrib/idna"] + path = contrib/idna + url = https://github.com/ada-url/idna.git diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 1b5ba15187f..02cb19d4c07 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -154,6 +154,7 @@ add_contrib (libpqxx-cmake libpqxx) add_contrib (libpq-cmake libpq) add_contrib (nuraft-cmake NuRaft) add_contrib (fast_float-cmake fast_float) +add_contrib (idna-cmake idna) add_contrib (datasketches-cpp-cmake datasketches-cpp) add_contrib (incbin-cmake incbin) add_contrib (sqids-cpp-cmake sqids-cpp) diff --git a/contrib/idna b/contrib/idna new file mode 160000 index 00000000000..3c8be01d42b --- /dev/null +++ b/contrib/idna @@ -0,0 +1 @@ +Subproject commit 3c8be01d42b75649f1ac9b697d0ef757eebfe667 diff --git a/contrib/idna-cmake/CMakeLists.txt b/contrib/idna-cmake/CMakeLists.txt new file mode 100644 index 00000000000..1138b836192 --- /dev/null +++ b/contrib/idna-cmake/CMakeLists.txt @@ -0,0 +1,24 @@ +option(ENABLE_IDNA "Enable idna support" ${ENABLE_LIBRARIES}) +if ((NOT ENABLE_IDNA)) + message (STATUS "Not using idna") + return() +endif() +set (LIBRARY_DIR "${ClickHouse_SOURCE_DIR}/contrib/idna") + +set (SRCS + "${LIBRARY_DIR}/src/idna.cpp" + "${LIBRARY_DIR}/src/mapping.cpp" + "${LIBRARY_DIR}/src/mapping_tables.cpp" + "${LIBRARY_DIR}/src/normalization.cpp" + "${LIBRARY_DIR}/src/normalization_tables.cpp" + "${LIBRARY_DIR}/src/punycode.cpp" + "${LIBRARY_DIR}/src/to_ascii.cpp" + "${LIBRARY_DIR}/src/to_unicode.cpp" + "${LIBRARY_DIR}/src/unicode_transcoding.cpp" + "${LIBRARY_DIR}/src/validity.cpp" +) + +add_library (_idna ${SRCS}) +target_include_directories(_idna PUBLIC "${LIBRARY_DIR}/include") + +add_library (ch_contrib::idna ALIAS _idna) diff --git a/docs/en/sql-reference/functions/string-functions.md b/docs/en/sql-reference/functions/string-functions.md index 4f3c6e1e858..e3f5c037839 100644 --- a/docs/en/sql-reference/functions/string-functions.md +++ b/docs/en/sql-reference/functions/string-functions.md @@ -1383,6 +1383,71 @@ Result: └──────────────────┘ ``` +## punycodeEncode + +Returns the [Punycode](https://en.wikipedia.org/wiki/Punycode) of a string. +The string must be UTF8-encoded, otherwise results are undefined. + +**Syntax** + +``` sql +punycodeEncode(val) +``` + +**Arguments** + +- `val` - Input value. [String](../data-types/string.md) + +**Returned value** + +- A Punycode representation of the input value. [String](../data-types/string.md) + +**Example** + +``` sql +select punycodeEncode('München'); +``` + +Result: + +```result +┌─punycodeEncode('München')─┐ +│ Mnchen-3ya │ +└───────────────────────────┘ +``` + +## punycodeDecode + +Returns the UTF8-encoded plaintext of a [Punycode](https://en.wikipedia.org/wiki/Punycode)-encoded string. + +**Syntax** + +``` sql +punycodeEncode(val) +``` + +**Arguments** + +- `val` - Punycode-encoded string. [String](../data-types/string.md) + +**Returned value** + +- The plaintext of the input value. [String](../data-types/string.md) + +**Example** + +``` sql +select punycodeDecode('Mnchen-3ya'); +``` + +Result: + +```result +┌─punycodeEncode('Mnchen-3ya')─┐ +│ München │ +└──────────────────────────────┘ +``` + ## byteHammingDistance Calculates the [hamming distance](https://en.wikipedia.org/wiki/Hamming_distance) between two byte strings. diff --git a/src/Common/config.h.in b/src/Common/config.h.in index f84e28942c5..5b3388a3b7d 100644 --- a/src/Common/config.h.in +++ b/src/Common/config.h.in @@ -28,6 +28,7 @@ #cmakedefine01 USE_S2_GEOMETRY #cmakedefine01 USE_FASTOPS #cmakedefine01 USE_SQIDS +#cmakedefine01 USE_IDNA #cmakedefine01 USE_NLP #cmakedefine01 USE_VECTORSCAN #cmakedefine01 USE_LIBURING diff --git a/src/Functions/CMakeLists.txt b/src/Functions/CMakeLists.txt index 89676594581..a06e898b7c5 100644 --- a/src/Functions/CMakeLists.txt +++ b/src/Functions/CMakeLists.txt @@ -83,6 +83,10 @@ if (TARGET ch_contrib::sqids) list (APPEND PRIVATE_LIBS ch_contrib::sqids) endif() +if (TARGET ch_contrib::idna) + list (APPEND PRIVATE_LIBS ch_contrib::idna) +endif() + if (TARGET ch_contrib::h3) list (APPEND PRIVATE_LIBS ch_contrib::h3) endif() diff --git a/src/Functions/punycode.cpp b/src/Functions/punycode.cpp new file mode 100644 index 00000000000..5279c1d7312 --- /dev/null +++ b/src/Functions/punycode.cpp @@ -0,0 +1,199 @@ +#include "config.h" + +#ifdef USE_IDNA + +#include +#include + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnewline-eof" +#endif +# include +# include +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int BAD_ARGUMENTS; + extern const int ILLEGAL_COLUMN; +} + +struct PunycodeEncodeImpl +{ + static void vector( + const ColumnString::Chars & data, + const ColumnString::Offsets & offsets, + ColumnString::Chars & res_data, + ColumnString::Offsets & res_offsets) + { + const size_t rows = offsets.size(); + res_data.resize(rows * 64); /// just a guess + res_offsets.resize(rows); + + size_t prev_offset = 0; + size_t prev_res_offset = 0; + size_t res_data_bytes_written = 0; + std::u32string value_utf32; + std::string value_puny; + for (size_t row = 0; row < rows; ++row) + { + const char * value = reinterpret_cast(&data[prev_offset]); + const size_t value_length = offsets[row] - prev_offset - 1; + + size_t value_utf32_length = ada::idna::utf32_length_from_utf8(value, value_length); + value_utf32.resize(value_utf32_length, '\0'); + + ada::idna::utf8_to_utf32(value, value_length, value_utf32.data()); + + bool ok = ada::idna::utf32_to_punycode(value_utf32, value_puny); + if (!ok) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Internal error during Punycode encoding"); + + const size_t bytes_to_write = value_puny.size() + 1; + if (res_data_bytes_written + bytes_to_write > res_data.size()) + { + size_t new_size = std::max(res_data.size() * 2, res_data_bytes_written + bytes_to_write); + res_data.resize(new_size); + } + + std::memcpy(&res_data[res_data_bytes_written], value_puny.data(), value_puny.size()); + res_data_bytes_written += value_puny.size(); + + res_data[res_data_bytes_written] = '\0'; + res_data_bytes_written += 1; + + res_offsets[row] = prev_res_offset + bytes_to_write; + + prev_offset = offsets[row]; + prev_res_offset = res_offsets[row]; + value_utf32.clear(); + value_puny.clear(); + } + + res_data.resize(res_data_bytes_written); + } + + [[noreturn]] static void vectorFixed(const ColumnString::Chars &, size_t, ColumnString::Chars &) + { + throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Column of type FixedString is not supported by punycodeEncode function"); + } +}; + +struct PunycodeDecodeImpl +{ + static void vector( + const ColumnString::Chars & data, + const ColumnString::Offsets & offsets, + ColumnString::Chars & res_data, + ColumnString::Offsets & res_offsets) + { + const size_t rows = offsets.size(); + res_data.resize(rows * 64); /// just a guess + res_offsets.resize(rows); + + size_t prev_offset = 0; + size_t prev_res_offset = 0; + size_t res_data_bytes_written = 0; + std::u32string value_utf32; + std::string value_utf8; + for (size_t row = 0; row < rows; ++row) + { + const char * value = reinterpret_cast(&data[prev_offset]); + const size_t value_length = offsets[row] - prev_offset - 1; + + std::string_view value_punycode(value, value_length); + bool ok = ada::idna::punycode_to_utf32(value_punycode, value_utf32); + if (!ok) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Internal error during Punycode decoding"); + + size_t utf8_length = ada::idna::utf8_length_from_utf32(value_utf32.data(), value_utf32.size()); + value_utf8.resize(utf8_length, '\0'); + + ada::idna::utf32_to_utf8(value_utf32.data(), value_utf32.size(), value_utf8.data()); + + const size_t bytes_to_write = value_utf8.size() + 1; + if (res_data_bytes_written + bytes_to_write > res_data.size()) + { + size_t new_size = std::max(res_data.size() * 2, res_data_bytes_written + bytes_to_write); + res_data.resize(new_size); + } + + std::memcpy(&res_data[res_data_bytes_written], value_utf8.data(), value_utf8.size()); + res_data_bytes_written += value_utf8.size(); + + res_data[res_data_bytes_written] = '\0'; + res_data_bytes_written += 1; + + res_offsets[row] = prev_res_offset + bytes_to_write; + + prev_offset = offsets[row]; + prev_res_offset = res_offsets[row]; + value_utf32.clear(); + value_utf8.clear(); + } + + res_data.resize(res_data_bytes_written); + } + + [[noreturn]] static void vectorFixed(const ColumnString::Chars &, size_t, ColumnString::Chars &) + { + throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Column of type FixedString is not supported by punycodeDecode function"); + } +}; + +struct NamePunycodeEncode +{ + static constexpr auto name = "punycodeEncode"; +}; + +struct NamePunycodeDecode +{ + static constexpr auto name = "punycodeDecode"; +}; + +REGISTER_FUNCTION(Punycode) +{ + factory.registerFunction>(FunctionDocumentation{ + .description=R"( +Computes a Punycode representation of a string.)", + .syntax="punycodeEncode(str)", + .arguments={{"str", "Input string"}}, + .returned_value="The punycode representation [String](/docs/en/sql-reference/data-types/string.md).", + .examples={ + {"simple", + "SELECT punycodeEncode('München') AS puny;", + R"( +┌─puny───────┐ +│ Mnchen-3ya │ +└────────────┘ + )" + }} + }); + + factory.registerFunction>(FunctionDocumentation{ + .description=R"( +Computes a Punycode representation of a string.)", + .syntax="punycodeDecode(str)", + .arguments={{"str", "A Punycode-encoded string"}}, + .returned_value="The plaintext representation [String](/docs/en/sql-reference/data-types/string.md).", + .examples={ + {"simple", + "SELECT punycodeDecode('Mnchen-3ya') AS plain;", + R"( +┌─plain───┐ +│ München │ +└─────────┘ + )" + }} + }); +} + +} + +#endif diff --git a/src/configure_config.cmake b/src/configure_config.cmake index c3c6d9be6da..01c4fd7b5f8 100644 --- a/src/configure_config.cmake +++ b/src/configure_config.cmake @@ -131,6 +131,9 @@ endif() if (TARGET ch_contrib::sqids) set(USE_SQIDS 1) endif() +if (TARGET ch_contrib::idna) + set(USE_IDNA 1) +endif() if (TARGET ch_contrib::vectorscan) set(USE_VECTORSCAN 1) endif() diff --git a/tests/queries/0_stateless/02932_punycode.reference b/tests/queries/0_stateless/02932_punycode.reference new file mode 100644 index 00000000000..7a39a221e08 --- /dev/null +++ b/tests/queries/0_stateless/02932_punycode.reference @@ -0,0 +1,35 @@ +-- Negative tests +-- Regular cases +a a- a +A A- A +-- --- -- +London London- London +Lloyd-Atkinson Lloyd-Atkinson- Lloyd-Atkinson +This has spaces This has spaces- This has spaces +-> $1.00 <- -> $1.00 <-- -> $1.00 <- +а 80a а +ü tda ü +α mxa α +例 fsq 例 +😉 n28h 😉 +αβγ mxacd αβγ +München Mnchen-3ya München +Mnchen-3ya Mnchen-3ya- Mnchen-3ya +München-Ost Mnchen-Ost-9db München-Ost +Bahnhof München-Ost Bahnhof Mnchen-Ost-u6b Bahnhof München-Ost +abæcdöef abcdef-qua4k abæcdöef +правда 80aafi6cg правда +ยจฆฟคฏข 22cdfh1b8fsa ยจฆฟคฏข +ドメイン名例 eckwd4c7cu47r2wf ドメイン名例 +MajiでKoiする5秒前 MajiKoi5-783gue6qz075azm5e MajiでKoiする5秒前 +「bücher」 bcher-kva8445foa 「bücher」 +团淄 3bs854c 团淄 +-- Special cases + + +\N +\N +Wenn Sie ... vom Hauptbahnhof in München ... mit zehn Minuten, ohne, dass Sie am Flughafen noch einchecken müssen, dann starten Sie im Grunde genommen am Flughafen ... am ... am Hauptbahnhof in München starten Sie Ihren Flug. Zehn Minuten. Schauen Sie sich mal die großen Flughäfen an, wenn Sie in Heathrow in London oder sonst wo, meine se ... Charles de Gaulle äh in Frankreich oder in ...äh... in ... in...äh...in Rom. Wenn Sie sich mal die Entfernungen ansehen, wenn Sie Frankfurt sich ansehen, dann werden Sie feststellen, dass zehn Minuten... Sie jederzeit locker in Frankfurt brauchen, um ihr Gate zu finden. Wenn Sie vom Flug ... vom ... vom Hauptbahnhof starten - Sie steigen in den Hauptbahnhof ein, Sie fahren mit dem Transrapid in zehn Minuten an den Flughafen in ... an den Flughafen Franz Josef Strauß. Dann starten Sie praktisch hier am Hauptbahnhof in München. Das bedeutet natürlich, dass der Hauptbahnhof im Grunde genommen näher an Bayern ... an die bayerischen Städte heranwächst, weil das ja klar ist, weil auf dem Hauptbahnhof viele Linien aus Bayern zusammenlaufen. Wenn Sie ... vom Hauptbahnhof in Mnchen ... mit zehn Minuten, ohne, dass Sie am Flughafen noch einchecken mssen, dann starten Sie im Grunde genommen am Flughafen ... am ... am Hauptbahnhof in Mnchen starten Sie Ihren Flug. Zehn Minuten. Schauen Sie sich mal die groen Flughfen an, wenn Sie in Heathrow in London oder sonst wo, meine se ... Charles de Gaulle h in Frankreich oder in ...h... in ... in...h...in Rom. Wenn Sie sich mal die Entfernungen ansehen, wenn Sie Frankfurt sich ansehen, dann werden Sie feststellen, dass zehn Minuten... Sie jederzeit locker in Frankfurt brauchen, um ihr Gate zu finden. Wenn Sie vom Flug ... vom ... vom Hauptbahnhof starten - Sie steigen in den Hauptbahnhof ein, Sie fahren mit dem Transrapid in zehn Minuten an den Flughafen in ... an den Flughafen Franz Josef Strau. Dann starten Sie praktisch hier am Hauptbahnhof in Mnchen. Das bedeutet natrlich, dass der Hauptbahnhof im Grunde genommen nher an Bayern ... an die bayerischen Stdte heranwchst, weil das ja klar ist, weil auf dem Hauptbahnhof viele Linien aus Bayern zusammenlaufen.-pu7fjtp0npc1ar54cibk471wdc9d18axa Wenn Sie ... vom Hauptbahnhof in München ... mit zehn Minuten, ohne, dass Sie am Flughafen noch einchecken müssen, dann starten Sie im Grunde genommen am Flughafen ... am ... am Hauptbahnhof in München starten Sie Ihren Flug. Zehn Minuten. Schauen Sie sich mal die großen Flughäfen an, wenn Sie in Heathrow in London oder sonst wo, meine se ... Charles de Gaulle äh in Frankreich oder in ...äh... in ... in...äh...in Rom. Wenn Sie sich mal die Entfernungen ansehen, wenn Sie Frankfurt sich ansehen, dann werden Sie feststellen, dass zehn Minuten... Sie jederzeit locker in Frankfurt brauchen, um ihr Gate zu finden. Wenn Sie vom Flug ... vom ... vom Hauptbahnhof starten - Sie steigen in den Hauptbahnhof ein, Sie fahren mit dem Transrapid in zehn Minuten an den Flughafen in ... an den Flughafen Franz Josef Strauß. Dann starten Sie praktisch hier am Hauptbahnhof in München. Das bedeutet natürlich, dass der Hauptbahnhof im Grunde genommen näher an Bayern ... an die bayerischen Städte heranwächst, weil das ja klar ist, weil auf dem Hauptbahnhof viele Linien aus Bayern zusammenlaufen. +München Mnchen-3ya München +abc abc- abc +aäoöuü aou-qla5gqb aäoöuü diff --git a/tests/queries/0_stateless/02932_punycode.sql b/tests/queries/0_stateless/02932_punycode.sql new file mode 100644 index 00000000000..fd128507a8f --- /dev/null +++ b/tests/queries/0_stateless/02932_punycode.sql @@ -0,0 +1,63 @@ +-- Tags: no-fasttest +-- no-fasttest: requires idna library + +SELECT '-- Negative tests'; + +SELECT punycodeDecode(); -- { serverError NUMBER_OF_ARGUMENTS_DOESNT_MATCH } +SELECT punycodeEncode(); -- { serverError NUMBER_OF_ARGUMENTS_DOESNT_MATCH } +SELECT punycodeDecode(1); -- { serverError ILLEGAL_TYPE_OF_ARGUMENT } +SELECT punycodeEncode(1); -- { serverError ILLEGAL_TYPE_OF_ARGUMENT } +SELECT punycodeDecode('two', 'strings'); -- { serverError NUMBER_OF_ARGUMENTS_DOESNT_MATCH } +SELECT punycodeEncode('two', 'strings'); -- { serverError NUMBER_OF_ARGUMENTS_DOESNT_MATCH } +SELECT punycodeDecode(toFixedString('two', 3)); -- { serverError ILLEGAL_COLUMN } +SELECT punycodeEncode(toFixedString('two', 3)); -- { serverError ILLEGAL_COLUMN } + +SELECT '-- Regular cases'; + +-- The test cases originate from the idna unit tests: +--- https://github.com/ada-url/idna/blob/8cd03ef867dbd06be87bd61df9cf69aa1182ea21/tests/fixtures/utf8_punycode_alternating.txt + +SELECT 'a' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'A' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT '--' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'London' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'Lloyd-Atkinson' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'This has spaces' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT '-> $1.00 <-' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'а' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'ü' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'α' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT '例' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT '😉' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'αβγ' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'München' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'Mnchen-3ya' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'München-Ost' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'Bahnhof München-Ost' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'abæcdöef' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'правда' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'ยจฆฟคฏข' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'ドメイン名例' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT 'MajiでKoiする5秒前' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT '「bücher」' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; +SELECT '团淄' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; + +SELECT '-- Special cases'; + +SELECT punycodeDecode(''); +SELECT punycodeEncode(''); +SELECT punycodeDecode(NULL); +SELECT punycodeEncode(NULL); + +-- garbage Punycode-encoded values +SELECT punycodeDecode('no punycode'); -- { serverError BAD_ARGUMENTS } + +-- long input +SELECT 'Wenn Sie ... vom Hauptbahnhof in München ... mit zehn Minuten, ohne, dass Sie am Flughafen noch einchecken müssen, dann starten Sie im Grunde genommen am Flughafen ... am ... am Hauptbahnhof in München starten Sie Ihren Flug. Zehn Minuten. Schauen Sie sich mal die großen Flughäfen an, wenn Sie in Heathrow in London oder sonst wo, meine se ... Charles de Gaulle äh in Frankreich oder in ...äh... in ... in...äh...in Rom. Wenn Sie sich mal die Entfernungen ansehen, wenn Sie Frankfurt sich ansehen, dann werden Sie feststellen, dass zehn Minuten... Sie jederzeit locker in Frankfurt brauchen, um ihr Gate zu finden. Wenn Sie vom Flug ... vom ... vom Hauptbahnhof starten - Sie steigen in den Hauptbahnhof ein, Sie fahren mit dem Transrapid in zehn Minuten an den Flughafen in ... an den Flughafen Franz Josef Strauß. Dann starten Sie praktisch hier am Hauptbahnhof in München. Das bedeutet natürlich, dass der Hauptbahnhof im Grunde genommen näher an Bayern ... an die bayerischen Städte heranwächst, weil das ja klar ist, weil auf dem Hauptbahnhof viele Linien aus Bayern zusammenlaufen.' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; + +-- non-const values +DROP TABLE IF EXISTS tab; +CREATE TABLE tab (str String) ENGINE=MergeTree ORDER BY str; +INSERT INTO tab VALUES ('abc') ('aäoöuü') ('München'); +SELECT str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original FROM tab; +DROP TABLE tab; From ba34b80087e82149783a35f141903ac5fd63161d Mon Sep 17 00:00:00 2001 From: zhanglistar Date: Mon, 18 Dec 2023 16:43:45 +0800 Subject: [PATCH 065/253] If improvement add comment and performance test. --- src/Functions/if.cpp | 3 +++ tests/performance/if.xml | 8 ++++++++ 2 files changed, 11 insertions(+) create mode 100644 tests/performance/if.xml diff --git a/src/Functions/if.cpp b/src/Functions/if.cpp index 320210288df..90ed8b9153d 100644 --- a/src/Functions/if.cpp +++ b/src/Functions/if.cpp @@ -52,6 +52,7 @@ inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const Arr bool a_is_short = a.size() < size; bool b_is_short = b.size() < size; + /// Below code attempts to use a branch-free check (multiply with the condition) for better performance instead of conditional evaluation. if (a_is_short && b_is_short) { size_t a_index = 0, b_index = 0; @@ -106,6 +107,7 @@ inline void fillVectorConstant(const ArrayCond & cond, const ArrayA & a, B b, Ar { size_t size = cond.size(); bool a_is_short = a.size() < size; + /// Below code attempts to use a branch-free check (multiply with the condition) for better performance instead of conditional evaluation. if (a_is_short) { size_t a_index = 0; @@ -133,6 +135,7 @@ inline void fillConstantVector(const ArrayCond & cond, A a, const ArrayB & b, Ar { size_t size = cond.size(); bool b_is_short = b.size() < size; + /// Below code attempts to use a branch-free check (multiply with the condition) for better performance instead of conditional evaluation. if (b_is_short) { size_t b_index = 0; diff --git a/tests/performance/if.xml b/tests/performance/if.xml new file mode 100644 index 00000000000..209c88fc29d --- /dev/null +++ b/tests/performance/if.xml @@ -0,0 +1,8 @@ + + + SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() > 42949673, zero + 1, zero + 2)) + SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 3865470566, zero + 1, zero + 2)) + SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 2147483647, zero + 1, zero + 2)) +SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 42949673, zero + 1, zero + 2)) + + \ No newline at end of file From aaf6564711e42f0a9b29e781ee4523927da6d219 Mon Sep 17 00:00:00 2001 From: zhanglistar Date: Mon, 18 Dec 2023 16:47:25 +0800 Subject: [PATCH 066/253] if performance code format --- src/Functions/if.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Functions/if.cpp b/src/Functions/if.cpp index 90ed8b9153d..f1a51d19f32 100644 --- a/src/Functions/if.cpp +++ b/src/Functions/if.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -24,7 +23,7 @@ #include #include #include - +#include namespace DB { From 6d73c8b157de7485f145ca54f91e917ac4119065 Mon Sep 17 00:00:00 2001 From: zhanglistar Date: Mon, 18 Dec 2023 16:48:27 +0800 Subject: [PATCH 067/253] code format of if.xml --- tests/performance/if.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/performance/if.xml b/tests/performance/if.xml index 209c88fc29d..9381bc322ad 100644 --- a/tests/performance/if.xml +++ b/tests/performance/if.xml @@ -5,4 +5,4 @@ SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 2147483647, zero + 1, zero + 2)) SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 42949673, zero + 1, zero + 2)) - \ No newline at end of file + From f2a02cca2fb614c9ec23b146082c7c9ef4e5b5a1 Mon Sep 17 00:00:00 2001 From: zhanglistar Date: Mon, 18 Dec 2023 16:55:24 +0800 Subject: [PATCH 068/253] if.xml add more perf tests. --- tests/performance/if.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/performance/if.xml b/tests/performance/if.xml index 9381bc322ad..ec386724f85 100644 --- a/tests/performance/if.xml +++ b/tests/performance/if.xml @@ -3,6 +3,10 @@ SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() > 42949673, zero + 1, zero + 2)) SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 3865470566, zero + 1, zero + 2)) SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 2147483647, zero + 1, zero + 2)) -SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 42949673, zero + 1, zero + 2)) + SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 42949673, zero + 1, zero + 2)) + + SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 42949673, zero + 1, 2)) + SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 42949673, 1, zero + 2)) + SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 42949673, 1, 2)) From 403d9c809d0158a581260a460f22bcc92b8e122b Mon Sep 17 00:00:00 2001 From: joelynch Date: Mon, 18 Dec 2023 10:41:12 +0100 Subject: [PATCH 069/253] fix test for postgresql addresses_expr --- .../configs/named_collections.xml | 10 +++++++- .../test_storage_postgresql/test.py | 24 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_storage_postgresql/configs/named_collections.xml b/tests/integration/test_storage_postgresql/configs/named_collections.xml index ebe9f7ce9ce..4923c21d0a6 100644 --- a/tests/integration/test_storage_postgresql/configs/named_collections.xml +++ b/tests/integration/test_storage_postgresql/configs/named_collections.xml @@ -16,7 +16,8 @@ postgres mysecretpassword - postgres1:1111 + postgres1 + 1111 postgres test_table
@@ -28,5 +29,12 @@ postgres test_replicas
+ + postgres + mysecretpassword + postgres1:5432 + postgres + test_table
+
diff --git a/tests/integration/test_storage_postgresql/test.py b/tests/integration/test_storage_postgresql/test.py index 11729a5ab18..f4108c80526 100644 --- a/tests/integration/test_storage_postgresql/test.py +++ b/tests/integration/test_storage_postgresql/test.py @@ -82,6 +82,30 @@ def test_postgres_select_insert(started_cluster): cursor.execute(f"DROP TABLE {table_name} ") +def test_postgres_addresses_expr(started_cluster): + cursor = started_cluster.postgres_conn.cursor() + table_name = "test_table" + table = f"""postgresql(`postgres5`)""" + cursor.execute(f"DROP TABLE IF EXISTS {table_name}") + cursor.execute(f"CREATE TABLE {table_name} (a integer, b text, c integer)") + + node1.query( + f""" + INSERT INTO TABLE FUNCTION {table} + SELECT number, concat('name_', toString(number)), 3 from numbers(10000)""" + ) + check1 = f"SELECT count() FROM {table}" + check2 = f"SELECT Sum(c) FROM {table}" + check3 = f"SELECT count(c) FROM {table} WHERE a % 2 == 0" + check4 = f"SELECT count() FROM {table} WHERE b LIKE concat('name_', toString(1))" + assert (node1.query(check1)).rstrip() == "10000" + assert (node1.query(check2)).rstrip() == "30000" + assert (node1.query(check3)).rstrip() == "5000" + assert (node1.query(check4)).rstrip() == "1" + + cursor.execute(f"DROP TABLE {table_name} ") + + def test_postgres_conversions(started_cluster): cursor = started_cluster.postgres_conn.cursor() cursor.execute(f"DROP TABLE IF EXISTS test_types") From 44f1644622a126ebafc1473f18dfefac00d9fd97 Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Mon, 18 Dec 2023 11:01:32 +0100 Subject: [PATCH 070/253] Update 02944_dynamically_change_filesystem_cache_size.reference --- ..._dynamically_change_filesystem_cache_size.reference | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference index 7fa32ec2b09..cd155b6ca29 100644 --- a/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference +++ b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference @@ -1,20 +1,20 @@ -100 10 10 10 0 0 98 10 /var/lib/clickhouse/caches/s3_cache_02944/ 5 5000 0 1 +100 10 10 10 0 0 98 10 /var/lib/clickhouse/filesystem_caches/s3_cache_02944/ 5 5000 0 1 0 10 98 set max_size from 100 to 10 -10 10 10 10 0 0 8 1 /var/lib/clickhouse/caches/s3_cache_02944/ 5 5000 0 1 +10 10 10 10 0 0 8 1 /var/lib/clickhouse/filesystem_caches/s3_cache_02944/ 5 5000 0 1 1 8 set max_size from 10 to 100 -100 10 10 10 0 0 8 1 /var/lib/clickhouse/caches/s3_cache_02944/ 5 5000 0 1 +100 10 10 10 0 0 8 1 /var/lib/clickhouse/filesystem_caches/s3_cache_02944/ 5 5000 0 1 10 98 set max_elements from 10 to 2 -100 2 10 10 0 0 18 2 /var/lib/clickhouse/caches/s3_cache_02944/ 5 5000 0 1 +100 2 10 10 0 0 18 2 /var/lib/clickhouse/filesystem_caches/s3_cache_02944/ 5 5000 0 1 2 18 set max_elements from 2 to 10 -100 10 10 10 0 0 18 2 /var/lib/clickhouse/caches/s3_cache_02944/ 5 5000 0 1 +100 10 10 10 0 0 18 2 /var/lib/clickhouse/filesystem_caches/s3_cache_02944/ 5 5000 0 1 10 98 From 29d70c12f63fcdae8991c97cf3fb83987082cecf Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Mon, 18 Dec 2023 10:47:40 +0000 Subject: [PATCH 071/253] Fix CI checks --- src/Functions/FunctionSqid.cpp | 2 +- src/Functions/punycode.cpp | 2 +- utils/check-style/aspell-ignore/en/aspell-dict.txt | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Functions/FunctionSqid.cpp b/src/Functions/FunctionSqid.cpp index 546263914c2..4517bba963e 100644 --- a/src/Functions/FunctionSqid.cpp +++ b/src/Functions/FunctionSqid.cpp @@ -1,6 +1,6 @@ #include "config.h" -#ifdef ENABLE_SQIDS +#if USE_SQIDS #include #include diff --git a/src/Functions/punycode.cpp b/src/Functions/punycode.cpp index 5279c1d7312..e90cba82b1f 100644 --- a/src/Functions/punycode.cpp +++ b/src/Functions/punycode.cpp @@ -1,6 +1,6 @@ #include "config.h" -#ifdef USE_IDNA +#if USE_IDNA #include #include diff --git a/utils/check-style/aspell-ignore/en/aspell-dict.txt b/utils/check-style/aspell-ignore/en/aspell-dict.txt index 637ab0ce6d4..d863d0e865c 100644 --- a/utils/check-style/aspell-ignore/en/aspell-dict.txt +++ b/utils/check-style/aspell-ignore/en/aspell-dict.txt @@ -711,6 +711,7 @@ Promtail Protobuf ProtobufSingle ProxySQL +Punycode PyArrow PyCharm QEMU @@ -2069,6 +2070,8 @@ pseudorandom pseudorandomize psql ptrs +punycodeDecode +punycodeEncode pushdown pwrite py From 41a4227bb02b0a377cec46b34d3e4afa640a0eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 18 Dec 2023 12:19:12 +0100 Subject: [PATCH 072/253] Break tests --- ...rum_insert_have_data_before_quorum_zookeeper_long.sql | 1 - ...um_insert_lost_part_and_alive_part_zookeeper_long.sql | 1 - .../00732_quorum_insert_lost_part_zookeeper_long.sql | 1 - ...t_with_old_data_and_without_quorum_zookeeper_long.sql | 1 - ..._quorum_insert_simple_test_1_parts_zookeeper_long.sql | 1 - ..._quorum_insert_simple_test_2_parts_zookeeper_long.sql | 1 - .../01090_zookeeper_mutations_and_insert_quorum_long.sql | 1 - .../01451_replicated_detach_drop_and_quorum_long.sql | 7 +++---- .../01451_replicated_detach_drop_part_long.sql | 9 ++++----- .../0_stateless/01459_manual_write_to_replicas.sh | 4 ---- ...1459_manual_write_to_replicas_quorum_detach_attach.sh | 2 +- .../01509_check_many_parallel_quorum_inserts_long.sh | 2 +- .../01509_check_parallel_quorum_inserts_long.sh | 2 +- .../0_stateless/01509_parallel_quorum_and_merge_long.sh | 7 +++---- .../01509_parallel_quorum_insert_no_replicas_long.sql | 2 -- ...ithout_select_sequence_consistency_zookeeper_long.sql | 1 - .../01532_execute_merges_on_single_replica_long.sql | 4 +--- .../01650_fetch_patition_with_macro_in_zk_path_long.sql | 6 +++--- 18 files changed, 17 insertions(+), 36 deletions(-) diff --git a/tests/queries/0_stateless/00732_quorum_insert_have_data_before_quorum_zookeeper_long.sql b/tests/queries/0_stateless/00732_quorum_insert_have_data_before_quorum_zookeeper_long.sql index 23b368549f8..bff8c7e73ee 100644 --- a/tests/queries/0_stateless/00732_quorum_insert_have_data_before_quorum_zookeeper_long.sql +++ b/tests/queries/0_stateless/00732_quorum_insert_have_data_before_quorum_zookeeper_long.sql @@ -20,7 +20,6 @@ SET select_sequential_consistency=1; SELECT x FROM quorum1 ORDER BY x; SELECT x FROM quorum2 ORDER BY x; -SET insert_keeper_fault_injection_probability=0; SET insert_quorum=2, insert_quorum_parallel=0; INSERT INTO quorum1 VALUES (4, '1990-11-15'); diff --git a/tests/queries/0_stateless/00732_quorum_insert_lost_part_and_alive_part_zookeeper_long.sql b/tests/queries/0_stateless/00732_quorum_insert_lost_part_and_alive_part_zookeeper_long.sql index 74399c9f27c..a1859220c6c 100644 --- a/tests/queries/0_stateless/00732_quorum_insert_lost_part_and_alive_part_zookeeper_long.sql +++ b/tests/queries/0_stateless/00732_quorum_insert_lost_part_and_alive_part_zookeeper_long.sql @@ -11,7 +11,6 @@ CREATE TABLE quorum2(x UInt32, y Date) ENGINE ReplicatedMergeTree('/clickhouse/t SET insert_quorum=2, insert_quorum_parallel=0; SET select_sequential_consistency=1; -SET insert_keeper_fault_injection_probability=0; INSERT INTO quorum1 VALUES (1, '2018-11-15'); INSERT INTO quorum1 VALUES (2, '2018-11-15'); diff --git a/tests/queries/0_stateless/00732_quorum_insert_lost_part_zookeeper_long.sql b/tests/queries/0_stateless/00732_quorum_insert_lost_part_zookeeper_long.sql index a61672249a8..61394447c3d 100644 --- a/tests/queries/0_stateless/00732_quorum_insert_lost_part_zookeeper_long.sql +++ b/tests/queries/0_stateless/00732_quorum_insert_lost_part_zookeeper_long.sql @@ -11,7 +11,6 @@ CREATE TABLE quorum2(x UInt32, y Date) ENGINE ReplicatedMergeTree('/clickhouse/t SET insert_quorum=2, insert_quorum_parallel=0; SET select_sequential_consistency=1; -SET insert_keeper_fault_injection_probability=0; SET insert_quorum_timeout=0; diff --git a/tests/queries/0_stateless/00732_quorum_insert_select_with_old_data_and_without_quorum_zookeeper_long.sql b/tests/queries/0_stateless/00732_quorum_insert_select_with_old_data_and_without_quorum_zookeeper_long.sql index e821d7587ee..e3e5aa7949f 100644 --- a/tests/queries/0_stateless/00732_quorum_insert_select_with_old_data_and_without_quorum_zookeeper_long.sql +++ b/tests/queries/0_stateless/00732_quorum_insert_select_with_old_data_and_without_quorum_zookeeper_long.sql @@ -17,7 +17,6 @@ SYSTEM SYNC REPLICA quorum2; SET select_sequential_consistency=1; SET insert_quorum=2, insert_quorum_parallel=0; -SET insert_keeper_fault_injection_probability=0; SET insert_quorum_timeout=0; diff --git a/tests/queries/0_stateless/00732_quorum_insert_simple_test_1_parts_zookeeper_long.sql b/tests/queries/0_stateless/00732_quorum_insert_simple_test_1_parts_zookeeper_long.sql index 22fb40f9f85..4eb263c75c2 100644 --- a/tests/queries/0_stateless/00732_quorum_insert_simple_test_1_parts_zookeeper_long.sql +++ b/tests/queries/0_stateless/00732_quorum_insert_simple_test_1_parts_zookeeper_long.sql @@ -11,7 +11,6 @@ CREATE TABLE quorum2(x UInt32, y Date) ENGINE ReplicatedMergeTree('/clickhouse/t SET insert_quorum=2, insert_quorum_parallel=0; SET select_sequential_consistency=1; -SET insert_keeper_fault_injection_probability=0; INSERT INTO quorum1 VALUES (1, '2018-11-15'); INSERT INTO quorum1 VALUES (2, '2018-11-15'); diff --git a/tests/queries/0_stateless/00732_quorum_insert_simple_test_2_parts_zookeeper_long.sql b/tests/queries/0_stateless/00732_quorum_insert_simple_test_2_parts_zookeeper_long.sql index a97b7438da0..7fb23936819 100644 --- a/tests/queries/0_stateless/00732_quorum_insert_simple_test_2_parts_zookeeper_long.sql +++ b/tests/queries/0_stateless/00732_quorum_insert_simple_test_2_parts_zookeeper_long.sql @@ -11,7 +11,6 @@ CREATE TABLE quorum2(x UInt32, y Date) ENGINE ReplicatedMergeTree('/clickhouse/t SET insert_quorum=2, insert_quorum_parallel=0; SET select_sequential_consistency=1; -SET insert_keeper_fault_injection_probability=0; INSERT INTO quorum1 VALUES (1, '2018-11-15'); INSERT INTO quorum1 VALUES (2, '2018-11-15'); diff --git a/tests/queries/0_stateless/01090_zookeeper_mutations_and_insert_quorum_long.sql b/tests/queries/0_stateless/01090_zookeeper_mutations_and_insert_quorum_long.sql index db6555e593e..67534a4611e 100644 --- a/tests/queries/0_stateless/01090_zookeeper_mutations_and_insert_quorum_long.sql +++ b/tests/queries/0_stateless/01090_zookeeper_mutations_and_insert_quorum_long.sql @@ -9,7 +9,6 @@ CREATE TABLE mutations_and_quorum2 (`server_date` Date, `something` String) ENGI -- Should not be larger then 600e6 (default timeout in clickhouse-test) SET insert_quorum=2, insert_quorum_parallel=0, insert_quorum_timeout=300e3; -SET insert_keeper_fault_injection_probability=0; INSERT INTO mutations_and_quorum1 VALUES ('2019-01-01', 'test1'), ('2019-02-01', 'test2'), ('2019-03-01', 'test3'), ('2019-04-01', 'test4'), ('2019-05-01', 'test1'), ('2019-06-01', 'test2'), ('2019-07-01', 'test3'), ('2019-08-01', 'test4'), ('2019-09-01', 'test1'), ('2019-10-01', 'test2'), ('2019-11-01', 'test3'), ('2019-12-01', 'test4'); diff --git a/tests/queries/0_stateless/01451_replicated_detach_drop_and_quorum_long.sql b/tests/queries/0_stateless/01451_replicated_detach_drop_and_quorum_long.sql index eea231c9f58..21b65995482 100644 --- a/tests/queries/0_stateless/01451_replicated_detach_drop_and_quorum_long.sql +++ b/tests/queries/0_stateless/01451_replicated_detach_drop_and_quorum_long.sql @@ -1,6 +1,5 @@ -- Tags: long, replica, no-replicated-database -SET insert_keeper_fault_injection_probability=0; -- disable fault injection; part ids are non-deterministic in case of insert retries SET replication_alter_partitions_sync = 2; @@ -10,7 +9,7 @@ DROP TABLE IF EXISTS replica2; CREATE TABLE replica1 (v UInt8) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/test/01451/quorum', 'r1') order by tuple() settings max_replicated_merges_in_queue = 0; CREATE TABLE replica2 (v UInt8) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/test/01451/quorum', 'r2') order by tuple() settings max_replicated_merges_in_queue = 0; -INSERT INTO replica1 VALUES (0); +INSERT INTO replica1 SETTINGS insert_keeper_fault_injection_probability=0 VALUES (0); SYSTEM SYNC REPLICA replica2; @@ -27,7 +26,7 @@ ALTER TABLE replica2 DROP PARTITION ID 'all'; SET insert_quorum = 2, insert_quorum_parallel = 0; -INSERT INTO replica2 VALUES (1); +INSERT INTO replica2 SETTINGS insert_keeper_fault_injection_probability=0 VALUES (1); SYSTEM SYNC REPLICA replica2; @@ -39,7 +38,7 @@ SELECT COUNT() FROM replica1; SET insert_quorum_parallel=1; -INSERT INTO replica2 VALUES (2); +INSERT INTO replica2 SETTINGS insert_keeper_fault_injection_probability=0 VALUES (2); -- should work, parallel quorum nodes exists only during insert ALTER TABLE replica1 DROP PART 'all_3_3_0'; diff --git a/tests/queries/0_stateless/01451_replicated_detach_drop_part_long.sql b/tests/queries/0_stateless/01451_replicated_detach_drop_part_long.sql index bf7a471fa40..25b2923ddd9 100644 --- a/tests/queries/0_stateless/01451_replicated_detach_drop_part_long.sql +++ b/tests/queries/0_stateless/01451_replicated_detach_drop_part_long.sql @@ -1,7 +1,6 @@ -- Tags: long, replica, no-replicated-database -- Tag no-replicated-database: Fails due to additional replicas or shards -SET insert_keeper_fault_injection_probability=0; -- disable fault injection; part ids are non-deterministic in case of insert retries SET replication_alter_partitions_sync = 2; DROP TABLE IF EXISTS replica1 SYNC; @@ -10,9 +9,9 @@ DROP TABLE IF EXISTS replica2 SYNC; CREATE TABLE replica1 (v UInt8) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/'||currentDatabase()||'test/01451/attach', 'r1') order by tuple() settings max_replicated_merges_in_queue = 0; CREATE TABLE replica2 (v UInt8) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/'||currentDatabase()||'test/01451/attach', 'r2') order by tuple() settings max_replicated_merges_in_queue = 0; -INSERT INTO replica1 VALUES (0); -INSERT INTO replica1 VALUES (1); -INSERT INTO replica1 VALUES (2); +INSERT INTO replica1 SETTINGS insert_keeper_fault_injection_probability=0 VALUES (0); +INSERT INTO replica1 SETTINGS insert_keeper_fault_injection_probability=0 VALUES (1); +INSERT INTO replica1 SETTINGS insert_keeper_fault_injection_probability=0 VALUES (2); ALTER TABLE replica1 DETACH PART 'all_100_100_0'; -- { serverError 232 } @@ -25,7 +24,7 @@ SELECT v FROM replica1 ORDER BY v; SELECT name FROM system.detached_parts WHERE table = 'replica2' AND database = currentDatabase(); -ALTER TABLE replica2 ATTACH PART 'all_1_1_0'; +ALTER TABLE replica2 ATTACH PART 'all_1_1_0' SETTINGS insert_keeper_fault_injection_probability=0; SYSTEM SYNC REPLICA replica1; SELECT v FROM replica1 ORDER BY v; diff --git a/tests/queries/0_stateless/01459_manual_write_to_replicas.sh b/tests/queries/0_stateless/01459_manual_write_to_replicas.sh index c05d813ca7f..a9a6d27c145 100755 --- a/tests/queries/0_stateless/01459_manual_write_to_replicas.sh +++ b/tests/queries/0_stateless/01459_manual_write_to_replicas.sh @@ -20,10 +20,6 @@ function thread { for x in {0..99}; do # sometimes we can try to commit obsolete part if fetches will be quite fast, # so supress warning messages like "Tried to commit obsolete part ... covered by ..." - # (2) keeper fault injection for inserts because - # it can be a cause of deduplicated parts be visible to SELECTs for sometime (until cleanup thread remove them), - # so the same SELECT on different replicas can return different results, i.e. test output will be non-deterministic - # (see #9712) $CLICKHOUSE_CLIENT --insert_keeper_fault_injection_probability=0 --query "INSERT INTO r$1 SELECT $x % $NUM_REPLICAS = $1 ? $x - 1 : $x" 2>/dev/null # Replace some records as duplicates so they will be written by other replicas done } diff --git a/tests/queries/0_stateless/01459_manual_write_to_replicas_quorum_detach_attach.sh b/tests/queries/0_stateless/01459_manual_write_to_replicas_quorum_detach_attach.sh index 01c88336282..1f76a2efc6b 100755 --- a/tests/queries/0_stateless/01459_manual_write_to_replicas_quorum_detach_attach.sh +++ b/tests/queries/0_stateless/01459_manual_write_to_replicas_quorum_detach_attach.sh @@ -24,7 +24,7 @@ function thread { while true; do $CLICKHOUSE_CLIENT --query "DETACH TABLE r$1" $CLICKHOUSE_CLIENT --query "ATTACH TABLE r$1" - $CLICKHOUSE_CLIENT --insert_quorum 3 --insert_quorum_parallel 0 --insert_keeper_fault_injection_probability=0 --query "INSERT INTO r$1 SELECT $x" 2>&1 | grep -qE "$valid_exceptions_to_retry" || break + $CLICKHOUSE_CLIENT --insert_quorum 3 --insert_quorum_parallel 0 --query "INSERT INTO r$1 SELECT $x" 2>&1 | grep -qE "$valid_exceptions_to_retry" || break done done } diff --git a/tests/queries/0_stateless/01509_check_many_parallel_quorum_inserts_long.sh b/tests/queries/0_stateless/01509_check_many_parallel_quorum_inserts_long.sh index 1ccbe34b10a..22cd6fb8127 100755 --- a/tests/queries/0_stateless/01509_check_many_parallel_quorum_inserts_long.sh +++ b/tests/queries/0_stateless/01509_check_many_parallel_quorum_inserts_long.sh @@ -20,7 +20,7 @@ done function thread { i=0 retries=300 while [[ $i -lt $retries ]]; do # server can be dead - $CLICKHOUSE_CLIENT --insert_quorum 3 --insert_quorum_parallel 1 --insert_keeper_fault_injection_probability=0 --query "INSERT INTO r$1 SELECT $2" && break + $CLICKHOUSE_CLIENT --insert_quorum 3 --insert_quorum_parallel 1 --query "INSERT INTO r$1 SELECT $2" && break ((++i)) sleep 0.1 done diff --git a/tests/queries/0_stateless/01509_check_parallel_quorum_inserts_long.sh b/tests/queries/0_stateless/01509_check_parallel_quorum_inserts_long.sh index 6fbdf42914c..1589f17c752 100755 --- a/tests/queries/0_stateless/01509_check_parallel_quorum_inserts_long.sh +++ b/tests/queries/0_stateless/01509_check_parallel_quorum_inserts_long.sh @@ -21,7 +21,7 @@ done $CLICKHOUSE_CLIENT -n -q "SYSTEM STOP REPLICATION QUEUES r2;" function thread { - $CLICKHOUSE_CLIENT --insert_quorum 2 --insert_quorum_parallel 1 --insert_keeper_fault_injection_probability=0 --query "INSERT INTO r1 SELECT $1" + $CLICKHOUSE_CLIENT --insert_quorum 2 --insert_quorum_parallel 1 --query "INSERT INTO r1 SELECT $1" } for i in $(seq 1 $NUM_INSERTS); do diff --git a/tests/queries/0_stateless/01509_parallel_quorum_and_merge_long.sh b/tests/queries/0_stateless/01509_parallel_quorum_and_merge_long.sh index bf88ad0e0b2..a814759ab10 100755 --- a/tests/queries/0_stateless/01509_parallel_quorum_and_merge_long.sh +++ b/tests/queries/0_stateless/01509_parallel_quorum_and_merge_long.sh @@ -20,10 +20,9 @@ $CLICKHOUSE_CLIENT -q "CREATE TABLE parallel_q2 (x UInt64) ENGINE=ReplicatedMerg $CLICKHOUSE_CLIENT -q "SYSTEM STOP REPLICATION QUEUES parallel_q2" -$CLICKHOUSE_CLIENT --insert_keeper_fault_injection_probability=0 -q "INSERT INTO parallel_q1 VALUES (1)" - -# disable keeper fault injection during insert since test checks part names. Part names can differ in case of retries during insert -$CLICKHOUSE_CLIENT --insert_quorum 2 --insert_quorum_parallel 1 --insert_keeper_fault_injection_probability=0 --query="INSERT INTO parallel_q1 VALUES (2)" & +# This test depends on part names and those aren't deterministic with faults +$CLICKHOUSE_CLIENT --insert_keeper_fault_injection_probability=0 -q "INSERT INTO parallel_q1 VALUES (1)" +$CLICKHOUSE_CLIENT --insert_keeper_fault_injection_probability=0 --insert_quorum 2 --insert_quorum_parallel 1 --query="INSERT INTO parallel_q1 VALUES (2)" & part_count=$($CLICKHOUSE_CLIENT --query="SELECT COUNT() FROM system.parts WHERE table='parallel_q1' and database='${CLICKHOUSE_DATABASE}'") diff --git a/tests/queries/0_stateless/01509_parallel_quorum_insert_no_replicas_long.sql b/tests/queries/0_stateless/01509_parallel_quorum_insert_no_replicas_long.sql index 5a23473dd0a..24b368090e7 100644 --- a/tests/queries/0_stateless/01509_parallel_quorum_insert_no_replicas_long.sql +++ b/tests/queries/0_stateless/01509_parallel_quorum_insert_no_replicas_long.sql @@ -16,8 +16,6 @@ CREATE TABLE r2 ( ENGINE = ReplicatedMergeTree('/clickhouse/{database}/01509_parallel_quorum_insert_no_replicas', '2') ORDER BY tuple(); -SET insert_keeper_fault_injection_probability=0; - SET insert_quorum_parallel=1; SET insert_quorum=3; diff --git a/tests/queries/0_stateless/01513_count_without_select_sequence_consistency_zookeeper_long.sql b/tests/queries/0_stateless/01513_count_without_select_sequence_consistency_zookeeper_long.sql index 4a992449a16..f800ff86aa5 100644 --- a/tests/queries/0_stateless/01513_count_without_select_sequence_consistency_zookeeper_long.sql +++ b/tests/queries/0_stateless/01513_count_without_select_sequence_consistency_zookeeper_long.sql @@ -20,7 +20,6 @@ SYSTEM SYNC REPLICA quorum3; SET select_sequential_consistency=0; SET optimize_trivial_count_query=1; SET insert_quorum=2, insert_quorum_parallel=0; -SET insert_keeper_fault_injection_probability=0; SYSTEM STOP FETCHES quorum1; diff --git a/tests/queries/0_stateless/01532_execute_merges_on_single_replica_long.sql b/tests/queries/0_stateless/01532_execute_merges_on_single_replica_long.sql index 30beb29251e..49ef9d8b79f 100644 --- a/tests/queries/0_stateless/01532_execute_merges_on_single_replica_long.sql +++ b/tests/queries/0_stateless/01532_execute_merges_on_single_replica_long.sql @@ -2,8 +2,6 @@ -- Tag no-replicated-database: Fails due to additional replicas or shards -- Tag no-parallel: static zk path -SET insert_keeper_fault_injection_probability=0; -- disable fault injection; part ids are non-deterministic in case of insert retries - DROP TABLE IF EXISTS execute_on_single_replica_r1 SYNC; DROP TABLE IF EXISTS execute_on_single_replica_r2 SYNC; @@ -11,7 +9,7 @@ DROP TABLE IF EXISTS execute_on_single_replica_r2 SYNC; CREATE TABLE execute_on_single_replica_r1 (x UInt64) ENGINE=ReplicatedMergeTree('/clickhouse/tables/test_01532/execute_on_single_replica', 'r1') ORDER BY tuple() SETTINGS execute_merges_on_single_replica_time_threshold=10; CREATE TABLE execute_on_single_replica_r2 (x UInt64) ENGINE=ReplicatedMergeTree('/clickhouse/tables/test_01532/execute_on_single_replica', 'r2') ORDER BY tuple() SETTINGS execute_merges_on_single_replica_time_threshold=10; -INSERT INTO execute_on_single_replica_r1 VALUES (1); +INSERT INTO execute_on_single_replica_r1 SETTINGS insert_keeper_fault_injection_probability=0 VALUES (1); SYSTEM SYNC REPLICA execute_on_single_replica_r2; SET optimize_throw_if_noop=1; diff --git a/tests/queries/0_stateless/01650_fetch_patition_with_macro_in_zk_path_long.sql b/tests/queries/0_stateless/01650_fetch_patition_with_macro_in_zk_path_long.sql index f4afcb8d55e..029a17f87dc 100644 --- a/tests/queries/0_stateless/01650_fetch_patition_with_macro_in_zk_path_long.sql +++ b/tests/queries/0_stateless/01650_fetch_patition_with_macro_in_zk_path_long.sql @@ -4,7 +4,7 @@ DROP TABLE IF EXISTS test_01640; DROP TABLE IF EXISTS restore_01640; CREATE TABLE test_01640(i Int64, d Date, s String) -ENGINE = ReplicatedMergeTree('/clickhouse/{database}/{shard}/tables/test_01640','{replica}') +ENGINE = ReplicatedMergeTree('/clickhouse/{database}/{shard}/tables/test_01640','{replica}') PARTITION BY toYYYYMM(d) ORDER BY i SETTINGS allow_remote_fs_zero_copy_replication=0; @@ -16,13 +16,13 @@ PARTITION BY toYYYYMM(d) ORDER BY i SETTINGS allow_remote_fs_zero_copy_replication=0; ALTER TABLE restore_01640 FETCH PARTITION tuple(toYYYYMM(toDate('2021-01-01'))) - FROM '/clickhouse/{database}/{shard}/tables/test_01640'; + FROM '/clickhouse/{database}/{shard}/tables/test_01640' SETTINGS insert_keeper_fault_injection_probability=0; SELECT partition_id FROM system.detached_parts WHERE (table = 'restore_01640') AND (database = currentDatabase()); -ALTER TABLE restore_01640 ATTACH PARTITION tuple(toYYYYMM(toDate('2021-01-01'))); +ALTER TABLE restore_01640 ATTACH PARTITION tuple(toYYYYMM(toDate('2021-01-01'))) SETTINGS insert_keeper_fault_injection_probability=0;; SELECT partition_id FROM system.detached_parts From 03468508974755c4f7308a4812616c613a09bfee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 18 Dec 2023 12:54:15 +0100 Subject: [PATCH 073/253] Support keeper failures in quorum check --- .../MergeTree/ReplicatedMergeTreeSink.cpp | 79 +++++++++++-------- src/Storages/MergeTree/ZooKeeperRetries.h | 1 + 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp index b6b3908701f..a488fc72208 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp @@ -1073,7 +1073,27 @@ std::pair, bool> ReplicatedMergeTreeSinkImpl:: if (quorum_parallel) quorum_info.status_path = storage.zookeeper_path + "/quorum/parallel/" + retry_context.actual_part_name; - waitForQuorum(zookeeper, retry_context.actual_part_name, quorum_info.status_path, quorum_info.is_active_node_version, replicas_num); + ZooKeeperRetriesControl new_retry_controller = retries_ctl; + new_retry_controller.requestUnconditionalRetry(); + new_retry_controller.actionAfterLastFailedRetry([&] + { + /// We do not know whether or not data has been inserted in other replicas + new_retry_controller.setUserError( + ErrorCodes::UNKNOWN_STATUS_OF_INSERT, + "Unknown quorum status. The data was inserted in the local replica but we could not verify quorum. Reason: {}", + new_retry_controller.getLastKeeperErrorMessage()); + }); + + new_retry_controller.retryLoop([&]() + { + zookeeper->setKeeper(storage.getZooKeeper()); + waitForQuorum( + zookeeper, + retry_context.actual_part_name, + quorum_info.status_path, + quorum_info.is_active_node_version, + replicas_num); + }); } } @@ -1106,49 +1126,38 @@ void ReplicatedMergeTreeSinkImpl::waitForQuorum( /// We are waiting for quorum to be satisfied. LOG_TRACE(log, "Waiting for quorum '{}' for part {}{}", quorum_path, part_name, quorumLogMessage(replicas_num)); - try + fiu_do_on(FailPoints::replicated_merge_tree_insert_quorum_fail_0, { zookeeper->forceFailureBeforeOperation(); }); + + while (true) { - fiu_do_on(FailPoints::replicated_merge_tree_insert_quorum_fail_0, { zookeeper->forceFailureBeforeOperation(); }); + zkutil::EventPtr event = std::make_shared(); - while (true) - { - zkutil::EventPtr event = std::make_shared(); + std::string value; + /// `get` instead of `exists` so that `watch` does not leak if the node is no longer there. + if (!zookeeper->tryGet(quorum_path, value, nullptr, event)) + break; - std::string value; - /// `get` instead of `exists` so that `watch` does not leak if the node is no longer there. - if (!zookeeper->tryGet(quorum_path, value, nullptr, event)) - break; + LOG_TRACE(log, "Quorum node {} still exists, will wait for updates", quorum_path); - LOG_TRACE(log, "Quorum node {} still exists, will wait for updates", quorum_path); + ReplicatedMergeTreeQuorumEntry quorum_entry(value); - ReplicatedMergeTreeQuorumEntry quorum_entry(value); + /// If the node has time to disappear, and then appear again for the next insert. + if (quorum_entry.part_name != part_name) + break; - /// If the node has time to disappear, and then appear again for the next insert. - if (quorum_entry.part_name != part_name) - break; + if (!event->tryWait(quorum_timeout_ms)) + throw Exception(ErrorCodes::UNKNOWN_STATUS_OF_INSERT, "Timeout while waiting for quorum"); - if (!event->tryWait(quorum_timeout_ms)) - throw Exception(ErrorCodes::TIMEOUT_EXCEEDED, "Timeout while waiting for quorum"); - - LOG_TRACE(log, "Quorum {} for part {} updated, will check quorum node still exists", quorum_path, part_name); - } - - /// And what if it is possible that the current replica at this time has ceased to be active - /// and the quorum is marked as failed and deleted? - Coordination::Stat stat; - String value; - if (!zookeeper->tryGet(storage.replica_path + "/is_active", value, &stat) - || stat.version != is_active_node_version) - throw Exception(ErrorCodes::NO_ACTIVE_REPLICAS, "Replica become inactive while waiting for quorum"); - } - catch (...) - { - /// We do not know whether or not data has been inserted - /// - whether other replicas have time to download the part and mark the quorum as done. - throw Exception(ErrorCodes::UNKNOWN_STATUS_OF_INSERT, "Unknown status, client must retry. Reason: {}", - getCurrentExceptionMessage(false)); + LOG_TRACE(log, "Quorum {} for part {} updated, will check quorum node still exists", quorum_path, part_name); } + /// And what if it is possible that the current replica at this time has ceased to be active + /// and the quorum is marked as failed and deleted? + Coordination::Stat stat; + String value; + if (!zookeeper->tryGet(storage.replica_path + "/is_active", value, &stat) || stat.version != is_active_node_version) + throw Exception(ErrorCodes::UNKNOWN_STATUS_OF_INSERT, "Replica become inactive while waiting for quorum"); + LOG_TRACE(log, "Quorum '{}' for part {} satisfied", quorum_path, part_name); } diff --git a/src/Storages/MergeTree/ZooKeeperRetries.h b/src/Storages/MergeTree/ZooKeeperRetries.h index e46c3f974c7..b93e8323574 100644 --- a/src/Storages/MergeTree/ZooKeeperRetries.h +++ b/src/Storages/MergeTree/ZooKeeperRetries.h @@ -177,6 +177,7 @@ public: bool isRetry() const { return retries_info.retry_count > 0; } Coordination::Error getLastKeeperErrorCode() const { return keeper_error.code; } + std::string getLastKeeperErrorMessage() const { return keeper_error.message; } /// action will be called only once and only after latest failed retry void actionAfterLastFailedRetry(std::function f) { action_after_last_failed_retry = std::move(f); } From fdda0cf8ac3f94e8c1b8f7b28a0f7bf172938ba3 Mon Sep 17 00:00:00 2001 From: chen768959 <934103231@qq.com> Date: Mon, 18 Dec 2023 21:43:07 +0800 Subject: [PATCH 074/253] Literal aliases incorrectly classified as 'complex_aliases'. --- .../RequiredSourceColumnsVisitor.cpp | 3 ++- ..._literal_alias_misclassification.reference | 2 ++ .../02946_literal_alias_misclassification.sql | 24 +++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/02946_literal_alias_misclassification.reference create mode 100644 tests/queries/0_stateless/02946_literal_alias_misclassification.sql diff --git a/src/Interpreters/RequiredSourceColumnsVisitor.cpp b/src/Interpreters/RequiredSourceColumnsVisitor.cpp index 1bcec02f0c0..c07d783788a 100644 --- a/src/Interpreters/RequiredSourceColumnsVisitor.cpp +++ b/src/Interpreters/RequiredSourceColumnsVisitor.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace DB { @@ -126,7 +127,7 @@ void RequiredSourceColumnsMatcher::visit(const ASTSelectQuery & select, const AS if (const auto * identifier = node->as()) data.addColumnIdentifier(*identifier); - else + else if (!node->as()) data.addColumnAliasIfAny(*node); } diff --git a/tests/queries/0_stateless/02946_literal_alias_misclassification.reference b/tests/queries/0_stateless/02946_literal_alias_misclassification.reference new file mode 100644 index 00000000000..d8e5a437352 --- /dev/null +++ b/tests/queries/0_stateless/02946_literal_alias_misclassification.reference @@ -0,0 +1,2 @@ +const 1 +const 2 diff --git a/tests/queries/0_stateless/02946_literal_alias_misclassification.sql b/tests/queries/0_stateless/02946_literal_alias_misclassification.sql new file mode 100644 index 00000000000..0d001bf1e4c --- /dev/null +++ b/tests/queries/0_stateless/02946_literal_alias_misclassification.sql @@ -0,0 +1,24 @@ +DROP TABLE IF EXISTS literal_alias_misclassification; + +CREATE TABLE literal_alias_misclassification +( + `id` Int64, + `a` Nullable(String), + `b` Nullable(Int64) +) +ENGINE = MergeTree +ORDER BY id; + + +INSERT INTO literal_alias_misclassification values(1, 'a', 1); +INSERT INTO literal_alias_misclassification values(2, 'b', 2); + +SELECT 'const' AS r, b +FROM + ( SELECT a AS r, b FROM literal_alias_misclassification ) AS t1 + LEFT JOIN + ( SELECT a AS r FROM literal_alias_misclassification ) AS t2 + ON t1.r = t2.r +ORDER BY b; + +DROP TABLE IF EXISTS literal_alias_misclassification; From 88dcaefde8434199e70d9c2837e8f0acd5d1aadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 18 Dec 2023 14:51:46 +0100 Subject: [PATCH 075/253] Style --- src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp index a488fc72208..68a6a36d199 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp @@ -40,8 +40,6 @@ namespace ErrorCodes extern const int READONLY; extern const int UNKNOWN_STATUS_OF_INSERT; extern const int INSERT_WAS_DEDUPLICATED; - extern const int TIMEOUT_EXCEEDED; - extern const int NO_ACTIVE_REPLICAS; extern const int DUPLICATE_DATA_PART; extern const int PART_IS_TEMPORARILY_LOCKED; extern const int LOGICAL_ERROR; From 99d1659b4c813c3afbae015de928aed715666602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 18 Dec 2023 15:28:54 +0100 Subject: [PATCH 076/253] Replace rust's BLAKE3 with llvm's implementation --- rust/BLAKE3/CMakeLists.txt | 3 - rust/BLAKE3/Cargo.toml | 20 - rust/BLAKE3/include/blake3.h | 15 - rust/BLAKE3/src/lib.rs | 31 -- rust/CMakeLists.txt | 1 - rust/Cargo.lock | 683 +++++++++++++++---------------- rust/Cargo.toml | 1 - src/Functions/FunctionsHashing.h | 16 +- src/configure_config.cmake | 4 +- 9 files changed, 346 insertions(+), 428 deletions(-) delete mode 100644 rust/BLAKE3/CMakeLists.txt delete mode 100644 rust/BLAKE3/Cargo.toml delete mode 100644 rust/BLAKE3/include/blake3.h delete mode 100644 rust/BLAKE3/src/lib.rs diff --git a/rust/BLAKE3/CMakeLists.txt b/rust/BLAKE3/CMakeLists.txt deleted file mode 100644 index ceb0a647b66..00000000000 --- a/rust/BLAKE3/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -clickhouse_import_crate(MANIFEST_PATH Cargo.toml) -target_include_directories(_ch_rust_blake3 INTERFACE include) -add_library(ch_rust::blake3 ALIAS _ch_rust_blake3) diff --git a/rust/BLAKE3/Cargo.toml b/rust/BLAKE3/Cargo.toml deleted file mode 100644 index ed414fa54c1..00000000000 --- a/rust/BLAKE3/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "_ch_rust_blake3" -version = "0.1.0" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -blake3 = "1.2.0" -libc = "0.2.132" - -[lib] -crate-type = ["staticlib"] - -[profile.release] -debug = true - -[profile.release-thinlto] -inherits = "release" -# BLAKE3 module requires "full" LTO (not "thin") to get additional 10% performance benefit -lto = true diff --git a/rust/BLAKE3/include/blake3.h b/rust/BLAKE3/include/blake3.h deleted file mode 100644 index 5dc7d5bd902..00000000000 --- a/rust/BLAKE3/include/blake3.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef BLAKE3_H -#define BLAKE3_H - -#include - - -extern "C" { - -char *blake3_apply_shim(const char *begin, uint32_t _size, uint8_t *out_char_data); - -void blake3_free_char_pointer(char *ptr_to_free); - -} // extern "C" - -#endif /* BLAKE3_H */ diff --git a/rust/BLAKE3/src/lib.rs b/rust/BLAKE3/src/lib.rs deleted file mode 100644 index 7a3be8a2ae7..00000000000 --- a/rust/BLAKE3/src/lib.rs +++ /dev/null @@ -1,31 +0,0 @@ -extern crate blake3; -extern crate libc; - -use std::ffi::{CString}; -use std::slice; -use std::os::raw::c_char; - -#[no_mangle] -pub unsafe extern "C" fn blake3_apply_shim( - begin: *const c_char, - size: u32, - out_char_data: *mut u8, -) -> *mut c_char { - if begin.is_null() { - let err_str = CString::new("input was a null pointer").unwrap(); - return err_str.into_raw(); - } - let input_res = slice::from_raw_parts(begin as *const u8, size as usize); - let mut hasher = blake3::Hasher::new(); - hasher.update(input_res); - let mut reader = hasher.finalize_xof(); - - reader.fill(std::slice::from_raw_parts_mut(out_char_data, blake3::OUT_LEN)); - std::ptr::null_mut() -} - -// Freeing memory according to docs: https://doc.rust-lang.org/std/ffi/struct.CString.html#method.into_raw -#[no_mangle] -pub unsafe extern "C" fn blake3_free_char_pointer(ptr_to_free: *mut c_char) { - std::mem::drop(CString::from_raw(ptr_to_free)); -} diff --git a/rust/CMakeLists.txt b/rust/CMakeLists.txt index 5ea806baa3b..66694ee16f8 100644 --- a/rust/CMakeLists.txt +++ b/rust/CMakeLists.txt @@ -99,6 +99,5 @@ function(add_rust_subdirectory src) VERBATIM) endfunction() -add_rust_subdirectory (BLAKE3) add_rust_subdirectory (skim) add_rust_subdirectory (prql) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 04569cd3b3a..86bbec5579f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2,14 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "_ch_rust_blake3" -version = "0.1.0" -dependencies = [ - "blake3", - "libc", -] - [[package]] name = "_ch_rust_prql" version = "0.1.0" @@ -30,9 +22,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -45,24 +37,31 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ - "getrandom", + "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -95,43 +94,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" dependencies = [ "backtrace", ] @@ -146,12 +145,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - [[package]] name = "arrayvec" version = "0.7.4" @@ -166,9 +159,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -193,44 +186,24 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" - -[[package]] -name = "blake3" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "digest", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -240,24 +213,23 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", - "time 0.1.45", "wasm-bindgen", - "winapi", + "windows-targets 0.48.5", ] [[package]] name = "chumsky" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23170228b96236b5a7299057ac284a321457700bc8c41a4476052f0f4ba5349d" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ "hashbrown", "stacker", @@ -279,17 +251,11 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "constant_time_eq" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crossbeam" @@ -307,9 +273,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" dependencies = [ "cfg-if", "crossbeam-utils", @@ -317,9 +283,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -328,22 +294,21 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset 0.9.0", - "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "b9bcf5bdbfdd6030fb4a1c497b5d5fc5921aa2f60d359a17e249c0e6df3de153" dependencies = [ "cfg-if", "crossbeam-utils", @@ -351,28 +316,18 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" dependencies = [ "cfg-if", ] -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "csv" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" dependencies = [ "csv-core", "itoa", @@ -382,18 +337,18 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" dependencies = [ "memchr", ] [[package]] name = "cxx" -version = "1.0.102" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f68e12e817cb19eaab81aaec582b4052d07debd3c3c6b083b9d361db47c7dc9d" +checksum = "e9fc0c733f71e58dedf4f034cd2a266f80b94cc9ed512729e1798651b68c2cba" dependencies = [ "cc", "cxxbridge-flags", @@ -403,9 +358,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.102" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e789217e4ab7cf8cc9ce82253180a9fe331f35f5d339f0ccfe0270b39433f397" +checksum = "51bc81d2664db24cf1d35405f66e18a85cffd4d49ab930c71a5c6342a410f38c" dependencies = [ "cc", "codespan-reporting", @@ -413,24 +368,24 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.27", + "syn 2.0.41", ] [[package]] name = "cxxbridge-flags" -version = "1.0.102" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a19f4c80fd9ab6c882286fa865e92e07688f4387370a209508014ead8751d0" +checksum = "8511afbe34ea242697784da5cb2c5d4a0afb224ca8b136bdf93bfe180cbe5884" [[package]] name = "cxxbridge-macro" -version = "1.0.102" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fcfa71f66c8563c4fa9dd2bb68368d50267856f831ac5d85367e0805f9606c" +checksum = "5c6888cd161769d65134846d4d4981d5a6654307cc46ec83fb917e530aea5f84" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.41", ] [[package]] @@ -478,6 +433,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "deranged" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive_builder" version = "0.11.2" @@ -509,17 +473,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - [[package]] name = "dirs-next" version = "2.0.0" @@ -556,28 +509,17 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.41", ] [[package]] name = "errno" -version = "0.3.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -595,40 +537,31 @@ dependencies = [ "thread_local", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash", + "allocator-api2", ] [[package]] @@ -639,22 +572,22 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -680,16 +613,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", + "windows-sys 0.48.0", ] [[package]] @@ -702,16 +626,25 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.9" +name = "itertools" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -724,9 +657,20 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] [[package]] name = "link-cplusplus" @@ -739,21 +683,21 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -825,37 +769,27 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "pin-utils" @@ -864,19 +798,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "proc-macro2" -version = "1.0.66" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "prql-ast" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71194e75f14dbe7debdf2b5eca0812c978021a1bd23d6fe1da98b58e407e035a" +checksum = "d9d91522f9f16d055409b9ffec55693a96e3424fe5d8e7c8331adcf6d7ee363a" dependencies = [ "enum-as-inner", "semver", @@ -886,9 +826,9 @@ dependencies = [ [[package]] name = "prql-compiler" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ff28e838b1be4227cc567a75c11caa3be25c5015f0e5fd21279c06e944ba44f" +checksum = "f4d56865532fcf1abaa31fbb6da6fd9e90edc441c5c78bfe2870ee75187c7a3c" dependencies = [ "anstream", "anyhow", @@ -912,9 +852,9 @@ dependencies = [ [[package]] name = "prql-parser" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3182e2ef0465a960eb02519b18768e39123d3c3a0037a2d2934055a3ef901870" +checksum = "9360352e413390cfd26345f49279622b87581a3b748340d3f42d4d616c2a1ec1" dependencies = [ "chumsky", "itertools 0.11.0", @@ -933,18 +873,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -952,41 +892,39 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.9.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -996,9 +934,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -1007,9 +945,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc-demangle" @@ -1019,15 +957,15 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.6" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1038,15 +976,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "scratch" @@ -1056,38 +988,38 @@ checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" [[package]] name = "semver" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.174" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b88756493a5bd5e5395d53baa70b194b05764ab85b59e43e4b8f4e1192fa9b1" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.174" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5c3a298c7f978e53536f95a63bdc4c4a64550582f31a0359a9afda6aede62e" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.41", ] [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1112,7 +1044,7 @@ dependencies = [ "nix 0.25.1", "rayon", "regex", - "time 0.3.23", + "time", "timer", "tuikit", "unicode-width", @@ -1121,20 +1053,20 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" dependencies = [ - "itertools 0.10.5", + "itertools 0.12.0", "nom", "unicode_categories", ] [[package]] name = "sqlparser" -version = "0.36.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eaa1e88e78d2c2460d78b7dc3f0c08dbb606ab4222f9aff36f420d36e307d87" +checksum = "37ae05a8250b968a3f7db93155a84d68b2e6cea1583949af5ca5b5170c76c075" dependencies = [ "log", "serde", @@ -1170,23 +1102,17 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.25.1" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6069ca09d878a33f883cc06aaa9718ede171841d3832450354410b718b097232" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.27", + "syn 2.0.41", ] -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - [[package]] name = "syn" version = "1.0.109" @@ -1200,9 +1126,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.27" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -1222,31 +1148,31 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.41", ] [[package]] @@ -1261,30 +1187,21 @@ dependencies = [ [[package]] name = "time" -version = "0.1.45" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", + "powerfmt", "serde", "time-core", ] [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "timer" @@ -1309,23 +1226,17 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode_categories" @@ -1366,12 +1277,6 @@ dependencies = [ "quote", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1380,9 +1285,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1390,24 +1295,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.41", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1415,22 +1320,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "winapi" @@ -1450,9 +1355,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1464,12 +1369,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1478,68 +1383,154 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 2a2b582cea8..ac8b31a7290 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,6 @@ # workspace is required to vendor crates for all packages. [workspace] members = [ - "BLAKE3", "skim", "prql", ] diff --git a/src/Functions/FunctionsHashing.h b/src/Functions/FunctionsHashing.h index 9468bc259e3..7dfd910bea4 100644 --- a/src/Functions/FunctionsHashing.h +++ b/src/Functions/FunctionsHashing.h @@ -16,7 +16,7 @@ #include #if USE_BLAKE3 -# include +# include #endif #include @@ -833,13 +833,13 @@ struct ImplBLAKE3 #else static void apply(const char * begin, const size_t size, unsigned char* out_char_data) { - auto err_msg = blake3_apply_shim(begin, safe_cast(size), out_char_data); - if (err_msg != nullptr) - { - auto err_st = std::string(err_msg); - blake3_free_char_pointer(err_msg); - throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Function returned error message: {}", err_st); - } + static_assert(LLVM_BLAKE3_OUT_LEN == ImplBLAKE3::length); + auto & result = *reinterpret_cast *>(out_char_data); + + llvm::BLAKE3 hasher; + if (size > 0) + hasher.update(llvm::StringRef(begin, size)); + hasher.final(result); } #endif }; diff --git a/src/configure_config.cmake b/src/configure_config.cmake index c3c6d9be6da..871a5ff6644 100644 --- a/src/configure_config.cmake +++ b/src/configure_config.cmake @@ -19,9 +19,6 @@ endif() if (TARGET ch_contrib::rdkafka) set(USE_RDKAFKA 1) endif() -if (TARGET ch_rust::blake3) - set(USE_BLAKE3 1) -endif() if (TARGET ch_rust::skim) set(USE_SKIM 1) endif() @@ -103,6 +100,7 @@ endif() if (TARGET ch_contrib::llvm) set(USE_EMBEDDED_COMPILER ${ENABLE_EMBEDDED_COMPILER}) set(USE_DWARF_PARSER ${ENABLE_DWARF_PARSER}) + set(USE_BLAKE3 1) endif() if (TARGET ch_contrib::unixodbc) set(USE_ODBC 1) From 6740316a88a6dbee87e0d81b36239572df7be08a Mon Sep 17 00:00:00 2001 From: yariks5s Date: Mon, 18 Dec 2023 15:02:51 +0000 Subject: [PATCH 077/253] init --- src/Core/Joins.cpp | 1 + src/Core/Joins.h | 4 +- src/Interpreters/ExpressionAnalyzer.cpp | 4 + src/Interpreters/InterpreterSelectQuery.cpp | 2 +- src/Interpreters/PasteJoin.h | 92 ++++++ src/Parsers/ASTTablesInSelectQuery.cpp | 3 + src/Parsers/ParserTablesInSelectQuery.cpp | 9 +- .../Transforms/PasteJoinTransform.cpp | 276 ++++++++++++++++++ .../Transforms/PasteJoinTransform.h | 87 ++++++ src/QueryPipeline/QueryPipelineBuilder.cpp | 18 +- .../0_stateless/02933_paste_join.reference | 28 ++ .../queries/0_stateless/02933_paste_join.sql | 10 + 12 files changed, 525 insertions(+), 9 deletions(-) create mode 100644 src/Interpreters/PasteJoin.h create mode 100644 src/Processors/Transforms/PasteJoinTransform.cpp create mode 100644 src/Processors/Transforms/PasteJoinTransform.h create mode 100644 tests/queries/0_stateless/02933_paste_join.reference create mode 100644 tests/queries/0_stateless/02933_paste_join.sql diff --git a/src/Core/Joins.cpp b/src/Core/Joins.cpp index 9c8ece82224..77568223d71 100644 --- a/src/Core/Joins.cpp +++ b/src/Core/Joins.cpp @@ -13,6 +13,7 @@ const char * toString(JoinKind kind) case JoinKind::Full: return "FULL"; case JoinKind::Cross: return "CROSS"; case JoinKind::Comma: return "COMMA"; + case JoinKind::Paste: return "PASTE"; } }; diff --git a/src/Core/Joins.h b/src/Core/Joins.h index 6884e8dfd9a..cc69f07263d 100644 --- a/src/Core/Joins.h +++ b/src/Core/Joins.h @@ -13,7 +13,8 @@ enum class JoinKind Right, Full, Cross, /// Direct product. Strictness and condition doesn't matter. - Comma /// Same as direct product. Intended to be converted to INNER JOIN with conditions from WHERE. + Comma, /// Same as direct product. Intended to be converted to INNER JOIN with conditions from WHERE. + Paste, /// Used to join parts without `ON` clause. }; const char * toString(JoinKind kind); @@ -27,6 +28,7 @@ inline constexpr bool isRightOrFull(JoinKind kind) { return kind == JoinKind::R inline constexpr bool isLeftOrFull(JoinKind kind) { return kind == JoinKind::Left || kind == JoinKind::Full; } inline constexpr bool isInnerOrRight(JoinKind kind) { return kind == JoinKind::Inner || kind == JoinKind::Right; } inline constexpr bool isInnerOrLeft(JoinKind kind) { return kind == JoinKind::Inner || kind == JoinKind::Left; } +inline constexpr bool isPaste(JoinKind kind) { return kind == JoinKind::Paste; } /// Allows more optimal JOIN for typical cases. enum class JoinStrictness diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index 1c724ac41c2..e44ada4f8fc 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include @@ -952,6 +953,9 @@ static std::shared_ptr tryCreateJoin( std::unique_ptr & joined_plan, ContextPtr context) { + if (analyzed_join->kind() == JoinKind::Paste) + return std::make_shared(analyzed_join, right_sample_block); + if (algorithm == JoinAlgorithm::DIRECT || algorithm == JoinAlgorithm::DEFAULT) { JoinPtr direct_join = tryKeyValueJoin(analyzed_join, right_sample_block); diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 400fdf2871c..745fd75dca9 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -1699,7 +1699,7 @@ void InterpreterSelectQuery::executeImpl(QueryPlan & query_plan, std::optional

pipelineType() == JoinPipelineType::YShaped) + if (expressions.join->pipelineType() == JoinPipelineType::YShaped && expressions.join->getTableJoin().kind() != JoinKind::Paste) { const auto & table_join = expressions.join->getTableJoin(); const auto & join_clause = table_join.getOnlyClause(); diff --git a/src/Interpreters/PasteJoin.h b/src/Interpreters/PasteJoin.h new file mode 100644 index 00000000000..dce0bc1721c --- /dev/null +++ b/src/Interpreters/PasteJoin.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; + extern const int NOT_IMPLEMENTED; +} + +/// Dummy class, actual joining is done by MergeTransform +class PasteJoin : public IJoin +{ +public: + explicit PasteJoin(std::shared_ptr table_join_, const Block & right_sample_block_) + : table_join(table_join_) + , right_sample_block(right_sample_block_) + { + LOG_TRACE(&Poco::Logger::get("PasteJoin"), "Will use paste join"); + } + + std::string getName() const override { return "PasteJoin"; } + const TableJoin & getTableJoin() const override { return *table_join; } + + bool addBlockToJoin(const Block & /* block */, bool /* check_limits */) override + { + throw Exception(ErrorCodes::LOGICAL_ERROR, "PasteJoin::addBlockToJoin should not be called"); + } + + static bool isSupported(const std::shared_ptr & table_join) + { + bool support_storage = !table_join->isSpecialStorage(); + + /// Key column can change nullability and it's not handled on type conversion stage, so algorithm should be aware of it + bool support_using_and_nulls = !table_join->hasUsing() || !table_join->joinUseNulls(); + + return support_using_and_nulls && support_storage; + } + + void checkTypesOfKeys(const Block & /*left_block*/) const override + { + if (!isSupported(table_join)) + throw DB::Exception(ErrorCodes::NOT_IMPLEMENTED, "PasteJoin doesn't support specified query"); + } + + /// Used just to get result header + void joinBlock(Block & block, std::shared_ptr & /* not_processed */) override + { + for (const auto & col : right_sample_block) + block.insert(col); + block = materializeBlock(block).cloneEmpty(); + } + + void setTotals(const Block & block) override { totals = block; } + const Block & getTotals() const override { return totals; } + + size_t getTotalRowCount() const override + { + throw Exception(ErrorCodes::LOGICAL_ERROR, "PasteJoin::getTotalRowCount should not be called"); + } + + size_t getTotalByteCount() const override + { + throw Exception(ErrorCodes::LOGICAL_ERROR, "PasteJoin::getTotalByteCount should not be called"); + } + + bool alwaysReturnsEmptySet() const override { return false; } + + IBlocksStreamPtr + getNonJoinedBlocks(const Block & /* left_sample_block */, const Block & /* result_sample_block */, UInt64 /* max_block_size */) const override + { + throw Exception(ErrorCodes::LOGICAL_ERROR, "PasteJoin::getNonJoinedBlocks should not be called"); + } + + /// Left and right streams have the same priority and are processed simultaneously + JoinPipelineType pipelineType() const override { return JoinPipelineType::YShaped; } + +private: + std::shared_ptr table_join; + Block right_sample_block; + Block totals; +}; + +} diff --git a/src/Parsers/ASTTablesInSelectQuery.cpp b/src/Parsers/ASTTablesInSelectQuery.cpp index e4e8c00879e..8ca2ee0efae 100644 --- a/src/Parsers/ASTTablesInSelectQuery.cpp +++ b/src/Parsers/ASTTablesInSelectQuery.cpp @@ -211,6 +211,9 @@ void ASTTableJoin::formatImplBeforeTable(const FormatSettings & settings, Format case JoinKind::Comma: settings.ostr << ","; break; + case JoinKind::Paste: + settings.ostr << "PASTE JOIN"; + break; } settings.ostr << (settings.hilite ? hilite_none : ""); diff --git a/src/Parsers/ParserTablesInSelectQuery.cpp b/src/Parsers/ParserTablesInSelectQuery.cpp index b3ae6ca0bb9..ca209739dad 100644 --- a/src/Parsers/ParserTablesInSelectQuery.cpp +++ b/src/Parsers/ParserTablesInSelectQuery.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace DB @@ -166,6 +167,8 @@ bool ParserTablesInSelectQueryElement::parseImpl(Pos & pos, ASTPtr & node, Expec table_join->kind = JoinKind::Full; else if (ParserKeyword("CROSS").ignore(pos)) table_join->kind = JoinKind::Cross; + else if (ParserKeyword("PASTE").ignore(pos)) + table_join->kind = JoinKind::Paste; else no_kind = true; @@ -191,8 +194,8 @@ bool ParserTablesInSelectQueryElement::parseImpl(Pos & pos, ASTPtr & node, Expec } if (table_join->strictness != JoinStrictness::Unspecified - && table_join->kind == JoinKind::Cross) - throw Exception(ErrorCodes::SYNTAX_ERROR, "You must not specify ANY or ALL for CROSS JOIN."); + && (table_join->kind == JoinKind::Cross || table_join->kind == JoinKind::Paste)) + throw Exception(ErrorCodes::SYNTAX_ERROR, "You must not specify ANY or ALL for {} JOIN.", toString(table_join->kind)); if ((table_join->strictness == JoinStrictness::Semi || table_join->strictness == JoinStrictness::Anti) && (table_join->kind != JoinKind::Left && table_join->kind != JoinKind::Right)) @@ -206,7 +209,7 @@ bool ParserTablesInSelectQueryElement::parseImpl(Pos & pos, ASTPtr & node, Expec return false; if (table_join->kind != JoinKind::Comma - && table_join->kind != JoinKind::Cross) + && table_join->kind != JoinKind::Cross && table_join->kind != JoinKind::Paste) { if (ParserKeyword("USING").ignore(pos, expected)) { diff --git a/src/Processors/Transforms/PasteJoinTransform.cpp b/src/Processors/Transforms/PasteJoinTransform.cpp new file mode 100644 index 00000000000..f897ab2fed0 --- /dev/null +++ b/src/Processors/Transforms/PasteJoinTransform.cpp @@ -0,0 +1,276 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; + extern const int NOT_IMPLEMENTED; + extern const int LOGICAL_ERROR; +} + +namespace +{ + +template +int nullableCompareAt(const IColumn & left_column, const IColumn & right_column, size_t lhs_pos, size_t rhs_pos, int null_direction_hint = 1) +{ + if constexpr (has_left_nulls && has_right_nulls) + { + const auto * left_nullable = checkAndGetColumn(left_column); + const auto * right_nullable = checkAndGetColumn(right_column); + + if (left_nullable && right_nullable) + { + int res = left_nullable->compareAt(lhs_pos, rhs_pos, right_column, null_direction_hint); + if (res) + return res; + + /// NULL != NULL case + if (left_nullable->isNullAt(lhs_pos)) + return null_direction_hint; + + return 0; + } + } + + if constexpr (has_left_nulls) + { + if (const auto * left_nullable = checkAndGetColumn(left_column)) + { + if (left_nullable->isNullAt(lhs_pos)) + return null_direction_hint; + return left_nullable->getNestedColumn().compareAt(lhs_pos, rhs_pos, right_column, null_direction_hint); + } + } + + if constexpr (has_right_nulls) + { + if (const auto * right_nullable = checkAndGetColumn(right_column)) + { + if (right_nullable->isNullAt(rhs_pos)) + return -null_direction_hint; + return left_column.compareAt(lhs_pos, rhs_pos, right_nullable->getNestedColumn(), null_direction_hint); + } + } + + return left_column.compareAt(lhs_pos, rhs_pos, right_column, null_direction_hint); +} + +ColumnPtr replicateRow(const IColumn & column, size_t num) +{ + MutableColumnPtr res = column.cloneEmpty(); + res->insertManyFrom(column, 0, num); + return res; +} + +template +void copyColumnsResized(const TColumns & cols, size_t start, size_t size, Chunk & result_chunk) +{ + for (const auto & col : cols) + { + if (col->empty()) + { + /// add defaults + result_chunk.addColumn(col->cloneResized(size)); + } + else if (col->size() == 1) + { + /// copy same row n times + result_chunk.addColumn(replicateRow(*col, size)); + } + else + { + /// cut column + assert(start + size <= col->size()); + result_chunk.addColumn(col->cut(start, size)); + } + } +} + +} + +PasteJoinAlgorithm::PasteJoinAlgorithm( + JoinPtr table_join_, + const Blocks & input_headers, + size_t max_block_size_) + : table_join(table_join_) + , max_block_size(max_block_size_) + , log(&Poco::Logger::get("PasteJoinAlgorithm")) +{ + if (input_headers.size() != 2) + throw Exception(ErrorCodes::LOGICAL_ERROR, "PasteJoinAlgorithm requires exactly two inputs"); + + auto strictness = table_join->getTableJoin().strictness(); + if (strictness != JoinStrictness::Any && strictness != JoinStrictness::All) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "PasteJoinAlgorithm is not implemented for strictness {}", strictness); + + auto kind = table_join->getTableJoin().kind(); + if (!isPaste(kind)) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "PasteJoinAlgorithm is not implemented for kind {}", kind); + + for (const auto & [left_key, right_key] : table_join->getTableJoin().leftToRightKeyRemap()) + { + size_t left_idx = input_headers[0].getPositionByName(left_key); + size_t right_idx = input_headers[1].getPositionByName(right_key); + left_to_right_key_remap[left_idx] = right_idx; + } +} + +void PasteJoinAlgorithm::logElapsed(double seconds) +{ + LOG_TRACE(log, + "Finished pocessing in {} seconds" + ", left: {} blocks, {} rows; right: {} blocks, {} rows" + ", max blocks loaded to memory: {}", + seconds, stat.num_blocks[0], stat.num_rows[0], stat.num_blocks[1], stat.num_rows[1], + stat.max_blocks_loaded); +} + +static void prepareChunk(Chunk & chunk) +{ + if (!chunk) + return; + + auto num_rows = chunk.getNumRows(); + auto columns = chunk.detachColumns(); + for (auto & column : columns) + column = column->convertToFullColumnIfConst(); + + chunk.setColumns(std::move(columns), num_rows); +} + +void PasteJoinAlgorithm::initialize(Inputs inputs) +{ + if (inputs.size() != 2) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Two inputs are required, got {}", inputs.size()); + + for (size_t i = 0; i < inputs.size(); ++i) + { + consume(inputs[i], i); + } +} + +void PasteJoinAlgorithm::consume(Input & input, size_t source_num) +{ + if (input.skip_last_row) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "skip_last_row is not supported"); + + if (input.permutation) + throw DB::Exception(ErrorCodes::NOT_IMPLEMENTED, "permutation is not supported"); + + if (input.chunk) + { + stat.num_blocks[source_num] += 1; + stat.num_rows[source_num] += input.chunk.getNumRows(); + } + + prepareChunk(input.chunk); + chunks[source_num] = std::move(input.chunk); +} + +/// if `source_num == 0` get data from left cursor and fill defaults at right +/// otherwise - vice versa +Chunk PasteJoinAlgorithm::createBlockWithDefaults(size_t source_num, size_t start, size_t num_rows) const +{ + ColumnRawPtrs cols; + { + const auto & columns_left = chunks[0].getColumns(); + const auto & columns_right = chunks[1].getColumns(); + + for (size_t i = 0; i < columns_left.size(); ++i) + { + if (auto it = left_to_right_key_remap.find(i); source_num == 0 || it == left_to_right_key_remap.end()) + { + cols.push_back(columns_left[i].get()); + } + else + { + cols.push_back(columns_right[it->second].get()); + } + } + + for (const auto & col : columns_right) + { + cols.push_back(col.get()); + } + } + + Chunk result_chunk; + copyColumnsResized(cols, start, num_rows, result_chunk); + return result_chunk; +} + +enum ChunkToCut +{ + First, + Second, + None, +}; + +IMergingAlgorithm::Status PasteJoinAlgorithm::merge() +{ + PaddedPODArray indices[2]; + + Chunk result; + for (size_t source_num = 0; source_num < 2; ++source_num) + { + ChunkToCut to_cut = None; + if (chunks[0].getNumRows() != chunks[1].getNumRows()) + to_cut = chunks[0].getNumRows() > chunks[1].getNumRows() ? ChunkToCut::First : ChunkToCut::Second; + for (const auto & col : chunks[source_num].getColumns()) + { + if (to_cut == ChunkToCut::First) + result.addColumn(col->cut(0, chunks[1].getNumRows())); + else if (to_cut == ChunkToCut::Second) + result.addColumn(col->cut(0, chunks[0].getNumRows())); + else + result.addColumn(col); + } + } + return Status(std::move(result), true); +} + +PasteJoinTransform::PasteJoinTransform( + JoinPtr table_join, + const Blocks & input_headers, + const Block & output_header, + size_t max_block_size, + UInt64 limit_hint_) + : IMergingTransform( + input_headers, + output_header, + /* have_all_inputs_= */ true, + limit_hint_, + /* always_read_till_end_= */ false, + /* empty_chunk_on_finish_= */ true, + table_join, input_headers, max_block_size) + , log(&Poco::Logger::get("PasteJoinTransform")) +{ + LOG_TRACE(log, "Use PasteJoinTransform"); +} + +void PasteJoinTransform::onFinish() +{ + algorithm.logElapsed(total_stopwatch.elapsedSeconds()); +} + +} diff --git a/src/Processors/Transforms/PasteJoinTransform.h b/src/Processors/Transforms/PasteJoinTransform.h new file mode 100644 index 00000000000..4b8c125bf9a --- /dev/null +++ b/src/Processors/Transforms/PasteJoinTransform.h @@ -0,0 +1,87 @@ +#pragma once +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace Poco { class Logger; } + +namespace DB +{ + +class IJoin; +using JoinPtr = std::shared_ptr; + +/* + * This class is used to join chunks from two sorted streams. + * It is used in MergeJoinTransform. + */ +class PasteJoinAlgorithm final : public IMergingAlgorithm +{ +public: + explicit PasteJoinAlgorithm(JoinPtr table_join, const Blocks & input_headers, size_t max_block_size_); + + const char * getName() const override { return "PasteJoinAlgorithm"; } + virtual void initialize(Inputs inputs) override; + virtual void consume(Input & input, size_t source_num) override; + virtual Status merge() override; + + void logElapsed(double seconds); + +private: + Chunk createBlockWithDefaults(size_t source_num); + Chunk createBlockWithDefaults(size_t source_num, size_t start, size_t num_rows) const; + + /// For `USING` join key columns should have values from right side instead of defaults + std::unordered_map left_to_right_key_remap; + + std::array chunks; + + JoinPtr table_join; + + size_t max_block_size; + + struct Statistic + { + size_t num_blocks[2] = {0, 0}; + size_t num_rows[2] = {0, 0}; + + size_t max_blocks_loaded = 0; + }; + + Statistic stat; + + Poco::Logger * log; +}; + +class PasteJoinTransform final : public IMergingTransform +{ + using Base = IMergingTransform; + +public: + PasteJoinTransform( + JoinPtr table_join, + const Blocks & input_headers, + const Block & output_header, + size_t max_block_size, + UInt64 limit_hint = 0); + + String getName() const override { return "PasteJoinTransform"; } + +protected: + void onFinish() override; + + Poco::Logger * log; +}; + +} diff --git a/src/QueryPipeline/QueryPipelineBuilder.cpp b/src/QueryPipeline/QueryPipelineBuilder.cpp index f13d1c56d7f..155dec065a8 100644 --- a/src/QueryPipeline/QueryPipelineBuilder.cpp +++ b/src/QueryPipeline/QueryPipelineBuilder.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -354,7 +355,9 @@ std::unique_ptr QueryPipelineBuilder::joinPipelinesYShaped left->pipe.dropExtremes(); right->pipe.dropExtremes(); - if (left->getNumStreams() != 1 || right->getNumStreams() != 1) + if ((left->getNumStreams() != 1 || right->getNumStreams() != 1) && join->getTableJoin().kind() == JoinKind::Paste) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Paste JOIN requires sorted tables only"); + else if (left->getNumStreams() != 1 || right->getNumStreams() != 1) throw Exception(ErrorCodes::LOGICAL_ERROR, "Join is supported only for pipelines with one output port"); if (left->hasTotals() || right->hasTotals()) @@ -362,9 +365,16 @@ std::unique_ptr QueryPipelineBuilder::joinPipelinesYShaped Blocks inputs = {left->getHeader(), right->getHeader()}; - auto joining = std::make_shared(join, inputs, out_header, max_block_size); - - return mergePipelines(std::move(left), std::move(right), std::move(joining), collected_processors); + if (join->getTableJoin().kind() == JoinKind::Paste) + { + auto joining = std::make_shared(join, inputs, out_header, max_block_size); + return mergePipelines(std::move(left), std::move(right), std::move(joining), collected_processors); + } + else + { + auto joining = std::make_shared(join, inputs, out_header, max_block_size); + return mergePipelines(std::move(left), std::move(right), std::move(joining), collected_processors); + } } std::unique_ptr QueryPipelineBuilder::joinPipelinesRightLeft( diff --git a/tests/queries/0_stateless/02933_paste_join.reference b/tests/queries/0_stateless/02933_paste_join.reference new file mode 100644 index 00000000000..abd97ded89c --- /dev/null +++ b/tests/queries/0_stateless/02933_paste_join.reference @@ -0,0 +1,28 @@ +0 0 +1 1 +2 2 +3 3 +4 4 +5 5 +6 6 +7 7 +8 8 +9 9 +0 9 +1 8 +2 7 +3 6 +4 5 +5 4 +6 3 +7 2 +8 1 +9 0 +0 0 +1 1 +2 2 +3 3 +4 4 +5 5 +0 0 +1 1 diff --git a/tests/queries/0_stateless/02933_paste_join.sql b/tests/queries/0_stateless/02933_paste_join.sql new file mode 100644 index 00000000000..e272ea04f1a --- /dev/null +++ b/tests/queries/0_stateless/02933_paste_join.sql @@ -0,0 +1,10 @@ +select * from (SELECT number as a FROM numbers(10)) t1 PASTE JOIN (select number as a from numbers(10)) t2; +select * from (SELECT number as a FROM numbers(10)) t1 PASTE JOIN (select number as a from numbers(10) order by a desc) t2; +create table if not exists test (num UInt64) engine=Memory; +insert into test select number from numbers(6); +insert into test select number from numbers(5); +select * from (SELECT number as a FROM numbers(11)) t1 PASTE JOIN test t2 SETTINGS max_threads=1; +select * from (SELECT number as a FROM numbers(11)) t1 PASTE JOIN (select * from test limit 2) t2 SETTINGs max_threads=1; +select * from (SELECT number as a FROM numbers(10)) t1 ANY PASTE JOIN (select number as a from numbers(10)) t2; -- { clientError SYNTAX_ERROR } +select * from (SELECT number as a FROM numbers(10)) t1 ALL PASTE JOIN (select number as a from numbers(10)) t2; -- { clientError SYNTAX_ERROR } +select * from (SELECT number as a FROM numbers_mt(10)) t1 PASTE JOIN (select number as a from numbers(10) ORDER BY a DESC) t2 SETTINGS max_block_size=3; -- { serverError BAD_ARGUMENTS } From 37216ab3127367033f5ea8c273eb1f6c7277933b Mon Sep 17 00:00:00 2001 From: yariks5s Date: Mon, 18 Dec 2023 15:06:34 +0000 Subject: [PATCH 078/253] docs --- docs/en/sql-reference/statements/select/join.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/sql-reference/statements/select/join.md b/docs/en/sql-reference/statements/select/join.md index 281a1d0436c..ab6d9ef2848 100644 --- a/docs/en/sql-reference/statements/select/join.md +++ b/docs/en/sql-reference/statements/select/join.md @@ -34,6 +34,7 @@ All standard [SQL JOIN](https://en.wikipedia.org/wiki/Join_(SQL)) types are supp - `RIGHT OUTER JOIN`, non-matching rows from right table are returned in addition to matching rows. - `FULL OUTER JOIN`, non-matching rows from both tables are returned in addition to matching rows. - `CROSS JOIN`, produces cartesian product of whole tables, “join keys” are **not** specified. +- `PASTE JOIN`, allow to join tables without using of `ON` clause. `JOIN` without specified type implies `INNER`. Keyword `OUTER` can be safely omitted. Alternative syntax for `CROSS JOIN` is specifying multiple tables in [FROM clause](../../../sql-reference/statements/select/from.md) separated by commas. From 975c954d1a9430079005605025fd52ee00f392ef Mon Sep 17 00:00:00 2001 From: yariks5s Date: Mon, 18 Dec 2023 15:42:50 +0000 Subject: [PATCH 079/253] style check --- src/Processors/Transforms/PasteJoinTransform.cpp | 1 - src/QueryPipeline/QueryPipelineBuilder.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Processors/Transforms/PasteJoinTransform.cpp b/src/Processors/Transforms/PasteJoinTransform.cpp index f897ab2fed0..25d733d5b2d 100644 --- a/src/Processors/Transforms/PasteJoinTransform.cpp +++ b/src/Processors/Transforms/PasteJoinTransform.cpp @@ -24,7 +24,6 @@ namespace ErrorCodes { extern const int LOGICAL_ERROR; extern const int NOT_IMPLEMENTED; - extern const int LOGICAL_ERROR; } namespace diff --git a/src/QueryPipeline/QueryPipelineBuilder.cpp b/src/QueryPipeline/QueryPipelineBuilder.cpp index 155dec065a8..06cb9efa846 100644 --- a/src/QueryPipeline/QueryPipelineBuilder.cpp +++ b/src/QueryPipeline/QueryPipelineBuilder.cpp @@ -37,6 +37,7 @@ namespace ErrorCodes { extern const int LOGICAL_ERROR; extern const int NOT_IMPLEMENTED; + extern const int BAD_ARGUMENTS; } void QueryPipelineBuilder::checkInitialized() From 566aeaa17aaf247f973fb4516ba7c46c3460624e Mon Sep 17 00:00:00 2001 From: kssenii Date: Mon, 18 Dec 2023 17:23:41 +0100 Subject: [PATCH 080/253] Better config --- tests/config/config.d/storage_conf.xml | 11 --------- tests/config/config.d/storage_conf_02944.xml | 25 ++++++++++++++++++++ tests/config/install.sh | 1 + 3 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 tests/config/config.d/storage_conf_02944.xml diff --git a/tests/config/config.d/storage_conf.xml b/tests/config/config.d/storage_conf.xml index 7a5caad9139..18652826d83 100644 --- a/tests/config/config.d/storage_conf.xml +++ b/tests/config/config.d/storage_conf.xml @@ -29,17 +29,6 @@ 0 0 - - cache - s3_disk - s3_cache_02944/ - 100 - 100 - 10 - 10 - 100 - 0 - local_blob_storage diff --git a/tests/config/config.d/storage_conf_02944.xml b/tests/config/config.d/storage_conf_02944.xml new file mode 100644 index 00000000000..5f45640a923 --- /dev/null +++ b/tests/config/config.d/storage_conf_02944.xml @@ -0,0 +1,25 @@ + + + + + s3 + s3_disk/ + http://localhost:11111/test/test_02944/ + clickhouse + clickhouse + 20000 + + + cache + s3_disk + s3_cache_02944/ + 100 + 10 + 10 + 10 + 100 + 0 + + + + diff --git a/tests/config/install.sh b/tests/config/install.sh index 6046f05c922..2f9fd44c9b0 100755 --- a/tests/config/install.sh +++ b/tests/config/install.sh @@ -156,6 +156,7 @@ if [[ -n "$EXPORT_S3_STORAGE_POLICIES" ]]; then fi ln -sf $SRC_PATH/config.d/storage_conf.xml $DEST_SERVER_PATH/config.d/ + ln -sf $SRC_PATH/config.d/storage_conf_02944.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/users.d/s3_cache.xml $DEST_SERVER_PATH/users.d/ ln -sf $SRC_PATH/users.d/s3_cache_new.xml $DEST_SERVER_PATH/users.d/ fi From 46067adce810014d15aaffa14a53a44d8bc7af61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 18 Dec 2023 17:43:10 +0100 Subject: [PATCH 081/253] Improve ZooKeeperRetriesControl so it's easier to reuse --- src/Backups/BackupEntriesCollector.cpp | 19 +- src/Backups/WithRetries.cpp | 11 +- src/Interpreters/executeDDLQueryOnCluster.cpp | 16 +- .../MergeTree/ReplicatedMergeTreeSink.cpp | 20 ++- .../MergeTree/ReplicatedMergeTreeSink.h | 1 - src/Storages/MergeTree/ZooKeeperRetries.h | 165 +++++++++--------- src/Storages/StorageReplicatedMergeTree.cpp | 4 +- .../02887_insert_quorum_wo_keeper_retries.sql | 1 + 8 files changed, 122 insertions(+), 115 deletions(-) diff --git a/src/Backups/BackupEntriesCollector.cpp b/src/Backups/BackupEntriesCollector.cpp index a335b92fe3e..e7d58d263de 100644 --- a/src/Backups/BackupEntriesCollector.cpp +++ b/src/Backups/BackupEntriesCollector.cpp @@ -96,18 +96,19 @@ BackupEntriesCollector::BackupEntriesCollector( , read_settings(read_settings_) , context(context_) , on_cluster_first_sync_timeout(context->getConfigRef().getUInt64("backups.on_cluster_first_sync_timeout", 180000)) - , collect_metadata_timeout(context->getConfigRef().getUInt64("backups.collect_metadata_timeout", context->getConfigRef().getUInt64("backups.consistent_metadata_snapshot_timeout", 600000))) + , collect_metadata_timeout(context->getConfigRef().getUInt64( + "backups.collect_metadata_timeout", context->getConfigRef().getUInt64("backups.consistent_metadata_snapshot_timeout", 600000))) , attempts_to_collect_metadata_before_sleep(context->getConfigRef().getUInt("backups.attempts_to_collect_metadata_before_sleep", 2)) - , min_sleep_before_next_attempt_to_collect_metadata(context->getConfigRef().getUInt64("backups.min_sleep_before_next_attempt_to_collect_metadata", 100)) - , max_sleep_before_next_attempt_to_collect_metadata(context->getConfigRef().getUInt64("backups.max_sleep_before_next_attempt_to_collect_metadata", 5000)) + , min_sleep_before_next_attempt_to_collect_metadata( + context->getConfigRef().getUInt64("backups.min_sleep_before_next_attempt_to_collect_metadata", 100)) + , max_sleep_before_next_attempt_to_collect_metadata( + context->getConfigRef().getUInt64("backups.max_sleep_before_next_attempt_to_collect_metadata", 5000)) , compare_collected_metadata(context->getConfigRef().getBool("backups.compare_collected_metadata", true)) , log(&Poco::Logger::get("BackupEntriesCollector")) , global_zookeeper_retries_info( - "BackupEntriesCollector", - log, - context->getSettingsRef().backup_restore_keeper_max_retries, - context->getSettingsRef().backup_restore_keeper_retry_initial_backoff_ms, - context->getSettingsRef().backup_restore_keeper_retry_max_backoff_ms) + context->getSettingsRef().backup_restore_keeper_max_retries, + context->getSettingsRef().backup_restore_keeper_retry_initial_backoff_ms, + context->getSettingsRef().backup_restore_keeper_retry_max_backoff_ms) , threadpool(threadpool_) { } @@ -580,7 +581,7 @@ std::vector> BackupEntriesCollector::findTablesInD { /// Database or table could be replicated - so may use ZooKeeper. We need to retry. auto zookeeper_retries_info = global_zookeeper_retries_info; - ZooKeeperRetriesControl retries_ctl("getTablesForBackup", zookeeper_retries_info, nullptr); + ZooKeeperRetriesControl retries_ctl("getTablesForBackup", log, zookeeper_retries_info, nullptr); retries_ctl.retryLoop([&](){ db_tables = database->getTablesForBackup(filter_by_table_name, context); }); } catch (Exception & e) diff --git a/src/Backups/WithRetries.cpp b/src/Backups/WithRetries.cpp index 40ae8d06462..55809dc6958 100644 --- a/src/Backups/WithRetries.cpp +++ b/src/Backups/WithRetries.cpp @@ -20,22 +20,19 @@ WithRetries::KeeperSettings WithRetries::KeeperSettings::fromContext(ContextPtr }; } -WithRetries::WithRetries(Poco::Logger * log_, zkutil::GetZooKeeper get_zookeeper_, const KeeperSettings & settings_, RenewerCallback callback_) +WithRetries::WithRetries( + Poco::Logger * log_, zkutil::GetZooKeeper get_zookeeper_, const KeeperSettings & settings_, RenewerCallback callback_) : log(log_) , get_zookeeper(get_zookeeper_) , settings(settings_) , callback(callback_) , global_zookeeper_retries_info( - log->name(), - log, - settings.keeper_max_retries, - settings.keeper_retry_initial_backoff_ms, - settings.keeper_retry_max_backoff_ms) + settings.keeper_max_retries, settings.keeper_retry_initial_backoff_ms, settings.keeper_retry_max_backoff_ms) {} WithRetries::RetriesControlHolder::RetriesControlHolder(const WithRetries * parent, const String & name) : info(parent->global_zookeeper_retries_info) - , retries_ctl(name, info, nullptr) + , retries_ctl(name, parent->log, info, nullptr) , faulty_zookeeper(parent->getFaultyZooKeeper()) {} diff --git a/src/Interpreters/executeDDLQueryOnCluster.cpp b/src/Interpreters/executeDDLQueryOnCluster.cpp index 5d8a9e0582d..9486350a0f6 100644 --- a/src/Interpreters/executeDDLQueryOnCluster.cpp +++ b/src/Interpreters/executeDDLQueryOnCluster.cpp @@ -41,12 +41,9 @@ static ZooKeeperRetriesInfo getRetriesInfo() { const auto & config_ref = Context::getGlobalContextInstance()->getConfigRef(); return ZooKeeperRetriesInfo( - "DistributedDDL", - &Poco::Logger::get("DDLQueryStatusSource"), config_ref.getInt("distributed_ddl_keeper_max_retries", 5), config_ref.getInt("distributed_ddl_keeper_initial_backoff_ms", 100), - config_ref.getInt("distributed_ddl_keeper_max_backoff_ms", 5000) - ); + config_ref.getInt("distributed_ddl_keeper_max_backoff_ms", 5000)); } bool isSupportedAlterTypeForOnClusterDDLQuery(int type) @@ -438,8 +435,8 @@ Chunk DDLQueryStatusSource::generate() Strings tmp_active_hosts; { - auto retries_info = getRetriesInfo(); - auto retries_ctl = ZooKeeperRetriesControl("executeDDLQueryOnCluster", retries_info, context->getProcessListElement()); + auto retries_ctl = ZooKeeperRetriesControl( + "executeDDLQueryOnCluster", &Poco::Logger::get("DDLQueryStatusSource"), getRetriesInfo(), context->getProcessListElement()); retries_ctl.retryLoop([&]() { auto zookeeper = context->getZooKeeper(); @@ -478,8 +475,11 @@ Chunk DDLQueryStatusSource::generate() String status_data; bool finished_exists = false; - auto retries_info = getRetriesInfo(); - auto retries_ctl = ZooKeeperRetriesControl("executeDDLQueryOnCluster", retries_info, context->getProcessListElement()); + auto retries_ctl = ZooKeeperRetriesControl( + "executeDDLQueryOnCluster", + &Poco::Logger::get("DDLQueryStatusSource"), + getRetriesInfo(), + context->getProcessListElement()); retries_ctl.retryLoop([&]() { finished_exists = context->getZooKeeper()->tryGet(fs::path(node_path) / "finished" / host_id, status_data); diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp index 68a6a36d199..35e5b62d77a 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp @@ -158,7 +158,12 @@ size_t ReplicatedMergeTreeSinkImpl::checkQuorumPrecondition(const size_t replicas_number = 0; - ZooKeeperRetriesControl quorum_retries_ctl("checkQuorumPrecondition", zookeeper_retries_info, context->getProcessListElement()); + const auto & settings = context->getSettingsRef(); + ZooKeeperRetriesControl quorum_retries_ctl( + "checkQuorumPrecondition", + log, + {settings.insert_keeper_max_retries, settings.insert_keeper_retry_initial_backoff_ms, settings.insert_keeper_retry_max_backoff_ms}, + context->getProcessListElement()); quorum_retries_ctl.retryLoop( [&]() { @@ -253,12 +258,6 @@ void ReplicatedMergeTreeSinkImpl::consume(Chunk chunk) auto block = getHeader().cloneWithColumns(chunk.detachColumns()); const auto & settings = context->getSettingsRef(); - zookeeper_retries_info = ZooKeeperRetriesInfo( - "ReplicatedMergeTreeSink::consume", - settings.insert_keeper_max_retries ? log : nullptr, - settings.insert_keeper_max_retries, - settings.insert_keeper_retry_initial_backoff_ms, - settings.insert_keeper_retry_max_backoff_ms); ZooKeeperWithFaultInjectionPtr zookeeper = ZooKeeperWithFaultInjection::createInstance( settings.insert_keeper_fault_injection_probability, @@ -634,7 +633,12 @@ std::pair, bool> ReplicatedMergeTreeSinkImpl:: CommitRetryContext retry_context; - ZooKeeperRetriesControl retries_ctl("commitPart", zookeeper_retries_info, context->getProcessListElement()); + const auto & settings = context->getSettingsRef(); + ZooKeeperRetriesControl retries_ctl( + "commitPart", + log, + {settings.insert_keeper_max_retries, settings.insert_keeper_retry_initial_backoff_ms, settings.insert_keeper_retry_max_backoff_ms}, + context->getProcessListElement()); auto resolve_duplicate_stage = [&] () -> CommitRetryContext::Stages { diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeSink.h b/src/Storages/MergeTree/ReplicatedMergeTreeSink.h index ded35aa015b..5c70d0c76e1 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeSink.h +++ b/src/Storages/MergeTree/ReplicatedMergeTreeSink.h @@ -74,7 +74,6 @@ private: using BlockIDsType = std::conditional_t, String>; - ZooKeeperRetriesInfo zookeeper_retries_info; struct QuorumInfo { String status_path; diff --git a/src/Storages/MergeTree/ZooKeeperRetries.h b/src/Storages/MergeTree/ZooKeeperRetries.h index b93e8323574..f5b902cbd4f 100644 --- a/src/Storages/MergeTree/ZooKeeperRetries.h +++ b/src/Storages/MergeTree/ZooKeeperRetries.h @@ -5,6 +5,8 @@ #include #include +#include + namespace DB { @@ -15,29 +17,31 @@ namespace ErrorCodes struct ZooKeeperRetriesInfo { - ZooKeeperRetriesInfo() = default; - ZooKeeperRetriesInfo(std::string name_, Poco::Logger * logger_, UInt64 max_retries_, UInt64 initial_backoff_ms_, UInt64 max_backoff_ms_) - : name(std::move(name_)) - , logger(logger_) - , max_retries(max_retries_) - , curr_backoff_ms(std::min(initial_backoff_ms_, max_backoff_ms_)) - , max_backoff_ms(max_backoff_ms_) + ZooKeeperRetriesInfo(UInt64 max_retries_, UInt64 initial_backoff_ms_, UInt64 max_backoff_ms_) + : max_retries(max_retries_), initial_backoff_ms(std::min(initial_backoff_ms_, max_backoff_ms_)), max_backoff_ms(max_backoff_ms_) { } - std::string name; - Poco::Logger * logger = nullptr; - UInt64 max_retries = 0; - UInt64 curr_backoff_ms = 0; - UInt64 max_backoff_ms = 0; - UInt64 retry_count = 0; + UInt64 max_retries; + UInt64 initial_backoff_ms; + UInt64 max_backoff_ms; }; class ZooKeeperRetriesControl { public: - ZooKeeperRetriesControl(std::string name_, ZooKeeperRetriesInfo & retries_info_, QueryStatusPtr elem) - : name(std::move(name_)), retries_info(retries_info_), process_list_element(elem) + ZooKeeperRetriesControl(std::string name_, Poco::Logger * logger_, ZooKeeperRetriesInfo retries_info_, QueryStatusPtr elem) + : name(std::move(name_)), logger(logger_), retries_info(retries_info_), process_list_element(elem) + { + } + + ZooKeeperRetriesControl(const ZooKeeperRetriesControl & other) + : name(other.name) + , logger(other.logger) + , retries_info(other.retries_info) + , total_failures(other.total_failures) + , process_list_element(other.process_list_element) + , current_backoff_ms(other.current_backoff_ms) { } @@ -46,7 +50,7 @@ public: retryLoop(f, []() {}); } - /// retryLoop() executes f() until it succeeds/max_retries is reached/non-retrialable error is encountered + /// retryLoop() executes f() until it succeeds/max_retries is reached/non-retryable error is encountered /// /// the callable f() can provide feedback in terms of errors in two ways: /// 1. throw KeeperException exception: @@ -56,9 +60,13 @@ public: /// The idea is that if the caller has some semantics on top of non-hardware keeper errors, /// then it can provide feedback to retries controller via user errors /// + /// It is possible to use it multiple times (it will share nº of errors over the total amount of calls) void retryLoop(auto && f, auto && iteration_cleanup) { - while (canTry()) + current_iteration = 0; + current_backoff_ms = retries_info.initial_backoff_ms; + + while (current_iteration == 0 || canTry()) { try { @@ -79,6 +87,7 @@ public: iteration_cleanup(); throw; } + current_iteration++; } } @@ -102,13 +111,11 @@ public: void setUserError(std::exception_ptr exception, int code, std::string message) { - if (retries_info.logger) - LOG_TRACE( - retries_info.logger, "ZooKeeperRetriesControl: {}/{}: setUserError: error={} message={}", retries_info.name, name, code, message); + if (logger) + LOG_TRACE(logger, "ZooKeeperRetriesControl: {}: setUserError: error={} message={}", name, code, message); - /// if current iteration is already failed, keep initial error - if (!iteration_succeeded) - return; + if (iteration_succeeded) + total_failures++; iteration_succeeded = false; user_error.code = code; @@ -136,13 +143,11 @@ public: void setKeeperError(std::exception_ptr exception, Coordination::Error code, std::string message) { - if (retries_info.logger) - LOG_TRACE( - retries_info.logger, "ZooKeeperRetriesControl: {}/{}: setKeeperError: error={} message={}", retries_info.name, name, code, message); + if (logger) + LOG_TRACE(logger, "ZooKeeperRetriesControl: {}: setKeeperError: error={} message={}", name, code, message); - /// if current iteration is already failed, keep initial error - if (!iteration_succeeded) - return; + if (iteration_succeeded) + total_failures++; iteration_succeeded = false; keeper_error.code = code; @@ -172,16 +177,19 @@ public: void requestUnconditionalRetry() { unconditional_retry = true; } - bool isLastRetry() const { return retries_info.retry_count >= retries_info.max_retries; } + bool isLastRetry() const { return total_failures >= retries_info.max_retries; } - bool isRetry() const { return retries_info.retry_count > 0; } + bool isRetry() const { return current_iteration > 1; } - Coordination::Error getLastKeeperErrorCode() const { return keeper_error.code; } - std::string getLastKeeperErrorMessage() const { return keeper_error.message; } + const std::string & getLastKeeperErrorMessage() const { return keeper_error.message; } /// action will be called only once and only after latest failed retry void actionAfterLastFailedRetry(std::function f) { action_after_last_failed_retry = std::move(f); } + const std::string & getName() const { return name; } + + Poco::Logger * getLogger() const { return logger; } + private: struct KeeperError { @@ -200,56 +208,49 @@ private: bool canTry() { - ++iteration_count; - /// first iteration is ordinary execution, no further checks needed - if (0 == iteration_count) - return true; - - if (process_list_element && !process_list_element->checkTimeLimitSoft()) - return false; - if (unconditional_retry) { + LOG_DEBUG(logger, "ZooKeeperRetriesControl: unconditional_retry"); unconditional_retry = false; return true; } - /// iteration succeeded -> no need to retry if (iteration_succeeded) { - /// avoid unnecessary logs, - print something only in case of retries - if (retries_info.logger && iteration_count > 1) + if (logger && total_failures > 0) LOG_DEBUG( - retries_info.logger, - "ZooKeeperRetriesControl: {}/{}: succeeded after: iterations={} total_retries={}", - retries_info.name, + logger, + "ZooKeeperRetriesControl: {}: succeeded after: Iterations={} Total keeper failures={}/{}", name, - iteration_count, - retries_info.retry_count); + current_iteration, + total_failures, + retries_info.max_retries); return false; } if (stop_retries) { - logLastError("stop retries on request"); action_after_last_failed_retry(); + logLastError("stop retries on request"); throwIfError(); return false; } - if (retries_info.retry_count >= retries_info.max_retries) + if (total_failures > retries_info.max_retries) { - logLastError("retry limit is reached"); action_after_last_failed_retry(); + logLastError("retry limit is reached"); throwIfError(); return false; } + if (process_list_element && !process_list_element->checkTimeLimitSoft()) + return false; + /// retries - ++retries_info.retry_count; logLastError("will retry due to error"); - sleepForMilliseconds(retries_info.curr_backoff_ms); - retries_info.curr_backoff_ms = std::min(retries_info.curr_backoff_ms * 2, retries_info.max_backoff_ms); + sleepForMilliseconds(current_backoff_ms); + current_backoff_ms = std::min(current_backoff_ms * 2, retries_info.max_backoff_ms); /// reset the flag, it will be set to false in case of error iteration_succeeded = true; @@ -266,42 +267,43 @@ private: std::rethrow_exception(keeper_error.exception); } - void logLastError(std::string_view header) + void logLastError(const std::string_view & header) { + if (!logger) + return; if (user_error.code == ErrorCodes::OK) { - if (retries_info.logger) - LOG_DEBUG( - retries_info.logger, - "ZooKeeperRetriesControl: {}/{}: {}: retry_count={} timeout={}ms error={} message={}", - retries_info.name, - name, - header, - retries_info.retry_count, - retries_info.curr_backoff_ms, - keeper_error.code, - keeper_error.message); + LOG_DEBUG( + logger, + "ZooKeeperRetriesControl: {}: {}: retry_count={}/{} timeout={}ms error={} message={}", + name, + header, + current_iteration, + retries_info.max_retries, + current_backoff_ms, + keeper_error.code, + keeper_error.message); } else { - if (retries_info.logger) - LOG_DEBUG( - retries_info.logger, - "ZooKeeperRetriesControl: {}/{}: {}: retry_count={} timeout={}ms error={} message={}", - retries_info.name, - name, - header, - retries_info.retry_count, - retries_info.curr_backoff_ms, - user_error.code, - user_error.message); + LOG_DEBUG( + logger, + "ZooKeeperRetriesControl: {}: {}: retry_count={}/{} timeout={}ms error={} message={}", + name, + header, + current_iteration, + retries_info.max_retries, + current_backoff_ms, + user_error.code, + user_error.message); } } std::string name; - ZooKeeperRetriesInfo & retries_info; - Int64 iteration_count = -1; + Poco::Logger * logger = nullptr; + ZooKeeperRetriesInfo retries_info; + UInt64 total_failures = 0; UserError user_error; KeeperError keeper_error; std::function action_after_last_failed_retry = []() {}; @@ -309,6 +311,9 @@ private: bool iteration_succeeded = true; bool stop_retries = false; QueryStatusPtr process_list_element; + + UInt64 current_iteration = 0; + UInt64 current_backoff_ms = 0; }; } diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 3e4d6309ec8..a4a78a39c1d 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -10261,7 +10261,7 @@ void StorageReplicatedMergeTree::backupData( bool exists = false; Strings mutation_ids; { - ZooKeeperRetriesControl retries_ctl("getMutations", zookeeper_retries_info, nullptr); + ZooKeeperRetriesControl retries_ctl("getMutations", log, zookeeper_retries_info, nullptr); retries_ctl.retryLoop([&]() { if (!zookeeper || zookeeper->expired()) @@ -10280,7 +10280,7 @@ void StorageReplicatedMergeTree::backupData( bool mutation_id_exists = false; String mutation; - ZooKeeperRetriesControl retries_ctl("getMutation", zookeeper_retries_info, nullptr); + ZooKeeperRetriesControl retries_ctl("getMutation", log, zookeeper_retries_info, nullptr); retries_ctl.retryLoop([&]() { if (!zookeeper || zookeeper->expired()) diff --git a/tests/queries/0_stateless/02887_insert_quorum_wo_keeper_retries.sql b/tests/queries/0_stateless/02887_insert_quorum_wo_keeper_retries.sql index 489d25d7433..3e75d415089 100644 --- a/tests/queries/0_stateless/02887_insert_quorum_wo_keeper_retries.sql +++ b/tests/queries/0_stateless/02887_insert_quorum_wo_keeper_retries.sql @@ -7,6 +7,7 @@ CREATE TABLE quorum1(x UInt32) ENGINE ReplicatedMergeTree('/clickhouse/tables/{d CREATE TABLE quorum2(x UInt32) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/test_02887/quorum', '2') ORDER BY x; SET insert_keeper_fault_injection_probability=0; +SET insert_keeper_max_retries = 0; SET insert_quorum = 2; system enable failpoint replicated_merge_tree_insert_quorum_fail_0; From a12fe7473cfd500d093f033d0133ca294a5f870f Mon Sep 17 00:00:00 2001 From: Sema Checherinda Date: Mon, 18 Dec 2023 18:08:57 +0100 Subject: [PATCH 082/253] add killed by timeout message --- docker/test/stateful/run.sh | 4 ++-- docker/test/stateless/run.sh | 4 ++-- docker/test/stateless/utils.lib | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docker/test/stateful/run.sh b/docker/test/stateful/run.sh index 806b57c4616..82587efcb3d 100755 --- a/docker/test/stateful/run.sh +++ b/docker/test/stateful/run.sh @@ -78,7 +78,7 @@ function start() tail -n1000 /var/log/clickhouse-server/clickhouse-server.log break fi - timeout 120 service clickhouse-server start + timeout_with_logging 120 service clickhouse-server start sleep 0.5 counter=$((counter + 1)) done @@ -163,7 +163,7 @@ function run_tests() } export -f run_tests -timeout "$MAX_RUN_TIME" bash -c run_tests ||: +timeout_with_logging "$MAX_RUN_TIME" bash -c run_tests ||: echo "Files in current directory" ls -la ./ diff --git a/docker/test/stateless/run.sh b/docker/test/stateless/run.sh index bfa9f9938ab..bd44d1724ae 100755 --- a/docker/test/stateless/run.sh +++ b/docker/test/stateless/run.sh @@ -216,11 +216,11 @@ export -f run_tests if [ "$NUM_TRIES" -gt "1" ]; then # We don't run tests with Ordinary database in PRs, only in master. # So run new/changed tests with Ordinary at least once in flaky check. - timeout "$MAX_RUN_TIME" bash -c 'NUM_TRIES=1; USE_DATABASE_ORDINARY=1; run_tests' \ + timeout_with_logging "$MAX_RUN_TIME" bash -c 'NUM_TRIES=1; USE_DATABASE_ORDINARY=1; run_tests' \ | sed 's/All tests have finished//' | sed 's/No tests were run//' ||: fi -timeout "$MAX_RUN_TIME" bash -c run_tests ||: +timeout_with_logging "$MAX_RUN_TIME" bash -c run_tests ||: echo "Files in current directory" ls -la ./ diff --git a/docker/test/stateless/utils.lib b/docker/test/stateless/utils.lib index 1204434d853..a30e05b46ff 100644 --- a/docker/test/stateless/utils.lib +++ b/docker/test/stateless/utils.lib @@ -35,4 +35,19 @@ function fn_exists() { declare -F "$1" > /dev/null; } +function timeout_with_logging() { + local exit_code=0 + + timeout "${@}" || exit_code="${?}" + + if [[ "${exit_code}" -eq "124" ]] + then + echo "The command 'timeout ${*}' has been killed by timeout" + fi + + return $exit_code +} + # vi: ft=bash + +} From 80af5cb78a4bd557538dc3dc6268101ea1c56ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 18 Dec 2023 18:32:31 +0100 Subject: [PATCH 083/253] Always build the necessary LLVM modules if blake3 is enabled --- contrib/llvm-project-cmake/CMakeLists.txt | 120 ++++++++++++---------- src/Functions/FunctionsHashing.h | 4 +- src/configure_config.cmake | 2 +- 3 files changed, 71 insertions(+), 55 deletions(-) diff --git a/contrib/llvm-project-cmake/CMakeLists.txt b/contrib/llvm-project-cmake/CMakeLists.txt index 406bac73e90..d09060912d8 100644 --- a/contrib/llvm-project-cmake/CMakeLists.txt +++ b/contrib/llvm-project-cmake/CMakeLists.txt @@ -11,7 +11,9 @@ option (ENABLE_EMBEDDED_COMPILER "Enable support for JIT compilation during quer option (ENABLE_DWARF_PARSER "Enable support for DWARF input format (uses LLVM library)" ${ENABLE_DWARF_PARSER_DEFAULT}) -if (NOT ENABLE_EMBEDDED_COMPILER AND NOT ENABLE_DWARF_PARSER) +option (ENABLE_BLAKE3 "Enable BLAKE3 function" ${ENABLE_LIBRARIES}) + +if (NOT ENABLE_EMBEDDED_COMPILER AND NOT ENABLE_DWARF_PARSER AND NOT ENABLE_BLAKE3) message(STATUS "Not using LLVM") return() endif() @@ -26,61 +28,75 @@ set (LLVM_LIBRARY_DIRS "${ClickHouse_BINARY_DIR}/contrib/llvm-project/llvm") # and llvm cannot be compiled with bundled libcxx and 20 standard. set (CMAKE_CXX_STANDARD 14) -# This list was generated by listing all LLVM libraries, compiling the binary and removing all libraries while it still compiles. -set (REQUIRED_LLVM_LIBRARIES - LLVMExecutionEngine - LLVMRuntimeDyld - LLVMAsmPrinter - LLVMDebugInfoDWARF - LLVMGlobalISel - LLVMSelectionDAG - LLVMMCDisassembler - LLVMPasses - LLVMCodeGen - LLVMipo - LLVMBitWriter - LLVMInstrumentation - LLVMScalarOpts - LLVMAggressiveInstCombine - LLVMInstCombine - LLVMVectorize - LLVMTransformUtils - LLVMTarget - LLVMAnalysis - LLVMProfileData - LLVMObject - LLVMBitReader - LLVMCore - LLVMRemarks - LLVMBitstreamReader - LLVMMCParser - LLVMMC - LLVMBinaryFormat - LLVMDebugInfoCodeView - LLVMSupport - LLVMDemangle -) +if (ARCH_AMD64) + set (LLVM_TARGETS_TO_BUILD "X86" CACHE INTERNAL "") +elseif (ARCH_AARCH64) + set (LLVM_TARGETS_TO_BUILD "AArch64" CACHE INTERNAL "") +elseif (ARCH_PPC64LE) + set (LLVM_TARGETS_TO_BUILD "PowerPC" CACHE INTERNAL "") +elseif (ARCH_S390X) + set (LLVM_TARGETS_TO_BUILD "SystemZ" CACHE INTERNAL "") +elseif (ARCH_RISCV64) + set (LLVM_TARGETS_TO_BUILD "RISCV" CACHE INTERNAL "") +endif () + + +if (NOT ENABLE_EMBEDDED_COMPILER AND NOT ENABLE_DWARF_PARSER) + # Only compiling blake3 + set (REQUIRED_LLVM_LIBRARIES LLVMSupport) +else() + # This list was generated by listing all LLVM libraries, compiling the binary and removing all libraries while it still compiles. + set (REQUIRED_LLVM_LIBRARIES + LLVMExecutionEngine + LLVMRuntimeDyld + LLVMAsmPrinter + LLVMDebugInfoDWARF + LLVMGlobalISel + LLVMSelectionDAG + LLVMMCDisassembler + LLVMPasses + LLVMCodeGen + LLVMipo + LLVMBitWriter + LLVMInstrumentation + LLVMScalarOpts + LLVMAggressiveInstCombine + LLVMInstCombine + LLVMVectorize + LLVMTransformUtils + LLVMTarget + LLVMAnalysis + LLVMProfileData + LLVMObject + LLVMBitReader + LLVMCore + LLVMRemarks + LLVMBitstreamReader + LLVMMCParser + LLVMMC + LLVMBinaryFormat + LLVMDebugInfoCodeView + LLVMSupport + LLVMDemangle + ) + + if (ARCH_AMD64) + list(APPEND REQUIRED_LLVM_LIBRARIES LLVMX86Info LLVMX86Desc LLVMX86CodeGen) + elseif (ARCH_AARCH64) + list(APPEND REQUIRED_LLVM_LIBRARIES LLVMAArch64Info LLVMAArch64Desc LLVMAArch64CodeGen) + elseif (ARCH_PPC64LE) + list(APPEND REQUIRED_LLVM_LIBRARIES LLVMPowerPCInfo LLVMPowerPCDesc LLVMPowerPCCodeGen) + elseif (ARCH_S390X) + list(APPEND REQUIRED_LLVM_LIBRARIES LLVMSystemZInfo LLVMSystemZDesc LLVMSystemZCodeGen) + elseif (ARCH_RISCV64) + list(APPEND REQUIRED_LLVM_LIBRARIES LLVMRISCVInfo LLVMRISCVDesc LLVMRISCVCodeGen) + endif () +endif() + # Skip useless "install" instructions from CMake: set (LLVM_INSTALL_TOOLCHAIN_ONLY 1 CACHE INTERNAL "") -if (ARCH_AMD64) - set (LLVM_TARGETS_TO_BUILD "X86" CACHE INTERNAL "") - list(APPEND REQUIRED_LLVM_LIBRARIES LLVMX86Info LLVMX86Desc LLVMX86CodeGen) -elseif (ARCH_AARCH64) - set (LLVM_TARGETS_TO_BUILD "AArch64" CACHE INTERNAL "") - list(APPEND REQUIRED_LLVM_LIBRARIES LLVMAArch64Info LLVMAArch64Desc LLVMAArch64CodeGen) -elseif (ARCH_PPC64LE) - set (LLVM_TARGETS_TO_BUILD "PowerPC" CACHE INTERNAL "") - list(APPEND REQUIRED_LLVM_LIBRARIES LLVMPowerPCInfo LLVMPowerPCDesc LLVMPowerPCCodeGen) -elseif (ARCH_S390X) - set (LLVM_TARGETS_TO_BUILD "SystemZ" CACHE INTERNAL "") - list(APPEND REQUIRED_LLVM_LIBRARIES LLVMSystemZInfo LLVMSystemZDesc LLVMSystemZCodeGen) -elseif (ARCH_RISCV64) - set (LLVM_TARGETS_TO_BUILD "RISCV" CACHE INTERNAL "") - list(APPEND REQUIRED_LLVM_LIBRARIES LLVMRISCVInfo LLVMRISCVDesc LLVMRISCVCodeGen) -endif () - message (STATUS "LLVM TARGETS TO BUILD ${LLVM_TARGETS_TO_BUILD}") set (CMAKE_INSTALL_RPATH "ON") # Do not adjust RPATH in llvm, since then it will not be able to find libcxx/libcxxabi/libunwind diff --git a/src/Functions/FunctionsHashing.h b/src/Functions/FunctionsHashing.h index 7dfd910bea4..c83c3b7f41b 100644 --- a/src/Functions/FunctionsHashing.h +++ b/src/Functions/FunctionsHashing.h @@ -825,10 +825,10 @@ struct ImplBLAKE3 static constexpr auto name = "BLAKE3"; enum { length = 32 }; -#if !USE_BLAKE3 +#ifndef USE_BLAKE3 [[noreturn]] static void apply(const char * /*begin*/, const size_t /*size*/, unsigned char * /*out_char_data*/) { - throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "BLAKE3 is not available. Rust code or BLAKE3 itself may be disabled."); + throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "BLAKE3 is not available"); } #else static void apply(const char * begin, const size_t size, unsigned char* out_char_data) diff --git a/src/configure_config.cmake b/src/configure_config.cmake index 871a5ff6644..9358abdf7f8 100644 --- a/src/configure_config.cmake +++ b/src/configure_config.cmake @@ -100,7 +100,7 @@ endif() if (TARGET ch_contrib::llvm) set(USE_EMBEDDED_COMPILER ${ENABLE_EMBEDDED_COMPILER}) set(USE_DWARF_PARSER ${ENABLE_DWARF_PARSER}) - set(USE_BLAKE3 1) + set(USE_BLAKE3 ${ENABLE_LIBRARIES}) endif() if (TARGET ch_contrib::unixodbc) set(USE_ODBC 1) From 0a45ffef3090a1245a0f9853646edd33c1bcd230 Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Mon, 18 Dec 2023 19:14:45 +0100 Subject: [PATCH 084/253] Update 02944_dynamically_change_filesystem_cache_size.sh --- .../02944_dynamically_change_filesystem_cache_size.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh index 021493eaa82..e47e13a7e40 100755 --- a/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh +++ b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh @@ -23,7 +23,7 @@ $CLICKHOUSE_CLIENT --query "SELECT * FROM test FORMAT Null" $CLICKHOUSE_CLIENT --query "SELECT count() FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" $CLICKHOUSE_CLIENT --query "SELECT sum(size) FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" -config_path=/etc/clickhouse-server/config.d/storage_conf.xml +config_path=/etc/clickhouse-server/config.d/storage_conf_02944.xml config_path_tmp=$config_path.tmp echo 'set max_size from 100 to 10' From 5601f97e0c4ae09eea8a31a9cb8733f8c5e20d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 18 Dec 2023 19:14:47 +0100 Subject: [PATCH 085/253] Move FunctionsStringHashFixedString template to single file --- src/Functions/FunctionsHashing.h | 320 ++----------- src/Functions/FunctionsHashingMisc.cpp | 41 +- src/Functions/FunctionsHashingSSL.cpp | 177 ------- .../FunctionsStringHashFixedString.cpp | 441 ++++++++++++++++++ 4 files changed, 503 insertions(+), 476 deletions(-) delete mode 100644 src/Functions/FunctionsHashingSSL.cpp create mode 100644 src/Functions/FunctionsStringHashFixedString.cpp diff --git a/src/Functions/FunctionsHashing.h b/src/Functions/FunctionsHashing.h index c83c3b7f41b..8fb21cd1ad4 100644 --- a/src/Functions/FunctionsHashing.h +++ b/src/Functions/FunctionsHashing.h @@ -15,24 +15,13 @@ #endif #include -#if USE_BLAKE3 -# include -#endif - #include #include #include #include #if USE_SSL -# include # include -# include -#if USE_BORINGSSL -# include -#else -# include -#endif #endif #include @@ -191,6 +180,40 @@ T combineHashesFunc(T t1, T t2) } +struct SipHash64Impl +{ + static constexpr auto name = "sipHash64"; + using ReturnType = UInt64; + + static UInt64 apply(const char * begin, size_t size) { return sipHash64(begin, size); } + static UInt64 combineHashes(UInt64 h1, UInt64 h2) { return combineHashesFunc(h1, h2); } + + static constexpr bool use_int_hash_for_pods = false; +}; + +struct SipHash64KeyedImpl +{ + static constexpr auto name = "sipHash64Keyed"; + using ReturnType = UInt64; + using Key = impl::SipHashKey; + using KeyColumns = impl::SipHashKeyColumns; + + static KeyColumns parseKeyColumns(const ColumnWithTypeAndName & key) { return impl::parseSipHashKeyColumns(key); } + static Key getKey(const KeyColumns & key, size_t i) { return key.getKey(i); } + + static UInt64 applyKeyed(const Key & key, const char * begin, size_t size) { return sipHash64Keyed(key.key0, key.key1, begin, size); } + + static UInt64 combineHashesKeyed(const Key & key, UInt64 h1, UInt64 h2) + { + transformEndianness(h1); + transformEndianness(h2); + const UInt64 hashes[]{h1, h2}; + return applyKeyed(key, reinterpret_cast(hashes), sizeof(hashes)); + } + + static constexpr bool use_int_hash_for_pods = false; +}; + #if USE_SSL struct HalfMD5Impl { @@ -225,159 +248,8 @@ struct HalfMD5Impl static constexpr bool use_int_hash_for_pods = false; }; - -struct MD4Impl -{ - static constexpr auto name = "MD4"; - enum { length = MD4_DIGEST_LENGTH }; - - static void apply(const char * begin, const size_t size, unsigned char * out_char_data) - { - MD4_CTX ctx; - MD4_Init(&ctx); - MD4_Update(&ctx, reinterpret_cast(begin), size); - MD4_Final(out_char_data, &ctx); - } -}; - -struct MD5Impl -{ - static constexpr auto name = "MD5"; - enum { length = MD5_DIGEST_LENGTH }; - - static void apply(const char * begin, const size_t size, unsigned char * out_char_data) - { - MD5_CTX ctx; - MD5_Init(&ctx); - MD5_Update(&ctx, reinterpret_cast(begin), size); - MD5_Final(out_char_data, &ctx); - } -}; - -struct SHA1Impl -{ - static constexpr auto name = "SHA1"; - enum { length = SHA_DIGEST_LENGTH }; - - static void apply(const char * begin, const size_t size, unsigned char * out_char_data) - { - SHA_CTX ctx; - SHA1_Init(&ctx); - SHA1_Update(&ctx, reinterpret_cast(begin), size); - SHA1_Final(out_char_data, &ctx); - } -}; - -struct SHA224Impl -{ - static constexpr auto name = "SHA224"; - enum { length = SHA224_DIGEST_LENGTH }; - - static void apply(const char * begin, const size_t size, unsigned char * out_char_data) - { - SHA256_CTX ctx; - SHA224_Init(&ctx); - SHA224_Update(&ctx, reinterpret_cast(begin), size); - SHA224_Final(out_char_data, &ctx); - } -}; - -struct SHA256Impl -{ - static constexpr auto name = "SHA256"; - enum { length = SHA256_DIGEST_LENGTH }; - - static void apply(const char * begin, const size_t size, unsigned char * out_char_data) - { - SHA256_CTX ctx; - SHA256_Init(&ctx); - SHA256_Update(&ctx, reinterpret_cast(begin), size); - SHA256_Final(out_char_data, &ctx); - } -}; - -struct SHA384Impl -{ - static constexpr auto name = "SHA384"; - enum { length = SHA384_DIGEST_LENGTH }; - - static void apply(const char * begin, const size_t size, unsigned char * out_char_data) - { - SHA512_CTX ctx; - SHA384_Init(&ctx); - SHA384_Update(&ctx, reinterpret_cast(begin), size); - SHA384_Final(out_char_data, &ctx); - } -}; - -struct SHA512Impl -{ - static constexpr auto name = "SHA512"; - enum { length = 64 }; - - static void apply(const char * begin, const size_t size, unsigned char * out_char_data) - { - SHA512_CTX ctx; - SHA512_Init(&ctx); - SHA512_Update(&ctx, reinterpret_cast(begin), size); - SHA512_Final(out_char_data, &ctx); - } -}; - -struct SHA512Impl256 -{ - static constexpr auto name = "SHA512_256"; - enum { length = 32 }; - - static void apply(const char * begin, const size_t size, unsigned char * out_char_data) - { - /// Here, we use the EVP interface that is common to both BoringSSL and OpenSSL. Though BoringSSL is the default - /// SSL library that we use, for S390X architecture only OpenSSL is supported. But the SHA512-256, SHA512_256_Init, - /// SHA512_256_Update, SHA512_256_Final methods to calculate hash (similar to the other SHA functions) aren't available - /// in the current version of OpenSSL that we use which necessitates the use of the EVP interface. - auto md_ctx = EVP_MD_CTX_create(); - EVP_DigestInit_ex(md_ctx, EVP_sha512_256(), nullptr /*engine*/); - EVP_DigestUpdate(md_ctx, begin, size); - EVP_DigestFinal_ex(md_ctx, out_char_data, nullptr /*size*/); - EVP_MD_CTX_destroy(md_ctx); - } -}; #endif -struct SipHash64Impl -{ - static constexpr auto name = "sipHash64"; - using ReturnType = UInt64; - - static UInt64 apply(const char * begin, size_t size) { return sipHash64(begin, size); } - static UInt64 combineHashes(UInt64 h1, UInt64 h2) { return combineHashesFunc(h1, h2); } - - static constexpr bool use_int_hash_for_pods = false; -}; - -struct SipHash64KeyedImpl -{ - static constexpr auto name = "sipHash64Keyed"; - using ReturnType = UInt64; - using Key = impl::SipHashKey; - using KeyColumns = impl::SipHashKeyColumns; - - static KeyColumns parseKeyColumns(const ColumnWithTypeAndName & key) { return impl::parseSipHashKeyColumns(key); } - static Key getKey(const KeyColumns & key, size_t i) { return key.getKey(i); } - - static UInt64 applyKeyed(const Key & key, const char * begin, size_t size) { return sipHash64Keyed(key.key0, key.key1, begin, size); } - - static UInt64 combineHashesKeyed(const Key & key, UInt64 h1, UInt64 h2) - { - transformEndianness(h1); - transformEndianness(h2); - const UInt64 hashes[]{h1, h2}; - return applyKeyed(key, reinterpret_cast(hashes), sizeof(hashes)); - } - - static constexpr bool use_int_hash_for_pods = false; -}; - struct SipHash128Impl { static constexpr auto name = "sipHash128"; @@ -820,121 +692,6 @@ struct ImplXXH3 static constexpr bool use_int_hash_for_pods = false; }; -struct ImplBLAKE3 -{ - static constexpr auto name = "BLAKE3"; - enum { length = 32 }; - -#ifndef USE_BLAKE3 - [[noreturn]] static void apply(const char * /*begin*/, const size_t /*size*/, unsigned char * /*out_char_data*/) - { - throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "BLAKE3 is not available"); - } -#else - static void apply(const char * begin, const size_t size, unsigned char* out_char_data) - { - static_assert(LLVM_BLAKE3_OUT_LEN == ImplBLAKE3::length); - auto & result = *reinterpret_cast *>(out_char_data); - - llvm::BLAKE3 hasher; - if (size > 0) - hasher.update(llvm::StringRef(begin, size)); - hasher.final(result); - } -#endif -}; - -template -class FunctionStringHashFixedString : public IFunction -{ -public: - static constexpr auto name = Impl::name; - static FunctionPtr create(ContextPtr) { return std::make_shared(); } - - String getName() const override - { - return name; - } - - size_t getNumberOfArguments() const override { return 1; } - - DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override - { - if (!isStringOrFixedString(arguments[0]) && !isIPv6(arguments[0])) - throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of argument of function {}", - arguments[0]->getName(), getName()); - - return std::make_shared(Impl::length); - } - - bool useDefaultImplementationForConstants() const override { return true; } - - bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return true; } - - ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr &, size_t /*input_rows_count*/) const override - { - if (const ColumnString * col_from = checkAndGetColumn(arguments[0].column.get())) - { - auto col_to = ColumnFixedString::create(Impl::length); - - const typename ColumnString::Chars & data = col_from->getChars(); - const typename ColumnString::Offsets & offsets = col_from->getOffsets(); - auto & chars_to = col_to->getChars(); - const auto size = offsets.size(); - chars_to.resize(size * Impl::length); - - ColumnString::Offset current_offset = 0; - for (size_t i = 0; i < size; ++i) - { - Impl::apply( - reinterpret_cast(&data[current_offset]), - offsets[i] - current_offset - 1, - reinterpret_cast(&chars_to[i * Impl::length])); - - current_offset = offsets[i]; - } - - return col_to; - } - else if ( - const ColumnFixedString * col_from_fix = checkAndGetColumn(arguments[0].column.get())) - { - auto col_to = ColumnFixedString::create(Impl::length); - const typename ColumnFixedString::Chars & data = col_from_fix->getChars(); - const auto size = col_from_fix->size(); - auto & chars_to = col_to->getChars(); - const auto length = col_from_fix->getN(); - chars_to.resize(size * Impl::length); - for (size_t i = 0; i < size; ++i) - { - Impl::apply( - reinterpret_cast(&data[i * length]), length, reinterpret_cast(&chars_to[i * Impl::length])); - } - return col_to; - } - else if ( - const ColumnIPv6 * col_from_ip = checkAndGetColumn(arguments[0].column.get())) - { - auto col_to = ColumnFixedString::create(Impl::length); - const typename ColumnIPv6::Container & data = col_from_ip->getData(); - const auto size = col_from_ip->size(); - auto & chars_to = col_to->getChars(); - const auto length = IPV6_BINARY_LENGTH; - chars_to.resize(size * Impl::length); - for (size_t i = 0; i < size; ++i) - { - Impl::apply( - reinterpret_cast(&data[i * length]), length, reinterpret_cast(&chars_to[i * Impl::length])); - } - return col_to; - } - else - throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Illegal column {} of first argument of function {}", - arguments[0].column->getName(), getName()); - } -}; - - DECLARE_MULTITARGET_CODE( template @@ -1817,15 +1574,7 @@ using FunctionSipHash64Keyed = FunctionAnyHash; using FunctionIntHash64 = FunctionIntHash; #if USE_SSL -using FunctionMD4 = FunctionStringHashFixedString; using FunctionHalfMD5 = FunctionAnyHash; -using FunctionMD5 = FunctionStringHashFixedString; -using FunctionSHA1 = FunctionStringHashFixedString; -using FunctionSHA224 = FunctionStringHashFixedString; -using FunctionSHA256 = FunctionStringHashFixedString; -using FunctionSHA384 = FunctionStringHashFixedString; -using FunctionSHA512 = FunctionStringHashFixedString; -using FunctionSHA512_256 = FunctionStringHashFixedString; #endif using FunctionSipHash128 = FunctionAnyHash; using FunctionSipHash128Keyed = FunctionAnyHash; @@ -1854,7 +1603,6 @@ using FunctionXxHash64 = FunctionAnyHash; using FunctionXXH3 = FunctionAnyHash; using FunctionWyHash64 = FunctionAnyHash; -using FunctionBLAKE3 = FunctionStringHashFixedString; } #ifdef __clang__ diff --git a/src/Functions/FunctionsHashingMisc.cpp b/src/Functions/FunctionsHashingMisc.cpp index f56568b2508..38f16af0e6d 100644 --- a/src/Functions/FunctionsHashingMisc.cpp +++ b/src/Functions/FunctionsHashingMisc.cpp @@ -46,19 +46,34 @@ REGISTER_FUNCTION(Hashing) factory.registerFunction(); +#if USE_SSL + factory.registerFunction(FunctionDocumentation{ + .description = R"( +[Interprets](../..//sql-reference/functions/type-conversion-functions.md/#type_conversion_functions-reinterpretAsString) all the input +parameters as strings and calculates the MD5 hash value for each of them. Then combines hashes, takes the first 8 bytes of the hash of the +resulting string, and interprets them as [UInt64](../../../sql-reference/data-types/int-uint.md) in big-endian byte order. The function is +relatively slow (5 million short strings per second per processor core). - factory.registerFunction( - FunctionDocumentation{ - .description=R"( -Calculates BLAKE3 hash string and returns the resulting set of bytes as FixedString. -This cryptographic hash-function is integrated into ClickHouse with BLAKE3 Rust library. -The function is rather fast and shows approximately two times faster performance compared to SHA-2, while generating hashes of the same length as SHA-256. -It returns a BLAKE3 hash as a byte array with type FixedString(32). -)", - .examples{ - {"hash", "SELECT hex(BLAKE3('ABC'))", ""}}, - .categories{"Hash"} - }, - FunctionFactory::CaseSensitive); +Consider using the [sipHash64](../../sql-reference/functions/hash-functions.md/#hash_functions-siphash64) function instead. + )", + .syntax = "SELECT halfMD5(par1,par2,...,parN);", + .arguments + = {{"par1,par2,...,parN", + R"( +The function takes a variable number of input parameters. Arguments can be any of the supported data types. For some data types calculated +value of hash function may be the same for the same values even if types of arguments differ (integers of different size, named and unnamed +Tuple with the same data, Map and the corresponding Array(Tuple(key, value)) type with the same data). + )"}}, + .returned_value = "The computed half MD5 hash of the given input params returned as a " + "[UInt64](../../../sql-reference/data-types/int-uint.md) in big-endian byte order.", + .examples + = {{"", + "SELECT HEX(halfMD5('abc', 'cde', 'fgh'));", + R"( +┌─hex(halfMD5('abc', 'cde', 'fgh'))─┐ +│ 2C9506B7374CFAF4 │ +└───────────────────────────────────┘ + )"}}}); +#endif } } diff --git a/src/Functions/FunctionsHashingSSL.cpp b/src/Functions/FunctionsHashingSSL.cpp deleted file mode 100644 index 3e109b8a11d..00000000000 --- a/src/Functions/FunctionsHashingSSL.cpp +++ /dev/null @@ -1,177 +0,0 @@ -#include "config.h" - -#if USE_SSL - -#include "FunctionsHashing.h" -#include - -/// FunctionsHashing instantiations are separated into files FunctionsHashing*.cpp -/// to better parallelize the build procedure and avoid MSan build failure -/// due to excessive resource consumption. - -namespace DB -{ - -REGISTER_FUNCTION(HashingSSL) -{ - factory.registerFunction(FunctionDocumentation{ - .description = R"(Calculates the MD4 hash of the given string.)", - .syntax = "SELECT MD4(s);", - .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, - .returned_value - = "The MD4 hash of the given input string returned as a [FixedString(16)](../../sql-reference/data-types/fixedstring.md).", - .examples - = {{"", - "SELECT HEX(MD4('abc'));", - R"( -┌─hex(MD4('abc'))──────────────────┐ -│ A448017AAF21D8525FC10AE87AA6729D │ -└──────────────────────────────────┘ - )" - }} - }); - factory.registerFunction(FunctionDocumentation{ - .description = R"( -[Interprets](../..//sql-reference/functions/type-conversion-functions.md/#type_conversion_functions-reinterpretAsString) all the input -parameters as strings and calculates the MD5 hash value for each of them. Then combines hashes, takes the first 8 bytes of the hash of the -resulting string, and interprets them as [UInt64](../../../sql-reference/data-types/int-uint.md) in big-endian byte order. The function is -relatively slow (5 million short strings per second per processor core). - -Consider using the [sipHash64](../../sql-reference/functions/hash-functions.md/#hash_functions-siphash64) function instead. - )", - .syntax = "SELECT halfMD5(par1,par2,...,parN);", - .arguments = {{"par1,par2,...,parN", - R"( -The function takes a variable number of input parameters. Arguments can be any of the supported data types. For some data types calculated -value of hash function may be the same for the same values even if types of arguments differ (integers of different size, named and unnamed -Tuple with the same data, Map and the corresponding Array(Tuple(key, value)) type with the same data). - )" - }}, - .returned_value - = "The computed half MD5 hash of the given input params returned as a [UInt64](../../../sql-reference/data-types/int-uint.md) in big-endian byte order.", - .examples - = {{"", - "SELECT HEX(halfMD5('abc', 'cde', 'fgh'));", - R"( -┌─hex(halfMD5('abc', 'cde', 'fgh'))─┐ -│ 2C9506B7374CFAF4 │ -└───────────────────────────────────┘ - )" - }} - }); - factory.registerFunction(FunctionDocumentation{ - .description = R"(Calculates the MD5 hash of the given string.)", - .syntax = "SELECT MD5(s);", - .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, - .returned_value - = "The MD5 hash of the given input string returned as a [FixedString(16)](../../sql-reference/data-types/fixedstring.md).", - .examples - = {{"", - "SELECT HEX(MD5('abc'));", - R"( -┌─hex(MD5('abc'))──────────────────┐ -│ 900150983CD24FB0D6963F7D28E17F72 │ -└──────────────────────────────────┘ - )" - }} - }); - factory.registerFunction(FunctionDocumentation{ - .description = R"(Calculates the SHA1 hash of the given string.)", - .syntax = "SELECT SHA1(s);", - .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, - .returned_value - = "The SHA1 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", - .examples - = {{"", - "SELECT HEX(SHA1('abc'));", - R"( -┌─hex(SHA1('abc'))─────────────────────────┐ -│ A9993E364706816ABA3E25717850C26C9CD0D89D │ -└──────────────────────────────────────────┘ - )" - }} - }); - factory.registerFunction(FunctionDocumentation{ - .description = R"(Calculates the SHA224 hash of the given string.)", - .syntax = "SELECT SHA224(s);", - .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, - .returned_value - = "The SHA224 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", - .examples - = {{"", - "SELECT HEX(SHA224('abc'));", - R"( -┌─hex(SHA224('abc'))───────────────────────────────────────┐ -│ 23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7 │ -└──────────────────────────────────────────────────────────┘ - )" - }} - }); - factory.registerFunction(FunctionDocumentation{ - .description = R"(Calculates the SHA256 hash of the given string.)", - .syntax = "SELECT SHA256(s);", - .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, - .returned_value - = "The SHA256 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", - .examples - = {{"", - "SELECT HEX(SHA256('abc'));", - R"( -┌─hex(SHA256('abc'))───────────────────────────────────────────────┐ -│ BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD │ -└──────────────────────────────────────────────────────────────────┘ - )" - }} - }); - factory.registerFunction(FunctionDocumentation{ - .description = R"(Calculates the SHA384 hash of the given string.)", - .syntax = "SELECT SHA384(s);", - .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, - .returned_value - = "The SHA384 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", - .examples - = {{"", - "SELECT HEX(SHA384('abc'));", - R"( -┌─hex(SHA384('abc'))───────────────────────────────────────────────────────────────────────────────┐ -│ CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED1631A8B605A43FF5BED8086072BA1E7CC2358BAECA134C825A7 │ -└──────────────────────────────────────────────────────────────────────────────────────────────────┘ - )" - }} - }); - factory.registerFunction(FunctionDocumentation{ - .description = R"(Calculates the SHA512 hash of the given string.)", - .syntax = "SELECT SHA512(s);", - .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, - .returned_value - = "The SHA512 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", - .examples - = {{"", - "SELECT HEX(SHA512('abc'));", - R"( -┌─hex(SHA512('abc'))───────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA20A9EEEE64B55D39A2192992A274FC1A836BA3C23A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F │ -└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - )" - }} - }); - factory.registerFunction(FunctionDocumentation{ - .description = R"(Calculates the SHA512_256 hash of the given string.)", - .syntax = "SELECT SHA512_256(s);", - .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, - .returned_value - = "The SHA512_256 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", - .examples - = {{"", - "SELECT HEX(SHA512_256('abc'));", - R"( -┌─hex(SHA512_256('abc'))───────────────────────────────────────────┐ -│ 53048E2681941EF99B2E29B76B4C7DABE4C2D0C634FC6D46E0E2F13107E7AF23 │ -└──────────────────────────────────────────────────────────────────┘ - )" - }} - }); -} -} - -#endif diff --git a/src/Functions/FunctionsStringHashFixedString.cpp b/src/Functions/FunctionsStringHashFixedString.cpp new file mode 100644 index 00000000000..f7134953d52 --- /dev/null +++ b/src/Functions/FunctionsStringHashFixedString.cpp @@ -0,0 +1,441 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#if USE_BLAKE3 +# include +#endif + +#if USE_SSL +# include +# include +# include +# if USE_BORINGSSL +# include +# else +# include +# endif +#endif + +/// Instatiating only the functions that require FunctionStringHashFixedString in a separate file +/// to better parallelize the build procedure and avoid MSan build failure +/// due to excessive resource consumption. + +namespace DB +{ +namespace ErrorCodes +{ +extern const int ILLEGAL_COLUMN; +} + + +#if USE_SSL + +struct MD4Impl +{ + static constexpr auto name = "MD4"; + enum + { + length = MD4_DIGEST_LENGTH + }; + + static void apply(const char * begin, const size_t size, unsigned char * out_char_data) + { + MD4_CTX ctx; + MD4_Init(&ctx); + MD4_Update(&ctx, reinterpret_cast(begin), size); + MD4_Final(out_char_data, &ctx); + } +}; + +struct MD5Impl +{ + static constexpr auto name = "MD5"; + enum + { + length = MD5_DIGEST_LENGTH + }; + + static void apply(const char * begin, const size_t size, unsigned char * out_char_data) + { + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, reinterpret_cast(begin), size); + MD5_Final(out_char_data, &ctx); + } +}; + +struct SHA1Impl +{ + static constexpr auto name = "SHA1"; + enum + { + length = SHA_DIGEST_LENGTH + }; + + static void apply(const char * begin, const size_t size, unsigned char * out_char_data) + { + SHA_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, reinterpret_cast(begin), size); + SHA1_Final(out_char_data, &ctx); + } +}; + +struct SHA224Impl +{ + static constexpr auto name = "SHA224"; + enum + { + length = SHA224_DIGEST_LENGTH + }; + + static void apply(const char * begin, const size_t size, unsigned char * out_char_data) + { + SHA256_CTX ctx; + SHA224_Init(&ctx); + SHA224_Update(&ctx, reinterpret_cast(begin), size); + SHA224_Final(out_char_data, &ctx); + } +}; + +struct SHA256Impl +{ + static constexpr auto name = "SHA256"; + enum + { + length = SHA256_DIGEST_LENGTH + }; + + static void apply(const char * begin, const size_t size, unsigned char * out_char_data) + { + SHA256_CTX ctx; + SHA256_Init(&ctx); + SHA256_Update(&ctx, reinterpret_cast(begin), size); + SHA256_Final(out_char_data, &ctx); + } +}; + +struct SHA384Impl +{ + static constexpr auto name = "SHA384"; + enum + { + length = SHA384_DIGEST_LENGTH + }; + + static void apply(const char * begin, const size_t size, unsigned char * out_char_data) + { + SHA512_CTX ctx; + SHA384_Init(&ctx); + SHA384_Update(&ctx, reinterpret_cast(begin), size); + SHA384_Final(out_char_data, &ctx); + } +}; + +struct SHA512Impl +{ + static constexpr auto name = "SHA512"; + enum + { + length = 64 + }; + + static void apply(const char * begin, const size_t size, unsigned char * out_char_data) + { + SHA512_CTX ctx; + SHA512_Init(&ctx); + SHA512_Update(&ctx, reinterpret_cast(begin), size); + SHA512_Final(out_char_data, &ctx); + } +}; + +struct SHA512Impl256 +{ + static constexpr auto name = "SHA512_256"; + enum + { + length = 32 + }; + + static void apply(const char * begin, const size_t size, unsigned char * out_char_data) + { + /// Here, we use the EVP interface that is common to both BoringSSL and OpenSSL. Though BoringSSL is the default + /// SSL library that we use, for S390X architecture only OpenSSL is supported. But the SHA512-256, SHA512_256_Init, + /// SHA512_256_Update, SHA512_256_Final methods to calculate hash (similar to the other SHA functions) aren't available + /// in the current version of OpenSSL that we use which necessitates the use of the EVP interface. + auto md_ctx = EVP_MD_CTX_create(); + EVP_DigestInit_ex(md_ctx, EVP_sha512_256(), nullptr /*engine*/); + EVP_DigestUpdate(md_ctx, begin, size); + EVP_DigestFinal_ex(md_ctx, out_char_data, nullptr /*size*/); + EVP_MD_CTX_destroy(md_ctx); + } +}; +#endif + +#if USE_BLAKE3 +struct ImplBLAKE3 +{ + static constexpr auto name = "BLAKE3"; + enum + { + length = 32 + }; + + static void apply(const char * begin, const size_t size, unsigned char * out_char_data) + { + static_assert(LLVM_BLAKE3_OUT_LEN == ImplBLAKE3::length); + auto & result = *reinterpret_cast *>(out_char_data); + + llvm::BLAKE3 hasher; + if (size > 0) + hasher.update(llvm::StringRef(begin, size)); + hasher.final(result); + } +}; + +#endif + +template +class FunctionStringHashFixedString : public IFunction +{ +public: + static constexpr auto name = Impl::name; + static FunctionPtr create(ContextPtr) { return std::make_shared(); } + + String getName() const override { return name; } + + size_t getNumberOfArguments() const override { return 1; } + + DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override + { + if (!isStringOrFixedString(arguments[0]) && !isIPv6(arguments[0])) + throw Exception( + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of argument of function {}", arguments[0]->getName(), getName()); + + return std::make_shared(Impl::length); + } + + bool useDefaultImplementationForConstants() const override { return true; } + + bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return true; } + + ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr &, size_t /*input_rows_count*/) const override + { + if (const ColumnString * col_from = checkAndGetColumn(arguments[0].column.get())) + { + auto col_to = ColumnFixedString::create(Impl::length); + + const typename ColumnString::Chars & data = col_from->getChars(); + const typename ColumnString::Offsets & offsets = col_from->getOffsets(); + auto & chars_to = col_to->getChars(); + const auto size = offsets.size(); + chars_to.resize(size * Impl::length); + + ColumnString::Offset current_offset = 0; + for (size_t i = 0; i < size; ++i) + { + Impl::apply( + reinterpret_cast(&data[current_offset]), + offsets[i] - current_offset - 1, + reinterpret_cast(&chars_to[i * Impl::length])); + + current_offset = offsets[i]; + } + + return col_to; + } + else if (const ColumnFixedString * col_from_fix = checkAndGetColumn(arguments[0].column.get())) + { + auto col_to = ColumnFixedString::create(Impl::length); + const typename ColumnFixedString::Chars & data = col_from_fix->getChars(); + const auto size = col_from_fix->size(); + auto & chars_to = col_to->getChars(); + const auto length = col_from_fix->getN(); + chars_to.resize(size * Impl::length); + for (size_t i = 0; i < size; ++i) + { + Impl::apply( + reinterpret_cast(&data[i * length]), length, reinterpret_cast(&chars_to[i * Impl::length])); + } + return col_to; + } + else if (const ColumnIPv6 * col_from_ip = checkAndGetColumn(arguments[0].column.get())) + { + auto col_to = ColumnFixedString::create(Impl::length); + const typename ColumnIPv6::Container & data = col_from_ip->getData(); + const auto size = col_from_ip->size(); + auto & chars_to = col_to->getChars(); + const auto length = IPV6_BINARY_LENGTH; + chars_to.resize(size * Impl::length); + for (size_t i = 0; i < size; ++i) + { + Impl::apply( + reinterpret_cast(&data[i * length]), length, reinterpret_cast(&chars_to[i * Impl::length])); + } + return col_to; + } + else + throw Exception( + ErrorCodes::ILLEGAL_COLUMN, + "Illegal column {} of first argument of function {}", + arguments[0].column->getName(), + getName()); + } +}; + +#if USE_SSL || USE_BLAKE3 +REGISTER_FUNCTION(HashFixedStrings) +{ +# if USE_SSL + using FunctionMD4 = FunctionStringHashFixedString; + using FunctionMD5 = FunctionStringHashFixedString; + using FunctionSHA1 = FunctionStringHashFixedString; + using FunctionSHA224 = FunctionStringHashFixedString; + using FunctionSHA256 = FunctionStringHashFixedString; + using FunctionSHA384 = FunctionStringHashFixedString; + using FunctionSHA512 = FunctionStringHashFixedString; + using FunctionSHA512_256 = FunctionStringHashFixedString; + + factory.registerFunction(FunctionDocumentation{ + .description = R"(Calculates the MD4 hash of the given string.)", + .syntax = "SELECT MD4(s);", + .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, + .returned_value + = "The MD4 hash of the given input string returned as a [FixedString(16)](../../sql-reference/data-types/fixedstring.md).", + .examples + = {{"", + "SELECT HEX(MD4('abc'));", + R"( +┌─hex(MD4('abc'))──────────────────┐ +│ A448017AAF21D8525FC10AE87AA6729D │ +└──────────────────────────────────┘ + )"}}}); + factory.registerFunction(FunctionDocumentation{ + .description = R"(Calculates the MD5 hash of the given string.)", + .syntax = "SELECT MD5(s);", + .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, + .returned_value + = "The MD5 hash of the given input string returned as a [FixedString(16)](../../sql-reference/data-types/fixedstring.md).", + .examples + = {{"", + "SELECT HEX(MD5('abc'));", + R"( +┌─hex(MD5('abc'))──────────────────┐ +│ 900150983CD24FB0D6963F7D28E17F72 │ +└──────────────────────────────────┘ + )"}}}); + factory.registerFunction(FunctionDocumentation{ + .description = R"(Calculates the SHA1 hash of the given string.)", + .syntax = "SELECT SHA1(s);", + .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, + .returned_value + = "The SHA1 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", + .examples + = {{"", + "SELECT HEX(SHA1('abc'));", + R"( +┌─hex(SHA1('abc'))─────────────────────────┐ +│ A9993E364706816ABA3E25717850C26C9CD0D89D │ +└──────────────────────────────────────────┘ + )"}}}); + factory.registerFunction(FunctionDocumentation{ + .description = R"(Calculates the SHA224 hash of the given string.)", + .syntax = "SELECT SHA224(s);", + .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, + .returned_value + = "The SHA224 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", + .examples + = {{"", + "SELECT HEX(SHA224('abc'));", + R"( +┌─hex(SHA224('abc'))───────────────────────────────────────┐ +│ 23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7 │ +└──────────────────────────────────────────────────────────┘ + )"}}}); + factory.registerFunction(FunctionDocumentation{ + .description = R"(Calculates the SHA256 hash of the given string.)", + .syntax = "SELECT SHA256(s);", + .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, + .returned_value + = "The SHA256 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", + .examples + = {{"", + "SELECT HEX(SHA256('abc'));", + R"( +┌─hex(SHA256('abc'))───────────────────────────────────────────────┐ +│ BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD │ +└──────────────────────────────────────────────────────────────────┘ + )"}}}); + factory.registerFunction(FunctionDocumentation{ + .description = R"(Calculates the SHA384 hash of the given string.)", + .syntax = "SELECT SHA384(s);", + .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, + .returned_value + = "The SHA384 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", + .examples + = {{"", + "SELECT HEX(SHA384('abc'));", + R"( +┌─hex(SHA384('abc'))───────────────────────────────────────────────────────────────────────────────┐ +│ CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED1631A8B605A43FF5BED8086072BA1E7CC2358BAECA134C825A7 │ +└──────────────────────────────────────────────────────────────────────────────────────────────────┘ + )"}}}); + factory.registerFunction(FunctionDocumentation{ + .description = R"(Calculates the SHA512 hash of the given string.)", + .syntax = "SELECT SHA512(s);", + .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, + .returned_value + = "The SHA512 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", + .examples + = {{"", + "SELECT HEX(SHA512('abc'));", + R"( +┌─hex(SHA512('abc'))───────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA20A9EEEE64B55D39A2192992A274FC1A836BA3C23A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + )"}}}); + factory.registerFunction(FunctionDocumentation{ + .description = R"(Calculates the SHA512_256 hash of the given string.)", + .syntax = "SELECT SHA512_256(s);", + .arguments = {{"s", "The input [String](../../sql-reference/data-types/string.md)."}}, + .returned_value + = "The SHA512_256 hash of the given input string returned as a [FixedString](../../sql-reference/data-types/fixedstring.md).", + .examples + = {{"", + "SELECT HEX(SHA512_256('abc'));", + R"( +┌─hex(SHA512_256('abc'))───────────────────────────────────────────┐ +│ 53048E2681941EF99B2E29B76B4C7DABE4C2D0C634FC6D46E0E2F13107E7AF23 │ +└──────────────────────────────────────────────────────────────────┘ + )"}}}); + + +# endif + +# if USE_BLAKE3 + using FunctionBLAKE3 = FunctionStringHashFixedString; + factory.registerFunction( + FunctionDocumentation{ + .description = R"( + Calculates BLAKE3 hash string and returns the resulting set of bytes as FixedString. + This cryptographic hash-function is integrated into ClickHouse with BLAKE3 Rust library. + The function is rather fast and shows approximately two times faster performance compared to SHA-2, while generating hashes of the same length as SHA-256. + It returns a BLAKE3 hash as a byte array with type FixedString(32). + )", + .examples{{"hash", "SELECT hex(BLAKE3('ABC'))", ""}}, + .categories{"Hash"}}, + FunctionFactory::CaseSensitive); +# endif + +#endif +} + +} From 140f6dafd8c01b2fed08b58abdfbab400982a071 Mon Sep 17 00:00:00 2001 From: Sema Checherinda Date: Mon, 18 Dec 2023 19:16:50 +0100 Subject: [PATCH 086/253] hung_check check with lighter queries --- tests/clickhouse-test | 140 +++++++++++++++++++++++++++++------------- 1 file changed, 97 insertions(+), 43 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 1609d8f3c07..c1bc8c9e559 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -32,7 +32,7 @@ from typing import Tuple, Union, Optional, Dict, Set, List import subprocess from subprocess import Popen from subprocess import PIPE -from datetime import datetime +from datetime import datetime, timedelta from time import time, sleep from errno import ESRCH @@ -278,37 +278,44 @@ def need_retry(args, stdout, stderr, total_time): msg in stderr for msg in MESSAGES_TO_RETRY ) +def get_processlist_size(args): + if args.replicated_database: + return int( + clickhouse_execute( + args, + """ + SELECT + count() + FROM + FROM system.processes + WHERE query NOT LIKE '%system.processes%' + )) + FORMAT Vertical + """, + ).strip() + ) + else: + return int( + clickhouse_execute( + args, + """ + SELECT + count() + FROM system.processes + WHERE query NOT LIKE '%system.processes%' + FORMAT Vertical + """, + ).strip() + ) def get_processlist_with_stacktraces(args): - try: - if args.replicated_database: - return clickhouse_execute( - args, - """ - SELECT materialize(hostName() || '::' || tcpPort()::String) as host_port, * - -- NOTE: view() here to do JOIN on shards, instead of initiator - FROM clusterAllReplicas('test_cluster_database_replicated', view( - SELECT - p.*, - arrayStringConcat(groupArray('Thread ID ' || toString(s.thread_id) || '\n' || arrayStringConcat(arrayMap( - x -> concat(addressToLine(x), '::', demangle(addressToSymbol(x))), - s.trace), '\n') AS stacktrace - )) AS stacktraces - FROM system.processes p - JOIN system.stack_trace s USING (query_id) - WHERE query NOT LIKE '%system.processes%' - GROUP BY p.* - )) - ORDER BY elapsed DESC FORMAT Vertical - """, - settings={ - "allow_introspection_functions": 1, - }, - ) - else: - return clickhouse_execute( - args, - """ + if args.replicated_database: + return clickhouse_execute( + args, + """ + SELECT materialize(hostName() || '::' || tcpPort()::String) as host_port, * + -- NOTE: view() here to do JOIN on shards, instead of initiator + FROM clusterAllReplicas('test_cluster_database_replicated', view( SELECT p.*, arrayStringConcat(groupArray('Thread ID ' || toString(s.thread_id) || '\n' || arrayStringConcat(arrayMap( @@ -319,14 +326,36 @@ def get_processlist_with_stacktraces(args): JOIN system.stack_trace s USING (query_id) WHERE query NOT LIKE '%system.processes%' GROUP BY p.* - ORDER BY elapsed DESC FORMAT Vertical - """, - settings={ - "allow_introspection_functions": 1, - }, - ) - except Exception as e: - return "Failed to get processlist: " + str(e) + )) + ORDER BY elapsed DESC FORMAT Vertical + """, + settings={ + "allow_introspection_functions": 1, + }, + timeout=120, + ) + else: + return clickhouse_execute( + args, + """ + SELECT + p.*, + arrayStringConcat(groupArray('Thread ID ' || toString(s.thread_id) || '\n' || arrayStringConcat(arrayMap( + x -> concat(addressToLine(x), '::', demangle(addressToSymbol(x))), + s.trace), '\n') AS stacktrace + )) AS stacktraces + FROM system.processes p + JOIN system.stack_trace s USING (query_id) + WHERE query NOT LIKE '%system.processes%' + GROUP BY p.* + ORDER BY elapsed DESC FORMAT Vertical + """, + settings={ + "allow_introspection_functions": 1, + }, + timeout=120, + ) + def get_transactions_list(args): @@ -2420,11 +2449,36 @@ def main(args): if args.hung_check: # Some queries may execute in background for some time after test was finished. This is normal. - for _ in range(1, 60): - processlist = get_processlist_with_stacktraces(args) - if not processlist: - break - sleep(1) + print("Checking the hung queries: ", end='') + hung_count = 0 + try: + deadline = datetime.now() + timedelta(seconds=90) + while datetime.now() < deadline: + hung_count = get_processlist_size(args) + if hung_count == 0: + print(" done") + break + print(". ", end='') + except Exception as e: + print( + colored( + "\nHung check failed. Failed to get processlist size: " + str(e), args, "red", attrs=["bold"] + ) + ) + exit_code.value = 1 + + + processlist = "" + if hung_count > 0: + try: + processlist = get_processlist_with_stacktraces(args) + except Exception as e: + print( + colored( + "\nHung check failed, Failed to get processlist with stacktraces: " + str(e), args, "red", attrs=["bold"] + ) + ) + exit_code.value = 1 if processlist: print( From 939d602c3c83df7019d63fe4899b46a364dc26d5 Mon Sep 17 00:00:00 2001 From: Sema Checherinda Date: Mon, 18 Dec 2023 19:26:33 +0100 Subject: [PATCH 087/253] fix typo --- docker/test/stateless/utils.lib | 2 -- tests/clickhouse-test | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/docker/test/stateless/utils.lib b/docker/test/stateless/utils.lib index a30e05b46ff..9b6ab535a90 100644 --- a/docker/test/stateless/utils.lib +++ b/docker/test/stateless/utils.lib @@ -49,5 +49,3 @@ function timeout_with_logging() { } # vi: ft=bash - -} diff --git a/tests/clickhouse-test b/tests/clickhouse-test index c1bc8c9e559..1ba67a3b2f4 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -288,8 +288,7 @@ def get_processlist_size(args): count() FROM FROM system.processes - WHERE query NOT LIKE '%system.processes%' - )) + WHERE query NOT LIKE '%system.processes%' FORMAT Vertical """, ).strip() @@ -2475,7 +2474,7 @@ def main(args): except Exception as e: print( colored( - "\nHung check failed, Failed to get processlist with stacktraces: " + str(e), args, "red", attrs=["bold"] + "\nHung check failed. Failed to get processlist with stacktraces: " + str(e), args, "red", attrs=["bold"] ) ) exit_code.value = 1 From 4109304b2a629cfe3a5ff6b345914ebf72e652e0 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Mon, 18 Dec 2023 19:50:01 +0000 Subject: [PATCH 088/253] Test parallel replicas with force_primary_key setting --- .../02898_parallel_replicas_progress_bar.sql | 8 +-- ...allel_replicas_force_primary_key.reference | 6 +++ ...46_parallel_replicas_force_primary_key.sql | 49 +++++++++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 tests/queries/0_stateless/02946_parallel_replicas_force_primary_key.reference create mode 100644 tests/queries/0_stateless/02946_parallel_replicas_force_primary_key.sql diff --git a/tests/queries/0_stateless/02898_parallel_replicas_progress_bar.sql b/tests/queries/0_stateless/02898_parallel_replicas_progress_bar.sql index 6b2f146efd0..d8bfec12b3a 100644 --- a/tests/queries/0_stateless/02898_parallel_replicas_progress_bar.sql +++ b/tests/queries/0_stateless/02898_parallel_replicas_progress_bar.sql @@ -2,9 +2,9 @@ DROP TABLE IF EXISTS t1 SYNC; DROP TABLE IF EXISTS t2 SYNC; DROP TABLE IF EXISTS t3 SYNC; -CREATE TABLE t1(k UInt32, v String) ENGINE ReplicatedMergeTree('/parallel_replicas/{database}/test_tbl', 'r1') ORDER BY k; -CREATE TABLE t2(k UInt32, v String) ENGINE ReplicatedMergeTree('/parallel_replicas/{database}/test_tbl', 'r2') ORDER BY k; -CREATE TABLE t3(k UInt32, v String) ENGINE ReplicatedMergeTree('/parallel_replicas/{database}/test_tbl', 'r3') ORDER BY k; +CREATE TABLE t1(k UInt32, v String) ENGINE ReplicatedMergeTree('/02898_parallel_replicas/{database}/test_tbl', 'r1') ORDER BY k; +CREATE TABLE t2(k UInt32, v String) ENGINE ReplicatedMergeTree('/02898_parallel_replicas/{database}/test_tbl', 'r2') ORDER BY k; +CREATE TABLE t3(k UInt32, v String) ENGINE ReplicatedMergeTree('/02898_parallel_replicas/{database}/test_tbl', 'r3') ORDER BY k; insert into t1 select number, toString(number) from numbers(1000, 1000); insert into t2 select number, toString(number) from numbers(2000, 1000); @@ -14,7 +14,7 @@ system sync replica t1; system sync replica t2; system sync replica t3; -SET allow_experimental_parallel_reading_from_replicas=1, max_parallel_replicas=3, parallel_replicas_for_non_replicated_merge_tree=1, cluster_for_parallel_replicas='test_cluster_one_shard_three_replicas_localhost'; +SET allow_experimental_parallel_reading_from_replicas=1, max_parallel_replicas=3, cluster_for_parallel_replicas='test_cluster_one_shard_three_replicas_localhost'; -- default coordinator SELECT count(), min(k), max(k), avg(k) FROM t1 SETTINGS log_comment='02898_default_190aed82-2423-413b-ad4c-24dcca50f65b'; diff --git a/tests/queries/0_stateless/02946_parallel_replicas_force_primary_key.reference b/tests/queries/0_stateless/02946_parallel_replicas_force_primary_key.reference new file mode 100644 index 00000000000..64dfee7b7a1 --- /dev/null +++ b/tests/queries/0_stateless/02946_parallel_replicas_force_primary_key.reference @@ -0,0 +1,6 @@ +1 750 +2 750 +3 750 +1 750 +2 750 +3 750 diff --git a/tests/queries/0_stateless/02946_parallel_replicas_force_primary_key.sql b/tests/queries/0_stateless/02946_parallel_replicas_force_primary_key.sql new file mode 100644 index 00000000000..d33c8cdbc93 --- /dev/null +++ b/tests/queries/0_stateless/02946_parallel_replicas_force_primary_key.sql @@ -0,0 +1,49 @@ +DROP TABLE IF EXISTS t1 SYNC; +DROP TABLE IF EXISTS t2 SYNC; +DROP TABLE IF EXISTS t3 SYNC; + +CREATE TABLE t1(k UInt32, v String) ENGINE ReplicatedMergeTree('/02946_parallel_replicas/{database}/test_tbl', 'r1') ORDER BY k; +CREATE TABLE t2(k UInt32, v String) ENGINE ReplicatedMergeTree('/02946_parallel_replicas/{database}/test_tbl', 'r2') ORDER BY k; +CREATE TABLE t3(k UInt32, v String) ENGINE ReplicatedMergeTree('/02946_parallel_replicas/{database}/test_tbl', 'r3') ORDER BY k; + +insert into t1 select number % 4, toString(number) from numbers(1000, 1000); +insert into t2 select number % 4, toString(number) from numbers(2000, 1000); +insert into t3 select number % 4, toString(number) from numbers(3000, 1000); + +system sync replica t1; +system sync replica t2; +system sync replica t3; + +-- w/o parallel replicas +SELECT + k, + count() +FROM t1 +WHERE k > 0 +GROUP BY k +ORDER BY k +SETTINGS force_primary_key = 1, allow_experimental_parallel_reading_from_replicas = 0; + +-- parallel replicas, primary key is used +SET allow_experimental_parallel_reading_from_replicas=1, max_parallel_replicas=3, cluster_for_parallel_replicas='test_cluster_one_shard_three_replicas_localhost'; +SELECT + k, + count() +FROM t1 +WHERE k > 0 +GROUP BY k +ORDER BY k +SETTINGS force_primary_key = 1; + +-- parallel replicas, primary key is NOT used +SELECT + k, + count() +FROM t1 +GROUP BY k +ORDER BY k +SETTINGS force_primary_key = 1; -- { serverError INDEX_NOT_USED } + +DROP TABLE t1 SYNC; +DROP TABLE t2 SYNC; +DROP TABLE t3 SYNC; From a8ef051bb2e1837fd301d2db0c6dcc6c8f1b85d6 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 18 Dec 2023 20:59:22 +0100 Subject: [PATCH 089/253] Update 00002_log_and_exception_messages_formatting.sql --- .../0_stateless/00002_log_and_exception_messages_formatting.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql index 062806baae9..45ce28929e0 100644 --- a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql +++ b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql @@ -10,7 +10,7 @@ create view logs as select * from system.text_log where now() - toIntervalMinute -- Check that we don't have too many messages formatted with fmt::runtime or strings concatenation. -- 0.001 threshold should be always enough, the value was about 0.00025 select 'runtime messages', greatest(coalesce(sum(length(message_format_string) = 0) / countOrNull(), 0), 0.001) from logs - where message not like '% Received from %clickhouse-staging.com:9440%'; + where message not like '% Received from %clickhouse-staging.com:9440%' and source_file not like '%/AWSLogger.cpp%'; -- Check the same for exceptions. The value was 0.03 select 'runtime exceptions', greatest(coalesce(sum(length(message_format_string) = 0) / countOrNull(), 0), 0.05) from logs From ddf5da225d8ce648aba4af50bc92728753bbf48b Mon Sep 17 00:00:00 2001 From: Nikita Mikhaylov Date: Mon, 18 Dec 2023 21:39:59 +0000 Subject: [PATCH 090/253] Fix integration test --- .../configs/timeouts_for_fetches.xml | 1 + .../test_replicated_fetches_timeouts/test.py | 30 ++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 tests/integration/test_replicated_fetches_timeouts/configs/timeouts_for_fetches.xml diff --git a/tests/integration/test_replicated_fetches_timeouts/configs/timeouts_for_fetches.xml b/tests/integration/test_replicated_fetches_timeouts/configs/timeouts_for_fetches.xml new file mode 100644 index 00000000000..b163c6f54a1 --- /dev/null +++ b/tests/integration/test_replicated_fetches_timeouts/configs/timeouts_for_fetches.xml @@ -0,0 +1 @@ + diff --git a/tests/integration/test_replicated_fetches_timeouts/test.py b/tests/integration/test_replicated_fetches_timeouts/test.py index 7d5da55549c..3505cbf1304 100644 --- a/tests/integration/test_replicated_fetches_timeouts/test.py +++ b/tests/integration/test_replicated_fetches_timeouts/test.py @@ -10,13 +10,20 @@ from helpers.network import PartitionManager cluster = ClickHouseCluster(__file__) node1 = cluster.add_instance( - "node1", with_zookeeper=True, main_configs=["configs/server.xml"] + "node1", with_zookeeper=True, main_configs=["configs/server.xml", "configs/timeouts_for_fetches.xml"] ) node2 = cluster.add_instance( - "node2", with_zookeeper=True, main_configs=["configs/server.xml"] + "node2", with_zookeeper=True, stay_alive=True, main_configs=["configs/server.xml", "configs/timeouts_for_fetches.xml"] ) +config = """ + + 30 + 1 + +""" + @pytest.fixture(scope="module") def started_cluster(): @@ -49,14 +56,10 @@ def test_no_stall(started_cluster): node2.query("SYSTEM STOP FETCHES t") node1.query( - "INSERT INTO t SELECT 1, '{}' FROM numbers(500)".format( - get_random_string(104857) - ) + f"INSERT INTO t SELECT 1, '{get_random_string(104857)}' FROM numbers(500)" ) node1.query( - "INSERT INTO t SELECT 2, '{}' FROM numbers(500)".format( - get_random_string(104857) - ) + f"INSERT INTO t SELECT 2, '{get_random_string(104857)}' FROM numbers(500)" ) with PartitionManager() as pm: @@ -82,14 +85,13 @@ def test_no_stall(started_cluster): print("Connection timeouts tested!") - # Increase connection timeout and wait for receive timeouts. - node2.query( - """ - ALTER TABLE t - MODIFY SETTING replicated_fetches_http_connection_timeout = 30, - replicated_fetches_http_receive_timeout = 1""" + node2.replace_config( + "/etc/clickhouse-server/config.d/timeouts_for_fetches.xml", + config ) + node2.restart_clickhouse() + while True: timeout_exceptions = int( node2.query( From 2be806618e28ce41312d829213e3ecc8bcf34597 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 18 Dec 2023 22:07:00 +0000 Subject: [PATCH 091/253] Automatic style fix --- .../test_replicated_fetches_timeouts/test.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_replicated_fetches_timeouts/test.py b/tests/integration/test_replicated_fetches_timeouts/test.py index 3505cbf1304..55fa4b909ba 100644 --- a/tests/integration/test_replicated_fetches_timeouts/test.py +++ b/tests/integration/test_replicated_fetches_timeouts/test.py @@ -10,11 +10,16 @@ from helpers.network import PartitionManager cluster = ClickHouseCluster(__file__) node1 = cluster.add_instance( - "node1", with_zookeeper=True, main_configs=["configs/server.xml", "configs/timeouts_for_fetches.xml"] + "node1", + with_zookeeper=True, + main_configs=["configs/server.xml", "configs/timeouts_for_fetches.xml"], ) node2 = cluster.add_instance( - "node2", with_zookeeper=True, stay_alive=True, main_configs=["configs/server.xml", "configs/timeouts_for_fetches.xml"] + "node2", + with_zookeeper=True, + stay_alive=True, + main_configs=["configs/server.xml", "configs/timeouts_for_fetches.xml"], ) config = """ @@ -86,8 +91,7 @@ def test_no_stall(started_cluster): print("Connection timeouts tested!") node2.replace_config( - "/etc/clickhouse-server/config.d/timeouts_for_fetches.xml", - config + "/etc/clickhouse-server/config.d/timeouts_for_fetches.xml", config ) node2.restart_clickhouse() From fd460566f64e59ece249414e2e35b5c46e3cc4a2 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy <99031427+yakov-olkhovskiy@users.noreply.github.com> Date: Mon, 18 Dec 2023 19:34:08 -0500 Subject: [PATCH 092/253] fix setting description --- src/Storages/MergeTree/MergeTreeSettings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/MergeTreeSettings.h b/src/Storages/MergeTree/MergeTreeSettings.h index d9b996b36ca..07051a695de 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -73,7 +73,7 @@ struct Settings; M(UInt64, number_of_mutations_to_throw, 1000, "If table has at least that many unfinished mutations, throw 'Too many mutations' exception. Disabled if set to 0", 0) \ M(UInt64, min_delay_to_mutate_ms, 10, "Min delay of mutating MergeTree table in milliseconds, if there are a lot of unfinished mutations", 0) \ M(UInt64, max_delay_to_mutate_ms, 1000, "Max delay of mutating MergeTree table in milliseconds, if there are a lot of unfinished mutations", 0) \ - M(Bool, exclude_deleted_rows_for_part_size_in_merge, false, "If true, estimated size (excluding lightweight deleted rows) will be used as source part size when selecting parts to merge", 0) \ + M(Bool, exclude_deleted_rows_for_part_size_in_merge, false, "Use an estimated source part size (excluding lightweight deleted rows) when selecting parts to merge", 0) \ \ /** Inserts settings. */ \ M(UInt64, parts_to_delay_insert, 1000, "If table contains at least that many active parts in single partition, artificially slow down insert into table. Disabled if set to 0", 0) \ From 0fc402c1068643d2689df2a2e8fe50bcff418a39 Mon Sep 17 00:00:00 2001 From: Julia Kartseva Date: Mon, 18 Dec 2023 22:48:48 +0000 Subject: [PATCH 093/253] Fix segfault in FuzzJSON engine Allow only String type for FuzzJSON engine table columns. Fixes: https://github.com/ClickHouse/ClickHouse/issues/57858 --- src/Storages/StorageFuzzJSON.cpp | 5 +++++ tests/queries/0_stateless/02919_storage_fuzzjson.sql | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Storages/StorageFuzzJSON.cpp b/src/Storages/StorageFuzzJSON.cpp index 6bf69efa1dd..631775f7493 100644 --- a/src/Storages/StorageFuzzJSON.cpp +++ b/src/Storages/StorageFuzzJSON.cpp @@ -719,6 +719,11 @@ void registerStorageFuzzJSON(StorageFactory & factory) throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, "Storage FuzzJSON must have arguments."); StorageFuzzJSON::Configuration configuration = StorageFuzzJSON::getConfiguration(engine_args, args.getLocalContext()); + + for (const auto& col : args.columns) + if (col.type->getTypeId() != TypeIndex::String) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "'StorageFuzzJSON' supports only columns of String type, got {}.", col.type->getName()); + return std::make_shared(args.table_id, args.columns, args.comment, configuration); }); } diff --git a/tests/queries/0_stateless/02919_storage_fuzzjson.sql b/tests/queries/0_stateless/02919_storage_fuzzjson.sql index 80b4a406a08..1a85748b061 100644 --- a/tests/queries/0_stateless/02919_storage_fuzzjson.sql +++ b/tests/queries/0_stateless/02919_storage_fuzzjson.sql @@ -42,3 +42,15 @@ CREATE TABLE 02919_test_table_reuse_args(str String) ENGINE = FuzzJSON( SELECT count() FROM (SELECT * FROM 02919_test_table_reuse_args LIMIT 100); DROP TABLE IF EXISTS 02919_test_table_reuse_args; + +-- +DROP TABLE IF EXISTS 02919_test_table_invalid_col_type; +CREATE TABLE 02919_test_table_invalid_col_type +( + str Nullable(Int64) +) +ENGINE = FuzzJSON('{"pet":"rat"}', NULL); -- { serverError BAD_ARGUMENTS } + +DROP TABLE IF EXISTS 02919_test_table_invalid_col_type; + +-- From 6014dca114a03130499fc66f71b614ab06c623cb Mon Sep 17 00:00:00 2001 From: Julia Kartseva Date: Tue, 19 Dec 2023 02:33:44 +0000 Subject: [PATCH 094/253] Allow multiple columns in StorageFuzzJSON --- src/Storages/StorageFuzzJSON.cpp | 6 +++++- .../queries/0_stateless/02919_storage_fuzzjson.reference | 1 + tests/queries/0_stateless/02919_storage_fuzzjson.sql | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Storages/StorageFuzzJSON.cpp b/src/Storages/StorageFuzzJSON.cpp index 631775f7493..4d94fe3cdb0 100644 --- a/src/Storages/StorageFuzzJSON.cpp +++ b/src/Storages/StorageFuzzJSON.cpp @@ -481,7 +481,11 @@ protected: { Columns columns; columns.reserve(block_header.columns()); - columns.emplace_back(createColumn()); + for (const auto& col : block_header) + { + chassert(col.type->getTypeId() == TypeIndex::String); + columns.emplace_back(createColumn()); + } return {std::move(columns), block_size}; } diff --git a/tests/queries/0_stateless/02919_storage_fuzzjson.reference b/tests/queries/0_stateless/02919_storage_fuzzjson.reference index a134ce52c11..8f4ee4a5615 100644 --- a/tests/queries/0_stateless/02919_storage_fuzzjson.reference +++ b/tests/queries/0_stateless/02919_storage_fuzzjson.reference @@ -1,3 +1,4 @@ 100 100 100 +100 100 diff --git a/tests/queries/0_stateless/02919_storage_fuzzjson.sql b/tests/queries/0_stateless/02919_storage_fuzzjson.sql index 1a85748b061..bf473f4b6b8 100644 --- a/tests/queries/0_stateless/02919_storage_fuzzjson.sql +++ b/tests/queries/0_stateless/02919_storage_fuzzjson.sql @@ -54,3 +54,12 @@ ENGINE = FuzzJSON('{"pet":"rat"}', NULL); -- { serverError BAD_ARGUMENTS } DROP TABLE IF EXISTS 02919_test_table_invalid_col_type; -- +DROP TABLE IF EXISTS 02919_test_multi_col; +CREATE TABLE 02919_test_multi_col +( + str1 String, + str2 String +) ENGINE = FuzzJSON('{"pet":"rat"}', 999); + +SELECT count(str1), count(str2) FROM (SELECT str1, str2 FROM 02919_test_multi_col LIMIT 100); +DROP TABLE IF EXISTS 02919_test_multi_col; From 31f04b66c5cb468ef9fb532c5101e771e8b75f6b Mon Sep 17 00:00:00 2001 From: Julia Kartseva Date: Tue, 19 Dec 2023 03:47:22 +0000 Subject: [PATCH 095/253] fix freebsd build https://github.com/ClickHouse/ClickHouse/actions/runs/7256640256/job/19769624432?pr=58015#step:8:5466 ``` Dec 19 03:09:41 /build/src/IO/AIO.cpp:140:15: error: use of undeclared identifier 'ErrnoException'; did you mean 'DB::ErrnoException'? Dec 19 03:09:41 140 | throw ErrnoException(DB::ErrorCodes::CANNOT_IOSETUP, "io_setup failed"); ``` --- src/IO/AIO.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IO/AIO.cpp b/src/IO/AIO.cpp index abad8a0727d..7a051950f52 100644 --- a/src/IO/AIO.cpp +++ b/src/IO/AIO.cpp @@ -137,7 +137,7 @@ AIOContext::AIOContext(unsigned int) { ctx = io_setup(); if (ctx < 0) - throw ErrnoException(DB::ErrorCodes::CANNOT_IOSETUP, "io_setup failed"); + throw DB::ErrnoException(DB::ErrorCodes::CANNOT_IOSETUP, "io_setup failed"); } AIOContext::~AIOContext() From b252b7182cf31d245f55847e0db1b10ef0b03fc1 Mon Sep 17 00:00:00 2001 From: zhanglistar Date: Tue, 19 Dec 2023 12:09:00 +0800 Subject: [PATCH 096/253] Function if performance improvment fix bug. --- src/Functions/if.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Functions/if.cpp b/src/Functions/if.cpp index f1a51d19f32..bfcfd5e9ad2 100644 --- a/src/Functions/if.cpp +++ b/src/Functions/if.cpp @@ -57,7 +57,7 @@ inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const Arr size_t a_index = 0, b_index = 0; for (size_t i = 0; i < size; ++i) { - if constexpr (std::is_integral_v) + if constexpr (std::is_arithmetic_v) { res[i] = cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b[b_index]); a_index += cond[i]; @@ -71,7 +71,7 @@ inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const Arr { size_t a_index = 0; for (size_t i = 0; i < size; ++i) - if constexpr (std::is_integral_v) + if constexpr (std::is_arithmetic_v) { res[i] = cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b[i]); a_index += cond[i]; @@ -83,7 +83,7 @@ inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const Arr { size_t b_index = 0; for (size_t i = 0; i < size; ++i) - if constexpr (std::is_integral_v) + if constexpr (std::is_arithmetic_v) { res[i] = cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b[b_index]); b_index += !cond[i]; @@ -94,7 +94,7 @@ inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const Arr else { for (size_t i = 0; i < size; ++i) - if constexpr (std::is_integral_v) + if constexpr (std::is_arithmetic_v) res[i] = cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b[i]); else res[i] = cond[i] ? static_cast(a[i]) : static_cast(b[i]); @@ -111,7 +111,7 @@ inline void fillVectorConstant(const ArrayCond & cond, const ArrayA & a, B b, Ar { size_t a_index = 0; for (size_t i = 0; i < size; ++i) - if constexpr (std::is_integral_v) + if constexpr (std::is_arithmetic_v) { res[i] = cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b); a_index += cond[i]; @@ -122,7 +122,7 @@ inline void fillVectorConstant(const ArrayCond & cond, const ArrayA & a, B b, Ar else { for (size_t i = 0; i < size; ++i) - if constexpr (std::is_integral_v) + if constexpr (std::is_arithmetic_v) res[i] = cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b); else res[i] = cond[i] ? static_cast(a[i]) : static_cast(b); @@ -139,9 +139,9 @@ inline void fillConstantVector(const ArrayCond & cond, A a, const ArrayB & b, Ar { size_t b_index = 0; for (size_t i = 0; i < size; ++i) - if constexpr (std::is_integral_v) + if constexpr (std::is_arithmetic_v) { - res[i] = cond[i] * static_cast(a) + (!cond[i]) * static_cast(b_index); + res[i] = cond[i] * static_cast(a) + (!cond[i]) * static_cast(b[b_index]); b_index += !cond[i]; } else @@ -150,7 +150,7 @@ inline void fillConstantVector(const ArrayCond & cond, A a, const ArrayB & b, Ar else { for (size_t i = 0; i < size; ++i) - if constexpr (std::is_integral_v) + if constexpr (std::is_arithmetic_v) res[i] = cond[i] * static_cast(a) + (!cond[i]) * static_cast(b[i]); else res[i] = cond[i] ? static_cast(a) : static_cast(b[i]); From 679a0e1300a72bcb770d7f8831906f4e797a336f Mon Sep 17 00:00:00 2001 From: Shani Elharrar Date: Thu, 14 Dec 2023 10:05:01 +0200 Subject: [PATCH 097/253] StorageS3 / TableFunctionS3: Allow passing session_token to AuthSettings This can help users that want to pass temporary credentials that issued by AWS in order to load data from S3 without changing configuration or creating an IAM User. Fixes #57848 --- docs/en/sql-reference/table-functions/s3.md | 4 +- .../table-functions/s3Cluster.md | 5 +- docs/ru/sql-reference/table-functions/s3.md | 2 +- .../table-functions/s3Cluster.md | 4 +- docs/zh/sql-reference/table-functions/s3.md | 2 +- src/Storages/StorageS3.cpp | 42 +++++++++++-- src/TableFunctions/TableFunctionS3.cpp | 62 ++++++++++++++++--- src/TableFunctions/TableFunctionS3.h | 6 +- src/TableFunctions/TableFunctionS3Cluster.h | 1 + 9 files changed, 107 insertions(+), 21 deletions(-) diff --git a/docs/en/sql-reference/table-functions/s3.md b/docs/en/sql-reference/table-functions/s3.md index dc11259c626..61a9187575d 100644 --- a/docs/en/sql-reference/table-functions/s3.md +++ b/docs/en/sql-reference/table-functions/s3.md @@ -16,7 +16,7 @@ When using the `s3 table function` with [`INSERT INTO...SELECT`](../../sql-refer **Syntax** ``` sql -s3(path [, NOSIGN | aws_access_key_id, aws_secret_access_key] [,format] [,structure] [,compression]) +s3(path [, NOSIGN | aws_access_key_id, aws_secret_access_key [,session_token]] [,format] [,structure] [,compression]) ``` :::tip GCS @@ -38,6 +38,8 @@ For GCS, substitute your HMAC key and HMAC secret where you see `aws_access_key_ ::: - `NOSIGN` - If this keyword is provided in place of credentials, all the requests will not be signed. +- `access_key_id`, `secret_access_key` — Keys that specify credentials to use with given endpoint. Optional. +- `session_token` - Session token to use with the given keys. Optional when passing keys. - `format` — The [format](../../interfaces/formats.md#formats) of the file. - `structure` — Structure of the table. Format `'column1_name column1_type, column2_name column2_type, ...'`. - `compression` — Parameter is optional. Supported values: `none`, `gzip/gz`, `brotli/br`, `xz/LZMA`, `zstd/zst`. By default, it will autodetect compression by file extension. diff --git a/docs/en/sql-reference/table-functions/s3Cluster.md b/docs/en/sql-reference/table-functions/s3Cluster.md index 799eb31446a..080c9860519 100644 --- a/docs/en/sql-reference/table-functions/s3Cluster.md +++ b/docs/en/sql-reference/table-functions/s3Cluster.md @@ -10,14 +10,15 @@ Allows processing files from [Amazon S3](https://aws.amazon.com/s3/) and Google **Syntax** ``` sql -s3Cluster(cluster_name, source, [,access_key_id, secret_access_key] [,format] [,structure]) +s3Cluster(cluster_name, source, [,access_key_id, secret_access_key, [session_token]] [,format] [,structure]) ``` **Arguments** - `cluster_name` — Name of a cluster that is used to build a set of addresses and connection parameters to remote and local servers. - `source` — URL to a file or a bunch of files. Supports following wildcards in readonly mode: `*`, `**`, `?`, `{'abc','def'}` and `{N..M}` where `N`, `M` — numbers, `abc`, `def` — strings. For more information see [Wildcards In Path](../../engines/table-engines/integrations/s3.md#wildcards-in-path). -- `access_key_id` and `secret_access_key` — Keys that specify credentials to use with given endpoint. Optional. +- `access_key_id`, `secret_access_key` — Keys that specify credentials to use with given endpoint. Optional. +- `session_token` - Session token to use with the given keys. Optional when passing keys. - `format` — The [format](../../interfaces/formats.md#formats) of the file. - `structure` — Structure of the table. Format `'column1_name column1_type, column2_name column2_type, ...'`. diff --git a/docs/ru/sql-reference/table-functions/s3.md b/docs/ru/sql-reference/table-functions/s3.md index 7deef68f47f..fe40cb0c507 100644 --- a/docs/ru/sql-reference/table-functions/s3.md +++ b/docs/ru/sql-reference/table-functions/s3.md @@ -11,7 +11,7 @@ sidebar_label: s3 **Синтаксис** ``` sql -s3(path [,aws_access_key_id, aws_secret_access_key] [,format] [,structure] [,compression]) +s3(path [,access_key_id, secret_access_key [,session_token]] [,format] [,structure] [,compression]) ``` **Aргументы** diff --git a/docs/ru/sql-reference/table-functions/s3Cluster.md b/docs/ru/sql-reference/table-functions/s3Cluster.md index b8f34d805ff..b382bf5e384 100644 --- a/docs/ru/sql-reference/table-functions/s3Cluster.md +++ b/docs/ru/sql-reference/table-functions/s3Cluster.md @@ -11,14 +11,14 @@ sidebar_label: s3Cluster **Синтаксис** ``` sql -s3Cluster(cluster_name, source, [,access_key_id, secret_access_key] [,format] [,structure]) +s3Cluster(cluster_name, source, [,access_key_id, secret_access_key [,session_token]] [,format] [,structure]) ``` **Аргументы** - `cluster_name` — имя кластера, используемое для создания набора адресов и параметров подключения к удаленным и локальным серверам. - `source` — URL файла или нескольких файлов. Поддерживает следующие символы подстановки: `*`, `?`, `{'abc','def'}` и `{N..M}`, где `N`, `M` — числа, `abc`, `def` — строки. Подробнее смотрите в разделе [Символы подстановки](../../engines/table-engines/integrations/s3.md#wildcards-in-path). -- `access_key_id` и `secret_access_key` — ключи, указывающие на учетные данные для использования с точкой приема запроса. Необязательные параметры. +- `access_key_id`, `secret_access_key` и `session_token` — ключи, указывающие на учетные данные для использования с точкой приема запроса. Необязательные параметры. - `format` — [формат](../../interfaces/formats.md#formats) файла. - `structure` — структура таблицы. Формат `'column1_name column1_type, column2_name column2_type, ...'`. diff --git a/docs/zh/sql-reference/table-functions/s3.md b/docs/zh/sql-reference/table-functions/s3.md index a62fa9ebb19..f7384a7526e 100644 --- a/docs/zh/sql-reference/table-functions/s3.md +++ b/docs/zh/sql-reference/table-functions/s3.md @@ -11,7 +11,7 @@ sidebar_label: s3 **语法** ``` sql -s3(path, [aws_access_key_id, aws_secret_access_key,] format, structure, [compression]) +s3(path [,access_key_id, secret_access_key [,session_token]] ,format, structure, [compression]) ``` **参数** diff --git a/src/Storages/StorageS3.cpp b/src/Storages/StorageS3.cpp index e8f460525db..096e2e88f91 100644 --- a/src/Storages/StorageS3.cpp +++ b/src/Storages/StorageS3.cpp @@ -104,6 +104,7 @@ static const std::unordered_set optional_configuration_keys = "structure", "access_key_id", "secret_access_key", + "session_token", "filename", "use_environment_credentials", "max_single_read_retries", @@ -1521,11 +1522,14 @@ StorageS3::Configuration StorageS3::getConfiguration(ASTs & engine_args, Context /// S3('url', NOSIGN, 'format') /// S3('url', NOSIGN, 'format', 'compression') /// S3('url', 'aws_access_key_id', 'aws_secret_access_key') + /// S3('url', 'aws_access_key_id', 'aws_secret_access_key', 'session_token') /// S3('url', 'aws_access_key_id', 'aws_secret_access_key', 'format') + /// S3('url', 'aws_access_key_id', 'aws_secret_access_key', 'session_token', 'format') /// S3('url', 'aws_access_key_id', 'aws_secret_access_key', 'format', 'compression') + /// S3('url', 'aws_access_key_id', 'aws_secret_access_key', 'session_token', 'format', 'compression') /// with optional headers() function - if (engine_args.empty() || engine_args.size() > 5) + if (engine_args.empty() || engine_args.size() > 6) throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, "Storage S3 requires 1 to 5 arguments: " "url, [NOSIGN | access_key_id, secret_access_key], name of used format and [compression_method]"); @@ -1541,7 +1545,7 @@ StorageS3::Configuration StorageS3::getConfiguration(ASTs & engine_args, Context static std::unordered_map> size_to_engine_args { {1, {{}}}, - {5, {{"access_key_id", 1}, {"secret_access_key", 2}, {"format", 3}, {"compression_method", 4}}} + {6, {{"access_key_id", 1}, {"secret_access_key", 2}, {"session_token", 3}, {"format", 4}, {"compression_method", 5}}} }; std::unordered_map engine_args_to_idx; @@ -1577,7 +1581,8 @@ StorageS3::Configuration StorageS3::getConfiguration(ASTs & engine_args, Context else engine_args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}}; } - /// For 4 arguments we support 2 possible variants: + /// For 4 arguments we support 3 possible variants: + /// - s3(source, access_key_id, secret_access_key, session_token) /// - s3(source, access_key_id, secret_access_key, format) /// - s3(source, NOSIGN, format, compression_method) /// We can distinguish them by looking at the 2-nd argument: check if it's a NOSIGN or not. @@ -1590,7 +1595,32 @@ StorageS3::Configuration StorageS3::getConfiguration(ASTs & engine_args, Context engine_args_to_idx = {{"format", 2}, {"compression_method", 3}}; } else - engine_args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"format", 3}}; + { + auto fourth_arg = checkAndGetLiteralArgument(engine_args[3], "session_token/format"); + if (fourth_arg == "auto" || FormatFactory::instance().getAllFormats().contains(fourth_arg)) + { + engine_args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"format", 3}}; + } + else + { + engine_args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"session_token", 3}}; + } + } + } + /// For 5 arguments we support 2 possible variants: + /// - s3(source, access_key_id, secret_access_key, session_token, format) + /// - s3(source, access_key_id, secret_access_key, format, compression) + else if (engine_args.size() == 5) + { + auto fourth_arg = checkAndGetLiteralArgument(engine_args[3], "session_token/format"); + if (fourth_arg == "auto" || FormatFactory::instance().getAllFormats().contains(fourth_arg)) + { + engine_args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"format", 3}, {"compression", 4}}; + } + else + { + engine_args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"session_token", 3}, {"format", 4}}; + } } else { @@ -1612,6 +1642,10 @@ StorageS3::Configuration StorageS3::getConfiguration(ASTs & engine_args, Context if (engine_args_to_idx.contains("secret_access_key")) configuration.auth_settings.secret_access_key = checkAndGetLiteralArgument(engine_args[engine_args_to_idx["secret_access_key"]], "secret_access_key"); + if (engine_args_to_idx.contains("session_token")) + configuration.auth_settings.session_token = checkAndGetLiteralArgument(engine_args[engine_args_to_idx["session_token"]], "session_token"); + + configuration.auth_settings.no_sign_request = no_sign_request; } diff --git a/src/TableFunctions/TableFunctionS3.cpp b/src/TableFunctions/TableFunctionS3.cpp index e6ae75a5fd5..c52256fb984 100644 --- a/src/TableFunctions/TableFunctionS3.cpp +++ b/src/TableFunctions/TableFunctionS3.cpp @@ -71,7 +71,7 @@ void TableFunctionS3::parseArgumentsImpl(ASTs & args, const ContextPtr & context if (header_it != args.end()) args.erase(header_it); - if (args.empty() || args.size() > 6) + if (args.empty() || args.size() > 7) throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, "The signature of table function {} shall be the following:\n{}", getName(), getSignature()); for (auto & arg : args) @@ -81,7 +81,7 @@ void TableFunctionS3::parseArgumentsImpl(ASTs & args, const ContextPtr & context static std::unordered_map> size_to_args { {1, {{}}}, - {6, {{"access_key_id", 1}, {"secret_access_key", 2}, {"format", 3}, {"structure", 4}, {"compression_method", 5}}} + {7, {{"access_key_id", 1}, {"secret_access_key", 2}, {"session_token", 3}, {"format", 4}, {"structure", 5}, {"compression_method", 6}}} }; std::unordered_map args_to_idx; @@ -118,11 +118,12 @@ void TableFunctionS3::parseArgumentsImpl(ASTs & args, const ContextPtr & context else args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}}; } - /// For 4 arguments we support 3 possible variants: + /// For 4 arguments we support 4 possible variants: /// - s3(source, format, structure, compression_method), - /// - s3(source, access_key_id, access_key_id, format) + /// - s3(source, access_key_id, access_key_id, format), + /// - s3(source, access_key_id, access_key_id, session_token) /// - s3(source, NOSIGN, format, structure) - /// We can distinguish them by looking at the 2-nd argument: check if it's a format name or not. + /// We can distinguish them by looking at the 2-nd and 4-th argument: check if it's a format name or not. else if (args.size() == 4) { auto second_arg = checkAndGetLiteralArgument(args[1], "format/access_key_id/NOSIGN"); @@ -132,14 +133,28 @@ void TableFunctionS3::parseArgumentsImpl(ASTs & args, const ContextPtr & context args_to_idx = {{"format", 2}, {"structure", 3}}; } else if (second_arg == "auto" || FormatFactory::instance().getAllFormats().contains(second_arg)) + { args_to_idx = {{"format", 1}, {"structure", 2}, {"compression_method", 3}}; + } else - args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"format", 3}}; + { + auto fourth_arg = checkAndGetLiteralArgument(args[3], "format/session_token"); + if (fourth_arg == "auto" || FormatFactory::instance().getAllFormats().contains(fourth_arg)) + { + args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"format", 3}}; + } + else + { + args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"session_token", 3}}; + } + } } - /// For 5 arguments we support 2 possible variants: + /// For 5 arguments we support 3 possible variants: /// - s3(source, access_key_id, access_key_id, format, structure) + /// - s3(source, access_key_id, access_key_id, session_token, format) /// - s3(source, NOSIGN, format, structure, compression_method) - /// We can distinguish them by looking at the 2-nd argument: check if it's a NOSIGN keyword name or not. + /// We can distinguish them by looking at the 2-nd argument: check if it's a NOSIGN keyword name or no, + /// and by the 4-th argument, check if it's a format name or not else if (args.size() == 5) { auto second_arg = checkAndGetLiteralArgument(args[1], "NOSIGN/access_key_id"); @@ -149,7 +164,33 @@ void TableFunctionS3::parseArgumentsImpl(ASTs & args, const ContextPtr & context args_to_idx = {{"format", 2}, {"structure", 3}, {"compression_method", 4}}; } else - args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"format", 3}, {"structure", 4}}; + { + auto fourth_arg = checkAndGetLiteralArgument(args[3], "format/session_token"); + if (fourth_arg == "auto" || FormatFactory::instance().getAllFormats().contains(fourth_arg)) + { + args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"format", 3}, {"structure", 4}}; + } + else + { + args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"session_token", 3}, {"format", 4}}; + } + } + } + // For 6 arguments we support 2 possible variants: + /// - s3(source, access_key_id, access_key_id, format, structure, compression_method) + /// - s3(source, access_key_id, access_key_id, session_token, format, structure) + /// We can distinguish them by looking at the 4-th argument: check if it's a format name or not + else if (args.size() == 6) + { + auto fourth_arg = checkAndGetLiteralArgument(args[3], "format/session_token"); + if (fourth_arg == "auto" || FormatFactory::instance().getAllFormats().contains(fourth_arg)) + { + args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"format", 3}, {"structure", 4}, {"compression_method", 5}}; + } + else + { + args_to_idx = {{"access_key_id", 1}, {"secret_access_key", 2}, {"session_token", 3}, {"format", 4}, {"structure", 5}}; + } } else { @@ -181,6 +222,9 @@ void TableFunctionS3::parseArgumentsImpl(ASTs & args, const ContextPtr & context if (args_to_idx.contains("secret_access_key")) configuration.auth_settings.secret_access_key = checkAndGetLiteralArgument(args[args_to_idx["secret_access_key"]], "secret_access_key"); + if (args_to_idx.contains("session_token")) + configuration.auth_settings.session_token = checkAndGetLiteralArgument(args[args_to_idx["session_token"]], "session_token"); + configuration.auth_settings.no_sign_request = no_sign_request; if (configuration.format == "auto") diff --git a/src/TableFunctions/TableFunctionS3.h b/src/TableFunctions/TableFunctionS3.h index fc384176007..fa73c1d313e 100644 --- a/src/TableFunctions/TableFunctionS3.h +++ b/src/TableFunctions/TableFunctionS3.h @@ -22,11 +22,15 @@ public: static constexpr auto signature = " - url\n" " - url, format\n" " - url, format, structure\n" - " - url, access_key_id, secret_access_key\n" " - url, format, structure, compression_method\n" + " - url, access_key_id, secret_access_key\n" + " - url, access_key_id, secret_access_key, session_token\n" " - url, access_key_id, secret_access_key, format\n" + " - url, access_key_id, secret_access_key, session_token, format\n" " - url, access_key_id, secret_access_key, format, structure\n" + " - url, access_key_id, secret_access_key, session_token, format, structure\n" " - url, access_key_id, secret_access_key, format, structure, compression_method\n" + " - url, access_key_id, secret_access_key, session_token, format, structure, compression_method\n" "All signatures supports optional headers (specified as `headers('name'='value', 'name2'='value2')`)"; static size_t getMaxNumberOfArguments() { return 6; } diff --git a/src/TableFunctions/TableFunctionS3Cluster.h b/src/TableFunctions/TableFunctionS3Cluster.h index 4fe25079cf4..718b0d90de8 100644 --- a/src/TableFunctions/TableFunctionS3Cluster.h +++ b/src/TableFunctions/TableFunctionS3Cluster.h @@ -35,6 +35,7 @@ public: " - cluster, url, access_key_id, secret_access_key, format\n" " - cluster, url, access_key_id, secret_access_key, format, structure\n" " - cluster, url, access_key_id, secret_access_key, format, structure, compression_method\n" + " - cluster, url, access_key_id, secret_access_key, session_token, format, structure, compression_method\n" "All signatures supports optional headers (specified as `headers('name'='value', 'name2'='value2')`)"; String getName() const override From a077ad4c15936c2adae1397b4d9b8c3fabc30277 Mon Sep 17 00:00:00 2001 From: ubuntu <872237106@qq.com> Date: Tue, 19 Dec 2023 15:18:27 +0800 Subject: [PATCH 098/253] support new analyzer --- src/Analyzer/Passes/QueryAnalysisPass.cpp | 35 +++++++++++++++ src/Analyzer/QueryNode.h | 13 ++++++ src/Analyzer/QueryTreeBuilder.cpp | 1 + .../0_stateless/02943_order_by_all.reference | 45 +++++++++++++++++++ .../0_stateless/02943_order_by_all.sql | 35 +++++++++++++++ 5 files changed, 129 insertions(+) diff --git a/src/Analyzer/Passes/QueryAnalysisPass.cpp b/src/Analyzer/Passes/QueryAnalysisPass.cpp index 1e63d5ca8e4..4dd43984e23 100644 --- a/src/Analyzer/Passes/QueryAnalysisPass.cpp +++ b/src/Analyzer/Passes/QueryAnalysisPass.cpp @@ -119,6 +119,7 @@ namespace ErrorCodes extern const int NUMBER_OF_COLUMNS_DOESNT_MATCH; extern const int FUNCTION_CANNOT_HAVE_PARAMETERS; extern const int SYNTAX_ERROR; + extern const int UNEXPECTED_EXPRESSION; } /** Query analyzer implementation overview. Please check documentation in QueryAnalysisPass.h first. @@ -1209,6 +1210,8 @@ private: static void expandGroupByAll(QueryNode & query_tree_node_typed); + static void expandOrderByAll(QueryNode & query_tree_node_typed); + static std::string rewriteAggregateFunctionNameIfNeeded(const std::string & aggregate_function_name, NullsAction action, const ContextPtr & context); @@ -2312,6 +2315,35 @@ void QueryAnalyzer::expandGroupByAll(QueryNode & query_tree_node_typed) recursivelyCollectMaxOrdinaryExpressions(node, group_by_nodes); } +void QueryAnalyzer::expandOrderByAll(QueryNode & query_tree_node_typed) +{ + auto * all_node = query_tree_node_typed.getOrderBy().getNodes()[0]->as(); + if (!all_node) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Select analyze for not sort node."); + + auto & projection_nodes = query_tree_node_typed.getProjection().getNodes(); + auto list_node = std::make_shared(); + list_node->getNodes().reserve(projection_nodes.size()); + + for (auto & node : projection_nodes) + { + if (auto * identifier_node = node->as(); identifier_node != nullptr) + if (Poco::toUpper(identifier_node->getIdentifier().getFullName()) == "ALL" || Poco::toUpper(identifier_node->getAlias()) == "ALL") + throw Exception(ErrorCodes::UNEXPECTED_EXPRESSION, + "Cannot use ORDER BY ALL to sort a column with name 'all', please disable setting `enable_order_by_all` and try again"); + + if (auto * function_node = node->as(); function_node != nullptr) + if (Poco::toUpper(function_node->getAlias()) == "ALL") + throw Exception(ErrorCodes::UNEXPECTED_EXPRESSION, + "Cannot use ORDER BY ALL to sort a column with name 'all', please disable setting `enable_order_by_all` and try again"); + + auto sort_node = std::make_shared(node, all_node->getSortDirection(), all_node->getNullsSortDirection()); + list_node->getNodes().push_back(sort_node); + } + + query_tree_node_typed.getOrderByNode() = list_node; +} + std::string QueryAnalyzer::rewriteAggregateFunctionNameIfNeeded( const std::string & aggregate_function_name, NullsAction action, const ContextPtr & context) { @@ -6975,6 +7007,9 @@ void QueryAnalyzer::resolveQuery(const QueryTreeNodePtr & query_node, Identifier if (query_node_typed.hasHaving() && query_node_typed.isGroupByWithTotals() && is_rollup_or_cube) throw Exception(ErrorCodes::NOT_IMPLEMENTED, "WITH TOTALS and WITH ROLLUP or CUBE are not supported together in presence of HAVING"); + if (settings.enable_order_by_all && query_node_typed.isOrderByAll()) + expandOrderByAll(query_node_typed); + /// Initialize aliases in query node scope QueryExpressionsAliasVisitor visitor(scope); diff --git a/src/Analyzer/QueryNode.h b/src/Analyzer/QueryNode.h index 82bc72b7411..d8b8741afb2 100644 --- a/src/Analyzer/QueryNode.h +++ b/src/Analyzer/QueryNode.h @@ -219,6 +219,18 @@ public: is_group_by_all = is_group_by_all_value; } + /// Returns true, if query node has ORDER BY ALL modifier, false otherwise + bool isOrderByAll() const + { + return is_order_by_all; + } + + /// Set query node ORDER BY ALL modifier value + void setIsOrderByAll(bool is_order_by_all_value) + { + is_order_by_all = is_order_by_all_value; + } + /// Returns true if query node WITH section is not empty, false otherwise bool hasWith() const { @@ -590,6 +602,7 @@ private: bool is_group_by_with_cube = false; bool is_group_by_with_grouping_sets = false; bool is_group_by_all = false; + bool is_order_by_all = false; std::string cte_name; NamesAndTypes projection_columns; diff --git a/src/Analyzer/QueryTreeBuilder.cpp b/src/Analyzer/QueryTreeBuilder.cpp index c541888e5b9..4e2d0ad10a8 100644 --- a/src/Analyzer/QueryTreeBuilder.cpp +++ b/src/Analyzer/QueryTreeBuilder.cpp @@ -284,6 +284,7 @@ QueryTreeNodePtr QueryTreeBuilder::buildSelectExpression(const ASTPtr & select_q current_query_tree->setIsGroupByWithRollup(select_query_typed.group_by_with_rollup); current_query_tree->setIsGroupByWithGroupingSets(select_query_typed.group_by_with_grouping_sets); current_query_tree->setIsGroupByAll(select_query_typed.group_by_all); + current_query_tree->setIsOrderByAll(select_query_typed.order_by_all); current_query_tree->setOriginalAST(select_query); auto current_context = current_query_tree->getContext(); diff --git a/tests/queries/0_stateless/02943_order_by_all.reference b/tests/queries/0_stateless/02943_order_by_all.reference index f60c7976ae6..b4c6229ff93 100644 --- a/tests/queries/0_stateless/02943_order_by_all.reference +++ b/tests/queries/0_stateless/02943_order_by_all.reference @@ -42,3 +42,48 @@ B 3 10 D 1 20 A 2 30 C \N 40 +-- enable new analyzer +-- no modifiers +A 2 +B 3 +C \N +D 1 +1 D +2 A +3 B +\N C +-- with ASC/DESC modifiers +A 2 +B 3 +C \N +D 1 +D 1 +C \N +B 3 +A 2 +-- with NULLS FIRST/LAST modifiers +\N C +1 D +2 A +3 B +1 D +2 A +3 B +\N C +-- what happens if some column "all" already exists? +B 3 10 +D 1 20 +A 2 30 +C \N 40 +D 1 +A 2 +B 3 +C \N +A 2 +B 3 +D 1 +\N +B 3 10 +D 1 20 +A 2 30 +C \N 40 diff --git a/tests/queries/0_stateless/02943_order_by_all.sql b/tests/queries/0_stateless/02943_order_by_all.sql index c1e358178d5..876365300ea 100644 --- a/tests/queries/0_stateless/02943_order_by_all.sql +++ b/tests/queries/0_stateless/02943_order_by_all.sql @@ -43,4 +43,39 @@ SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY all SETTINGS enab SELECT a, b, all FROM order_by_all ORDER BY all, a; +SELECT '-- enable new analyzer'; +set allow_experimental_analyzer = 1; + +SELECT '-- no modifiers'; +SELECT a, b FROM order_by_all ORDER BY ALL; +SELECT b, a FROM order_by_all ORDER BY ALL; + +SELECT '-- with ASC/DESC modifiers'; +SELECT a, b FROM order_by_all ORDER BY ALL ASC; +SELECT a, b FROM order_by_all ORDER BY ALL DESC; + +SELECT '-- with NULLS FIRST/LAST modifiers'; +SELECT b, a FROM order_by_all ORDER BY ALL NULLS FIRST; +SELECT b, a FROM order_by_all ORDER BY ALL NULLS LAST; + +SELECT '-- what happens if some column "all" already exists?'; + +-- columns +SELECT a, b, all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b, all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b, all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; + +-- column aliases +SELECT a, b AS all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b AS all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b AS all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; + +-- expressions +SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } +SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } +SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; + +SELECT a, b, all FROM order_by_all ORDER BY all, a; + DROP TABLE order_by_all; + From 1d9c0db1f6feadbfbd96a818b99927d00943ad98 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Tue, 19 Dec 2023 08:00:59 +0000 Subject: [PATCH 099/253] Incorporate review feedback --- src/Functions/punycode.cpp | 74 ++++++-------------- tests/queries/0_stateless/02932_punycode.sql | 2 +- 2 files changed, 21 insertions(+), 55 deletions(-) diff --git a/src/Functions/punycode.cpp b/src/Functions/punycode.cpp index e90cba82b1f..c11409f0d1a 100644 --- a/src/Functions/punycode.cpp +++ b/src/Functions/punycode.cpp @@ -33,12 +33,10 @@ struct PunycodeEncodeImpl ColumnString::Offsets & res_offsets) { const size_t rows = offsets.size(); - res_data.resize(rows * 64); /// just a guess - res_offsets.resize(rows); + res_data.reserve(data.size()); /// just a guess, assuming the input is all-ASCII + res_offsets.reserve(rows); size_t prev_offset = 0; - size_t prev_res_offset = 0; - size_t res_data_bytes_written = 0; std::u32string value_utf32; std::string value_puny; for (size_t row = 0; row < rows; ++row) @@ -46,37 +44,22 @@ struct PunycodeEncodeImpl const char * value = reinterpret_cast(&data[prev_offset]); const size_t value_length = offsets[row] - prev_offset - 1; - size_t value_utf32_length = ada::idna::utf32_length_from_utf8(value, value_length); - value_utf32.resize(value_utf32_length, '\0'); - + const size_t value_utf32_length = ada::idna::utf32_length_from_utf8(value, value_length); + value_utf32.resize(value_utf32_length); ada::idna::utf8_to_utf32(value, value_length, value_utf32.data()); - bool ok = ada::idna::utf32_to_punycode(value_utf32, value_puny); + const bool ok = ada::idna::utf32_to_punycode(value_utf32, value_puny); if (!ok) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Internal error during Punycode encoding"); - const size_t bytes_to_write = value_puny.size() + 1; - if (res_data_bytes_written + bytes_to_write > res_data.size()) - { - size_t new_size = std::max(res_data.size() * 2, res_data_bytes_written + bytes_to_write); - res_data.resize(new_size); - } - - std::memcpy(&res_data[res_data_bytes_written], value_puny.data(), value_puny.size()); - res_data_bytes_written += value_puny.size(); - - res_data[res_data_bytes_written] = '\0'; - res_data_bytes_written += 1; - - res_offsets[row] = prev_res_offset + bytes_to_write; + res_data.insert(value_puny.c_str(), value_puny.c_str() + value_puny.size() + 1); + res_offsets.push_back(res_data.size()); prev_offset = offsets[row]; - prev_res_offset = res_offsets[row]; - value_utf32.clear(); - value_puny.clear(); - } - res_data.resize(res_data_bytes_written); + value_utf32.clear(); + value_puny.clear(); /// utf32_to_punycode() appends to its output string + } } [[noreturn]] static void vectorFixed(const ColumnString::Chars &, size_t, ColumnString::Chars &) @@ -94,12 +77,10 @@ struct PunycodeDecodeImpl ColumnString::Offsets & res_offsets) { const size_t rows = offsets.size(); - res_data.resize(rows * 64); /// just a guess - res_offsets.resize(rows); + res_data.reserve(data.size()); /// just a guess, assuming the input is all-ASCII + res_offsets.reserve(rows); size_t prev_offset = 0; - size_t prev_res_offset = 0; - size_t res_data_bytes_written = 0; std::u32string value_utf32; std::string value_utf8; for (size_t row = 0; row < rows; ++row) @@ -107,38 +88,23 @@ struct PunycodeDecodeImpl const char * value = reinterpret_cast(&data[prev_offset]); const size_t value_length = offsets[row] - prev_offset - 1; - std::string_view value_punycode(value, value_length); - bool ok = ada::idna::punycode_to_utf32(value_punycode, value_utf32); + const std::string_view value_punycode(value, value_length); + const bool ok = ada::idna::punycode_to_utf32(value_punycode, value_utf32); if (!ok) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Internal error during Punycode decoding"); - size_t utf8_length = ada::idna::utf8_length_from_utf32(value_utf32.data(), value_utf32.size()); - value_utf8.resize(utf8_length, '\0'); - + const size_t utf8_length = ada::idna::utf8_length_from_utf32(value_utf32.data(), value_utf32.size()); + value_utf8.resize(utf8_length); ada::idna::utf32_to_utf8(value_utf32.data(), value_utf32.size(), value_utf8.data()); - const size_t bytes_to_write = value_utf8.size() + 1; - if (res_data_bytes_written + bytes_to_write > res_data.size()) - { - size_t new_size = std::max(res_data.size() * 2, res_data_bytes_written + bytes_to_write); - res_data.resize(new_size); - } - - std::memcpy(&res_data[res_data_bytes_written], value_utf8.data(), value_utf8.size()); - res_data_bytes_written += value_utf8.size(); - - res_data[res_data_bytes_written] = '\0'; - res_data_bytes_written += 1; - - res_offsets[row] = prev_res_offset + bytes_to_write; + res_data.insert(value_utf8.c_str(), value_utf8.c_str() + value_utf8.size() + 1); + res_offsets.push_back(res_data.size()); prev_offset = offsets[row]; - prev_res_offset = res_offsets[row]; - value_utf32.clear(); + + value_utf32.clear(); /// punycode_to_utf32() appends to its output string value_utf8.clear(); } - - res_data.resize(res_data_bytes_written); } [[noreturn]] static void vectorFixed(const ColumnString::Chars &, size_t, ColumnString::Chars &) diff --git a/tests/queries/0_stateless/02932_punycode.sql b/tests/queries/0_stateless/02932_punycode.sql index fd128507a8f..dd18a43ecc9 100644 --- a/tests/queries/0_stateless/02932_punycode.sql +++ b/tests/queries/0_stateless/02932_punycode.sql @@ -14,7 +14,7 @@ SELECT punycodeEncode(toFixedString('two', 3)); -- { serverError ILLEGAL_COLUMN SELECT '-- Regular cases'; --- The test cases originate from the idna unit tests: +-- The test cases originate from the ada idna unit tests: --- https://github.com/ada-url/idna/blob/8cd03ef867dbd06be87bd61df9cf69aa1182ea21/tests/fixtures/utf8_punycode_alternating.txt SELECT 'a' AS str, punycodeEncode(str) AS puny, punycodeDecode(puny) AS original; From a8845ba729224ab10e21349a416ec5f3b5d84eab Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Tue, 19 Dec 2023 08:13:17 +0000 Subject: [PATCH 100/253] Reorganize the test (a bit) + whitespace fixes --- src/Analyzer/Passes/QueryAnalysisPass.cpp | 4 +- .../0_stateless/02943_order_by_all.reference | 69 ++++++++--------- .../0_stateless/02943_order_by_all.sql | 74 ++++++++++--------- 3 files changed, 75 insertions(+), 72 deletions(-) diff --git a/src/Analyzer/Passes/QueryAnalysisPass.cpp b/src/Analyzer/Passes/QueryAnalysisPass.cpp index 4dd43984e23..3290d918a8b 100644 --- a/src/Analyzer/Passes/QueryAnalysisPass.cpp +++ b/src/Analyzer/Passes/QueryAnalysisPass.cpp @@ -2330,7 +2330,7 @@ void QueryAnalyzer::expandOrderByAll(QueryNode & query_tree_node_typed) if (auto * identifier_node = node->as(); identifier_node != nullptr) if (Poco::toUpper(identifier_node->getIdentifier().getFullName()) == "ALL" || Poco::toUpper(identifier_node->getAlias()) == "ALL") throw Exception(ErrorCodes::UNEXPECTED_EXPRESSION, - "Cannot use ORDER BY ALL to sort a column with name 'all', please disable setting `enable_order_by_all` and try again"); + "Cannot use ORDER BY ALL to sort a column with name 'all', please disable setting `enable_order_by_all` and try again"); if (auto * function_node = node->as(); function_node != nullptr) if (Poco::toUpper(function_node->getAlias()) == "ALL") @@ -7008,7 +7008,7 @@ void QueryAnalyzer::resolveQuery(const QueryTreeNodePtr & query_node, Identifier throw Exception(ErrorCodes::NOT_IMPLEMENTED, "WITH TOTALS and WITH ROLLUP or CUBE are not supported together in presence of HAVING"); if (settings.enable_order_by_all && query_node_typed.isOrderByAll()) - expandOrderByAll(query_node_typed); + expandOrderByAll(query_node_typed); /// Initialize aliases in query node scope QueryExpressionsAliasVisitor visitor(scope); diff --git a/tests/queries/0_stateless/02943_order_by_all.reference b/tests/queries/0_stateless/02943_order_by_all.reference index b4c6229ff93..48d828b6924 100644 --- a/tests/queries/0_stateless/02943_order_by_all.reference +++ b/tests/queries/0_stateless/02943_order_by_all.reference @@ -7,6 +7,14 @@ D 1 2 A 3 B \N C +A 2 +B 3 +C \N +D 1 +1 D +2 A +3 B +\N C -- with ASC/DESC modifiers A 2 B 3 @@ -16,43 +24,6 @@ D 1 C \N B 3 A 2 --- with NULLS FIRST/LAST modifiers -\N C -1 D -2 A -3 B -1 D -2 A -3 B -\N C --- what happens if some column "all" already exists? -B 3 10 -D 1 20 -A 2 30 -C \N 40 -D 1 -A 2 -B 3 -C \N -A 2 -B 3 -D 1 -\N -B 3 10 -D 1 20 -A 2 30 -C \N 40 --- enable new analyzer --- no modifiers -A 2 -B 3 -C \N -D 1 -1 D -2 A -3 B -\N C --- with ASC/DESC modifiers A 2 B 3 C \N @@ -70,15 +41,35 @@ A 2 2 A 3 B \N C +\N C +1 D +2 A +3 B +1 D +2 A +3 B +\N C -- what happens if some column "all" already exists? B 3 10 D 1 20 A 2 30 C \N 40 +B 3 10 +D 1 20 +A 2 30 +C \N 40 D 1 A 2 B 3 C \N +D 1 +A 2 +B 3 +C \N +A 2 +B 3 +D 1 +\N A 2 B 3 D 1 @@ -87,3 +78,7 @@ B 3 10 D 1 20 A 2 30 C \N 40 +B 3 10 +D 1 20 +A 2 30 +C \N 40 diff --git a/tests/queries/0_stateless/02943_order_by_all.sql b/tests/queries/0_stateless/02943_order_by_all.sql index 876365300ea..0756563946c 100644 --- a/tests/queries/0_stateless/02943_order_by_all.sql +++ b/tests/queries/0_stateless/02943_order_by_all.sql @@ -13,69 +13,77 @@ ENGINE = Memory; INSERT INTO order_by_all VALUES ('B', 3, 10), ('C', NULL, 40), ('D', 1, 20), ('A', 2, 30); SELECT '-- no modifiers'; + +SET allow_experimental_analyzer = 0; +SELECT a, b FROM order_by_all ORDER BY ALL; +SELECT b, a FROM order_by_all ORDER BY ALL; + +SET allow_experimental_analyzer = 1; SELECT a, b FROM order_by_all ORDER BY ALL; SELECT b, a FROM order_by_all ORDER BY ALL; SELECT '-- with ASC/DESC modifiers'; + +SET allow_experimental_analyzer = 0; +SELECT a, b FROM order_by_all ORDER BY ALL ASC; +SELECT a, b FROM order_by_all ORDER BY ALL DESC; + +SET allow_experimental_analyzer = 1; SELECT a, b FROM order_by_all ORDER BY ALL ASC; SELECT a, b FROM order_by_all ORDER BY ALL DESC; SELECT '-- with NULLS FIRST/LAST modifiers'; + +SET allow_experimental_analyzer = 0; +SELECT b, a FROM order_by_all ORDER BY ALL NULLS FIRST; +SELECT b, a FROM order_by_all ORDER BY ALL NULLS LAST; + +SET allow_experimental_analyzer = 1; SELECT b, a FROM order_by_all ORDER BY ALL NULLS FIRST; SELECT b, a FROM order_by_all ORDER BY ALL NULLS LAST; SELECT '-- what happens if some column "all" already exists?'; -- columns + +SET allow_experimental_analyzer = 0; +SELECT a, b, all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b, all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b, all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; + +SET allow_experimental_analyzer = 1; SELECT a, b, all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } SELECT a, b, all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } SELECT a, b, all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; -- column aliases + +SET allow_experimental_analyzer = 0; +SELECT a, b AS all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b AS all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } +SELECT a, b AS all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; + +SET allow_experimental_analyzer = 1; SELECT a, b AS all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } SELECT a, b AS all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } SELECT a, b AS all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; -- expressions + +SET allow_experimental_analyzer = 0; SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; +SET allow_experimental_analyzer = 1; +SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } +SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } +SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; + +SET allow_experimental_analyzer = 0; SELECT a, b, all FROM order_by_all ORDER BY all, a; -SELECT '-- enable new analyzer'; -set allow_experimental_analyzer = 1; - -SELECT '-- no modifiers'; -SELECT a, b FROM order_by_all ORDER BY ALL; -SELECT b, a FROM order_by_all ORDER BY ALL; - -SELECT '-- with ASC/DESC modifiers'; -SELECT a, b FROM order_by_all ORDER BY ALL ASC; -SELECT a, b FROM order_by_all ORDER BY ALL DESC; - -SELECT '-- with NULLS FIRST/LAST modifiers'; -SELECT b, a FROM order_by_all ORDER BY ALL NULLS FIRST; -SELECT b, a FROM order_by_all ORDER BY ALL NULLS LAST; - -SELECT '-- what happens if some column "all" already exists?'; - --- columns -SELECT a, b, all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } -SELECT a, b, all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } -SELECT a, b, all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; - --- column aliases -SELECT a, b AS all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } -SELECT a, b AS all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } -SELECT a, b AS all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; - --- expressions -SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY all; -- { serverError UNEXPECTED_EXPRESSION } -SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY ALL; -- { serverError UNEXPECTED_EXPRESSION } -SELECT format('{} {}', a, b) AS all FROM order_by_all ORDER BY all SETTINGS enable_order_by_all = false; - +SET allow_experimental_analyzer = 1; SELECT a, b, all FROM order_by_all ORDER BY all, a; DROP TABLE order_by_all; - From 59b049ce0814130b5b674cc046322d94e6304bb1 Mon Sep 17 00:00:00 2001 From: zhanglistar Date: Tue, 19 Dec 2023 16:40:13 +0800 Subject: [PATCH 101/253] function if branch free fix tests. --- src/Functions/if.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Functions/if.cpp b/src/Functions/if.cpp index bfcfd5e9ad2..c55f9a92ccc 100644 --- a/src/Functions/if.cpp +++ b/src/Functions/if.cpp @@ -57,10 +57,10 @@ inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const Arr size_t a_index = 0, b_index = 0; for (size_t i = 0; i < size; ++i) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_integral_v) { - res[i] = cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b[b_index]); - a_index += cond[i]; + res[i] = !!cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b[b_index]); + a_index += !!cond[i]; b_index += !cond[i]; } else @@ -71,10 +71,10 @@ inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const Arr { size_t a_index = 0; for (size_t i = 0; i < size; ++i) - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_integral_v) { - res[i] = cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b[i]); - a_index += cond[i]; + res[i] = !!cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b[i]); + a_index += !!cond[i]; } else res[i] = cond[i] ? static_cast(a[a_index++]) : static_cast(b[i]); @@ -83,9 +83,9 @@ inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const Arr { size_t b_index = 0; for (size_t i = 0; i < size; ++i) - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_integral_v) { - res[i] = cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b[b_index]); + res[i] = !!cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b[b_index]); b_index += !cond[i]; } else @@ -94,8 +94,8 @@ inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const Arr else { for (size_t i = 0; i < size; ++i) - if constexpr (std::is_arithmetic_v) - res[i] = cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b[i]); + if constexpr (std::is_integral_v) + res[i] = !!cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b[i]); else res[i] = cond[i] ? static_cast(a[i]) : static_cast(b[i]); } @@ -111,10 +111,10 @@ inline void fillVectorConstant(const ArrayCond & cond, const ArrayA & a, B b, Ar { size_t a_index = 0; for (size_t i = 0; i < size; ++i) - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_integral_v) { - res[i] = cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b); - a_index += cond[i]; + res[i] = !!cond[i] * static_cast(a[a_index]) + (!cond[i]) * static_cast(b); + a_index += !!cond[i]; } else res[i] = cond[i] ? static_cast(a[a_index++]) : static_cast(b); @@ -122,8 +122,8 @@ inline void fillVectorConstant(const ArrayCond & cond, const ArrayA & a, B b, Ar else { for (size_t i = 0; i < size; ++i) - if constexpr (std::is_arithmetic_v) - res[i] = cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b); + if constexpr (std::is_integral_v) + res[i] = !!cond[i] * static_cast(a[i]) + (!cond[i]) * static_cast(b); else res[i] = cond[i] ? static_cast(a[i]) : static_cast(b); } @@ -139,9 +139,9 @@ inline void fillConstantVector(const ArrayCond & cond, A a, const ArrayB & b, Ar { size_t b_index = 0; for (size_t i = 0; i < size; ++i) - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_integral_v) { - res[i] = cond[i] * static_cast(a) + (!cond[i]) * static_cast(b[b_index]); + res[i] = !!cond[i] * static_cast(a) + (!cond[i]) * static_cast(b[b_index]); b_index += !cond[i]; } else @@ -150,8 +150,8 @@ inline void fillConstantVector(const ArrayCond & cond, A a, const ArrayB & b, Ar else { for (size_t i = 0; i < size; ++i) - if constexpr (std::is_arithmetic_v) - res[i] = cond[i] * static_cast(a) + (!cond[i]) * static_cast(b[i]); + if constexpr (std::is_integral_v) + res[i] = !!cond[i] * static_cast(a) + (!cond[i]) * static_cast(b[i]); else res[i] = cond[i] ? static_cast(a) : static_cast(b[i]); } From 5f38e1d94433d8589b83205d2983d007678d50d4 Mon Sep 17 00:00:00 2001 From: Shani Elharrar Date: Mon, 18 Dec 2023 16:31:01 +0200 Subject: [PATCH 102/253] S3 Session Tokens: Added tests --- tests/integration/test_storage_s3/test.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_storage_s3/test.py b/tests/integration/test_storage_s3/test.py index 16183733656..2549cb0d473 100644 --- a/tests/integration/test_storage_s3/test.py +++ b/tests/integration/test_storage_s3/test.py @@ -626,7 +626,7 @@ def test_wrong_s3_syntax(started_cluster): instance = started_cluster.instances["dummy"] # type: ClickHouseInstance expected_err_msg = "Code: 42" # NUMBER_OF_ARGUMENTS_DOESNT_MATCH - query = "create table test_table_s3_syntax (id UInt32) ENGINE = S3('', '', '', '', '', '')" + query = "create table test_table_s3_syntax (id UInt32) ENGINE = S3('', '', '', '', '', '', '')" assert expected_err_msg in instance.query_and_get_error(query) expected_err_msg = "Code: 36" # BAD_ARGUMENTS @@ -1395,6 +1395,7 @@ def test_schema_inference_from_globs(started_cluster): def test_signatures(started_cluster): + session_token = "session token that will not be checked by MiniIO" bucket = started_cluster.minio_bucket instance = started_cluster.instances["dummy"] @@ -1417,6 +1418,11 @@ def test_signatures(started_cluster): ) assert int(result) == 1 + result = instance.query( + f"select * from s3('http://{started_cluster.minio_host}:{started_cluster.minio_port}/{bucket}/test.arrow', 'minio', 'minio123', '{session_token}')" + ) + assert int(result) == 1 + result = instance.query( f"select * from s3('http://{started_cluster.minio_host}:{started_cluster.minio_port}/{bucket}/test.arrow', 'Arrow', 'x UInt64', 'auto')" ) @@ -1427,6 +1433,21 @@ def test_signatures(started_cluster): ) assert int(result) == 1 + result = instance.query( + f"select * from s3('http://{started_cluster.minio_host}:{started_cluster.minio_port}/{bucket}/test.arrow', 'minio', 'minio123', '{session_token}', 'Arrow')" + ) + assert int(result) == 1 + + lt = instance.query( + f"select * from s3('http://{started_cluster.minio_host}:{started_cluster.minio_port}/{bucket}/test.arrow', 'minio', 'minio123', '{session_token}', 'Arrow', 'x UInt64')" + ) + assert int(result) == 1 + + lt = instance.query( + f"select * from s3('http://{started_cluster.minio_host}:{started_cluster.minio_port}/{bucket}/test.arrow', 'minio', 'minio123', '{session_token}', 'Arrow', 'x UInt64', 'auto')" + ) + assert int(result) == 1 + def test_select_columns(started_cluster): bucket = started_cluster.minio_bucket From 06a2e86983fe46c203d20878f23b8e86d3f3e41c Mon Sep 17 00:00:00 2001 From: kssenii Date: Tue, 19 Dec 2023 10:02:45 +0100 Subject: [PATCH 103/253] Fix test --- ...lly_change_filesystem_cache_size.reference | 2 +- ...ynamically_change_filesystem_cache_size.sh | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference index cd155b6ca29..8620171cb99 100644 --- a/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference +++ b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.reference @@ -1,4 +1,4 @@ -100 10 10 10 0 0 98 10 /var/lib/clickhouse/filesystem_caches/s3_cache_02944/ 5 5000 0 1 +100 10 10 10 0 0 0 0 /var/lib/clickhouse/filesystem_caches/s3_cache_02944/ 5 5000 0 1 0 10 98 diff --git a/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh index e47e13a7e40..2e344a6b6e5 100755 --- a/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh +++ b/tests/queries/0_stateless/02944_dynamically_change_filesystem_cache_size.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-fasttest, no-parallel, no-s3-storage +# Tags: no-fasttest, no-parallel, no-s3-storage, no-random-settings CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh @@ -7,6 +7,7 @@ CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) disk_name="s3_cache_02944" +$CLICKHOUSE_CLIENT --query "SYSTEM DROP FILESYSTEM CACHE" $CLICKHOUSE_CLIENT --query "DESCRIBE FILESYSTEM CACHE '${disk_name}'" $CLICKHOUSE_CLIENT -nm --query " @@ -32,7 +33,9 @@ cat $config_path \ > $config_path_tmp mv $config_path_tmp $config_path -$CLICKHOUSE_CLIENT -nm --query "SYSTEM RELOAD CONFIG" +$CLICKHOUSE_CLIENT -nm --query " +set send_logs_level='fatal'; +SYSTEM RELOAD CONFIG" $CLICKHOUSE_CLIENT --query "DESCRIBE FILESYSTEM CACHE '${disk_name}'" $CLICKHOUSE_CLIENT --query "SELECT count() FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" @@ -44,7 +47,9 @@ cat $config_path \ > $config_path_tmp mv $config_path_tmp $config_path -$CLICKHOUSE_CLIENT -nm --query "SYSTEM RELOAD CONFIG" +$CLICKHOUSE_CLIENT -nm --query " +set send_logs_level='fatal'; +SYSTEM RELOAD CONFIG" $CLICKHOUSE_CLIENT --query "DESCRIBE FILESYSTEM CACHE '${disk_name}'" $CLICKHOUSE_CLIENT --query "SELECT * FROM test FORMAT Null" @@ -58,7 +63,9 @@ cat $config_path \ > $config_path_tmp mv $config_path_tmp $config_path -$CLICKHOUSE_CLIENT -nm --query "SYSTEM RELOAD CONFIG" +$CLICKHOUSE_CLIENT -nm --query " +set send_logs_level='fatal'; +SYSTEM RELOAD CONFIG" $CLICKHOUSE_CLIENT --query "DESCRIBE FILESYSTEM CACHE '${disk_name}'" $CLICKHOUSE_CLIENT --query "SELECT count() FROM system.filesystem_cache WHERE state = 'DOWNLOADED'" @@ -70,7 +77,9 @@ cat $config_path \ > $config_path_tmp mv $config_path_tmp $config_path -$CLICKHOUSE_CLIENT -nm --query "SYSTEM RELOAD CONFIG" +$CLICKHOUSE_CLIENT -nm --query " +set send_logs_level='fatal'; +SYSTEM RELOAD CONFIG" $CLICKHOUSE_CLIENT --query "DESCRIBE FILESYSTEM CACHE '${disk_name}'" $CLICKHOUSE_CLIENT --query "SELECT * FROM test FORMAT Null" From 276f040dd05d4e7bdfe0fe3752d0b86b4b4b3521 Mon Sep 17 00:00:00 2001 From: Max Kainov Date: Tue, 19 Dec 2023 10:01:37 +0000 Subject: [PATCH 104/253] ci fix https://github.com/ClickHouse/ClickHouse/actions/runs/7256640256/job/19769624432?pr=58015#step:8:5466 ``` Dec 19 03:09:41 /build/src/IO/AIO.cpp:140:15: error: use of undeclared identifier 'ErrnoException'; did you mean 'DB::ErrnoException'? Dec 19 03:09:41 140 | throw ErrnoException(DB::ErrorCodes::CANNOT_IOSETUP, "io_setup failed"); ``` --- tests/ci/ci.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/ci/ci.py b/tests/ci/ci.py index bed12d54fe3..42b172f049d 100644 --- a/tests/ci/ci.py +++ b/tests/ci/ci.py @@ -511,7 +511,14 @@ def _update_gh_statuses(indata: Dict, s3: S3Helper) -> None: def _fetch_commit_tokens(message: str) -> List[str]: pattern = r"#[\w-]+" matches = re.findall(pattern, message) - return matches + res = [ + match + for match in matches + if match == "#no-merge-commit" + or match.startswith("#job_") + or match.startswith("#job-") + ] + return res def main() -> int: From 3632688e55b2424dd2518c258b185a033f0fac19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Tue, 19 Dec 2023 11:10:11 +0100 Subject: [PATCH 105/253] Fix style --- src/Functions/FunctionsHashing.h | 1 - src/Functions/FunctionsStringHashFixedString.cpp | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Functions/FunctionsHashing.h b/src/Functions/FunctionsHashing.h index 8fb21cd1ad4..d0edd34e657 100644 --- a/src/Functions/FunctionsHashing.h +++ b/src/Functions/FunctionsHashing.h @@ -62,7 +62,6 @@ namespace ErrorCodes extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; extern const int NOT_IMPLEMENTED; extern const int ILLEGAL_COLUMN; - extern const int SUPPORT_IS_DISABLED; } namespace impl diff --git a/src/Functions/FunctionsStringHashFixedString.cpp b/src/Functions/FunctionsStringHashFixedString.cpp index f7134953d52..9b613b4026e 100644 --- a/src/Functions/FunctionsStringHashFixedString.cpp +++ b/src/Functions/FunctionsStringHashFixedString.cpp @@ -33,6 +33,7 @@ namespace DB namespace ErrorCodes { extern const int ILLEGAL_COLUMN; +extern const int ILLEGAL_TYPE_OF_ARGUMENT; } @@ -434,8 +435,6 @@ REGISTER_FUNCTION(HashFixedStrings) .categories{"Hash"}}, FunctionFactory::CaseSensitive); # endif - +} #endif } - -} From 8bef92c92adf0ffac2a9960b3999f5881c71a833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Tue, 19 Dec 2023 12:33:01 +0100 Subject: [PATCH 106/253] Fix clang-tidy --- src/Common/ArrayCache.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Common/ArrayCache.h b/src/Common/ArrayCache.h index 47b91ff4eef..bbcf1a55bed 100644 --- a/src/Common/ArrayCache.h +++ b/src/Common/ArrayCache.h @@ -179,13 +179,22 @@ private: { ptr = mmap(address_hint, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (MAP_FAILED == ptr) - throw ErrnoException(DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY, "Allocator: Cannot mmap {}", ReadableSize(size)); + throw DB::ErrnoException(DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY, "Allocator: Cannot mmap {}", ReadableSize(size)); } ~Chunk() { if (ptr && 0 != munmap(ptr, size)) - throw ErrnoException(DB::ErrorCodes::CANNOT_MUNMAP, "Allocator: Cannot munmap {}", ReadableSize(size)); + { + try + { + throw DB::ErrnoException(DB::ErrorCodes::CANNOT_MUNMAP, "Allocator: Cannot munmap {}", ReadableSize(size)); + } + catch (DB::ErrnoException &) + { + tryLogCurrentException(__PRETTY_FUNCTION__); + } + } } Chunk(Chunk && other) noexcept : ptr(other.ptr), size(other.size) From e09c68db32391af68a15350b6df04addf7434a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Tue, 19 Dec 2023 13:18:28 +0100 Subject: [PATCH 107/253] Remove fixed tests from analyzer_tech_debt.txt --- tests/analyzer_tech_debt.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/analyzer_tech_debt.txt b/tests/analyzer_tech_debt.txt index b0e611fa77b..735094df78b 100644 --- a/tests/analyzer_tech_debt.txt +++ b/tests/analyzer_tech_debt.txt @@ -2,7 +2,6 @@ 00717_merge_and_distributed 00725_memory_tracking 01062_pm_all_join_with_block_continuation -01064_incremental_streaming_from_2_src_with_feedback 01083_expressions_in_engine_arguments 01155_rename_move_materialized_view 01214_test_storage_merge_aliases_with_where @@ -20,9 +19,7 @@ 01761_cast_to_enum_nullable 01925_join_materialized_columns 01925_test_storage_merge_aliases -01947_mv_subquery 01952_optimize_distributed_group_by_sharding_key -02139_MV_with_scalar_subquery 02174_cte_scalar_cache_mv 02352_grouby_shadows_arg 02354_annoy @@ -36,7 +33,6 @@ 02404_memory_bound_merging 02725_agg_projection_resprect_PK 02763_row_policy_storage_merge_alias -02765_parallel_replicas_final_modifier 02784_parallel_replicas_automatic_decision_join 02818_parameterized_view_with_cte_multiple_usage 02815_range_dict_no_direct_join From 8ab6564538a06d45545ade8c7f37da39587012ac Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 19 Dec 2023 13:30:59 +0100 Subject: [PATCH 108/253] Revert "Fix bug window functions: revert #39631" --- src/Interpreters/InterpreterSelectQuery.cpp | 1 + src/Planner/Planner.cpp | 1 + src/Processors/QueryPlan/SortingStep.cpp | 78 ++++++- src/Processors/QueryPlan/SortingStep.h | 30 ++- src/Processors/QueryPlan/WindowStep.cpp | 3 +- .../ScatterByPartitionTransform.cpp | 129 +++++++++++ .../Transforms/ScatterByPartitionTransform.h | 34 +++ ...568_window_functions_distributed.reference | 29 +++ .../01568_window_functions_distributed.sql | 4 + .../02884_parallel_window_functions.reference | 100 ++++++++ .../02884_parallel_window_functions.sql | 119 ++++++++++ ...2_window_functions_logical_error.reference | 216 ------------------ .../02942_window_functions_logical_error.sql | 158 ------------- 13 files changed, 521 insertions(+), 381 deletions(-) create mode 100644 src/Processors/Transforms/ScatterByPartitionTransform.cpp create mode 100644 src/Processors/Transforms/ScatterByPartitionTransform.h create mode 100644 tests/queries/0_stateless/02884_parallel_window_functions.reference create mode 100644 tests/queries/0_stateless/02884_parallel_window_functions.sql delete mode 100644 tests/queries/0_stateless/02942_window_functions_logical_error.reference delete mode 100644 tests/queries/0_stateless/02942_window_functions_logical_error.sql diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 67245438156..4f4e96a9be7 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -2942,6 +2942,7 @@ void InterpreterSelectQuery::executeWindow(QueryPlan & query_plan) auto sorting_step = std::make_unique( query_plan.getCurrentDataStream(), window.full_sort_description, + window.partition_by, 0 /* LIMIT */, sort_settings, settings.optimize_sorting_by_input_stream_properties); diff --git a/src/Planner/Planner.cpp b/src/Planner/Planner.cpp index 2ab88491357..95c61f8d011 100644 --- a/src/Planner/Planner.cpp +++ b/src/Planner/Planner.cpp @@ -915,6 +915,7 @@ void addWindowSteps(QueryPlan & query_plan, auto sorting_step = std::make_unique( query_plan.getCurrentDataStream(), window_description.full_sort_description, + window_description.partition_by, 0 /*limit*/, sort_settings, settings.optimize_sorting_by_input_stream_properties); diff --git a/src/Processors/QueryPlan/SortingStep.cpp b/src/Processors/QueryPlan/SortingStep.cpp index 55ce763575e..641b9036d4c 100644 --- a/src/Processors/QueryPlan/SortingStep.cpp +++ b/src/Processors/QueryPlan/SortingStep.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -9,6 +10,8 @@ #include #include +#include +#include namespace CurrentMetrics { @@ -76,6 +79,21 @@ SortingStep::SortingStep( output_stream->sort_scope = DataStream::SortScope::Global; } +SortingStep::SortingStep( + const DataStream & input_stream, + const SortDescription & description_, + const SortDescription & partition_by_description_, + UInt64 limit_, + const Settings & settings_, + bool optimize_sorting_by_input_stream_properties_) + : SortingStep(input_stream, description_, limit_, settings_, optimize_sorting_by_input_stream_properties_) +{ + partition_by_description = partition_by_description_; + + output_stream->sort_description = result_description; + output_stream->sort_scope = DataStream::SortScope::Stream; +} + SortingStep::SortingStep( const DataStream & input_stream_, SortDescription prefix_description_, @@ -117,7 +135,11 @@ void SortingStep::updateOutputStream() { output_stream = createOutputStream(input_streams.front(), input_streams.front().header, getDataStreamTraits()); output_stream->sort_description = result_description; - output_stream->sort_scope = DataStream::SortScope::Global; + + if (partition_by_description.empty()) + output_stream->sort_scope = DataStream::SortScope::Global; + else + output_stream->sort_scope = DataStream::SortScope::Stream; } void SortingStep::updateLimit(size_t limit_) @@ -135,6 +157,55 @@ void SortingStep::convertToFinishSorting(SortDescription prefix_description_) prefix_description = std::move(prefix_description_); } +void SortingStep::scatterByPartitionIfNeeded(QueryPipelineBuilder& pipeline) +{ + size_t threads = pipeline.getNumThreads(); + size_t streams = pipeline.getNumStreams(); + + if (!partition_by_description.empty() && threads > 1) + { + Block stream_header = pipeline.getHeader(); + + ColumnNumbers key_columns; + key_columns.reserve(partition_by_description.size()); + for (auto & col : partition_by_description) + { + key_columns.push_back(stream_header.getPositionByName(col.column_name)); + } + + pipeline.transform([&](OutputPortRawPtrs ports) + { + Processors processors; + for (auto * port : ports) + { + auto scatter = std::make_shared(stream_header, threads, key_columns); + connect(*port, scatter->getInputs().front()); + processors.push_back(scatter); + } + return processors; + }); + + if (streams > 1) + { + pipeline.transform([&](OutputPortRawPtrs ports) + { + Processors processors; + for (size_t i = 0; i < threads; ++i) + { + size_t output_it = i; + auto resize = std::make_shared(stream_header, streams, 1); + auto & inputs = resize->getInputs(); + + for (auto input_it = inputs.begin(); input_it != inputs.end(); output_it += threads, ++input_it) + connect(*ports[output_it], *input_it); + processors.push_back(resize); + } + return processors; + }); + } + } +} + void SortingStep::finishSorting( QueryPipelineBuilder & pipeline, const SortDescription & input_sort_desc, const SortDescription & result_sort_desc, const UInt64 limit_) { @@ -260,10 +331,12 @@ void SortingStep::fullSortStreams( void SortingStep::fullSort( QueryPipelineBuilder & pipeline, const SortDescription & result_sort_desc, const UInt64 limit_, const bool skip_partial_sort) { + scatterByPartitionIfNeeded(pipeline); + fullSortStreams(pipeline, sort_settings, result_sort_desc, limit_, skip_partial_sort); /// If there are several streams, then we merge them into one - if (pipeline.getNumStreams() > 1) + if (pipeline.getNumStreams() > 1 && (partition_by_description.empty() || pipeline.getNumThreads() == 1)) { auto transform = std::make_shared( pipeline.getHeader(), @@ -295,6 +368,7 @@ void SortingStep::transformPipeline(QueryPipelineBuilder & pipeline, const Build { bool need_finish_sorting = (prefix_description.size() < result_description.size()); mergingSorted(pipeline, prefix_description, (need_finish_sorting ? 0 : limit)); + if (need_finish_sorting) { finishSorting(pipeline, prefix_description, result_description, limit); diff --git a/src/Processors/QueryPlan/SortingStep.h b/src/Processors/QueryPlan/SortingStep.h index 371a24ac6f2..52f48f66a32 100644 --- a/src/Processors/QueryPlan/SortingStep.h +++ b/src/Processors/QueryPlan/SortingStep.h @@ -40,6 +40,15 @@ public: const Settings & settings_, bool optimize_sorting_by_input_stream_properties_); + /// Full with partitioning + SortingStep( + const DataStream & input_stream, + const SortDescription & description_, + const SortDescription & partition_by_description_, + UInt64 limit_, + const Settings & settings_, + bool optimize_sorting_by_input_stream_properties_); + /// FinishSorting SortingStep( const DataStream & input_stream_, @@ -83,14 +92,24 @@ public: bool skip_partial_sort = false); private: + void scatterByPartitionIfNeeded(QueryPipelineBuilder& pipeline); void updateOutputStream() override; - static void - mergeSorting(QueryPipelineBuilder & pipeline, const Settings & sort_settings, const SortDescription & result_sort_desc, UInt64 limit_); + static void mergeSorting( + QueryPipelineBuilder & pipeline, + const Settings & sort_settings, + const SortDescription & result_sort_desc, + UInt64 limit_); - void mergingSorted(QueryPipelineBuilder & pipeline, const SortDescription & result_sort_desc, UInt64 limit_); + void mergingSorted( + QueryPipelineBuilder & pipeline, + const SortDescription & result_sort_desc, + UInt64 limit_); void finishSorting( - QueryPipelineBuilder & pipeline, const SortDescription & input_sort_desc, const SortDescription & result_sort_desc, UInt64 limit_); + QueryPipelineBuilder & pipeline, + const SortDescription & input_sort_desc, + const SortDescription & result_sort_desc, + UInt64 limit_); void fullSort( QueryPipelineBuilder & pipeline, const SortDescription & result_sort_desc, @@ -101,6 +120,9 @@ private: SortDescription prefix_description; const SortDescription result_description; + + SortDescription partition_by_description; + UInt64 limit; bool always_read_till_end = false; diff --git a/src/Processors/QueryPlan/WindowStep.cpp b/src/Processors/QueryPlan/WindowStep.cpp index 9c68a4b73d1..bb4f429d626 100644 --- a/src/Processors/QueryPlan/WindowStep.cpp +++ b/src/Processors/QueryPlan/WindowStep.cpp @@ -67,7 +67,8 @@ void WindowStep::transformPipeline(QueryPipelineBuilder & pipeline, const BuildQ // This resize is needed for cases such as `over ()` when we don't have a // sort node, and the input might have multiple streams. The sort node would // have resized it. - pipeline.resize(1); + if (window_description.full_sort_description.empty()) + pipeline.resize(1); pipeline.addSimpleTransform( [&](const Block & /*header*/) diff --git a/src/Processors/Transforms/ScatterByPartitionTransform.cpp b/src/Processors/Transforms/ScatterByPartitionTransform.cpp new file mode 100644 index 00000000000..6e3cdc0fda1 --- /dev/null +++ b/src/Processors/Transforms/ScatterByPartitionTransform.cpp @@ -0,0 +1,129 @@ +#include + +#include +#include + +namespace DB +{ +ScatterByPartitionTransform::ScatterByPartitionTransform(Block header, size_t output_size_, ColumnNumbers key_columns_) + : IProcessor(InputPorts{header}, OutputPorts{output_size_, header}) + , output_size(output_size_) + , key_columns(std::move(key_columns_)) + , hash(0) +{} + +IProcessor::Status ScatterByPartitionTransform::prepare() +{ + auto & input = getInputs().front(); + + /// Check all outputs are finished or ready to get data. + + bool all_finished = true; + for (auto & output : outputs) + { + if (output.isFinished()) + continue; + + all_finished = false; + } + + if (all_finished) + { + input.close(); + return Status::Finished; + } + + if (!all_outputs_processed) + { + auto output_it = outputs.begin(); + bool can_push = false; + for (size_t i = 0; i < output_size; ++i, ++output_it) + if (!was_output_processed[i] && output_it->canPush()) + can_push = true; + if (!can_push) + return Status::PortFull; + return Status::Ready; + } + /// Try get chunk from input. + + if (input.isFinished()) + { + for (auto & output : outputs) + output.finish(); + + return Status::Finished; + } + + input.setNeeded(); + if (!input.hasData()) + return Status::NeedData; + + chunk = input.pull(); + has_data = true; + was_output_processed.assign(outputs.size(), false); + + return Status::Ready; +} + +void ScatterByPartitionTransform::work() +{ + if (all_outputs_processed) + generateOutputChunks(); + all_outputs_processed = true; + + size_t chunk_number = 0; + for (auto & output : outputs) + { + auto & was_processed = was_output_processed[chunk_number]; + auto & output_chunk = output_chunks[chunk_number]; + ++chunk_number; + + if (was_processed) + continue; + + if (output.isFinished()) + continue; + + if (!output.canPush()) + { + all_outputs_processed = false; + continue; + } + + output.push(std::move(output_chunk)); + was_processed = true; + } + + if (all_outputs_processed) + { + has_data = false; + output_chunks.clear(); + } +} + +void ScatterByPartitionTransform::generateOutputChunks() +{ + auto num_rows = chunk.getNumRows(); + const auto & columns = chunk.getColumns(); + + hash.reset(num_rows); + + for (const auto & column_number : key_columns) + columns[column_number]->updateWeakHash32(hash); + + const auto & hash_data = hash.getData(); + IColumn::Selector selector(num_rows); + + for (size_t row = 0; row < num_rows; ++row) + selector[row] = hash_data[row] % output_size; + + output_chunks.resize(output_size); + for (const auto & column : columns) + { + auto filtered_columns = column->scatter(output_size, selector); + for (size_t i = 0; i < output_size; ++i) + output_chunks[i].addColumn(std::move(filtered_columns[i])); + } +} + +} diff --git a/src/Processors/Transforms/ScatterByPartitionTransform.h b/src/Processors/Transforms/ScatterByPartitionTransform.h new file mode 100644 index 00000000000..327f6dd62b4 --- /dev/null +++ b/src/Processors/Transforms/ScatterByPartitionTransform.h @@ -0,0 +1,34 @@ +#pragma once +#include +#include +#include + +namespace DB +{ + +struct ScatterByPartitionTransform : IProcessor +{ + ScatterByPartitionTransform(Block header, size_t output_size_, ColumnNumbers key_columns_); + + String getName() const override { return "ScatterByPartitionTransform"; } + + Status prepare() override; + void work() override; + +private: + + void generateOutputChunks(); + + size_t output_size; + ColumnNumbers key_columns; + + bool has_data = false; + bool all_outputs_processed = true; + std::vector was_output_processed; + Chunk chunk; + + WeakHash32 hash; + Chunks output_chunks; +}; + +} diff --git a/tests/queries/0_stateless/01568_window_functions_distributed.reference b/tests/queries/0_stateless/01568_window_functions_distributed.reference index 13ac0769a24..29ff2e7133c 100644 --- a/tests/queries/0_stateless/01568_window_functions_distributed.reference +++ b/tests/queries/0_stateless/01568_window_functions_distributed.reference @@ -22,6 +22,16 @@ select sum(number) over w as x, max(number) over w as y from t_01568 window w as 21 8 21 8 21 8 +select sum(number) over w, max(number) over w from t_01568 window w as (partition by p) order by p; +3 2 +3 2 +3 2 +12 5 +12 5 +12 5 +21 8 +21 8 +21 8 select sum(number) over w as x, max(number) over w as y from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p) order by x, y; 6 2 6 2 @@ -41,6 +51,25 @@ select sum(number) over w as x, max(number) over w as y from remote('127.0.0.{1, 42 8 42 8 42 8 +select sum(number) over w as x, max(number) over w as y from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p) order by x, y SETTINGS max_threads = 1; +6 2 +6 2 +6 2 +6 2 +6 2 +6 2 +24 5 +24 5 +24 5 +24 5 +24 5 +24 5 +42 8 +42 8 +42 8 +42 8 +42 8 +42 8 select distinct sum(number) over w as x, max(number) over w as y from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p) order by x, y; 6 2 24 5 diff --git a/tests/queries/0_stateless/01568_window_functions_distributed.sql b/tests/queries/0_stateless/01568_window_functions_distributed.sql index 95072d6460f..ecce7b412ba 100644 --- a/tests/queries/0_stateless/01568_window_functions_distributed.sql +++ b/tests/queries/0_stateless/01568_window_functions_distributed.sql @@ -15,8 +15,12 @@ from numbers(9); select sum(number) over w as x, max(number) over w as y from t_01568 window w as (partition by p) order by x, y; +select sum(number) over w, max(number) over w from t_01568 window w as (partition by p) order by p; + select sum(number) over w as x, max(number) over w as y from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p) order by x, y; +select sum(number) over w as x, max(number) over w as y from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p) order by x, y SETTINGS max_threads = 1; + select distinct sum(number) over w as x, max(number) over w as y from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p) order by x, y; -- window functions + aggregation w/shards diff --git a/tests/queries/0_stateless/02884_parallel_window_functions.reference b/tests/queries/0_stateless/02884_parallel_window_functions.reference new file mode 100644 index 00000000000..bac15838dc2 --- /dev/null +++ b/tests/queries/0_stateless/02884_parallel_window_functions.reference @@ -0,0 +1,100 @@ +1 +-- { echoOn } + +SELECT + nw, + sum(WR) AS R, + sumIf(WR, uniq_rows = 1) AS UNR +FROM +( + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + GROUP BY ac, nw +) +GROUP BY nw +ORDER BY nw ASC, R DESC +LIMIT 10; +0 2 0 +1 2 0 +2 2 0 +SELECT + nw, + sum(WR) AS R, + sumIf(WR, uniq_rows = 1) AS UNR +FROM +( + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + GROUP BY ac, nw +) +GROUP BY nw +ORDER BY nw ASC, R DESC +LIMIT 10 +SETTINGS max_threads = 1; +0 2 0 +1 2 0 +2 2 0 +SELECT + nw, + sum(WR) AS R, + sumIf(WR, uniq_rows = 1) AS UNR +FROM +( + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + WHERE (ac % 4) = 0 + GROUP BY + ac, + nw + UNION ALL + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + WHERE (ac % 4) = 1 + GROUP BY + ac, + nw + UNION ALL + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + WHERE (ac % 4) = 2 + GROUP BY + ac, + nw + UNION ALL + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + WHERE (ac % 4) = 3 + GROUP BY + ac, + nw +) +GROUP BY nw +ORDER BY nw ASC, R DESC +LIMIT 10; +0 2 0 +1 2 0 +2 2 0 diff --git a/tests/queries/0_stateless/02884_parallel_window_functions.sql b/tests/queries/0_stateless/02884_parallel_window_functions.sql new file mode 100644 index 00000000000..3151b42f896 --- /dev/null +++ b/tests/queries/0_stateless/02884_parallel_window_functions.sql @@ -0,0 +1,119 @@ +CREATE TABLE window_funtion_threading +Engine = MergeTree +ORDER BY (ac, nw) +AS SELECT + toUInt64(toFloat32(number % 2) % 20000000) as ac, + toFloat32(1) as wg, + toUInt16(toFloat32(number % 3) % 400) as nw +FROM numbers_mt(10000000); + +SELECT count() FROM (EXPLAIN PIPELINE SELECT + nw, + sum(WR) AS R, + sumIf(WR, uniq_rows = 1) AS UNR +FROM +( + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + GROUP BY ac, nw +) +GROUP BY nw +ORDER BY nw ASC, R DESC +LIMIT 10) where explain ilike '%ScatterByPartitionTransform%' SETTINGS max_threads = 4; + +-- { echoOn } + +SELECT + nw, + sum(WR) AS R, + sumIf(WR, uniq_rows = 1) AS UNR +FROM +( + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + GROUP BY ac, nw +) +GROUP BY nw +ORDER BY nw ASC, R DESC +LIMIT 10; + +SELECT + nw, + sum(WR) AS R, + sumIf(WR, uniq_rows = 1) AS UNR +FROM +( + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + GROUP BY ac, nw +) +GROUP BY nw +ORDER BY nw ASC, R DESC +LIMIT 10 +SETTINGS max_threads = 1; + +SELECT + nw, + sum(WR) AS R, + sumIf(WR, uniq_rows = 1) AS UNR +FROM +( + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + WHERE (ac % 4) = 0 + GROUP BY + ac, + nw + UNION ALL + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + WHERE (ac % 4) = 1 + GROUP BY + ac, + nw + UNION ALL + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + WHERE (ac % 4) = 2 + GROUP BY + ac, + nw + UNION ALL + SELECT + uniq(nw) OVER (PARTITION BY ac) AS uniq_rows, + AVG(wg) AS WR, + ac, + nw + FROM window_funtion_threading + WHERE (ac % 4) = 3 + GROUP BY + ac, + nw +) +GROUP BY nw +ORDER BY nw ASC, R DESC +LIMIT 10; diff --git a/tests/queries/0_stateless/02942_window_functions_logical_error.reference b/tests/queries/0_stateless/02942_window_functions_logical_error.reference deleted file mode 100644 index 73f8351d9df..00000000000 --- a/tests/queries/0_stateless/02942_window_functions_logical_error.reference +++ /dev/null @@ -1,216 +0,0 @@ -1 901 19 -1 911 19 -1 921 19 -1 931 19 -1 941 19 -1 951 20 -1 961 20 -1 971 20 -1 981 20 -1 991 20 -2 902 19 -2 912 19 -2 922 19 -2 932 19 -2 942 19 -2 952 20 -2 962 20 -2 972 20 -2 982 20 -2 992 20 -3 903 19 -3 913 19 -3 923 19 -3 933 19 -3 943 19 -3 953 20 -3 963 20 -3 973 20 -3 983 20 -3 993 20 -4 904 19 -4 914 19 -4 924 19 -4 934 19 -4 944 19 -4 954 20 -4 964 20 -4 974 20 -4 984 20 -4 994 20 -5 905 19 -5 915 19 -5 925 19 -5 935 19 -5 945 19 -5 955 20 -5 965 20 -5 975 20 -5 985 20 -5 995 20 -6 906 19 -6 916 19 -6 926 19 -6 936 19 -6 946 19 -6 956 20 -6 966 20 -6 976 20 -6 986 20 -6 996 20 -7 907 19 -7 917 19 -7 927 19 -7 937 19 -7 947 19 -7 957 20 -7 967 20 -7 977 20 -7 987 20 -7 997 20 -8 908 19 -8 918 19 -8 928 19 -8 938 19 -8 948 19 -8 958 20 -8 968 20 -8 978 20 -8 988 20 -8 998 20 -9 909 19 -9 919 19 -9 929 19 -9 939 19 -9 949 19 -9 959 20 -9 969 20 -9 979 20 -9 989 20 -9 999 20 -1 1301 19 -1 1311 19 -1 1321 19 -1 1331 19 -1 1341 19 -1 1351 19 -1 1361 19 -1 1371 20 -1 1381 20 -1 1391 20 -1 1401 20 -1 1411 20 -1 1421 20 -1 1431 20 -2 1302 19 -2 1312 19 -2 1322 19 -2 1332 19 -2 1342 19 -2 1352 19 -2 1362 19 -2 1372 20 -2 1382 20 -2 1392 20 -2 1402 20 -2 1412 20 -2 1422 20 -2 1432 20 -3 1303 19 -3 1313 19 -3 1323 19 -3 1333 19 -3 1343 19 -3 1353 19 -3 1363 19 -3 1373 20 -3 1383 20 -3 1393 20 -3 1403 20 -3 1413 20 -3 1423 20 -3 1433 20 -4 1304 19 -4 1314 19 -4 1324 19 -4 1334 19 -4 1344 19 -4 1354 19 -4 1364 19 -4 1374 20 -4 1384 20 -4 1394 20 -4 1404 20 -4 1414 20 -4 1424 20 -4 1434 20 -5 1305 19 -5 1315 19 -5 1325 19 -5 1335 19 -5 1345 19 -5 1355 19 -5 1365 19 -5 1375 20 -5 1385 20 -5 1395 20 -5 1405 20 -5 1415 20 -5 1425 20 -5 1435 20 -6 1306 19 -6 1316 19 -6 1326 19 -6 1336 19 -6 1346 19 -6 1356 19 -6 1366 19 -6 1376 20 -6 1386 20 -6 1396 20 -6 1406 20 -6 1416 20 -6 1426 20 -6 1436 20 -7 1307 19 -7 1317 19 -7 1327 19 -7 1337 19 -7 1347 19 -7 1357 19 -7 1367 19 -7 1377 20 -7 1387 20 -7 1397 20 -7 1407 20 -7 1417 20 -7 1427 20 -7 1437 20 -8 1308 19 -8 1318 19 -8 1328 19 -8 1338 19 -8 1348 19 -8 1358 19 -8 1368 19 -8 1378 20 -8 1388 20 -8 1398 20 -8 1408 20 -8 1418 20 -8 1428 20 -8 1438 20 -9 1309 19 -9 1319 19 -9 1329 19 -9 1339 19 -9 1349 19 -9 1359 19 -9 1369 19 -9 1379 20 -9 1389 20 -9 1399 20 -9 1409 20 -9 1419 20 -9 1429 20 -9 1439 20 diff --git a/tests/queries/0_stateless/02942_window_functions_logical_error.sql b/tests/queries/0_stateless/02942_window_functions_logical_error.sql deleted file mode 100644 index 1e4371a134f..00000000000 --- a/tests/queries/0_stateless/02942_window_functions_logical_error.sql +++ /dev/null @@ -1,158 +0,0 @@ -DROP TABLE IF EXISTS posts; -DROP TABLE IF EXISTS post_metrics; - -CREATE TABLE IF NOT EXISTS posts -( - `page_id` LowCardinality(String), - `post_id` String CODEC(LZ4), - `host_id` UInt32 CODEC(T64, LZ4), - `path_id` UInt32, - `created` DateTime CODEC(T64, LZ4), - `as_of` DateTime CODEC(T64, LZ4) -) -ENGINE = ReplacingMergeTree(as_of) -PARTITION BY toStartOfMonth(created) -ORDER BY (page_id, post_id) -TTL created + toIntervalMonth(26); - - -INSERT INTO posts SELECT - repeat('a', (number % 10) + 1), - toString(number), - number % 10, - number, - now() - toIntervalMinute(number), - now() -FROM numbers(1000); - - -CREATE TABLE IF NOT EXISTS post_metrics -( - `page_id` LowCardinality(String), - `post_id` String CODEC(LZ4), - `created` DateTime CODEC(T64, LZ4), - `impressions` UInt32 CODEC(T64, LZ4), - `clicks` UInt32 CODEC(T64, LZ4), - `as_of` DateTime CODEC(T64, LZ4) -) -ENGINE = ReplacingMergeTree(as_of) -PARTITION BY toStartOfMonth(created) -ORDER BY (page_id, post_id) -TTL created + toIntervalMonth(26); - - -INSERT INTO post_metrics SELECT - repeat('a', (number % 10) + 1), - toString(number), - now() - toIntervalMinute(number), - number * 100, - number * 10, - now() -FROM numbers(1000); - - -SELECT - host_id, - path_id, - max(rank) AS rank -FROM -( - WITH - as_of_posts AS - ( - SELECT - *, - row_number() OVER (PARTITION BY (page_id, post_id) ORDER BY as_of DESC) AS row_num - FROM posts - WHERE (created >= subtractHours(now(), 24)) AND (host_id > 0) - ), - as_of_post_metrics AS - ( - SELECT - *, - row_number() OVER (PARTITION BY (page_id, post_id) ORDER BY as_of DESC) AS row_num - FROM post_metrics - WHERE created >= subtractHours(now(), 24) - ) - SELECT - page_id, - post_id, - host_id, - path_id, - impressions, - clicks, - ntile(20) OVER (PARTITION BY page_id ORDER BY clicks ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS rank - FROM as_of_posts - GLOBAL LEFT JOIN as_of_post_metrics USING (page_id, post_id, row_num) - WHERE (row_num = 1) AND (impressions > 0) -) AS t -WHERE t.rank > 18 -GROUP BY - host_id, - path_id -ORDER BY host_id, path_id; - - -INSERT INTO posts SELECT - repeat('a', (number % 10) + 1), - toString(number), - number % 10, - number, - now() - toIntervalMinute(number), - now() -FROM numbers(100000); - - -INSERT INTO post_metrics SELECT - repeat('a', (number % 10) + 1), - toString(number), - now() - toIntervalMinute(number), - number * 100, - number * 10, - now() -FROM numbers(100000); - - -SELECT - host_id, - path_id, - max(rank) AS rank -FROM -( - WITH - as_of_posts AS - ( - SELECT - *, - row_number() OVER (PARTITION BY (page_id, post_id) ORDER BY as_of DESC) AS row_num - FROM posts - WHERE (created >= subtractHours(now(), 24)) AND (host_id > 0) - ), - as_of_post_metrics AS - ( - SELECT - *, - row_number() OVER (PARTITION BY (page_id, post_id) ORDER BY as_of DESC) AS row_num - FROM post_metrics - WHERE created >= subtractHours(now(), 24) - ) - SELECT - page_id, - post_id, - host_id, - path_id, - impressions, - clicks, - ntile(20) OVER (PARTITION BY page_id ORDER BY clicks ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS rank - FROM as_of_posts - GLOBAL LEFT JOIN as_of_post_metrics USING (page_id, post_id, row_num) - WHERE (row_num = 1) AND (impressions > 0) -) AS t -WHERE t.rank > 18 -GROUP BY - host_id, - path_id -ORDER BY host_id, path_id; - -DROP TABLE posts; -DROP TABLE post_metrics; From b4fec61814f2a57aab18f5a15b9bdff4fd703848 Mon Sep 17 00:00:00 2001 From: Sema Checherinda Date: Mon, 18 Dec 2023 20:50:58 +0100 Subject: [PATCH 109/253] fix style and black --- docker/test/stateful/run.sh | 4 ++-- tests/clickhouse-test | 37 +++++++++++++++++++++---------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/docker/test/stateful/run.sh b/docker/test/stateful/run.sh index 82587efcb3d..806b57c4616 100755 --- a/docker/test/stateful/run.sh +++ b/docker/test/stateful/run.sh @@ -78,7 +78,7 @@ function start() tail -n1000 /var/log/clickhouse-server/clickhouse-server.log break fi - timeout_with_logging 120 service clickhouse-server start + timeout 120 service clickhouse-server start sleep 0.5 counter=$((counter + 1)) done @@ -163,7 +163,7 @@ function run_tests() } export -f run_tests -timeout_with_logging "$MAX_RUN_TIME" bash -c run_tests ||: +timeout "$MAX_RUN_TIME" bash -c run_tests ||: echo "Files in current directory" ls -la ./ diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 1ba67a3b2f4..1df2bc8271f 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -278,6 +278,7 @@ def need_retry(args, stdout, stderr, total_time): msg in stderr for msg in MESSAGES_TO_RETRY ) + def get_processlist_size(args): if args.replicated_database: return int( @@ -286,27 +287,26 @@ def get_processlist_size(args): """ SELECT count() - FROM + FROM FROM system.processes WHERE query NOT LIKE '%system.processes%' - FORMAT Vertical """, - ).strip() + ).strip() ) else: return int( - clickhouse_execute( + clickhouse_execute( args, """ - SELECT - count() - FROM system.processes - WHERE query NOT LIKE '%system.processes%' - FORMAT Vertical - """, + SELECT + count() + FROM system.processes + WHERE query NOT LIKE '%system.processes%' + """, ).strip() ) + def get_processlist_with_stacktraces(args): if args.replicated_database: return clickhouse_execute( @@ -356,7 +356,6 @@ def get_processlist_with_stacktraces(args): ) - def get_transactions_list(args): try: if args.replicated_database: @@ -2448,7 +2447,7 @@ def main(args): if args.hung_check: # Some queries may execute in background for some time after test was finished. This is normal. - print("Checking the hung queries: ", end='') + print("Checking the hung queries: ", end="") hung_count = 0 try: deadline = datetime.now() + timedelta(seconds=90) @@ -2457,16 +2456,18 @@ def main(args): if hung_count == 0: print(" done") break - print(". ", end='') + print(". ", end="") except Exception as e: print( colored( - "\nHung check failed. Failed to get processlist size: " + str(e), args, "red", attrs=["bold"] + "\nHung check failed. Failed to get processlist size: " + str(e), + args, + "red", + attrs=["bold"], ) ) exit_code.value = 1 - processlist = "" if hung_count > 0: try: @@ -2474,7 +2475,11 @@ def main(args): except Exception as e: print( colored( - "\nHung check failed. Failed to get processlist with stacktraces: " + str(e), args, "red", attrs=["bold"] + "\nHung check failed. Failed to get processlist with stacktraces: " + + str(e), + args, + "red", + attrs=["bold"], ) ) exit_code.value = 1 From 64247e9033f4896eab97be8a628dba0de9ab8063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Tue, 19 Dec 2023 14:28:41 +0100 Subject: [PATCH 110/253] Try fix clang-tidy again --- src/Common/ArrayCache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/ArrayCache.h b/src/Common/ArrayCache.h index bbcf1a55bed..b6dde039227 100644 --- a/src/Common/ArrayCache.h +++ b/src/Common/ArrayCache.h @@ -192,7 +192,7 @@ private: } catch (DB::ErrnoException &) { - tryLogCurrentException(__PRETTY_FUNCTION__); + DB::tryLogCurrentException(__PRETTY_FUNCTION__); } } } From b458c77340992c6bd522bf7c266876a131b4fca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Tue, 19 Dec 2023 14:16:24 +0100 Subject: [PATCH 111/253] Fix integration test --- src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp | 10 ++++++++-- tests/integration/test_quorum_inserts_parallel/test.py | 3 +-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp index 35e5b62d77a..5913904ae65 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp @@ -1148,7 +1148,10 @@ void ReplicatedMergeTreeSinkImpl::waitForQuorum( break; if (!event->tryWait(quorum_timeout_ms)) - throw Exception(ErrorCodes::UNKNOWN_STATUS_OF_INSERT, "Timeout while waiting for quorum"); + throw Exception( + ErrorCodes::UNKNOWN_STATUS_OF_INSERT, + "Unknown quorum status. The data was inserted in the local replica but we could not verify quorum. Reason: " + "Timeout while waiting for quorum"); LOG_TRACE(log, "Quorum {} for part {} updated, will check quorum node still exists", quorum_path, part_name); } @@ -1158,7 +1161,10 @@ void ReplicatedMergeTreeSinkImpl::waitForQuorum( Coordination::Stat stat; String value; if (!zookeeper->tryGet(storage.replica_path + "/is_active", value, &stat) || stat.version != is_active_node_version) - throw Exception(ErrorCodes::UNKNOWN_STATUS_OF_INSERT, "Replica become inactive while waiting for quorum"); + throw Exception( + ErrorCodes::UNKNOWN_STATUS_OF_INSERT, + "Unknown quorum status. The data was inserted in the local replica but we could not verify quorum. Reason: " + "Replica became inactive while waiting for quorum"); LOG_TRACE(log, "Quorum '{}' for part {} satisfied", quorum_path, part_name); } diff --git a/tests/integration/test_quorum_inserts_parallel/test.py b/tests/integration/test_quorum_inserts_parallel/test.py index 72780c16319..f30f57cc1d6 100644 --- a/tests/integration/test_quorum_inserts_parallel/test.py +++ b/tests/integration/test_quorum_inserts_parallel/test.py @@ -115,9 +115,8 @@ def test_parallel_quorum_actually_quorum(started_cluster): error = node.query_and_get_error( "INSERT INTO q VALUES(3, 'Hi')", settings=settings ) - assert "DB::Exception: Unknown status, client must retry." in error, error assert ( - "DB::Exception: Timeout while waiting for quorum. (TIMEOUT_EXCEEDED)" + "DB::Exception: Unknown quorum status. The data was inserted in the local replica but we could not verify quorum. Reason: Timeout while waiting for quorum" in error ), error From a375b1eab835b2a6be545886a7c686b87d41831c Mon Sep 17 00:00:00 2001 From: Nikita Taranov Date: Tue, 19 Dec 2023 16:41:51 +0100 Subject: [PATCH 112/253] Fix handling of unavailable replicas before first request happened (#57933) --- .../ParallelReplicasReadingCoordinator.cpp | 36 +++++++++++-------- .../ParallelReplicasReadingCoordinator.h | 5 +++ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp b/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp index a2765c071a2..95313654c0e 100644 --- a/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp +++ b/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp @@ -223,13 +223,16 @@ void DefaultCoordinator::updateReadingState(InitialAllRangesAnnouncement announc void DefaultCoordinator::markReplicaAsUnavailable(size_t replica_number) { - LOG_DEBUG(log, "Replica number {} is unavailable", replica_number); + if (stats[replica_number].is_unavailable == false) + { + LOG_DEBUG(log, "Replica number {} is unavailable", replica_number); - ++unavailable_replicas_count; - stats[replica_number].is_unavailable = true; + stats[replica_number].is_unavailable = true; + ++unavailable_replicas_count; - if (sent_initial_requests == replicas_count - unavailable_replicas_count) - finalizeReadingState(); + if (sent_initial_requests == replicas_count - unavailable_replicas_count) + finalizeReadingState(); + } } void DefaultCoordinator::finalizeReadingState() @@ -405,12 +408,13 @@ public: template void InOrderCoordinator::markReplicaAsUnavailable(size_t replica_number) { - LOG_DEBUG(log, "Replica number {} is unavailable", replica_number); + if (stats[replica_number].is_unavailable == false) + { + LOG_DEBUG(log, "Replica number {} is unavailable", replica_number); - stats[replica_number].is_unavailable = true; - ++unavailable_replicas_count; - - /// There is nothing to do else. + stats[replica_number].is_unavailable = true; + ++unavailable_replicas_count; + } } template @@ -577,11 +581,9 @@ void ParallelReplicasReadingCoordinator::markReplicaAsUnavailable(size_t replica std::lock_guard lock(mutex); if (!pimpl) - { - initialize(); - } - - return pimpl->markReplicaAsUnavailable(replica_number); + unavailable_nodes_registered_before_initialization.push_back(replica_number); + else + pimpl->markReplicaAsUnavailable(replica_number); } void ParallelReplicasReadingCoordinator::initialize() @@ -598,8 +600,12 @@ void ParallelReplicasReadingCoordinator::initialize() pimpl = std::make_unique>(replicas_count); break; } + if (progress_callback) pimpl->setProgressCallback(std::move(progress_callback)); + + for (const auto replica : unavailable_nodes_registered_before_initialization) + pimpl->markReplicaAsUnavailable(replica); } ParallelReplicasReadingCoordinator::ParallelReplicasReadingCoordinator(size_t replicas_count_) : replicas_count(replicas_count_) {} diff --git a/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.h b/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.h index 449421797ce..795d7462278 100644 --- a/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.h +++ b/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.h @@ -39,6 +39,11 @@ private: std::atomic initialized{false}; std::unique_ptr pimpl; ProgressCallback progress_callback; // store the callback only to bypass it to coordinator implementation + + /// To initialize `pimpl` we need to know the coordinator mode. We can know it only from initial announcement or regular request. + /// The problem is `markReplicaAsUnavailable` might be called before any of these requests happened. + /// In this case we will remember the numbers of unavailable replicas and apply this knowledge later on initialization. + std::vector unavailable_nodes_registered_before_initialization; }; using ParallelReplicasReadingCoordinatorPtr = std::shared_ptr; From 16a440fc8f2dc6c9fbb64f34997afb07c68984c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Tue, 19 Dec 2023 16:42:16 +0100 Subject: [PATCH 113/253] More verbose errors on 00002_log_and_exception_messages_formatting --- ...nd_exception_messages_formatting.reference | 24 ++-- ..._log_and_exception_messages_formatting.sql | 116 +++++++++++++----- 2 files changed, 100 insertions(+), 40 deletions(-) diff --git a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.reference b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.reference index cd9f0142d45..76351ebbd67 100644 --- a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.reference +++ b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.reference @@ -1,15 +1,15 @@ -runtime messages 0.001 -runtime exceptions 0.05 -unknown runtime exceptions 0.01 -messages shorter than 10 1 -messages shorter than 16 3 -exceptions shorter than 30 3 [] -noisy messages 0.3 -noisy Trace messages 0.16 -noisy Debug messages 0.09 -noisy Info messages 0.05 -noisy Warning messages 0.01 -noisy Error messages 0.02 +runtime messages 0.001 [] +runtime exceptions 0.05 [] +unknown runtime exceptions 0.01 [] +messages shorter than 10 1 [] +messages shorter than 16 1 [] +exceptions shorter than 30 1 [] +noisy messages 0.1 +noisy Trace messages 0.16 +noisy Debug messages 0.09 +noisy Info messages 0.05 +noisy Warning messages 0.01 +noisy Error messages 0.03 no Fatal messages 0 number of too noisy messages 3 number of noisy messages 10 diff --git a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql index 062806baae9..4a30668ec41 100644 --- a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql +++ b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql @@ -9,17 +9,59 @@ create view logs as select * from system.text_log where now() - toIntervalMinute -- Check that we don't have too many messages formatted with fmt::runtime or strings concatenation. -- 0.001 threshold should be always enough, the value was about 0.00025 -select 'runtime messages', greatest(coalesce(sum(length(message_format_string) = 0) / countOrNull(), 0), 0.001) from logs - where message not like '% Received from %clickhouse-staging.com:9440%'; +WITH 0.001 AS threshold +SELECT + 'runtime messages', + greatest(coalesce(sum(length(message_format_string) = 0) / countOrNull(), 0) as v, threshold), + v <= threshold ? [] : + (SELECT groupArray((message, c)) FROM ( + SELECT message, count() as c FROM logs + WHERE + length(message_format_string) = 0 + AND message not like '% Received from %clickhouse-staging.com:9440%' + GROUP BY message ORDER BY c LIMIT 10 + )) +FROM logs +WHERE + message NOT LIKE '% Received from %clickhouse-staging.com:9440%'; -- Check the same for exceptions. The value was 0.03 -select 'runtime exceptions', greatest(coalesce(sum(length(message_format_string) = 0) / countOrNull(), 0), 0.05) from logs - where (message like '%DB::Exception%' or message like '%Coordination::Exception%') - and message not like '% Received from %clickhouse-staging.com:9440%'; +WITH 0.05 AS threshold +SELECT + 'runtime exceptions', + greatest(coalesce(sum(length(message_format_string) = 0) / countOrNull(), 0) as v, threshold), + v <= threshold ? [] : + (SELECT groupArray((message, c)) FROM ( + SELECT message, count() as c FROM logs + WHERE + length(message_format_string) = 0 + AND (message like '%DB::Exception%' or message like '%Coordination::Exception%') + AND message not like '% Received from %clickhouse-staging.com:9440%' + GROUP BY message ORDER BY c LIMIT 10 + )) +FROM logs +WHERE + message NOT LIKE '% Received from %clickhouse-staging.com:9440%' + AND (message like '%DB::Exception%' or message like '%Coordination::Exception%'); + +WITH 0.01 AS threshold +SELECT + 'unknown runtime exceptions', + greatest(coalesce(sum(length(message_format_string) = 0) / countOrNull(), 0) as v, threshold), + v <= threshold ? [] : + (SELECT groupArray((message, c)) FROM ( + SELECT message, count() as c FROM logs + WHERE + length(message_format_string) = 0 + AND (message like '%DB::Exception%' or message like '%Coordination::Exception%') + AND message not like '% Received from %' and message not like '%(SYNTAX_ERROR)%' + GROUP BY message ORDER BY c LIMIT 10 + )) +FROM logs +WHERE + (message like '%DB::Exception%' or message like '%Coordination::Exception%') + AND message not like '% Received from %' and message not like '%(SYNTAX_ERROR)%'; -select 'unknown runtime exceptions', greatest(coalesce(sum(length(message_format_string) = 0) / countOrNull(), 0), 0.01) from logs where - (message like '%DB::Exception%' or message like '%Coordination::Exception%') - and message not like '% Received from %' and message not like '%(SYNTAX_ERROR)%'; -- FIXME some of the following messages are not informative and it has to be fixed create temporary table known_short_messages (s String) as select * from (select @@ -51,15 +93,20 @@ create temporary table known_short_messages (s String) as select * from (select ] as arr) array join arr; -- Check that we don't have too many short meaningless message patterns. +WITH 1 AS max_messages select 'messages shorter than 10', - greatest(uniqExact(message_format_string), 1) + (uniqExact(message_format_string) as c) <= max_messages, + c <= max_messages ? [] : groupUniqArray(message_format_string) from logs where length(message_format_string) < 10 and message_format_string not in known_short_messages; -- Same as above. Feel free to update the threshold or remove this query if really necessary +WITH 3 AS max_messages select 'messages shorter than 16', - greatest(uniqExact(message_format_string), 3) - from logs where length(message_format_string) < 16 and message_format_string not in known_short_messages; + (uniqExact(message_format_string) as c) <= max_messages, + c <= max_messages ? [] : groupUniqArray(message_format_string) + from logs + where length(message_format_string) < 16 and message_format_string not in known_short_messages; -- Unlike above, here we look at length of the formatted message, not format string. Most short format strings are fine because they end up decorated with context from outer or inner exceptions, e.g.: -- "Expected end of line" -> "Code: 117. DB::Exception: Expected end of line: (in file/uri /var/lib/clickhouse/user_files/data_02118): (at row 1)" @@ -68,40 +115,53 @@ select 'messages shorter than 16', -- This table currently doesn't have enough information to do this reliably, so we just regex search for " (ERROR_NAME_IN_CAPS)" and hope that's good enough. -- For the "Code: 123. DB::Exception: " part, we just subtract 26 instead of searching for it. Because sometimes it's not at the start, e.g.: -- "Unexpected error, will try to restart main thread: Code: 341. DB::Exception: Unexpected error: Code: 57. DB::Exception:[...]" +WITH 3 AS max_messages select 'exceptions shorter than 30', - greatest(uniqExact(message_format_string), 3) AS c, - c = 3 ? [] : groupUniqArray(message_format_string) + (uniqExact(message_format_string) as c) <= max_messages, + c <= max_messages ? [] : groupUniqArray(message_format_string) from logs where message ilike '%DB::Exception%' and if(length(extract(message, '(.*)\\([A-Z0-9_]+\\)')) as pref > 0, pref, length(message)) < 30 + 26 and message_format_string not in known_short_messages; - --- Avoid too noisy messages: top 1 message frequency must be less than 30%. We should reduce the threshold -select 'noisy messages', - greatest((select count() from logs group by message_format_string order by count() desc limit 1) / (select count() from logs), 0.30); +-- Avoid too noisy messages: top 1 message frequency must be less than 10%. We should reduce the threshold +WITH 0.10 as threshold +select + 'noisy messages', + greatest(coalesce(((select message_format_string, count() from logs group by message_format_string order by count() desc limit 1) as top_message).2, 0) / (select count() from logs), threshold) as r, + r <= threshold ? '' : top_message.1; -- Same as above, but excluding Test level (actually finds top 1 Trace message) -with ('Access granted: {}{}', '{} -> {}') as frequent_in_tests -select 'noisy Trace messages', - greatest((select count() from logs where level!='Test' and message_format_string not in frequent_in_tests - group by message_format_string order by count() desc limit 1) / (select count() from logs), 0.16); +with 0.16 as threshold +select + 'noisy Trace messages', + greatest(coalesce(((select message_format_string, count() from logs where level = 'Trace' and message_format_string not in ('Access granted: {}{}', '{} -> {}') + group by message_format_string order by count() desc limit 1) as top_message).2, 0) / (select count() from logs), threshold) as r, + r <= threshold ? '' : top_message.1; -- Same as above for Debug +WITH 0.09 as threshold select 'noisy Debug messages', - greatest((select count() from logs where level <= 'Debug' group by message_format_string order by count() desc limit 1) / (select count() from logs), 0.09); + greatest(coalesce(((select message_format_string, count() from logs where level = 'Debug' group by message_format_string order by count() desc limit 1) as top_message).2, 0) / (select count() from logs), threshold) as r, + r <= threshold ? '' : top_message.1; -- Same as above for Info +WITH 0.05 as threshold select 'noisy Info messages', - greatest((select count() from logs where level <= 'Information' group by message_format_string order by count() desc limit 1) / (select count() from logs), 0.05); + greatest(coalesce(((select message_format_string, count() from logs where level = 'Information' group by message_format_string order by count() desc limit 1) as top_message).2, 0) / (select count() from logs), threshold) as r, + r <= threshold ? '' : top_message.1; -- Same as above for Warning -with ('Not enabled four letter command {}') as frequent_in_tests -select 'noisy Warning messages', - greatest(coalesce((select count() from logs where level = 'Warning' and message_format_string not in frequent_in_tests - group by message_format_string order by count() desc limit 1), 0) / (select count() from logs), 0.01); +with 0.01 as threshold +select + 'noisy Warning messages', + greatest(coalesce(((select message_format_string, count() from logs where level = 'Warning' and message_format_string not in ('Not enabled four letter command {}') + group by message_format_string order by count() desc limit 1) as top_message).2, 0) / (select count() from logs), threshold) as r, + r <= threshold ? '' : top_message.1; -- Same as above for Error +WITH 0.03 as threshold select 'noisy Error messages', - greatest(coalesce((select count() from logs where level = 'Error' group by message_format_string order by count() desc limit 1), 0) / (select count() from logs), 0.02); + greatest(coalesce(((select message_format_string, count() from logs where level = 'Error' group by message_format_string order by count() desc limit 1) as top_message).2, 0) / (select count() from logs), threshold) as r, + r <= threshold ? '' : top_message.1; select 'no Fatal messages', count() from logs where level = 'Fatal'; From a69005525b86fd8fd804d40b29bd643faa7d7143 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 19 Dec 2023 16:15:39 +0000 Subject: [PATCH 114/253] Fix function ntile. --- src/Processors/Transforms/WindowTransform.cpp | 128 ++++++++++++------ ...84_parallel_window_functions_bug.reference | 0 .../02884_parallel_window_functions_bug.sql | 84 ++++++++++++ 3 files changed, 174 insertions(+), 38 deletions(-) create mode 100644 tests/queries/0_stateless/02884_parallel_window_functions_bug.reference create mode 100644 tests/queries/0_stateless/02884_parallel_window_functions_bug.sql diff --git a/src/Processors/Transforms/WindowTransform.cpp b/src/Processors/Transforms/WindowTransform.cpp index 7afc7a38aab..6cf874d24ea 100644 --- a/src/Processors/Transforms/WindowTransform.cpp +++ b/src/Processors/Transforms/WindowTransform.cpp @@ -1585,17 +1585,21 @@ struct WindowFunctionExponentialTimeDecayedSum final : public StatefulWindowFunc static constexpr size_t ARGUMENT_VALUE = 0; static constexpr size_t ARGUMENT_TIME = 1; - WindowFunctionExponentialTimeDecayedSum(const std::string & name_, - const DataTypes & argument_types_, const Array & parameters_) - : StatefulWindowFunction(name_, argument_types_, parameters_, std::make_shared()) + static Float64 getDecayLength(const Array & parameters_, const std::string & name_) { if (parameters_.size() != 1) { throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function {} takes exactly one parameter", name_); } - decay_length = applyVisitor(FieldVisitorConvertToNumber(), parameters_[0]); + return applyVisitor(FieldVisitorConvertToNumber(), parameters_[0]); + } + WindowFunctionExponentialTimeDecayedSum(const std::string & name_, + const DataTypes & argument_types_, const Array & parameters_) + : StatefulWindowFunction(name_, argument_types_, parameters_, std::make_shared()) + , decay_length(getDecayLength(parameters_, name_)) + { if (argument_types.size() != 2) { throw Exception(ErrorCodes::BAD_ARGUMENTS, @@ -1670,7 +1674,7 @@ struct WindowFunctionExponentialTimeDecayedSum final : public StatefulWindowFunc } private: - Float64 decay_length; + const Float64 decay_length; }; struct WindowFunctionExponentialTimeDecayedMax final : public WindowFunction @@ -1678,17 +1682,21 @@ struct WindowFunctionExponentialTimeDecayedMax final : public WindowFunction static constexpr size_t ARGUMENT_VALUE = 0; static constexpr size_t ARGUMENT_TIME = 1; - WindowFunctionExponentialTimeDecayedMax(const std::string & name_, - const DataTypes & argument_types_, const Array & parameters_) - : WindowFunction(name_, argument_types_, parameters_, std::make_shared()) + static Float64 getDecayLength(const Array & parameters_, const std::string & name_) { if (parameters_.size() != 1) { throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function {} takes exactly one parameter", name_); } - decay_length = applyVisitor(FieldVisitorConvertToNumber(), parameters_[0]); + return applyVisitor(FieldVisitorConvertToNumber(), parameters_[0]); + } + WindowFunctionExponentialTimeDecayedMax(const std::string & name_, + const DataTypes & argument_types_, const Array & parameters_) + : WindowFunction(name_, argument_types_, parameters_, std::make_shared()) + , decay_length(getDecayLength(parameters_, name_)) + { if (argument_types.size() != 2) { throw Exception(ErrorCodes::BAD_ARGUMENTS, @@ -1742,24 +1750,28 @@ struct WindowFunctionExponentialTimeDecayedMax final : public WindowFunction } private: - Float64 decay_length; + const Float64 decay_length; }; struct WindowFunctionExponentialTimeDecayedCount final : public StatefulWindowFunction { static constexpr size_t ARGUMENT_TIME = 0; - WindowFunctionExponentialTimeDecayedCount(const std::string & name_, - const DataTypes & argument_types_, const Array & parameters_) - : StatefulWindowFunction(name_, argument_types_, parameters_, std::make_shared()) + static Float64 getDecayLength(const Array & parameters_, const std::string & name_) { if (parameters_.size() != 1) { throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function {} takes exactly one parameter", name_); } - decay_length = applyVisitor(FieldVisitorConvertToNumber(), parameters_[0]); + return applyVisitor(FieldVisitorConvertToNumber(), parameters_[0]); + } + WindowFunctionExponentialTimeDecayedCount(const std::string & name_, + const DataTypes & argument_types_, const Array & parameters_) + : StatefulWindowFunction(name_, argument_types_, parameters_, std::make_shared()) + , decay_length(getDecayLength(parameters_, name_)) + { if (argument_types.size() != 1) { throw Exception(ErrorCodes::BAD_ARGUMENTS, @@ -1823,7 +1835,7 @@ struct WindowFunctionExponentialTimeDecayedCount final : public StatefulWindowFu } private: - Float64 decay_length; + const Float64 decay_length; }; struct WindowFunctionExponentialTimeDecayedAvg final : public StatefulWindowFunction @@ -1831,17 +1843,21 @@ struct WindowFunctionExponentialTimeDecayedAvg final : public StatefulWindowFunc static constexpr size_t ARGUMENT_VALUE = 0; static constexpr size_t ARGUMENT_TIME = 1; - WindowFunctionExponentialTimeDecayedAvg(const std::string & name_, - const DataTypes & argument_types_, const Array & parameters_) - : StatefulWindowFunction(name_, argument_types_, parameters_, std::make_shared()) + static Float64 getDecayLength(const Array & parameters_, const std::string & name_) { if (parameters_.size() != 1) { throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function {} takes exactly one parameter", name_); } - decay_length = applyVisitor(FieldVisitorConvertToNumber(), parameters_[0]); + return applyVisitor(FieldVisitorConvertToNumber(), parameters_[0]); + } + WindowFunctionExponentialTimeDecayedAvg(const std::string & name_, + const DataTypes & argument_types_, const Array & parameters_) + : StatefulWindowFunction(name_, argument_types_, parameters_, std::make_shared()) + , decay_length(getDecayLength(parameters_, name_)) + { if (argument_types.size() != 2) { throw Exception(ErrorCodes::BAD_ARGUMENTS, @@ -1933,7 +1949,7 @@ struct WindowFunctionExponentialTimeDecayedAvg final : public StatefulWindowFunc } private: - Float64 decay_length; + const Float64 decay_length; }; struct WindowFunctionRowNumber final : public WindowFunction @@ -1955,12 +1971,30 @@ struct WindowFunctionRowNumber final : public WindowFunction } }; +namespace +{ + struct NtileState + { + UInt64 buckets = 0; + RowNumber start_row; + UInt64 current_partition_rows = 0; + UInt64 current_partition_inserted_row = 0; + + void windowInsertResultInto( + const WindowTransform * transform, + size_t function_index, + const DataTypes & argument_types); + + static void checkWindowFrameType(const WindowTransform * transform); + }; +} + // Usage: ntile(n). n is the number of buckets. -struct WindowFunctionNtile final : public WindowFunction +struct WindowFunctionNtile final : public StatefulWindowFunction { WindowFunctionNtile(const std::string & name_, const DataTypes & argument_types_, const Array & parameters_) - : WindowFunction(name_, argument_types_, parameters_, std::make_shared()) + : StatefulWindowFunction(name_, argument_types_, parameters_, std::make_shared()) { if (argument_types.size() != 1) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function {} takes exactly one argument", name_); @@ -1982,6 +2016,19 @@ struct WindowFunctionNtile final : public WindowFunction void windowInsertResultInto(const WindowTransform * transform, size_t function_index) override + { + const auto & workspace = transform->workspaces[function_index]; + auto & state = getState(workspace); + state.windowInsertResultInto(transform, function_index, argument_types); + } +}; + +namespace +{ + void NtileState::windowInsertResultInto( + const WindowTransform * transform, + size_t function_index, + const DataTypes & argument_types) { if (!buckets) [[unlikely]] { @@ -2072,13 +2119,8 @@ struct WindowFunctionNtile final : public WindowFunction bucket_num += 1; } } -private: - UInt64 buckets = 0; - RowNumber start_row; - UInt64 current_partition_rows = 0; - UInt64 current_partition_inserted_row = 0; - static void checkWindowFrameType(const WindowTransform * transform) + void NtileState::checkWindowFrameType(const WindowTransform * transform) { if (transform->order_by_indices.empty()) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Window frame for 'ntile' function must have ORDER BY clause"); @@ -2093,7 +2135,7 @@ private: throw Exception(ErrorCodes::BAD_ARGUMENTS, "Window frame for function 'ntile' should be 'ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING'"); } } -}; +} // ClickHouse-specific variant of lag/lead that respects the window frame. template @@ -2298,16 +2340,18 @@ struct NonNegativeDerivativeState Float64 previous_timestamp = 0; }; -// nonNegativeDerivative(metric_column, timestamp_column[, INTERVAL 1 SECOND]) -struct WindowFunctionNonNegativeDerivative final : public StatefulWindowFunction +struct NonNegativeDerivativeParams { static constexpr size_t ARGUMENT_METRIC = 0; static constexpr size_t ARGUMENT_TIMESTAMP = 1; static constexpr size_t ARGUMENT_INTERVAL = 2; - WindowFunctionNonNegativeDerivative(const std::string & name_, - const DataTypes & argument_types_, const Array & parameters_) - : StatefulWindowFunction(name_, argument_types_, parameters_, std::make_shared()) + Float64 interval_length = 1; + bool interval_specified = false; + Int64 ts_scale_multiplier = 0; + + NonNegativeDerivativeParams( + const std::string & name_, const DataTypes & argument_types, const Array & parameters) { if (!parameters.empty()) { @@ -2365,6 +2409,18 @@ struct WindowFunctionNonNegativeDerivative final : public StatefulWindowFunction interval_specified = true; } } +}; + +// nonNegativeDerivative(metric_column, timestamp_column[, INTERVAL 1 SECOND]) +struct WindowFunctionNonNegativeDerivative final : public StatefulWindowFunction, public NonNegativeDerivativeParams +{ + using Params = NonNegativeDerivativeParams; + + WindowFunctionNonNegativeDerivative(const std::string & name_, + const DataTypes & argument_types_, const Array & parameters_) + : StatefulWindowFunction(name_, argument_types_, parameters_, std::make_shared()) + , NonNegativeDerivativeParams(name, argument_types, parameters) + {} bool allocatesMemoryInArena() const override { return false; } @@ -2405,10 +2461,6 @@ struct WindowFunctionNonNegativeDerivative final : public StatefulWindowFunction WindowFunctionHelpers::setValueToOutputColumn(transform, function_index, result >= 0 ? result : 0); } -private: - Float64 interval_length = 1; - bool interval_specified = false; - Int64 ts_scale_multiplier = 0; }; diff --git a/tests/queries/0_stateless/02884_parallel_window_functions_bug.reference b/tests/queries/0_stateless/02884_parallel_window_functions_bug.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02884_parallel_window_functions_bug.sql b/tests/queries/0_stateless/02884_parallel_window_functions_bug.sql new file mode 100644 index 00000000000..84bc69e2310 --- /dev/null +++ b/tests/queries/0_stateless/02884_parallel_window_functions_bug.sql @@ -0,0 +1,84 @@ +CREATE TABLE IF NOT EXISTS posts +( + `page_id` LowCardinality(String), + `post_id` String CODEC(LZ4), + `host_id` UInt32 CODEC(T64, LZ4), + `path_id` UInt32, + `created` DateTime CODEC(T64, LZ4), + `as_of` DateTime CODEC(T64, LZ4) +) +ENGINE = ReplacingMergeTree(as_of) +PARTITION BY toStartOfMonth(created) +ORDER BY (page_id, post_id); + +CREATE TABLE IF NOT EXISTS post_metrics +( + `page_id` LowCardinality(String), + `post_id` String CODEC(LZ4), + `created` DateTime CODEC(T64, LZ4), + `impressions` UInt32 CODEC(T64, LZ4), + `clicks` UInt32 CODEC(T64, LZ4), + `as_of` DateTime CODEC(T64, LZ4) +) +ENGINE = ReplacingMergeTree(as_of) +PARTITION BY toStartOfMonth(created) +ORDER BY (page_id, post_id); + +INSERT INTO posts SELECT + repeat('a', (number % 10) + 1), + toString(number), + number % 10, + number, + now() - toIntervalMinute(number), + now() +FROM numbers(100000); + +INSERT INTO post_metrics SELECT + repeat('a', (number % 10) + 1), + toString(number), + now() - toIntervalMinute(number), + number * 100, + number * 10, + now() +FROM numbers(100000); + +SELECT + host_id, + path_id, + max(rank) AS rank +FROM +( + WITH + as_of_posts AS + ( + SELECT + *, + row_number() OVER (PARTITION BY (page_id, post_id) ORDER BY as_of DESC) AS row_num + FROM posts + WHERE (created >= subtractHours(now(), 24)) AND (host_id > 0) + ), + as_of_post_metrics AS + ( + SELECT + *, + row_number() OVER (PARTITION BY (page_id, post_id) ORDER BY as_of DESC) AS row_num + FROM post_metrics + WHERE created >= subtractHours(now(), 24) + ) + SELECT + page_id, + post_id, + host_id, + path_id, + impressions, + clicks, + ntile(20) OVER (PARTITION BY page_id ORDER BY clicks ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS rank + FROM as_of_posts + GLOBAL LEFT JOIN as_of_post_metrics USING (page_id, post_id, row_num) + WHERE (row_num = 1) AND (impressions > 0) +) AS t +WHERE t.rank > 18 +GROUP BY + host_id, + path_id +FORMAT Null; From ddb582c8b41ab89d5be29a988d1cb5f6147a97b0 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 19 Dec 2023 16:52:50 +0000 Subject: [PATCH 115/253] Make windowInsertResultInto constant --- src/Processors/Transforms/WindowTransform.cpp | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Processors/Transforms/WindowTransform.cpp b/src/Processors/Transforms/WindowTransform.cpp index 6cf874d24ea..47b5b900400 100644 --- a/src/Processors/Transforms/WindowTransform.cpp +++ b/src/Processors/Transforms/WindowTransform.cpp @@ -67,7 +67,7 @@ public: // Must insert the result for current_row. virtual void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) = 0; + size_t function_index) const = 0; virtual std::optional getDefaultFrame() const { return {}; } }; @@ -1463,7 +1463,7 @@ struct WindowFunctionRank final : public WindowFunction bool allocatesMemoryInArena() const override { return false; } void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) override + size_t function_index) const override { IColumn & to = *transform->blockAt(transform->current_row) .output_columns[function_index]; @@ -1482,7 +1482,7 @@ struct WindowFunctionDenseRank final : public WindowFunction bool allocatesMemoryInArena() const override { return false; } void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) override + size_t function_index) const override { IColumn & to = *transform->blockAt(transform->current_row) .output_columns[function_index]; @@ -1561,7 +1561,7 @@ struct StatefulWindowFunction : public WindowFunction bool hasTrivialDestructor() const override { return std::is_trivially_destructible_v; } - State & getState(const WindowFunctionWorkspace & workspace) + State & getState(const WindowFunctionWorkspace & workspace) const { return *static_cast(static_cast(workspace.aggregate_function_state.data())); } @@ -1626,7 +1626,7 @@ struct WindowFunctionExponentialTimeDecayedSum final : public StatefulWindowFunc bool allocatesMemoryInArena() const override { return false; } void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) override + size_t function_index) const override { const auto & workspace = transform->workspaces[function_index]; auto & state = getState(workspace); @@ -1723,7 +1723,7 @@ struct WindowFunctionExponentialTimeDecayedMax final : public WindowFunction bool allocatesMemoryInArena() const override { return false; } void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) override + size_t function_index) const override { Float64 result = std::numeric_limits::quiet_NaN(); @@ -1790,7 +1790,7 @@ struct WindowFunctionExponentialTimeDecayedCount final : public StatefulWindowFu bool allocatesMemoryInArena() const override { return false; } void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) override + size_t function_index) const override { const auto & workspace = transform->workspaces[function_index]; auto & state = getState(workspace); @@ -1884,7 +1884,7 @@ struct WindowFunctionExponentialTimeDecayedAvg final : public StatefulWindowFunc bool allocatesMemoryInArena() const override { return false; } void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) override + size_t function_index) const override { const auto & workspace = transform->workspaces[function_index]; auto & state = getState(workspace); @@ -1962,7 +1962,7 @@ struct WindowFunctionRowNumber final : public WindowFunction bool allocatesMemoryInArena() const override { return false; } void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) override + size_t function_index) const override { IColumn & to = *transform->blockAt(transform->current_row) .output_columns[function_index]; @@ -2015,7 +2015,7 @@ struct WindowFunctionNtile final : public StatefulWindowFunction } void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) override + size_t function_index) const override { const auto & workspace = transform->workspaces[function_index]; auto & state = getState(workspace); @@ -2207,7 +2207,7 @@ struct WindowFunctionLagLeadInFrame final : public WindowFunction bool allocatesMemoryInArena() const override { return false; } void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) override + size_t function_index) const override { const auto & current_block = transform->blockAt(transform->current_row); IColumn & to = *current_block.output_columns[function_index]; @@ -2297,7 +2297,7 @@ struct WindowFunctionNthValue final : public WindowFunction bool allocatesMemoryInArena() const override { return false; } void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) override + size_t function_index) const override { const auto & current_block = transform->blockAt(transform->current_row); IColumn & to = *current_block.output_columns[function_index]; @@ -2425,7 +2425,7 @@ struct WindowFunctionNonNegativeDerivative final : public StatefulWindowFunction bool allocatesMemoryInArena() const override { return false; } void windowInsertResultInto(const WindowTransform * transform, - size_t function_index) override + size_t function_index) const override { const auto & current_block = transform->blockAt(transform->current_row); const auto & workspace = transform->workspaces[function_index]; From b4d0d63259a38d2ca8ed30379d384fdb32c591b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Tue, 19 Dec 2023 18:03:57 +0100 Subject: [PATCH 116/253] Happy new year --- tests/queries/0_stateless/02833_local_with_dialect.reference | 1 - tests/queries/0_stateless/02833_local_with_dialect.sh | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/02833_local_with_dialect.reference b/tests/queries/0_stateless/02833_local_with_dialect.reference index dbb67375997..573541ac970 100644 --- a/tests/queries/0_stateless/02833_local_with_dialect.reference +++ b/tests/queries/0_stateless/02833_local_with_dialect.reference @@ -1,2 +1 @@ 0 -[?2004h[?2004lBye. diff --git a/tests/queries/0_stateless/02833_local_with_dialect.sh b/tests/queries/0_stateless/02833_local_with_dialect.sh index 012a6d91269..de009961cba 100755 --- a/tests/queries/0_stateless/02833_local_with_dialect.sh +++ b/tests/queries/0_stateless/02833_local_with_dialect.sh @@ -6,4 +6,5 @@ CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) . "$CUR_DIR"/../shell_config.sh -echo "exit" | ${CLICKHOUSE_LOCAL} --query "from s\"SELECT * FROM numbers(1)\"" --dialect prql --interactive +# Remove last line since the good bye message changes depending on the date +echo "exit" | ${CLICKHOUSE_LOCAL} --query "from s\"SELECT * FROM numbers(1)\"" --dialect prql --interactive | head -n -1 From 9510b2ccfee2c58787ddeebf9bf86e30a5f40668 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 19 Dec 2023 17:57:52 +0000 Subject: [PATCH 117/253] Follow up for 57691 --- src/Processors/Transforms/AggregatingTransform.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Processors/Transforms/AggregatingTransform.cpp b/src/Processors/Transforms/AggregatingTransform.cpp index 355271e0c05..ecf8163a9d9 100644 --- a/src/Processors/Transforms/AggregatingTransform.cpp +++ b/src/Processors/Transforms/AggregatingTransform.cpp @@ -377,9 +377,7 @@ private: auto & output = outputs.front(); auto chunk = std::move(single_level_chunks.back()); single_level_chunks.pop_back(); - const auto has_rows = chunk.hasRows(); - if (has_rows) - output.push(std::move(chunk)); + output.push(std::move(chunk)); if (finished && single_level_chunks.empty()) { @@ -387,7 +385,7 @@ private: return Status::Finished; } - return has_rows ? Status::PortFull : Status::Ready; + return Status::PortFull; } /// Read all sources and try to push current bucket. @@ -466,7 +464,8 @@ private: auto block = params->aggregator.prepareBlockAndFillWithoutKey( *first, params->final, first->type != AggregatedDataVariants::Type::without_key); - single_level_chunks.emplace_back(convertToChunk(block)); + if (block.rows() > 0) + single_level_chunks.emplace_back(convertToChunk(block)); } } @@ -493,7 +492,8 @@ private: auto blocks = params->aggregator.prepareBlockAndFillSingleLevel(*first, params->final); for (auto & block : blocks) - single_level_chunks.emplace_back(convertToChunk(block)); + if (block.rows() > 0) + single_level_chunks.emplace_back(convertToChunk(block)); finished = true; data.reset(); From 58f75c96790e54bce9e55e51c31f840b577d9c2b Mon Sep 17 00:00:00 2001 From: Julia Kartseva Date: Tue, 19 Dec 2023 10:17:15 -0800 Subject: [PATCH 118/253] Update src/Storages/StorageFuzzJSON.cpp Co-authored-by: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> --- src/Storages/StorageFuzzJSON.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/StorageFuzzJSON.cpp b/src/Storages/StorageFuzzJSON.cpp index 4d94fe3cdb0..87790dd2fdc 100644 --- a/src/Storages/StorageFuzzJSON.cpp +++ b/src/Storages/StorageFuzzJSON.cpp @@ -481,7 +481,7 @@ protected: { Columns columns; columns.reserve(block_header.columns()); - for (const auto& col : block_header) + for (const auto & col : block_header) { chassert(col.type->getTypeId() == TypeIndex::String); columns.emplace_back(createColumn()); From 4e418a89d91f96eecc73daca0f65d3202f541c79 Mon Sep 17 00:00:00 2001 From: yariks5s Date: Tue, 19 Dec 2023 19:00:37 +0000 Subject: [PATCH 119/253] fixed suggestions --- .../sql-reference/statements/select/join.md | 29 ++- src/Client/Suggest.cpp | 2 +- src/Interpreters/PasteJoin.h | 8 +- src/Planner/PlannerJoinTree.cpp | 2 +- src/Planner/PlannerJoins.cpp | 3 + .../Transforms/PasteJoinTransform.cpp | 176 ++---------------- .../Transforms/PasteJoinTransform.h | 1 + .../0_stateless/02933_paste_join.reference | 46 +++++ .../queries/0_stateless/02933_paste_join.sql | 26 +++ 9 files changed, 126 insertions(+), 167 deletions(-) diff --git a/docs/en/sql-reference/statements/select/join.md b/docs/en/sql-reference/statements/select/join.md index ab6d9ef2848..37fe1ef45e4 100644 --- a/docs/en/sql-reference/statements/select/join.md +++ b/docs/en/sql-reference/statements/select/join.md @@ -34,7 +34,6 @@ All standard [SQL JOIN](https://en.wikipedia.org/wiki/Join_(SQL)) types are supp - `RIGHT OUTER JOIN`, non-matching rows from right table are returned in addition to matching rows. - `FULL OUTER JOIN`, non-matching rows from both tables are returned in addition to matching rows. - `CROSS JOIN`, produces cartesian product of whole tables, “join keys” are **not** specified. -- `PASTE JOIN`, allow to join tables without using of `ON` clause. `JOIN` without specified type implies `INNER`. Keyword `OUTER` can be safely omitted. Alternative syntax for `CROSS JOIN` is specifying multiple tables in [FROM clause](../../../sql-reference/statements/select/from.md) separated by commas. @@ -44,6 +43,7 @@ Additional join types available in ClickHouse: - `LEFT ANTI JOIN` and `RIGHT ANTI JOIN`, a blacklist on “join keys”, without producing a cartesian product. - `LEFT ANY JOIN`, `RIGHT ANY JOIN` and `INNER ANY JOIN`, partially (for opposite side of `LEFT` and `RIGHT`) or completely (for `INNER` and `FULL`) disables the cartesian product for standard `JOIN` types. - `ASOF JOIN` and `LEFT ASOF JOIN`, joining sequences with a non-exact match. `ASOF JOIN` usage is described below. +- `PASTE JOIN`, performs a horizontal concatenation of two tables. :::note When [join_algorithm](../../../operations/settings/settings.md#settings-join_algorithm) is set to `partial_merge`, `RIGHT JOIN` and `FULL JOIN` are supported only with `ALL` strictness (`SEMI`, `ANTI`, `ANY`, and `ASOF` are not supported). @@ -270,6 +270,33 @@ For example, consider the following tables: `ASOF` join is **not** supported in the [Join](../../../engines/table-engines/special/join.md) table engine. ::: +## PASTE JOIN Usage + +The result of `PASTE JOIN` is a table that contains all columns from left subquery followed by all columns from the right subqiery. +The rows are matched based on their positions in the original tables (the order of rows should be defined). +If the subqueries return a different number of rows, extra rows will be cut. + +Example: +```SQL +SELECT * +FROM +( + SELECT number AS a + FROM numbers(2) +) AS t1 +PASTE JOIN +( + SELECT number AS a + FROM numbers(2) + ORDER BY a DESC +) AS t2 + +┌─a─┬─t2.a─┐ +│ 0 │ 1 │ +│ 1 │ 0 │ +└───┴──────┘ +``` + ## Distributed JOIN There are two ways to execute join involving distributed tables: diff --git a/src/Client/Suggest.cpp b/src/Client/Suggest.cpp index cbcccdf9b28..21a43cb6da1 100644 --- a/src/Client/Suggest.cpp +++ b/src/Client/Suggest.cpp @@ -45,7 +45,7 @@ Suggest::Suggest() "IN", "KILL", "QUERY", "SYNC", "ASYNC", "TEST", "BETWEEN", "TRUNCATE", "USER", "ROLE", "PROFILE", "QUOTA", "POLICY", "ROW", "GRANT", "REVOKE", "OPTION", "ADMIN", "EXCEPT", "REPLACE", "IDENTIFIED", "HOST", "NAME", "READONLY", "WRITABLE", "PERMISSIVE", "FOR", "RESTRICTIVE", "RANDOMIZED", - "INTERVAL", "LIMITS", "ONLY", "TRACKING", "IP", "REGEXP", "ILIKE", "CLEANUP", "APPEND" + "INTERVAL", "LIMITS", "ONLY", "TRACKING", "IP", "REGEXP", "ILIKE", "CLEANUP", "APPEND", "PASTE" }); } diff --git a/src/Interpreters/PasteJoin.h b/src/Interpreters/PasteJoin.h index dce0bc1721c..df7bb2f280c 100644 --- a/src/Interpreters/PasteJoin.h +++ b/src/Interpreters/PasteJoin.h @@ -40,9 +40,13 @@ public: bool support_storage = !table_join->isSpecialStorage(); /// Key column can change nullability and it's not handled on type conversion stage, so algorithm should be aware of it - bool support_using_and_nulls = !table_join->hasUsing() || !table_join->joinUseNulls(); + bool support_using = !table_join->hasUsing(); - return support_using_and_nulls && support_storage; + bool check_strictness = table_join->strictness() == JoinStrictness::All; + + bool if_has_keys = table_join->getClauses().empty(); + + return support_using && support_storage && check_strictness && if_has_keys; } void checkTypesOfKeys(const Block & /*left_block*/) const override diff --git a/src/Planner/PlannerJoinTree.cpp b/src/Planner/PlannerJoinTree.cpp index 53a006c2eb3..074bbdea397 100644 --- a/src/Planner/PlannerJoinTree.cpp +++ b/src/Planner/PlannerJoinTree.cpp @@ -1312,7 +1312,7 @@ JoinTreeQueryPlan buildQueryPlanForJoinNode(const QueryTreeNodePtr & join_table_ return step_raw_ptr; }; - if (join_algorithm->pipelineType() == JoinPipelineType::YShaped) + if (join_algorithm->pipelineType() == JoinPipelineType::YShaped && join_kind != JoinKind::Paste) { const auto & join_clause = table_join->getOnlyClause(); diff --git a/src/Planner/PlannerJoins.cpp b/src/Planner/PlannerJoins.cpp index 5fda2a8617d..5e9de4dedcf 100644 --- a/src/Planner/PlannerJoins.cpp +++ b/src/Planner/PlannerJoins.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -653,6 +654,8 @@ static std::shared_ptr tryCreateJoin(JoinAlgorithm algorithm, const Block & right_table_expression_header, const PlannerContextPtr & planner_context) { + if (table_join->kind() == JoinKind::Paste) + return std::make_shared(table_join, right_table_expression_header); /// Direct JOIN with special storages that support key value access. For example JOIN with Dictionary if (algorithm == JoinAlgorithm::DIRECT || algorithm == JoinAlgorithm::DEFAULT) { diff --git a/src/Processors/Transforms/PasteJoinTransform.cpp b/src/Processors/Transforms/PasteJoinTransform.cpp index 25d733d5b2d..ff3e2fb85e5 100644 --- a/src/Processors/Transforms/PasteJoinTransform.cpp +++ b/src/Processors/Transforms/PasteJoinTransform.cpp @@ -26,86 +26,6 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; } -namespace -{ - -template -int nullableCompareAt(const IColumn & left_column, const IColumn & right_column, size_t lhs_pos, size_t rhs_pos, int null_direction_hint = 1) -{ - if constexpr (has_left_nulls && has_right_nulls) - { - const auto * left_nullable = checkAndGetColumn(left_column); - const auto * right_nullable = checkAndGetColumn(right_column); - - if (left_nullable && right_nullable) - { - int res = left_nullable->compareAt(lhs_pos, rhs_pos, right_column, null_direction_hint); - if (res) - return res; - - /// NULL != NULL case - if (left_nullable->isNullAt(lhs_pos)) - return null_direction_hint; - - return 0; - } - } - - if constexpr (has_left_nulls) - { - if (const auto * left_nullable = checkAndGetColumn(left_column)) - { - if (left_nullable->isNullAt(lhs_pos)) - return null_direction_hint; - return left_nullable->getNestedColumn().compareAt(lhs_pos, rhs_pos, right_column, null_direction_hint); - } - } - - if constexpr (has_right_nulls) - { - if (const auto * right_nullable = checkAndGetColumn(right_column)) - { - if (right_nullable->isNullAt(rhs_pos)) - return -null_direction_hint; - return left_column.compareAt(lhs_pos, rhs_pos, right_nullable->getNestedColumn(), null_direction_hint); - } - } - - return left_column.compareAt(lhs_pos, rhs_pos, right_column, null_direction_hint); -} - -ColumnPtr replicateRow(const IColumn & column, size_t num) -{ - MutableColumnPtr res = column.cloneEmpty(); - res->insertManyFrom(column, 0, num); - return res; -} - -template -void copyColumnsResized(const TColumns & cols, size_t start, size_t size, Chunk & result_chunk) -{ - for (const auto & col : cols) - { - if (col->empty()) - { - /// add defaults - result_chunk.addColumn(col->cloneResized(size)); - } - else if (col->size() == 1) - { - /// copy same row n times - result_chunk.addColumn(replicateRow(*col, size)); - } - else - { - /// cut column - assert(start + size <= col->size()); - result_chunk.addColumn(col->cut(start, size)); - } - } -} - -} PasteJoinAlgorithm::PasteJoinAlgorithm( JoinPtr table_join_, @@ -125,23 +45,6 @@ PasteJoinAlgorithm::PasteJoinAlgorithm( auto kind = table_join->getTableJoin().kind(); if (!isPaste(kind)) throw Exception(ErrorCodes::NOT_IMPLEMENTED, "PasteJoinAlgorithm is not implemented for kind {}", kind); - - for (const auto & [left_key, right_key] : table_join->getTableJoin().leftToRightKeyRemap()) - { - size_t left_idx = input_headers[0].getPositionByName(left_key); - size_t right_idx = input_headers[1].getPositionByName(right_key); - left_to_right_key_remap[left_idx] = right_idx; - } -} - -void PasteJoinAlgorithm::logElapsed(double seconds) -{ - LOG_TRACE(log, - "Finished pocessing in {} seconds" - ", left: {} blocks, {} rows; right: {} blocks, {} rows" - ", max blocks loaded to memory: {}", - seconds, stat.num_blocks[0], stat.num_rows[0], stat.num_blocks[1], stat.num_rows[1], - stat.max_blocks_loaded); } static void prepareChunk(Chunk & chunk) @@ -151,8 +54,6 @@ static void prepareChunk(Chunk & chunk) auto num_rows = chunk.getNumRows(); auto columns = chunk.detachColumns(); - for (auto & column : columns) - column = column->convertToFullColumnIfConst(); chunk.setColumns(std::move(columns), num_rows); } @@ -176,76 +77,30 @@ void PasteJoinAlgorithm::consume(Input & input, size_t source_num) if (input.permutation) throw DB::Exception(ErrorCodes::NOT_IMPLEMENTED, "permutation is not supported"); - if (input.chunk) - { - stat.num_blocks[source_num] += 1; - stat.num_rows[source_num] += input.chunk.getNumRows(); - } + last_used_row[source_num] = 0; prepareChunk(input.chunk); chunks[source_num] = std::move(input.chunk); } -/// if `source_num == 0` get data from left cursor and fill defaults at right -/// otherwise - vice versa -Chunk PasteJoinAlgorithm::createBlockWithDefaults(size_t source_num, size_t start, size_t num_rows) const -{ - ColumnRawPtrs cols; - { - const auto & columns_left = chunks[0].getColumns(); - const auto & columns_right = chunks[1].getColumns(); - - for (size_t i = 0; i < columns_left.size(); ++i) - { - if (auto it = left_to_right_key_remap.find(i); source_num == 0 || it == left_to_right_key_remap.end()) - { - cols.push_back(columns_left[i].get()); - } - else - { - cols.push_back(columns_right[it->second].get()); - } - } - - for (const auto & col : columns_right) - { - cols.push_back(col.get()); - } - } - - Chunk result_chunk; - copyColumnsResized(cols, start, num_rows, result_chunk); - return result_chunk; -} - -enum ChunkToCut -{ - First, - Second, - None, -}; - IMergingAlgorithm::Status PasteJoinAlgorithm::merge() { - PaddedPODArray indices[2]; + if (chunks[0].empty() || chunks[1].empty()) + return Status({}, true); + if (last_used_row[0] >= chunks[0].getNumRows()) + return Status(0); + if (last_used_row[1] >= chunks[1].getNumRows()) + return Status(1); + /// We have unused rows from both inputs + size_t result_num_rows = std::min(chunks[0].getNumRows() - last_used_row[0], chunks[1].getNumRows() - last_used_row[1]); Chunk result; for (size_t source_num = 0; source_num < 2; ++source_num) - { - ChunkToCut to_cut = None; - if (chunks[0].getNumRows() != chunks[1].getNumRows()) - to_cut = chunks[0].getNumRows() > chunks[1].getNumRows() ? ChunkToCut::First : ChunkToCut::Second; for (const auto & col : chunks[source_num].getColumns()) - { - if (to_cut == ChunkToCut::First) - result.addColumn(col->cut(0, chunks[1].getNumRows())); - else if (to_cut == ChunkToCut::Second) - result.addColumn(col->cut(0, chunks[0].getNumRows())); - else - result.addColumn(col); - } - } - return Status(std::move(result), true); + result.addColumn(col->cut(last_used_row[source_num], result_num_rows)); + last_used_row[0] += result_num_rows; + last_used_row[1] += result_num_rows; + return Status(std::move(result)); } PasteJoinTransform::PasteJoinTransform( @@ -267,9 +122,6 @@ PasteJoinTransform::PasteJoinTransform( LOG_TRACE(log, "Use PasteJoinTransform"); } -void PasteJoinTransform::onFinish() -{ - algorithm.logElapsed(total_stopwatch.elapsedSeconds()); -} +void PasteJoinTransform::onFinish() {}; } diff --git a/src/Processors/Transforms/PasteJoinTransform.h b/src/Processors/Transforms/PasteJoinTransform.h index 4b8c125bf9a..7ecf70e18dc 100644 --- a/src/Processors/Transforms/PasteJoinTransform.h +++ b/src/Processors/Transforms/PasteJoinTransform.h @@ -62,6 +62,7 @@ private: Statistic stat; Poco::Logger * log; + UInt64 last_used_row[2] = {0, 0}; }; class PasteJoinTransform final : public IMergingTransform diff --git a/tests/queries/0_stateless/02933_paste_join.reference b/tests/queries/0_stateless/02933_paste_join.reference index abd97ded89c..84ae5987926 100644 --- a/tests/queries/0_stateless/02933_paste_join.reference +++ b/tests/queries/0_stateless/02933_paste_join.reference @@ -18,11 +18,57 @@ 7 2 8 1 9 0 +1 2 0 0 1 1 2 2 3 3 4 4 5 5 +6 0 +7 1 +8 2 +9 3 +10 4 0 0 1 1 +0 0 0 0 +1 1 1 1 +2 2 2 2 +3 3 3 3 +4 4 4 4 +5 5 5 5 +6 6 6 6 +7 7 7 7 +8 8 8 8 +9 9 9 9 +10 10 10 10 +11 11 11 11 +12 12 12 12 +13 13 13 13 +14 14 14 14 +15 15 15 15 +16 16 16 16 +17 17 17 17 +18 18 18 18 +19 19 19 19 +20 20 20 20 +21 21 21 21 +22 22 22 22 +23 23 23 23 +24 24 24 24 +25 25 25 25 +26 26 26 26 +27 27 27 27 +28 28 28 28 +29 29 29 29 +UInt64 +UInt64 +UInt64 +UInt64 +UInt64 +UInt64 +UInt64 +UInt64 +UInt64 +UInt64 diff --git a/tests/queries/0_stateless/02933_paste_join.sql b/tests/queries/0_stateless/02933_paste_join.sql index e272ea04f1a..eee41a52044 100644 --- a/tests/queries/0_stateless/02933_paste_join.sql +++ b/tests/queries/0_stateless/02933_paste_join.sql @@ -3,8 +3,34 @@ select * from (SELECT number as a FROM numbers(10)) t1 PASTE JOIN (select number create table if not exists test (num UInt64) engine=Memory; insert into test select number from numbers(6); insert into test select number from numbers(5); +SELECT * FROM (SELECT 1) t1 PASTE JOIN (SELECT 2) SETTINGS joined_subquery_requires_alias=0; select * from (SELECT number as a FROM numbers(11)) t1 PASTE JOIN test t2 SETTINGS max_threads=1; select * from (SELECT number as a FROM numbers(11)) t1 PASTE JOIN (select * from test limit 2) t2 SETTINGs max_threads=1; +CREATE TABLE t1 (a UInt64, b UInt64) ENGINE = Memory; +INSERT INTO t1 SELECT number, number FROM numbers(0, 3); +INSERT INTO t1 SELECT number, number FROM numbers(3, 2); +INSERT INTO t1 SELECT number, number FROM numbers(5, 7); +INSERT INTO t1 SELECT number, number FROM numbers(12, 2); +INSERT INTO t1 SELECT number, number FROM numbers(14, 1); +INSERT INTO t1 SELECT number, number FROM numbers(15, 2); +INSERT INTO t1 SELECT number, number FROM numbers(17, 1); +INSERT INTO t1 SELECT number, number FROM numbers(18, 2); +INSERT INTO t1 SELECT number, number FROM numbers(20, 2); +INSERT INTO t1 SELECT number, number FROM numbers(22, 2); +INSERT INTO t1 SELECT number, number FROM numbers(24, 2); +INSERT INTO t1 SELECT number, number FROM numbers(26, 2); +INSERT INTO t1 SELECT number, number FROM numbers(28, 2); + + +CREATE TABLE t2 (a UInt64, b UInt64) ENGINE = Memory; +INSERT INTO t2 SELECT number, number FROM numbers(0, 2); +INSERT INTO t2 SELECT number, number FROM numbers(2, 3); +INSERT INTO t2 SELECT number, number FROM numbers(5, 5); +INSERT INTO t2 SELECT number, number FROM numbers(10, 5); +INSERT INTO t2 SELECT number, number FROM numbers(15, 15); + +SELECT * FROM ( SELECT * from t1 ) t1 PASTE JOIN ( SELECT * from t2 ) t2 SETTINGS max_threads = 1; +SELECT toTypeName(a) FROM (SELECT number as a FROM numbers(11)) t1 PASTE JOIN (select number as a from numbers(10)) t2 SETTINGS join_use_nulls = 1; select * from (SELECT number as a FROM numbers(10)) t1 ANY PASTE JOIN (select number as a from numbers(10)) t2; -- { clientError SYNTAX_ERROR } select * from (SELECT number as a FROM numbers(10)) t1 ALL PASTE JOIN (select number as a from numbers(10)) t2; -- { clientError SYNTAX_ERROR } select * from (SELECT number as a FROM numbers_mt(10)) t1 PASTE JOIN (select number as a from numbers(10) ORDER BY a DESC) t2 SETTINGS max_block_size=3; -- { serverError BAD_ARGUMENTS } From d8383377ea12ac32f88a8c19231687f73f90f6a1 Mon Sep 17 00:00:00 2001 From: Max K Date: Tue, 19 Dec 2023 20:14:47 +0100 Subject: [PATCH 120/253] always run ast_fuzz and sqllancer #no-merge-commit (#58049) --- tests/ci/ci.py | 8 +++++--- tests/ci/ci_config.py | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/ci/ci.py b/tests/ci/ci.py index bed12d54fe3..e3cac7c6ec5 100644 --- a/tests/ci/ci.py +++ b/tests/ci/ci.py @@ -376,6 +376,8 @@ def _configure_jobs( if job_config.run_by_label in pr_labels: for batch in range(num_batches): # type: ignore batches_to_do.append(batch) + elif job_config.run_always: + batches_to_do.append(batch) else: # this job controlled by digest, add to todo if it's not successfully done before for batch in range(num_batches): # type: ignore @@ -400,10 +402,10 @@ def _configure_jobs( for token in commit_tokens if token.startswith("#job_") ] - assert any( - len(x) > 1 for x in requested_jobs - ), f"Invalid job names requested [{requested_jobs}]" if requested_jobs: + assert any( + len(x) > 1 for x in requested_jobs + ), f"Invalid job names requested [{requested_jobs}]" jobs_to_do_requested = [] for job in requested_jobs: job_with_parents = CI_CONFIG.get_job_with_parents(job) diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index f76aedac80b..8bf9c62a17b 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -37,6 +37,7 @@ class JobConfig: timeout: Optional[int] = None num_batches: int = 1 run_by_label: str = "" + run_always: bool = False @dataclass @@ -145,12 +146,11 @@ integration_check_digest = DigestConfig( "clickhouse/postgresql-java-client", ], ) -# FIXME: which tests are AST_FUZZER_TEST? just python? -# FIXME: should ast fuzzer test be non-skipable? + ast_fuzzer_check_digest = DigestConfig( - include_paths=["./tests/ci/ast_fuzzer_check.py"], - exclude_files=[".md"], - docker=["clickhouse/fuzzer"], + # include_paths=["./tests/ci/ast_fuzzer_check.py"], + # exclude_files=[".md"], + # docker=["clickhouse/fuzzer"], ) unit_check_digest = DigestConfig( include_paths=["./tests/ci/unit_tests_check.py"], @@ -166,9 +166,9 @@ perf_check_digest = DigestConfig( docker=["clickhouse/performance-comparison"], ) sqllancer_check_digest = DigestConfig( - include_paths=["./tests/ci/sqlancer_check.py"], - exclude_files=[".md"], - docker=["clickhouse/sqlancer-test"], + # include_paths=["./tests/ci/sqlancer_check.py"], + # exclude_files=[".md"], + # docker=["clickhouse/sqlancer-test"], ) sqllogic_check_digest = DigestConfig( include_paths=["./tests/ci/sqllogic_test.py"], @@ -226,6 +226,7 @@ upgrade_test_common_params = { astfuzzer_test_common_params = { "digest": ast_fuzzer_check_digest, "run_command": "ast_fuzzer_check.py", + "run_always": True, } integration_test_common_params = { "digest": integration_check_digest, @@ -242,6 +243,7 @@ perf_test_common_params = { sqllancer_test_common_params = { "digest": sqllancer_check_digest, "run_command": "sqlancer_check.py", + "run_always": True, } sqllogic_test_params = { "digest": sqllogic_check_digest, @@ -609,9 +611,7 @@ CI_CONFIG = CiConfig( "Style check": TestConfig( "", job_config=JobConfig( - digest=DigestConfig( - include_paths=["."], exclude_dirs=[".git", "__pycache__"] - ) + run_always=True, ), ), "tests bugfix validate check": TestConfig( From bbd7bc0dd9c31b2af69ca42ea19b78e830e39bc3 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 19 Dec 2023 20:26:19 +0100 Subject: [PATCH 121/253] Update SystemLogBase.cpp --- src/Common/SystemLogBase.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Common/SystemLogBase.cpp b/src/Common/SystemLogBase.cpp index a0b3d411e38..d82b582fee6 100644 --- a/src/Common/SystemLogBase.cpp +++ b/src/Common/SystemLogBase.cpp @@ -188,6 +188,9 @@ typename SystemLogQueue::Index SystemLogQueue::pop(std:: bool & should_prepare_tables_anyway, bool & exit_this_thread) { + /// Call dtors and deallocate strings without holding the global lock + output.resize(0); + std::unique_lock lock(mutex); flush_event.wait_for(lock, std::chrono::milliseconds(settings.flush_interval_milliseconds), @@ -200,7 +203,6 @@ typename SystemLogQueue::Index SystemLogQueue::pop(std:: queue_front_index += queue.size(); // Swap with existing array from previous flush, to save memory // allocations. - output.resize(0); queue.swap(output); should_prepare_tables_anyway = is_force_prepare_tables; From a58c35f2b43ea10b5369aac0c564848b45ece757 Mon Sep 17 00:00:00 2001 From: Yarik Briukhovetskyi <114298166+yariks5s@users.noreply.github.com> Date: Tue, 19 Dec 2023 21:30:35 +0100 Subject: [PATCH 122/253] fix docs --- docs/en/sql-reference/statements/select/join.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/sql-reference/statements/select/join.md b/docs/en/sql-reference/statements/select/join.md index 37fe1ef45e4..97090a7a699 100644 --- a/docs/en/sql-reference/statements/select/join.md +++ b/docs/en/sql-reference/statements/select/join.md @@ -272,7 +272,7 @@ For example, consider the following tables: ## PASTE JOIN Usage -The result of `PASTE JOIN` is a table that contains all columns from left subquery followed by all columns from the right subqiery. +The result of `PASTE JOIN` is a table that contains all columns from left subquery followed by all columns from the right subquery. The rows are matched based on their positions in the original tables (the order of rows should be defined). If the subqueries return a different number of rows, extra rows will be cut. From 7c281d9a6fbf95f4b2795bd0e24315aa5e4a726b Mon Sep 17 00:00:00 2001 From: Jordi Villar Date: Tue, 19 Dec 2023 23:16:03 +0100 Subject: [PATCH 123/253] Allow max_size_to_drop settings in query time (#57452) --- docs/en/operations/settings/settings.md | 22 +++++++++++++ src/Core/Settings.h | 4 +-- src/Interpreters/Context.cpp | 11 ++++++- src/Interpreters/Context.h | 2 ++ src/Storages/MergeTree/MergeTreeData.cpp | 20 ++++++++++-- src/Storages/MergeTree/MergeTreeData.h | 2 +- src/Storages/StorageMergeTree.cpp | 10 +++++- src/Storages/StorageReplicatedMergeTree.cpp | 10 +++++- ...932_query_settings_max_size_drop.reference | 0 .../02932_query_settings_max_size_drop.sql | 31 +++++++++++++++++++ 10 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 tests/queries/0_stateless/02932_query_settings_max_size_drop.reference create mode 100644 tests/queries/0_stateless/02932_query_settings_max_size_drop.sql diff --git a/docs/en/operations/settings/settings.md b/docs/en/operations/settings/settings.md index dc46a3f0dcd..b9e7c37485f 100644 --- a/docs/en/operations/settings/settings.md +++ b/docs/en/operations/settings/settings.md @@ -5134,3 +5134,25 @@ When set to `true` than for all s3 requests first two attempts are made with low When set to `false` than all attempts are made with identical timeouts. Default value: `true`. + +## max_partition_size_to_drop + +Restriction on dropping partitions in query time. + +Default value: 50 GB. +The value 0 means that you can drop partitions without any restrictions. + +:::note +This query setting overwrites its server setting equivalent, see [max_partition_size_to_drop](/docs/en/operations/server-configuration-parameters/settings.md/#max-partition-size-to-drop) +::: + +## max_table_size_to_drop + +Restriction on deleting tables in query time. + +Default value: 50 GB. +The value 0 means that you can delete all tables without any restrictions. + +:::note +This query setting overwrites its server setting equivalent, see [max_table_size_to_drop](/docs/en/operations/server-configuration-parameters/settings.md/#max-table-size-to-drop) +::: \ No newline at end of file diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 7e50a81ada8..b75004a3396 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -528,8 +528,8 @@ class IColumn; M(Int64, max_partitions_to_read, -1, "Limit the max number of partitions that can be accessed in one query. <= 0 means unlimited.", 0) \ M(Bool, check_query_single_value_result, true, "Return check query result as single 1/0 value", 0) \ M(Bool, allow_drop_detached, false, "Allow ALTER TABLE ... DROP DETACHED PART[ITION] ... queries", 0) \ - M(UInt64, max_table_size_to_drop, 0, "Only available in ClickHouse Cloud", 0) \ - M(UInt64, max_partition_size_to_drop, 0, "Only available in ClickHouse Cloud", 0) \ + M(UInt64, max_table_size_to_drop, 50000000000lu, "If size of a table is greater than this value (in bytes) than table could not be dropped with any DROP query.", 0) \ + M(UInt64, max_partition_size_to_drop, 50000000000lu, "Same as max_table_size_to_drop, but for the partitions.", 0) \ \ M(UInt64, postgresql_connection_pool_size, 16, "Connection pool size for PostgreSQL table engine and database engine.", 0) \ M(UInt64, postgresql_connection_pool_wait_timeout, 5000, "Connection pool push/pop timeout on empty pool for PostgreSQL table engine and database engine. By default it will block on empty pool.", 0) \ diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 7539a11f25e..633bca644a1 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -4053,7 +4053,8 @@ void Context::checkCanBeDropped(const String & database, const String & table, c "2. File '{}' intended to force DROP {}\n" "How to fix this:\n" "1. Either increase (or set to zero) max_[table/partition]_size_to_drop in server config\n" - "2. Either create forcing file {} and make sure that ClickHouse has write permission for it.\n" + "2. Either pass a bigger (or set to zero) max_[table/partition]_size_to_drop through query settings\n" + "3. Either create forcing file {} and make sure that ClickHouse has write permission for it.\n" "Example:\nsudo touch '{}' && sudo chmod 666 '{}'", backQuoteIfNeed(database), backQuoteIfNeed(table), size_str, max_size_to_drop_str, @@ -4081,6 +4082,10 @@ void Context::checkTableCanBeDropped(const String & database, const String & tab checkCanBeDropped(database, table, table_size, max_table_size_to_drop); } +void Context::checkTableCanBeDropped(const String & database, const String & table, const size_t & table_size, const size_t & max_table_size_to_drop) const +{ + checkCanBeDropped(database, table, table_size, max_table_size_to_drop); +} void Context::setMaxPartitionSizeToDrop(size_t max_size) { @@ -4100,6 +4105,10 @@ void Context::checkPartitionCanBeDropped(const String & database, const String & checkCanBeDropped(database, table, partition_size, max_partition_size_to_drop); } +void Context::checkPartitionCanBeDropped(const String & database, const String & table, const size_t & partition_size, const size_t & max_partition_size_to_drop) const +{ + checkCanBeDropped(database, table, partition_size, max_partition_size_to_drop); +} InputFormatPtr Context::getInputFormat(const String & name, ReadBuffer & buf, const Block & sample, UInt64 max_block_size, const std::optional & format_settings, const std::optional max_parsing_threads) const { diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index 74567e54c25..a844c0aaa7e 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -1084,11 +1084,13 @@ public: void setMaxTableSizeToDrop(size_t max_size); size_t getMaxTableSizeToDrop() const; void checkTableCanBeDropped(const String & database, const String & table, const size_t & table_size) const; + void checkTableCanBeDropped(const String & database, const String & table, const size_t & table_size, const size_t & max_table_size_to_drop) const; /// Prevents DROP PARTITION if its size is greater than max_size (50GB by default, max_size=0 turn off this check) void setMaxPartitionSizeToDrop(size_t max_size); size_t getMaxPartitionSizeToDrop() const; void checkPartitionCanBeDropped(const String & database, const String & table, const size_t & partition_size) const; + void checkPartitionCanBeDropped(const String & database, const String & table, const size_t & partition_size, const size_t & max_partition_size_to_drop) const; /// Lets you select the compression codec according to the conditions described in the configuration file. std::shared_ptr chooseCompressionCodec(size_t part_size, double part_size_ratio) const; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index d97f337c9c9..0ddeb0a6828 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -4835,10 +4835,18 @@ void MergeTreeData::checkPartitionCanBeDropped(const ASTPtr & partition, Context partition_size += part->getBytesOnDisk(); auto table_id = getStorageID(); + + const auto & query_settings = local_context->getSettingsRef(); + if (query_settings.max_partition_size_to_drop.changed) + { + getContext()->checkPartitionCanBeDropped(table_id.database_name, table_id.table_name, partition_size, query_settings.max_partition_size_to_drop); + return; + } + getContext()->checkPartitionCanBeDropped(table_id.database_name, table_id.table_name, partition_size); } -void MergeTreeData::checkPartCanBeDropped(const String & part_name) +void MergeTreeData::checkPartCanBeDropped(const String & part_name, ContextPtr local_context) { if (!supportsReplication() && isStaticStorage()) return; @@ -4848,6 +4856,14 @@ void MergeTreeData::checkPartCanBeDropped(const String & part_name) throw Exception(ErrorCodes::NO_SUCH_DATA_PART, "No part {} in committed state", part_name); auto table_id = getStorageID(); + + const auto & query_settings = local_context->getSettingsRef(); + if (query_settings.max_partition_size_to_drop.changed) + { + getContext()->checkPartitionCanBeDropped(table_id.database_name, table_id.table_name, part->getBytesOnDisk(), query_settings.max_partition_size_to_drop); + return; + } + getContext()->checkPartitionCanBeDropped(table_id.database_name, table_id.table_name, part->getBytesOnDisk()); } @@ -5035,7 +5051,7 @@ Pipe MergeTreeData::alterPartition( if (command.part) { auto part_name = command.partition->as().value.safeGet(); - checkPartCanBeDropped(part_name); + checkPartCanBeDropped(part_name, query_context); dropPart(part_name, command.detach, query_context); } else diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index fc1d9085527..c69c7aaba3d 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -795,7 +795,7 @@ public: /// We do not use mutex because it is not very important that the size could change during the operation. void checkPartitionCanBeDropped(const ASTPtr & partition, ContextPtr local_context); - void checkPartCanBeDropped(const String & part_name); + void checkPartCanBeDropped(const String & part_name, ContextPtr local_context); Pipe alterPartition( const StorageMetadataPtr & metadata_snapshot, diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index eb8c52f8936..1abb1a51361 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -280,12 +280,20 @@ StorageMergeTree::write(const ASTPtr & /*query*/, const StorageMetadataPtr & met *this, metadata_snapshot, settings.max_partitions_per_insert_block, local_context); } -void StorageMergeTree::checkTableCanBeDropped([[ maybe_unused ]] ContextPtr query_context) const +void StorageMergeTree::checkTableCanBeDropped(ContextPtr query_context) const { if (!supportsReplication() && isStaticStorage()) return; auto table_id = getStorageID(); + + const auto & query_settings = query_context->getSettingsRef(); + if (query_settings.max_table_size_to_drop.changed) + { + getContext()->checkTableCanBeDropped(table_id.database_name, table_id.table_name, getTotalActiveSizeInBytes(), query_settings.max_table_size_to_drop); + return; + } + getContext()->checkTableCanBeDropped(table_id.database_name, table_id.table_name, getTotalActiveSizeInBytes()); } diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 64359ddd299..dbfa88f077e 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -6423,9 +6423,17 @@ PartitionCommandsResultInfo StorageReplicatedMergeTree::attachPartition( } -void StorageReplicatedMergeTree::checkTableCanBeDropped([[ maybe_unused ]] ContextPtr query_context) const +void StorageReplicatedMergeTree::checkTableCanBeDropped(ContextPtr query_context) const { auto table_id = getStorageID(); + + const auto & query_settings = query_context->getSettingsRef(); + if (query_settings.max_table_size_to_drop.changed) + { + getContext()->checkTableCanBeDropped(table_id.database_name, table_id.table_name, getTotalActiveSizeInBytes(), query_settings.max_table_size_to_drop); + return; + } + getContext()->checkTableCanBeDropped(table_id.database_name, table_id.table_name, getTotalActiveSizeInBytes()); } diff --git a/tests/queries/0_stateless/02932_query_settings_max_size_drop.reference b/tests/queries/0_stateless/02932_query_settings_max_size_drop.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02932_query_settings_max_size_drop.sql b/tests/queries/0_stateless/02932_query_settings_max_size_drop.sql new file mode 100644 index 00000000000..1685861bd2e --- /dev/null +++ b/tests/queries/0_stateless/02932_query_settings_max_size_drop.sql @@ -0,0 +1,31 @@ +CREATE TABLE test_max_size_drop +Engine = MergeTree() +ORDER BY number +AS SELECT number +FROM numbers(1000) +; + +DROP TABLE test_max_size_drop SETTINGS max_table_size_to_drop = 1; -- { serverError 359 } +DROP TABLE test_max_size_drop; + +CREATE TABLE test_max_size_drop +Engine = MergeTree() +ORDER BY number +AS SELECT number +FROM numbers(1000) +; + +ALTER TABLE test_max_size_drop DROP PARTITION tuple() SETTINGS max_partition_size_to_drop = 1; -- { serverError 359 } +ALTER TABLE test_max_size_drop DROP PARTITION tuple(); +DROP TABLE test_max_size_drop; + +CREATE TABLE test_max_size_drop +Engine = MergeTree() +ORDER BY number +AS SELECT number +FROM numbers(1000) +; + +ALTER TABLE test_max_size_drop DROP PART 'all_1_1_0' SETTINGS max_partition_size_to_drop = 1; -- { serverError 359 } +ALTER TABLE test_max_size_drop DROP PART 'all_1_1_0'; +DROP TABLE test_max_size_drop; From ccff19826510c9564ac9954499fb467a5004df90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Tue, 19 Dec 2023 23:21:19 +0100 Subject: [PATCH 124/253] Rename canUseParallelReplicas to canUseTaskBasedParallelReplicas (#58025) --- src/Interpreters/ClusterProxy/executeQuery.cpp | 4 ++-- src/Interpreters/Context.cpp | 6 +++--- src/Interpreters/Context.h | 2 +- src/Interpreters/ExpressionAnalyzer.cpp | 5 +---- src/Interpreters/InterpreterSelectQuery.cpp | 2 +- src/Planner/Planner.cpp | 4 ++-- src/Processors/QueryPlan/ReadFromRemote.cpp | 4 ++-- 7 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/Interpreters/ClusterProxy/executeQuery.cpp b/src/Interpreters/ClusterProxy/executeQuery.cpp index 3a634f08b83..549eadcebd2 100644 --- a/src/Interpreters/ClusterProxy/executeQuery.cpp +++ b/src/Interpreters/ClusterProxy/executeQuery.cpp @@ -135,7 +135,7 @@ ContextMutablePtr updateSettingsForCluster(const Cluster & cluster, } /// disable parallel replicas if cluster contains only shards with 1 replica - if (context->canUseParallelReplicas()) + if (context->canUseTaskBasedParallelReplicas()) { bool disable_parallel_replicas = true; for (const auto & shard : cluster.getShardsInfo()) @@ -265,7 +265,7 @@ void executeQuery( // decide for each shard if parallel reading from replicas should be enabled // according to settings and number of replicas declared per shard const auto & addresses = cluster->getShardsAddresses().at(i); - bool parallel_replicas_enabled = addresses.size() > 1 && context->canUseParallelReplicas(); + bool parallel_replicas_enabled = addresses.size() > 1 && context->canUseTaskBasedParallelReplicas(); stream_factory.createForShard( shard_info, diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 633bca644a1..589d03cc074 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -5041,7 +5041,7 @@ Context::ParallelReplicasMode Context::getParallelReplicasMode() const return SAMPLE_KEY; } -bool Context::canUseParallelReplicas() const +bool Context::canUseTaskBasedParallelReplicas() const { const auto & settings_ref = getSettingsRef(); return getParallelReplicasMode() == ParallelReplicasMode::READ_TASKS && settings_ref.max_parallel_replicas > 1; @@ -5049,12 +5049,12 @@ bool Context::canUseParallelReplicas() const bool Context::canUseParallelReplicasOnInitiator() const { - return canUseParallelReplicas() && !getClientInfo().collaborate_with_initiator; + return canUseTaskBasedParallelReplicas() && !getClientInfo().collaborate_with_initiator; } bool Context::canUseParallelReplicasOnFollower() const { - return canUseParallelReplicas() && getClientInfo().collaborate_with_initiator; + return canUseTaskBasedParallelReplicas() && getClientInfo().collaborate_with_initiator; } void Context::setPreparedSetsCache(const PreparedSetsCachePtr & cache) diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index a844c0aaa7e..39d2212ce80 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -1234,7 +1234,7 @@ public: WriteSettings getWriteSettings() const; /** There are multiple conditions that have to be met to be able to use parallel replicas */ - bool canUseParallelReplicas() const; + bool canUseTaskBasedParallelReplicas() const; bool canUseParallelReplicasOnInitiator() const; bool canUseParallelReplicasOnFollower() const; diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index 3b389dcf61e..4f605344dd5 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -858,11 +858,8 @@ const ASTSelectQuery * ExpressionAnalyzer::getSelectQuery() const bool ExpressionAnalyzer::isRemoteStorage() const { - const Settings & csettings = getContext()->getSettingsRef(); // Consider any storage used in parallel replicas as remote, so the query is executed in multiple servers - const bool enable_parallel_processing_of_joins - = csettings.max_parallel_replicas > 1 && csettings.allow_experimental_parallel_reading_from_replicas > 0; - return syntax->is_remote_storage || enable_parallel_processing_of_joins; + return syntax->is_remote_storage || getContext()->canUseTaskBasedParallelReplicas(); } const ASTSelectQuery * SelectQueryExpressionAnalyzer::getAggregatingQuery() const diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 67245438156..f2d5df61f72 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -481,7 +481,7 @@ InterpreterSelectQuery::InterpreterSelectQuery( /// Check support for FINAL for parallel replicas bool is_query_with_final = isQueryWithFinal(query_info); - if (is_query_with_final && settings.allow_experimental_parallel_reading_from_replicas > 0) + if (is_query_with_final && context->canUseTaskBasedParallelReplicas()) { if (settings.allow_experimental_parallel_reading_from_replicas == 1) { diff --git a/src/Planner/Planner.cpp b/src/Planner/Planner.cpp index 2ab88491357..10fbf651d06 100644 --- a/src/Planner/Planner.cpp +++ b/src/Planner/Planner.cpp @@ -1340,7 +1340,7 @@ void Planner::buildPlanForQueryNode() const auto & settings = query_context->getSettingsRef(); - if (settings.allow_experimental_parallel_reading_from_replicas > 0) + if (query_context->canUseTaskBasedParallelReplicas()) { const auto & table_expression_nodes = planner_context->getTableExpressionNodeToData(); for (const auto & it : table_expression_nodes) @@ -1366,7 +1366,7 @@ void Planner::buildPlanForQueryNode() } } - if (settings.allow_experimental_parallel_reading_from_replicas > 0 || !settings.parallel_replicas_custom_key.value.empty()) + if (query_context->canUseTaskBasedParallelReplicas() || !settings.parallel_replicas_custom_key.value.empty()) { /// Check support for JOIN for parallel replicas with custom key if (planner_context->getTableExpressionNodeToData().size() > 1) diff --git a/src/Processors/QueryPlan/ReadFromRemote.cpp b/src/Processors/QueryPlan/ReadFromRemote.cpp index cd88f5cc93a..f1dff279792 100644 --- a/src/Processors/QueryPlan/ReadFromRemote.cpp +++ b/src/Processors/QueryPlan/ReadFromRemote.cpp @@ -236,7 +236,7 @@ void ReadFromRemote::addPipe(Pipes & pipes, const ClusterProxy::SelectStreamFact scalars["_shard_num"] = Block{{DataTypeUInt32().createColumnConst(1, shard.shard_info.shard_num), std::make_shared(), "_shard_num"}}; - if (context->canUseParallelReplicas()) + if (context->canUseTaskBasedParallelReplicas()) { if (context->getSettingsRef().cluster_for_parallel_replicas.changed) { @@ -258,7 +258,7 @@ void ReadFromRemote::addPipe(Pipes & pipes, const ClusterProxy::SelectStreamFact shard.shard_info.pool, query_string, output_stream->header, context, throttler, scalars, external_tables, stage); remote_query_executor->setLogger(log); - if (context->canUseParallelReplicas()) + if (context->canUseTaskBasedParallelReplicas()) { // when doing parallel reading from replicas (ParallelReplicasMode::READ_TASKS) on a shard: // establish a connection to a replica on the shard, the replica will instantiate coordinator to manage parallel reading from replicas on the shard. From 2dda0ccfb79babb504714cc625bd8be0cea05bc0 Mon Sep 17 00:00:00 2001 From: santrancisco Date: Wed, 20 Dec 2023 10:39:07 +1100 Subject: [PATCH 125/253] Updating query cache doc with security consideration --- docs/en/operations/query-cache.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/en/operations/query-cache.md b/docs/en/operations/query-cache.md index def0f48b968..f174d32ed14 100644 --- a/docs/en/operations/query-cache.md +++ b/docs/en/operations/query-cache.md @@ -29,6 +29,11 @@ Transactionally inconsistent caching is traditionally provided by client tools o the same caching logic and configuration is often duplicated. With ClickHouse's query cache, the caching logic moves to the server side. This reduces maintenance effort and avoids redundancy. +:::security consideration +The cached query result is tied to the user executing it. Authorization checks are performed when the query is executed, meaning that if there are any alterations to the user's role or permissions between one cached query and the next query, the query result will not reflect these changes. We recommend using different users to distingush between different level of access, instead of actively toggling roles for a single user between queries, as this practice may lead to unexpected query results. +::: + + ## Configuration Settings and Usage Setting [use_query_cache](settings/settings.md#use-query-cache) can be used to control whether a specific query or all queries of the From 82ffb570b08aa3beefc33a9dafbfe079ed2bd4db Mon Sep 17 00:00:00 2001 From: santrancisco Date: Wed, 20 Dec 2023 10:44:46 +1100 Subject: [PATCH 126/253] Remove newline --- docs/en/operations/query-cache.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/en/operations/query-cache.md b/docs/en/operations/query-cache.md index f174d32ed14..98895fc7b91 100644 --- a/docs/en/operations/query-cache.md +++ b/docs/en/operations/query-cache.md @@ -33,7 +33,6 @@ This reduces maintenance effort and avoids redundancy. The cached query result is tied to the user executing it. Authorization checks are performed when the query is executed, meaning that if there are any alterations to the user's role or permissions between one cached query and the next query, the query result will not reflect these changes. We recommend using different users to distingush between different level of access, instead of actively toggling roles for a single user between queries, as this practice may lead to unexpected query results. ::: - ## Configuration Settings and Usage Setting [use_query_cache](settings/settings.md#use-query-cache) can be used to control whether a specific query or all queries of the From 1e86fb298aeed0282a5c9f9cd6a61601dbcfd344 Mon Sep 17 00:00:00 2001 From: santrancisco Date: Wed, 20 Dec 2023 12:37:36 +1100 Subject: [PATCH 127/253] Fixing check for HTTP POST request --- src/Server/HTTPHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Server/HTTPHandler.cpp b/src/Server/HTTPHandler.cpp index 2b7cf9f168c..f53c0094ef7 100644 --- a/src/Server/HTTPHandler.cpp +++ b/src/Server/HTTPHandler.cpp @@ -729,8 +729,8 @@ void HTTPHandler::processQuery( /// to some other value. const auto & settings = context->getSettingsRef(); - /// Only readonly queries are allowed for HTTP GET requests. - if (request.getMethod() == HTTPServerRequest::HTTP_GET) + /// Anything else beside HTTP POST should be readonly queries. + if (request.getMethod() != HTTPServerRequest::HTTP_POST) { if (settings.readonly == 0) context->setSetting("readonly", 2); From 4859a74b16aa23c673af8548dcdc9058f7a2e2c8 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 20 Dec 2023 02:42:24 +0100 Subject: [PATCH 128/253] Correct values for randomization --- tests/clickhouse-test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index f0167d2da8f..6d4a3e69e7e 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -612,10 +612,10 @@ class SettingsRandomizer: "merge_tree_coarse_index_granularity": lambda: random.randint(2, 32), "optimize_distinct_in_order": lambda: random.randint(0, 1), "max_bytes_before_external_sort": threshold_generator( - 1.0, 0.5, 1, 10 * 1024 * 1024 * 1024 + 0.3, 0.5, 1, 10 * 1024 * 1024 * 1024 ), "max_bytes_before_external_group_by": threshold_generator( - 1.0, 0.5, 1, 10 * 1024 * 1024 * 1024 + 0.3, 0.5, 1, 10 * 1024 * 1024 * 1024 ), "max_bytes_before_remerge_sort": lambda: random.randint(1, 3000000000), "optimize_sorting_by_input_stream_properties": lambda: random.randint(0, 1), From d2d0c970aa1f064d6996044ceb4728818d611004 Mon Sep 17 00:00:00 2001 From: San Date: Wed, 20 Dec 2023 12:45:36 +1100 Subject: [PATCH 129/253] Update query-cache.md Fixing spelling and explain clearer. --- docs/en/operations/query-cache.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/operations/query-cache.md b/docs/en/operations/query-cache.md index 98895fc7b91..781d1f9bcd5 100644 --- a/docs/en/operations/query-cache.md +++ b/docs/en/operations/query-cache.md @@ -30,7 +30,7 @@ the same caching logic and configuration is often duplicated. With ClickHouse's This reduces maintenance effort and avoids redundancy. :::security consideration -The cached query result is tied to the user executing it. Authorization checks are performed when the query is executed, meaning that if there are any alterations to the user's role or permissions between one cached query and the next query, the query result will not reflect these changes. We recommend using different users to distingush between different level of access, instead of actively toggling roles for a single user between queries, as this practice may lead to unexpected query results. +The cached query result is tied to the user executing it. Authorization checks are performed when the query is executed. This means that if there are any alterations to the user's role or permissions between the time the query is cached and when the cache is accessed, the result will not reflect these changes. We recommend using different users to distinguish between different levels of access, instead of actively toggling roles for a single user between queries, as this practice may lead to unexpected query results. ::: ## Configuration Settings and Usage From 810305fafec0ab47351e38a7226c87fa75c566ca Mon Sep 17 00:00:00 2001 From: zhanglistar Date: Wed, 20 Dec 2023 10:58:46 +0800 Subject: [PATCH 130/253] Fix if.xml. --- src/Functions/if.cpp | 5 ++--- tests/performance/if.xml | 14 +++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Functions/if.cpp b/src/Functions/if.cpp index c55f9a92ccc..2f9e7662469 100644 --- a/src/Functions/if.cpp +++ b/src/Functions/if.cpp @@ -42,6 +42,8 @@ using namespace GatherUtils; /** Selection function by condition: if(cond, then, else). * cond - UInt8 * then, else - numeric types for which there is a general type, or dates, datetimes, or strings, or arrays of these types. + * For better performance, try to use branch free code for numeric types except floating point types because of inf or nan, besides the trick will not improve much on floating point types. eg. + * cond ? a : b -> !!cond * a + !cond * b */ template @@ -51,7 +53,6 @@ inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const Arr bool a_is_short = a.size() < size; bool b_is_short = b.size() < size; - /// Below code attempts to use a branch-free check (multiply with the condition) for better performance instead of conditional evaluation. if (a_is_short && b_is_short) { size_t a_index = 0, b_index = 0; @@ -106,7 +107,6 @@ inline void fillVectorConstant(const ArrayCond & cond, const ArrayA & a, B b, Ar { size_t size = cond.size(); bool a_is_short = a.size() < size; - /// Below code attempts to use a branch-free check (multiply with the condition) for better performance instead of conditional evaluation. if (a_is_short) { size_t a_index = 0; @@ -134,7 +134,6 @@ inline void fillConstantVector(const ArrayCond & cond, A a, const ArrayB & b, Ar { size_t size = cond.size(); bool b_is_short = b.size() < size; - /// Below code attempts to use a branch-free check (multiply with the condition) for better performance instead of conditional evaluation. if (b_is_short) { size_t b_index = 0; diff --git a/tests/performance/if.xml b/tests/performance/if.xml index ec386724f85..f4d0e8f9773 100644 --- a/tests/performance/if.xml +++ b/tests/performance/if.xml @@ -1,12 +1,12 @@ - SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() > 42949673, zero + 1, zero + 2)) - SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 3865470566, zero + 1, zero + 2)) - SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 2147483647, zero + 1, zero + 2)) - SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 42949673, zero + 1, zero + 2)) + 42949673, zero + 1, zero + 2)) ]]> + + + - SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 42949673, zero + 1, 2)) - SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 42949673, 1, zero + 2)) - SELECT count() FROM zeros(1000000000) WHERE NOT ignore(if(rand32() < 42949673, 1, 2)) + + + From 786879b062bb7f1f2e6b36e8e430073aa906f511 Mon Sep 17 00:00:00 2001 From: santrancisco Date: Wed, 20 Dec 2023 14:22:20 +1100 Subject: [PATCH 131/253] Adding test --- ..._non_post_request_should_be_readonly.reference | 2 ++ .../02947_non_post_request_should_be_readonly.sh | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/queries/0_stateless/02947_non_post_request_should_be_readonly.reference create mode 100755 tests/queries/0_stateless/02947_non_post_request_should_be_readonly.sh diff --git a/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.reference b/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.reference new file mode 100644 index 00000000000..ecb852773de --- /dev/null +++ b/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.reference @@ -0,0 +1,2 @@ +Cannot execute query in readonly mode +Internal Server Error \ No newline at end of file diff --git a/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.sh b/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.sh new file mode 100755 index 00000000000..64cf554e45a --- /dev/null +++ b/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +# This should fail +${CLICKHOUSE_CURL} -X GET -sS "${CLICKHOUSE_URL}&session_id=${SESSION_ID}&query=CREATE+DATABASE+non_post_request_test" | grep -o "Cannot execute query in readonly mode" + +# This should fail +${CLICKHOUSE_CURL} --head -sS "${CLICKHOUSE_URL}&session_id=${SESSION_ID}&query=CREATE+DATABASE+non_post_request_test" | grep -o "Internal Server Error" + +# This should pass - but will throw error "non_post_request_test already exists" if the database was created by any of the above request. +${CLICKHOUSE_CURL} -X POST -sS "${CLICKHOUSE_URL}&session_id=${SESSION_ID}" -d 'CREATE DATABASE non_post_request_test' +${CLICKHOUSE_CURL} -X POST -sS "${CLICKHOUSE_URL}&session_id=${SESSION_ID}" -d 'DROP DATABASE non_post_request_test' \ No newline at end of file From e3ab5437c008664a7e8791ff66dfa1374397ddb7 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 20 Dec 2023 06:54:06 +0300 Subject: [PATCH 132/253] Update 02947_non_post_request_should_be_readonly.sh --- .../0_stateless/02947_non_post_request_should_be_readonly.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.sh b/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.sh index 64cf554e45a..4250799b522 100755 --- a/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.sh +++ b/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Tags: no-parallel CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh @@ -10,6 +11,6 @@ ${CLICKHOUSE_CURL} -X GET -sS "${CLICKHOUSE_URL}&session_id=${SESSION_ID}&query= # This should fail ${CLICKHOUSE_CURL} --head -sS "${CLICKHOUSE_URL}&session_id=${SESSION_ID}&query=CREATE+DATABASE+non_post_request_test" | grep -o "Internal Server Error" -# This should pass - but will throw error "non_post_request_test already exists" if the database was created by any of the above request. +# This should pass - but will throw error "non_post_request_test already exists" if the database was created by any of the above requests. ${CLICKHOUSE_CURL} -X POST -sS "${CLICKHOUSE_URL}&session_id=${SESSION_ID}" -d 'CREATE DATABASE non_post_request_test' -${CLICKHOUSE_CURL} -X POST -sS "${CLICKHOUSE_URL}&session_id=${SESSION_ID}" -d 'DROP DATABASE non_post_request_test' \ No newline at end of file +${CLICKHOUSE_CURL} -X POST -sS "${CLICKHOUSE_URL}&session_id=${SESSION_ID}" -d 'DROP DATABASE non_post_request_test' From 50c723fd10c0afe1a71c4ecffe3d677dcfea88c3 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 20 Dec 2023 06:54:24 +0300 Subject: [PATCH 133/253] Update 02947_non_post_request_should_be_readonly.reference --- .../02947_non_post_request_should_be_readonly.reference | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.reference b/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.reference index ecb852773de..9cdea62b413 100644 --- a/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.reference +++ b/tests/queries/0_stateless/02947_non_post_request_should_be_readonly.reference @@ -1,2 +1,2 @@ Cannot execute query in readonly mode -Internal Server Error \ No newline at end of file +Internal Server Error From 98c9f830d6db8310c38864756335b78d58368956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Wed, 20 Dec 2023 10:16:47 +0100 Subject: [PATCH 134/253] Revert "Merge pull request #55710 from guoxiaolongzte/clickhouse-test-add-prinln-nowTime" This reverts commit efefad9e52227a2b2658d6c6194934bbfdaf5751, reversing changes made to 8f7852f82d60ea9578974776f9ce286b70e1e8cf. --- tests/clickhouse-test | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 6d4a3e69e7e..c868d882490 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1773,7 +1773,6 @@ def run_tests_array(all_tests_with_params: Tuple[List[str], int, TestSuite]): proc_name = multiprocessing.current_process().name print(f"\nRunning {about}{num_tests} {test_suite.suite} tests ({proc_name}).\n") - seria_num = 1 while True: if is_concurrent: case = queue.get(timeout=args.timeout * 1.1) @@ -1801,9 +1800,7 @@ def run_tests_array(all_tests_with_params: Tuple[List[str], int, TestSuite]): test_cace_name = removesuffix(test_case.name, ".gen", ".sql") + ": " if not is_concurrent: sys.stdout.flush() - sys.stdout.write( - f"Case SN: {seria_num} | Current Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | Case Name: {test_cace_name:72}" - ) + sys.stdout.write(f"{test_cace_name:72}") # This flush is needed so you can see the test name of the long # running test before it will finish. But don't do it in parallel # mode, so that the lines don't mix. @@ -1849,7 +1846,6 @@ def run_tests_array(all_tests_with_params: Tuple[List[str], int, TestSuite]): if failures_chain >= args.max_failures_chain: stop_tests() break - seria_num += 1 if failures_total > 0: print( From 3e3c29014ab2adac47766cd7c54228747d6f4c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Wed, 20 Dec 2023 10:11:32 +0100 Subject: [PATCH 135/253] Keep old threshold --- .../00002_log_and_exception_messages_formatting.reference | 2 +- .../00002_log_and_exception_messages_formatting.sql | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.reference b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.reference index 76351ebbd67..d8c0db3b996 100644 --- a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.reference +++ b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.reference @@ -4,7 +4,7 @@ unknown runtime exceptions 0.01 [] messages shorter than 10 1 [] messages shorter than 16 1 [] exceptions shorter than 30 1 [] -noisy messages 0.1 +noisy messages 0.3 noisy Trace messages 0.16 noisy Debug messages 0.09 noisy Info messages 0.05 diff --git a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql index 4a30668ec41..4d12bb7484a 100644 --- a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql +++ b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql @@ -122,8 +122,8 @@ select 'exceptions shorter than 30', from logs where message ilike '%DB::Exception%' and if(length(extract(message, '(.*)\\([A-Z0-9_]+\\)')) as pref > 0, pref, length(message)) < 30 + 26 and message_format_string not in known_short_messages; --- Avoid too noisy messages: top 1 message frequency must be less than 10%. We should reduce the threshold -WITH 0.10 as threshold +-- Avoid too noisy messages: top 1 message frequency must be less than 30%. We should reduce the threshold +WITH 0.30 as threshold select 'noisy messages', greatest(coalesce(((select message_format_string, count() from logs group by message_format_string order by count() desc limit 1) as top_message).2, 0) / (select count() from logs), threshold) as r, From 3cc8358b89af7a864d6ecf745b581c063dca3ecd Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 20 Dec 2023 10:20:23 +0000 Subject: [PATCH 136/253] Avoid preprocessing if Keeper leader is not ready --- src/Coordination/KeeperServer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index bc5e3a723f2..638ee11858a 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -859,6 +859,10 @@ nuraft::cb_func::ReturnCode KeeperServer::callbackFunc(nuraft::cb_func::Type typ initial_batch_committed = true; return nuraft::cb_func::ReturnCode::Ok; } + case nuraft::cb_func::PreAppendLogLeader: + { + return nuraft::cb_func::ReturnCode::ReturnNull; + } case nuraft::cb_func::PreAppendLogFollower: { const auto & entry = *static_cast(param->ctx); From 4547e60c9d72bb692f66a9de081af06a0ce336d5 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Tue, 19 Dec 2023 21:40:57 +0000 Subject: [PATCH 137/253] Bump Azure to v1.4.0 --- contrib/azure | 2 +- contrib/azure-cmake/CMakeLists.txt | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/contrib/azure b/contrib/azure index 352ff0a61cb..5b2e51d5799 160000 --- a/contrib/azure +++ b/contrib/azure @@ -1 +1 @@ -Subproject commit 352ff0a61cb319ac1cc38c4058443ddf70147530 +Subproject commit 5b2e51d57998df0ef4f493c93f6a2caa012e7c91 diff --git a/contrib/azure-cmake/CMakeLists.txt b/contrib/azure-cmake/CMakeLists.txt index bb44c993e79..89530e74ebc 100644 --- a/contrib/azure-cmake/CMakeLists.txt +++ b/contrib/azure-cmake/CMakeLists.txt @@ -12,26 +12,20 @@ file(GLOB AZURE_SDK_CORE_SRC "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/cryptography/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/http/*.cpp" - "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/http/curl/*.hpp" "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/http/curl/*.cpp" - "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/winhttp/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/io/*.cpp" - "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/private/*.hpp" ) file(GLOB AZURE_SDK_IDENTITY_SRC "${AZURE_SDK_LIBRARY_DIR}/identity/azure-identity/src/*.cpp" - "${AZURE_SDK_LIBRARY_DIR}/identity/azure-identity/src/private/*.hpp" ) file(GLOB AZURE_SDK_STORAGE_COMMON_SRC "${AZURE_SDK_LIBRARY_DIR}/storage/azure-storage-common/src/*.cpp" - "${AZURE_SDK_LIBRARY_DIR}/storage/azure-storage-common/src/private/*.cpp" ) file(GLOB AZURE_SDK_STORAGE_BLOBS_SRC "${AZURE_SDK_LIBRARY_DIR}/storage/azure-storage-blobs/src/*.cpp" - "${AZURE_SDK_LIBRARY_DIR}/storage/azure-storage-blobs/src/private/*.hpp" ) file(GLOB AZURE_SDK_UNIFIED_SRC From 7cf2a62c7e9cdf7381f8e3da05893fa0b1849253 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 20 Dec 2023 09:57:50 +0000 Subject: [PATCH 138/253] Bump Azure to 1.5.0 --- contrib/azure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/azure b/contrib/azure index 5b2e51d5799..309a64eb987 160000 --- a/contrib/azure +++ b/contrib/azure @@ -1 +1 @@ -Subproject commit 5b2e51d57998df0ef4f493c93f6a2caa012e7c91 +Subproject commit 309a64eb987dfa13d51b0f53f8a8b61cc46d8f43 From 4000342b9eb318e9d3fa474b5f4c566e1b12893c Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 20 Dec 2023 11:13:53 +0000 Subject: [PATCH 139/253] Bump Azure to 1.6.0 --- contrib/azure | 2 +- contrib/boringssl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/azure b/contrib/azure index 309a64eb987..a852d81f92f 160000 --- a/contrib/azure +++ b/contrib/azure @@ -1 +1 @@ -Subproject commit 309a64eb987dfa13d51b0f53f8a8b61cc46d8f43 +Subproject commit a852d81f92f153e109de165ee08546741e3f2a68 diff --git a/contrib/boringssl b/contrib/boringssl index 8061ac62d67..aa6d2f865a2 160000 --- a/contrib/boringssl +++ b/contrib/boringssl @@ -1 +1 @@ -Subproject commit 8061ac62d67953e61b793042e33baf1352e67510 +Subproject commit aa6d2f865a2eab01cf94f197e11e36b6de47b5b4 From cba28c9bd0702d9fc3ac1d1e48a119c19ce735eb Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 20 Dec 2023 11:53:07 +0000 Subject: [PATCH 140/253] Update test --- tests/queries/0_stateless/02884_parallel_window_functions.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02884_parallel_window_functions.sql b/tests/queries/0_stateless/02884_parallel_window_functions.sql index 3151b42f896..c5ab013a198 100644 --- a/tests/queries/0_stateless/02884_parallel_window_functions.sql +++ b/tests/queries/0_stateless/02884_parallel_window_functions.sql @@ -1,9 +1,11 @@ +-- Tags: long, no-tsan, no-asan, no-ubsan, no-msan, no-debug + CREATE TABLE window_funtion_threading Engine = MergeTree ORDER BY (ac, nw) AS SELECT toUInt64(toFloat32(number % 2) % 20000000) as ac, - toFloat32(1) as wg, + toFloat32(1) as wg, toUInt16(toFloat32(number % 3) % 400) as nw FROM numbers_mt(10000000); From 3beddc8d2d43acb1fd688daec6682b94775999e0 Mon Sep 17 00:00:00 2001 From: Sema Checherinda Date: Wed, 20 Dec 2023 13:46:21 +0100 Subject: [PATCH 141/253] fix typo --- .../02479_race_condition_between_insert_and_droppin_mv.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02479_race_condition_between_insert_and_droppin_mv.sh b/tests/queries/0_stateless/02479_race_condition_between_insert_and_droppin_mv.sh index 5d9844d5030..9ce4b459fce 100755 --- a/tests/queries/0_stateless/02479_race_condition_between_insert_and_droppin_mv.sh +++ b/tests/queries/0_stateless/02479_race_condition_between_insert_and_droppin_mv.sh @@ -42,7 +42,7 @@ TIMEOUT=55 for i in {1..4} do - timeout $TIMEOUT bash -c drop_mv $i & + timeout $TIMEOUT bash -c "drop_mv $i" & done for i in {1..4} From 2dbde231b25aa3516db7cbdd699f70b8841de54c Mon Sep 17 00:00:00 2001 From: yariks5s Date: Wed, 20 Dec 2023 12:55:11 +0000 Subject: [PATCH 142/253] test flaky fix --- tests/queries/0_stateless/02933_paste_join.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/0_stateless/02933_paste_join.sql b/tests/queries/0_stateless/02933_paste_join.sql index eee41a52044..1c346438d77 100644 --- a/tests/queries/0_stateless/02933_paste_join.sql +++ b/tests/queries/0_stateless/02933_paste_join.sql @@ -31,6 +31,7 @@ INSERT INTO t2 SELECT number, number FROM numbers(15, 15); SELECT * FROM ( SELECT * from t1 ) t1 PASTE JOIN ( SELECT * from t2 ) t2 SETTINGS max_threads = 1; SELECT toTypeName(a) FROM (SELECT number as a FROM numbers(11)) t1 PASTE JOIN (select number as a from numbers(10)) t2 SETTINGS join_use_nulls = 1; +SET max_threads = 2; select * from (SELECT number as a FROM numbers(10)) t1 ANY PASTE JOIN (select number as a from numbers(10)) t2; -- { clientError SYNTAX_ERROR } select * from (SELECT number as a FROM numbers(10)) t1 ALL PASTE JOIN (select number as a from numbers(10)) t2; -- { clientError SYNTAX_ERROR } select * from (SELECT number as a FROM numbers_mt(10)) t1 PASTE JOIN (select number as a from numbers(10) ORDER BY a DESC) t2 SETTINGS max_block_size=3; -- { serverError BAD_ARGUMENTS } From 341806d44017a9c9fff3b4872e3eec40acf014d2 Mon Sep 17 00:00:00 2001 From: Andrei Fedotov Date: Wed, 20 Dec 2023 16:29:17 +0300 Subject: [PATCH 143/253] Fix Integer overflow in Poco::UTF32Encoding --- .../Foundation/include/Poco/UTF32Encoding.h | 9 ++++ base/poco/Foundation/src/UTF32Encoding.cpp | 42 +++++++++---------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/base/poco/Foundation/include/Poco/UTF32Encoding.h b/base/poco/Foundation/include/Poco/UTF32Encoding.h index e6784e787cc..dafac005e83 100644 --- a/base/poco/Foundation/include/Poco/UTF32Encoding.h +++ b/base/poco/Foundation/include/Poco/UTF32Encoding.h @@ -70,6 +70,15 @@ public: int queryConvert(const unsigned char * bytes, int length) const; int sequenceLength(const unsigned char * bytes, int length) const; +protected: + static int safeToInt(Poco::UInt32 value) + { + if (value <= 0x10FFFF) + return static_cast(value); + else + return -1; + } + private: bool _flipBytes; static const char * _names[]; diff --git a/base/poco/Foundation/src/UTF32Encoding.cpp b/base/poco/Foundation/src/UTF32Encoding.cpp index ff07006a4fb..e600c5d9445 100644 --- a/base/poco/Foundation/src/UTF32Encoding.cpp +++ b/base/poco/Foundation/src/UTF32Encoding.cpp @@ -30,22 +30,22 @@ const char* UTF32Encoding::_names[] = const TextEncoding::CharacterMap UTF32Encoding::_charMap = { - /* 00 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* 10 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* 20 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* 30 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* 40 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* 50 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* 60 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* 70 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* 80 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* 90 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* a0 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* b0 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* c0 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* d0 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* e0 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - /* f0 */ -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + /* 00 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* 10 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* 20 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* 30 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* 40 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* 50 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* 60 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* 70 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* 80 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* 90 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* a0 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* b0 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* c0 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* d0 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* e0 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + /* f0 */ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, }; @@ -118,7 +118,7 @@ const TextEncoding::CharacterMap& UTF32Encoding::characterMap() const int UTF32Encoding::convert(const unsigned char* bytes) const { UInt32 uc; - unsigned char* p = (unsigned char*) &uc; + unsigned char* p = reinterpret_cast(&uc); *p++ = *bytes++; *p++ = *bytes++; *p++ = *bytes++; @@ -129,7 +129,7 @@ int UTF32Encoding::convert(const unsigned char* bytes) const ByteOrder::flipBytes(uc); } - return uc; + return safeToInt(uc); } @@ -138,7 +138,7 @@ int UTF32Encoding::convert(int ch, unsigned char* bytes, int length) const if (bytes && length >= 4) { UInt32 ch1 = _flipBytes ? ByteOrder::flipBytes((UInt32) ch) : (UInt32) ch; - unsigned char* p = (unsigned char*) &ch1; + unsigned char* p = reinterpret_cast(&ch1); *bytes++ = *p++; *bytes++ = *p++; *bytes++ = *p++; @@ -155,14 +155,14 @@ int UTF32Encoding::queryConvert(const unsigned char* bytes, int length) const if (length >= 4) { UInt32 uc; - unsigned char* p = (unsigned char*) &uc; + unsigned char* p = reinterpret_cast(&uc); *p++ = *bytes++; *p++ = *bytes++; *p++ = *bytes++; *p++ = *bytes++; if (_flipBytes) ByteOrder::flipBytes(uc); - return uc; + ret = safeToInt(uc); } return ret; From 5de255b3dfe2984dbb37306c21871c6a0650c21c Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 20 Dec 2023 14:05:45 +0000 Subject: [PATCH 144/253] *wip* Bump Azure to 1.7.2 --- contrib/azure | 2 +- contrib/azure-cmake/CMakeLists.txt | 19 ++++--------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/contrib/azure b/contrib/azure index a852d81f92f..060c54dfb0a 160000 --- a/contrib/azure +++ b/contrib/azure @@ -1 +1 @@ -Subproject commit a852d81f92f153e109de165ee08546741e3f2a68 +Subproject commit 060c54dfb0abe869c065143303a9d3e9c54c29e3 diff --git a/contrib/azure-cmake/CMakeLists.txt b/contrib/azure-cmake/CMakeLists.txt index 89530e74ebc..dd519b2f248 100644 --- a/contrib/azure-cmake/CMakeLists.txt +++ b/contrib/azure-cmake/CMakeLists.txt @@ -8,31 +8,20 @@ endif() set(AZURE_DIR "${ClickHouse_SOURCE_DIR}/contrib/azure") set(AZURE_SDK_LIBRARY_DIR "${AZURE_DIR}/sdk") -file(GLOB AZURE_SDK_CORE_SRC +file(GLOB AZURE_SDK_SRC "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/cryptography/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/http/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/http/curl/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/io/*.cpp" -) - -file(GLOB AZURE_SDK_IDENTITY_SRC + "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/tracing/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/identity/azure-identity/src/*.cpp" -) - -file(GLOB AZURE_SDK_STORAGE_COMMON_SRC + "${AZURE_SDK_LIBRARY_DIR}/storage/azure-storage-blobs/src/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/storage/azure-storage-common/src/*.cpp" ) -file(GLOB AZURE_SDK_STORAGE_BLOBS_SRC - "${AZURE_SDK_LIBRARY_DIR}/storage/azure-storage-blobs/src/*.cpp" -) - file(GLOB AZURE_SDK_UNIFIED_SRC - ${AZURE_SDK_CORE_SRC} - ${AZURE_SDK_IDENTITY_SRC} - ${AZURE_SDK_STORAGE_COMMON_SRC} - ${AZURE_SDK_STORAGE_BLOBS_SRC} + ${AZURE_SDK_SRC} ) set(AZURE_SDK_INCLUDES From be398d216bae44c684d76a503f75eef3d80befa1 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 20 Dec 2023 14:28:46 +0000 Subject: [PATCH 145/253] fix flakey test 02567_and_consistency --- .../queries/0_stateless/02567_and_consistency.sql | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/queries/0_stateless/02567_and_consistency.sql b/tests/queries/0_stateless/02567_and_consistency.sql index ca0c0e8ab77..0eeab99e539 100644 --- a/tests/queries/0_stateless/02567_and_consistency.sql +++ b/tests/queries/0_stateless/02567_and_consistency.sql @@ -5,6 +5,7 @@ FROM ) GROUP BY number HAVING 1 AND sin(sum(number)) +ORDER BY ALL SETTINGS enable_optimize_predicate_expression = 0; SELECT '====='; @@ -16,6 +17,7 @@ FROM ) GROUP BY number HAVING 1 AND sin(1) +ORDER BY ALL SETTINGS enable_optimize_predicate_expression = 0; SELECT '====='; @@ -27,6 +29,7 @@ FROM ) GROUP BY number HAVING x AND sin(sum(number)) +ORDER BY ALL SETTINGS enable_optimize_predicate_expression = 1; SELECT '====='; @@ -38,6 +41,7 @@ FROM ) GROUP BY number HAVING 1 AND sin(sum(number)) +ORDER BY ALL SETTINGS enable_optimize_predicate_expression = 0; SELECT '====='; @@ -57,6 +61,7 @@ FROM ) GROUP BY number HAVING 1 AND sin(sum(number)) +ORDER BY ALL SETTINGS enable_optimize_predicate_expression = 1; select '#45440'; @@ -72,12 +77,16 @@ SELECT NOT h, h IS NULL FROM t2 AS left -GROUP BY g; -select '='; +GROUP BY g +ORDER BY g DESC; + +SELECT '='; + SELECT MAX(left.c0), min2(left.c0, -(-left.c0) * (radians(left.c0) - radians(left.c0))) as g, (((-1925024212 IS NOT NULL) IS NOT NULL) != radians(tan(1216286224))) AND cos(lcm(MAX(left.c0), -1966575216) OR (MAX(left.c0) * 1180517420)) as h, not h, h is null FROM t2 AS left GROUP BY g HAVING h ORDER BY g DESC SETTINGS enable_optimize_predicate_expression = 0; -select '='; +SELECT '='; + SELECT MAX(left.c0), min2(left.c0, -(-left.c0) * (radians(left.c0) - radians(left.c0))) as g, (((-1925024212 IS NOT NULL) IS NOT NULL) != radians(tan(1216286224))) AND cos(lcm(MAX(left.c0), -1966575216) OR (MAX(left.c0) * 1180517420)) as h, not h, h is null FROM t2 AS left GROUP BY g HAVING h ORDER BY g DESC SETTINGS enable_optimize_predicate_expression = 1; From 2f1c22a4aaca8f63e3ec9511c5be7bc2f18521ff Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 20 Dec 2023 15:32:33 +0000 Subject: [PATCH 146/253] Maybe fix the build, maybe not fix it --- contrib/azure-cmake/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/azure-cmake/CMakeLists.txt b/contrib/azure-cmake/CMakeLists.txt index dd519b2f248..0d2512c9e6e 100644 --- a/contrib/azure-cmake/CMakeLists.txt +++ b/contrib/azure-cmake/CMakeLists.txt @@ -17,6 +17,7 @@ file(GLOB AZURE_SDK_SRC "${AZURE_SDK_LIBRARY_DIR}/core/azure-core/src/tracing/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/identity/azure-identity/src/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/storage/azure-storage-blobs/src/*.cpp" + "${AZURE_SDK_LIBRARY_DIR}/storage/azure-storage-blobs/src/private/*.cpp" "${AZURE_SDK_LIBRARY_DIR}/storage/azure-storage-common/src/*.cpp" ) From a78b00a854904f1e674343b50b8c07d6feeab900 Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 23 Nov 2023 10:38:32 +0000 Subject: [PATCH 147/253] Analyzer: Fix assert in tryReplaceAndEqualsChainsWithConstant --- .../Passes/IfTransformStringsToEnumPass.cpp | 17 +---------------- .../Passes/LogicalExpressionOptimizerPass.cpp | 19 +++++++++++++++++-- src/Analyzer/Utils.cpp | 16 ++++++++++++++++ src/Analyzer/Utils.h | 3 +++ ...imizer_removing_redundant_checks.reference | 1 + ...al_optimizer_removing_redundant_checks.sql | 2 ++ 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/Analyzer/Passes/IfTransformStringsToEnumPass.cpp b/src/Analyzer/Passes/IfTransformStringsToEnumPass.cpp index 901867b8889..76b14c1a867 100644 --- a/src/Analyzer/Passes/IfTransformStringsToEnumPass.cpp +++ b/src/Analyzer/Passes/IfTransformStringsToEnumPass.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -41,22 +42,6 @@ DataTypePtr getEnumType(const std::set & string_values) return getDataEnumType(string_values); } -QueryTreeNodePtr createCastFunction(QueryTreeNodePtr from, DataTypePtr result_type, ContextPtr context) -{ - auto enum_literal = std::make_shared(result_type->getName(), std::make_shared()); - auto enum_literal_node = std::make_shared(std::move(enum_literal)); - - auto cast_function = FunctionFactory::instance().get("_CAST", std::move(context)); - QueryTreeNodes arguments{ std::move(from), std::move(enum_literal_node) }; - - auto function_node = std::make_shared("_CAST"); - function_node->getArguments().getNodes() = std::move(arguments); - - function_node->resolveAsFunction(cast_function->build(function_node->getArgumentColumns())); - - return function_node; -} - /// if(arg1, arg2, arg3) will be transformed to if(arg1, _CAST(arg2, Enum...), _CAST(arg3, Enum...)) /// where Enum is generated based on the possible values stored in string_values void changeIfArguments( diff --git a/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp b/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp index 6fa6c8b0e78..3bafa8bbd6d 100644 --- a/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp +++ b/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp @@ -9,6 +9,8 @@ #include #include +#include + namespace DB { @@ -323,8 +325,21 @@ private: /// Because we reduce the number of operands here by eliminating the same equality checks, /// the only situation we can end up here is we had AND check where all the equality checks are the same so we know the type is UInt8. /// Otherwise, we will have > 1 operands and we don't have to do anything. - assert(!function_node.getResultType()->isNullable() && and_operands[0]->getResultType()->equals(*function_node.getResultType())); - node = std::move(and_operands[0]); + + auto operand_type = and_operands[0]->getResultType(); + auto function_type = function_node.getResultType(); + assert(!function_type->isNullable()); + if (!function_type->equals(*operand_type)) + { + /// Result of equality operator can be low cardinality, while AND always returns UInt8. + /// In that case we replace `(lc = 1) AND (lc = 1)` with `(lc = 1) AS UInt8` + assert(function_type->equals(*removeLowCardinality(operand_type))); + node = createCastFunction(std::move(and_operands[0]), function_type, getContext()); + } + else + { + node = std::move(and_operands[0]); + } return; } diff --git a/src/Analyzer/Utils.cpp b/src/Analyzer/Utils.cpp index 918126e0ccc..f75022220e7 100644 --- a/src/Analyzer/Utils.cpp +++ b/src/Analyzer/Utils.cpp @@ -667,4 +667,20 @@ NameSet collectIdentifiersFullNames(const QueryTreeNodePtr & node) return out; } +QueryTreeNodePtr createCastFunction(QueryTreeNodePtr node, DataTypePtr result_type, ContextPtr context) +{ + auto enum_literal = std::make_shared(result_type->getName(), std::make_shared()); + auto enum_literal_node = std::make_shared(std::move(enum_literal)); + + auto cast_function = FunctionFactory::instance().get("_CAST", std::move(context)); + QueryTreeNodes arguments{ std::move(node), std::move(enum_literal_node) }; + + auto function_node = std::make_shared("_CAST"); + function_node->getArguments().getNodes() = std::move(arguments); + + function_node->resolveAsFunction(cast_function->build(function_node->getArgumentColumns())); + + return function_node; +} + } diff --git a/src/Analyzer/Utils.h b/src/Analyzer/Utils.h index 060dc7d8bc0..e3316f5ad6b 100644 --- a/src/Analyzer/Utils.h +++ b/src/Analyzer/Utils.h @@ -99,4 +99,7 @@ void rerunFunctionResolve(FunctionNode * function_node, ContextPtr context); /// Just collect all identifiers from query tree NameSet collectIdentifiersFullNames(const QueryTreeNodePtr & node); +/// Wrap node into `_CAST` function +QueryTreeNodePtr createCastFunction(QueryTreeNodePtr node, DataTypePtr result_type, ContextPtr context); + } diff --git a/tests/queries/0_stateless/02668_logical_optimizer_removing_redundant_checks.reference b/tests/queries/0_stateless/02668_logical_optimizer_removing_redundant_checks.reference index 60ff2d76995..cc74b0237fe 100644 --- a/tests/queries/0_stateless/02668_logical_optimizer_removing_redundant_checks.reference +++ b/tests/queries/0_stateless/02668_logical_optimizer_removing_redundant_checks.reference @@ -88,3 +88,4 @@ QUERY id: 0 COLUMN id: 7, column_name: a, result_type: Int32, source_id: 3 CONSTANT id: 8, constant_value: UInt64_2, constant_value_type: UInt8 1 +1 diff --git a/tests/queries/0_stateless/02668_logical_optimizer_removing_redundant_checks.sql b/tests/queries/0_stateless/02668_logical_optimizer_removing_redundant_checks.sql index eebea322dbf..5dee450086c 100644 --- a/tests/queries/0_stateless/02668_logical_optimizer_removing_redundant_checks.sql +++ b/tests/queries/0_stateless/02668_logical_optimizer_removing_redundant_checks.sql @@ -25,4 +25,6 @@ EXPLAIN QUERY TREE SELECT * FROM 02668_logical_optimizer WHERE a = 3 AND b = 'an SELECT * FROM 02668_logical_optimizer WHERE a = 2 AND 2 = a; EXPLAIN QUERY TREE SELECT * FROM 02668_logical_optimizer WHERE a = 2 AND 2 = a; +SELECT a FROM 02668_logical_optimizer WHERE (b = 'test') AND ('test' = b); + SELECT (k = 3) OR ( (k = 1) OR (k = 2) OR ( (NULL OR 1) = k ) ) FROM ( SELECT materialize(1) AS k ); From 961cb894b580a5a5efd1f0a7c9b6836ed6c21808 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 20 Dec 2023 16:56:39 +0100 Subject: [PATCH 148/253] Update run.sh --- docker/test/stateless/run.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/test/stateless/run.sh b/docker/test/stateless/run.sh index bd44d1724ae..4e9486d7286 100755 --- a/docker/test/stateless/run.sh +++ b/docker/test/stateless/run.sh @@ -58,6 +58,7 @@ if [[ -n "$BUGFIX_VALIDATE_CHECK" ]] && [[ "$BUGFIX_VALIDATE_CHECK" -eq 1 ]]; th # it contains some new settings, but we can safely remove it rm /etc/clickhouse-server/users.d/s3_cache_new.xml + rm /etc/clickhouse-server/config.d/zero_copy_destructive_operations.xml fi # For flaky check we also enable thread fuzzer From dc701c4483e40b71be419257a4bad9156eeccfdd Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Wed, 20 Dec 2023 16:12:03 +0000 Subject: [PATCH 149/253] Test for parallel replicas with remote() --- .../02946_parallel_replicas_distributed.sql | 2 +- .../02947_parallel_replicas_remote.reference | 0 .../0_stateless/02947_parallel_replicas_remote.sql | 12 ++++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/02947_parallel_replicas_remote.reference create mode 100644 tests/queries/0_stateless/02947_parallel_replicas_remote.sql diff --git a/tests/queries/0_stateless/02946_parallel_replicas_distributed.sql b/tests/queries/0_stateless/02946_parallel_replicas_distributed.sql index 6c7fbd0f752..1afd4ff0192 100644 --- a/tests/queries/0_stateless/02946_parallel_replicas_distributed.sql +++ b/tests/queries/0_stateless/02946_parallel_replicas_distributed.sql @@ -11,7 +11,7 @@ ENGINE = Distributed(test_cluster_one_shard_three_replicas_localhost, currentDat SELECT count(), sum(id) FROM test_d -SETTINGS allow_experimental_parallel_reading_from_replicas = 1, max_parallel_replicas = 3, prefer_localhost_replica = 0; +SETTINGS allow_experimental_parallel_reading_from_replicas = 2, max_parallel_replicas = 3, prefer_localhost_replica = 0, parallel_replicas_for_non_replicated_merge_tree=1; DROP TABLE test_d; DROP TABLE test; diff --git a/tests/queries/0_stateless/02947_parallel_replicas_remote.reference b/tests/queries/0_stateless/02947_parallel_replicas_remote.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02947_parallel_replicas_remote.sql b/tests/queries/0_stateless/02947_parallel_replicas_remote.sql new file mode 100644 index 00000000000..345d9f9cb03 --- /dev/null +++ b/tests/queries/0_stateless/02947_parallel_replicas_remote.sql @@ -0,0 +1,12 @@ +DROP TABLE IF EXISTS test; + +CREATE TABLE test (id UInt64, date Date) +ENGINE = MergeTree +ORDER BY id +AS select *, '2023-12-25' from numbers(100); + +SELECT count(), sum(id) +FROM remote('127.0.0.1|127.0.0.2|127.0.0.3|127.0.0.4', currentDatabase(), test) +SETTINGS allow_experimental_parallel_reading_from_replicas = 2, max_parallel_replicas = 4, prefer_localhost_replica = 0, parallel_replicas_for_non_replicated_merge_tree = 1; -- { serverError CLUSTER_DOESNT_EXIST } + +DROP TABLE test; From d57ac6dfdb07883373b18464d39e6f390d4eb9df Mon Sep 17 00:00:00 2001 From: Nikita Taranov Date: Wed, 20 Dec 2023 17:33:05 +0100 Subject: [PATCH 150/253] Set replica number to its position in cluster definition (#57800) * impl * fix --- src/Interpreters/ClusterProxy/executeQuery.cpp | 9 +++------ src/Processors/QueryPlan/ReadFromRemote.cpp | 8 ++++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Interpreters/ClusterProxy/executeQuery.cpp b/src/Interpreters/ClusterProxy/executeQuery.cpp index 549eadcebd2..18f7280dd19 100644 --- a/src/Interpreters/ClusterProxy/executeQuery.cpp +++ b/src/Interpreters/ClusterProxy/executeQuery.cpp @@ -382,7 +382,6 @@ void executeQueryWithParallelReplicas( shard_num = column->getUInt(0); } - size_t all_replicas_count = 0; ClusterPtr new_cluster; /// if got valid shard_num from query initiator, then parallel replicas scope is the specified shard /// shards are numbered in order of appearance in the cluster config @@ -406,16 +405,14 @@ void executeQueryWithParallelReplicas( // shard_num is 1-based, but getClusterWithSingleShard expects 0-based index auto single_shard_cluster = not_optimized_cluster->getClusterWithSingleShard(shard_num - 1); // convert cluster to representation expected by parallel replicas - new_cluster = single_shard_cluster->getClusterWithReplicasAsShards(settings); + new_cluster = single_shard_cluster->getClusterWithReplicasAsShards(settings, settings.max_parallel_replicas); } else { - new_cluster = not_optimized_cluster->getClusterWithReplicasAsShards(settings); + new_cluster = not_optimized_cluster->getClusterWithReplicasAsShards(settings, settings.max_parallel_replicas); } - all_replicas_count = std::min(static_cast(settings.max_parallel_replicas), new_cluster->getShardCount()); - - auto coordinator = std::make_shared(all_replicas_count); + auto coordinator = std::make_shared(new_cluster->getShardCount()); auto external_tables = new_context->getExternalTables(); auto read_from_remote = std::make_unique( query_ast, diff --git a/src/Processors/QueryPlan/ReadFromRemote.cpp b/src/Processors/QueryPlan/ReadFromRemote.cpp index f1dff279792..0d1fae0d239 100644 --- a/src/Processors/QueryPlan/ReadFromRemote.cpp +++ b/src/Processors/QueryPlan/ReadFromRemote.cpp @@ -367,7 +367,9 @@ void ReadFromParallelRemoteReplicasStep::initializePipeline(QueryPipelineBuilder IConnections::ReplicaInfo replica_info { .all_replicas_count = all_replicas_count, - .number_of_current_replica = 0 + /// `shard_num` will be equal to the number of the given replica in the cluster (set by `Cluster::getClusterWithReplicasAsShards`). + /// we should use this number specifically because efficiency of data distribution by consistent hash depends on it. + .number_of_current_replica = shard.shard_num - 1, }; addPipeForSingeReplica(pipes, shard.pool, replica_info); @@ -386,7 +388,9 @@ void ReadFromParallelRemoteReplicasStep::initializePipeline(QueryPipelineBuilder IConnections::ReplicaInfo replica_info { .all_replicas_count = all_replicas_count, - .number_of_current_replica = pipes.size() + /// `shard_num` will be equal to the number of the given replica in the cluster (set by `Cluster::getClusterWithReplicasAsShards`). + /// we should use this number specifically because efficiency of data distribution by consistent hash depends on it. + .number_of_current_replica = current_shard->shard_num - 1, }; addPipeForSingeReplica(pipes, current_shard->pool, replica_info); From a58145056739fe634bfb737d3b96e666c4539865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Wed, 20 Dec 2023 18:42:36 +0100 Subject: [PATCH 151/253] Cosmetic --- src/Backups/BackupInfo.cpp | 7 +++++-- src/Common/ZooKeeper/ZooKeeperWithFaultInjection.cpp | 5 +++++ src/Common/ZooKeeper/ZooKeeperWithFaultInjection.h | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Backups/BackupInfo.cpp b/src/Backups/BackupInfo.cpp index f993d7ed984..2bff400d4fe 100644 --- a/src/Backups/BackupInfo.cpp +++ b/src/Backups/BackupInfo.cpp @@ -78,13 +78,16 @@ BackupInfo BackupInfo::fromAST(const IAST & ast) } } - res.args.reserve(list->children.size() - index); - for (; index < list->children.size(); ++index) + size_t args_size = list->children.size(); + res.args.reserve(args_size - index); + for (; index < args_size; ++index) { const auto & elem = list->children[index]; const auto * lit = elem->as(); if (!lit) + { throw Exception(ErrorCodes::BAD_ARGUMENTS, "Expected literal, got {}", serializeAST(*elem)); + } res.args.push_back(lit->value); } } diff --git a/src/Common/ZooKeeper/ZooKeeperWithFaultInjection.cpp b/src/Common/ZooKeeper/ZooKeeperWithFaultInjection.cpp index 4de11cdbc7e..72923ca0487 100644 --- a/src/Common/ZooKeeper/ZooKeeperWithFaultInjection.cpp +++ b/src/Common/ZooKeeper/ZooKeeperWithFaultInjection.cpp @@ -290,6 +290,11 @@ bool ZooKeeperWithFaultInjection::exists(const std::string & path, Coordination: return executeWithFaultSync(__func__, path, [&]() { return keeper->exists(path, stat, watch); }); } +bool ZooKeeperWithFaultInjection::anyExists(const std::vector & paths) +{ + return executeWithFaultSync(__func__, !paths.empty() ? paths.front() : "", [&]() { return keeper->anyExists(paths); }); +} + zkutil::ZooKeeper::MultiExistsResponse ZooKeeperWithFaultInjection::exists(const std::vector & paths) { return executeWithFaultSync(__func__, !paths.empty() ? paths.front() : "", [&]() { return keeper->exists(paths); }); diff --git a/src/Common/ZooKeeper/ZooKeeperWithFaultInjection.h b/src/Common/ZooKeeper/ZooKeeperWithFaultInjection.h index 9354c53df26..57e1f0f3b87 100644 --- a/src/Common/ZooKeeper/ZooKeeperWithFaultInjection.h +++ b/src/Common/ZooKeeper/ZooKeeperWithFaultInjection.h @@ -59,6 +59,7 @@ private: class ZooKeeperWithFaultInjection { zkutil::ZooKeeper::Ptr keeper; + std::unique_ptr fault_policy; std::string name; Poco::Logger * logger = nullptr; @@ -203,6 +204,8 @@ public: zkutil::ZooKeeper::MultiExistsResponse exists(const std::vector & paths); + bool anyExists(const std::vector & paths); + std::string create(const std::string & path, const std::string & data, int32_t mode); Coordination::Error tryCreate(const std::string & path, const std::string & data, int32_t mode, std::string & path_created); From 9def89d416a94659b7b776e939329fbe71e7681b Mon Sep 17 00:00:00 2001 From: Christoph Wurm Date: Wed, 20 Dec 2023 18:26:36 +0000 Subject: [PATCH 152/253] Fix anchors to settings.md --- .../table-engines/integrations/embedded-rocksdb.md | 2 +- docs/en/engines/table-engines/integrations/hdfs.md | 2 +- docs/en/engines/table-engines/integrations/kafka.md | 4 ++-- docs/en/engines/table-engines/integrations/nats.md | 2 +- docs/en/engines/table-engines/integrations/rabbitmq.md | 2 +- docs/en/engines/table-engines/integrations/s3.md | 2 +- docs/en/engines/table-engines/special/distributed.md | 6 +++--- docs/en/engines/table-engines/special/file.md | 4 ++-- docs/en/engines/table-engines/special/filelog.md | 2 +- docs/en/interfaces/http.md | 10 +++++----- docs/en/operations/monitoring.md | 2 +- .../optimizing-performance/sampling-query-profiler.md | 2 +- docs/en/operations/query-cache.md | 4 ++-- .../server-configuration-parameters/settings.md | 6 +++--- docs/en/operations/settings/query-complexity.md | 2 +- docs/en/operations/settings/settings.md | 6 +++--- docs/en/operations/system-tables/clusters.md | 4 ++-- docs/en/operations/system-tables/query_log.md | 6 +++--- docs/en/operations/system-tables/query_thread_log.md | 2 +- docs/en/operations/system-tables/query_views_log.md | 2 +- docs/en/operations/system-tables/table_engines.md | 2 +- .../aggregate-functions/reference/count.md | 2 +- docs/en/sql-reference/functions/array-functions.md | 2 +- docs/en/sql-reference/functions/introspection.md | 2 +- docs/en/sql-reference/statements/select/join.md | 10 +++++----- docs/en/sql-reference/syntax.md | 2 +- docs/en/sql-reference/table-functions/cluster.md | 4 ++-- docs/en/sql-reference/table-functions/file.md | 4 ++-- docs/en/sql-reference/table-functions/hdfs.md | 2 +- docs/en/sql-reference/table-functions/remote.md | 2 +- docs/en/sql-reference/table-functions/s3.md | 2 +- 31 files changed, 53 insertions(+), 53 deletions(-) diff --git a/docs/en/engines/table-engines/integrations/embedded-rocksdb.md b/docs/en/engines/table-engines/integrations/embedded-rocksdb.md index 9af857b0835..44febe78c77 100644 --- a/docs/en/engines/table-engines/integrations/embedded-rocksdb.md +++ b/docs/en/engines/table-engines/integrations/embedded-rocksdb.md @@ -212,5 +212,5 @@ ORDER BY key ASC ``` ### More information on Joins -- [`join_algorithm` setting](/docs/en/operations/settings/settings.md#settings-join_algorithm) +- [`join_algorithm` setting](/docs/en/operations/settings/settings.md#join_algorithm) - [JOIN clause](/docs/en/sql-reference/statements/select/join.md) diff --git a/docs/en/engines/table-engines/integrations/hdfs.md b/docs/en/engines/table-engines/integrations/hdfs.md index 19221c256f9..96e6bab6997 100644 --- a/docs/en/engines/table-engines/integrations/hdfs.md +++ b/docs/en/engines/table-engines/integrations/hdfs.md @@ -236,7 +236,7 @@ libhdfs3 support HDFS namenode HA. ## Storage Settings {#storage-settings} -- [hdfs_truncate_on_insert](/docs/en/operations/settings/settings.md#hdfs-truncate-on-insert) - allows to truncate file before insert into it. Disabled by default. +- [hdfs_truncate_on_insert](/docs/en/operations/settings/settings.md#hdfs_truncate_on_insert) - allows to truncate file before insert into it. Disabled by default. - [hdfs_create_multiple_files](/docs/en/operations/settings/settings.md#hdfs_allow_create_multiple_files) - allows to create a new file on each insert if format has suffix. Disabled by default. - [hdfs_skip_empty_files](/docs/en/operations/settings/settings.md#hdfs_skip_empty_files) - allows to skip empty files while reading. Disabled by default. diff --git a/docs/en/engines/table-engines/integrations/kafka.md b/docs/en/engines/table-engines/integrations/kafka.md index de1a090d491..141d87fed20 100644 --- a/docs/en/engines/table-engines/integrations/kafka.md +++ b/docs/en/engines/table-engines/integrations/kafka.md @@ -54,7 +54,7 @@ Optional parameters: - `kafka_schema` — Parameter that must be used if the format requires a schema definition. For example, [Cap’n Proto](https://capnproto.org/) requires the path to the schema file and the name of the root `schema.capnp:Message` object. - `kafka_num_consumers` — The number of consumers per table. Specify more consumers if the throughput of one consumer is insufficient. The total number of consumers should not exceed the number of partitions in the topic, since only one consumer can be assigned per partition, and must not be greater than the number of physical cores on the server where ClickHouse is deployed. Default: `1`. -- `kafka_max_block_size` — The maximum batch size (in messages) for poll. Default: [max_insert_block_size](../../../operations/settings/settings.md#setting-max_insert_block_size). +- `kafka_max_block_size` — The maximum batch size (in messages) for poll. Default: [max_insert_block_size](../../../operations/settings/settings.md#max_insert_block_size). - `kafka_skip_broken_messages` — Kafka message parser tolerance to schema-incompatible messages per block. If `kafka_skip_broken_messages = N` then the engine skips *N* Kafka messages that cannot be parsed (a message equals a row of data). Default: `0`. - `kafka_commit_every_batch` — Commit every consumed and handled batch instead of a single commit after writing a whole block. Default: `0`. - `kafka_client_id` — Client identifier. Empty by default. @@ -151,7 +151,7 @@ Example: SELECT level, sum(total) FROM daily GROUP BY level; ``` -To improve performance, received messages are grouped into blocks the size of [max_insert_block_size](../../../operations/settings/settings.md#settings-max_insert_block_size). If the block wasn’t formed within [stream_flush_interval_ms](../../../operations/settings/settings.md/#stream-flush-interval-ms) milliseconds, the data will be flushed to the table regardless of the completeness of the block. +To improve performance, received messages are grouped into blocks the size of [max_insert_block_size](../../../operations/settings/settings.md#max_insert_block_size). If the block wasn’t formed within [stream_flush_interval_ms](../../../operations/settings/settings.md/#stream-flush-interval-ms) milliseconds, the data will be flushed to the table regardless of the completeness of the block. To stop receiving topic data or to change the conversion logic, detach the materialized view: diff --git a/docs/en/engines/table-engines/integrations/nats.md b/docs/en/engines/table-engines/integrations/nats.md index 37a41159fab..e898d1f1b82 100644 --- a/docs/en/engines/table-engines/integrations/nats.md +++ b/docs/en/engines/table-engines/integrations/nats.md @@ -58,7 +58,7 @@ Optional parameters: - `nats_reconnect_wait` – Amount of time in milliseconds to sleep between each reconnect attempt. Default: `5000`. - `nats_server_list` - Server list for connection. Can be specified to connect to NATS cluster. - `nats_skip_broken_messages` - NATS message parser tolerance to schema-incompatible messages per block. Default: `0`. If `nats_skip_broken_messages = N` then the engine skips *N* RabbitMQ messages that cannot be parsed (a message equals a row of data). -- `nats_max_block_size` - Number of row collected by poll(s) for flushing data from NATS. Default: [max_insert_block_size](../../../operations/settings/settings.md#setting-max_insert_block_size). +- `nats_max_block_size` - Number of row collected by poll(s) for flushing data from NATS. Default: [max_insert_block_size](../../../operations/settings/settings.md#max_insert_block_size). - `nats_flush_interval_ms` - Timeout for flushing data read from NATS. Default: [stream_flush_interval_ms](../../../operations/settings/settings.md#stream-flush-interval-ms). - `nats_username` - NATS username. - `nats_password` - NATS password. diff --git a/docs/en/engines/table-engines/integrations/rabbitmq.md b/docs/en/engines/table-engines/integrations/rabbitmq.md index 53c6e089a70..0f3fef3d6fb 100644 --- a/docs/en/engines/table-engines/integrations/rabbitmq.md +++ b/docs/en/engines/table-engines/integrations/rabbitmq.md @@ -65,7 +65,7 @@ Optional parameters: - `rabbitmq_deadletter_exchange` - Specify name for a [dead letter exchange](https://www.rabbitmq.com/dlx.html). You can create another table with this exchange name and collect messages in cases when they are republished to dead letter exchange. By default dead letter exchange is not specified. - `rabbitmq_persistent` - If set to 1 (true), in insert query delivery mode will be set to 2 (marks messages as 'persistent'). Default: `0`. - `rabbitmq_skip_broken_messages` – RabbitMQ message parser tolerance to schema-incompatible messages per block. If `rabbitmq_skip_broken_messages = N` then the engine skips *N* RabbitMQ messages that cannot be parsed (a message equals a row of data). Default: `0`. -- `rabbitmq_max_block_size` - Number of row collected before flushing data from RabbitMQ. Default: [max_insert_block_size](../../../operations/settings/settings.md#setting-max_insert_block_size). +- `rabbitmq_max_block_size` - Number of row collected before flushing data from RabbitMQ. Default: [max_insert_block_size](../../../operations/settings/settings.md#max_insert_block_size). - `rabbitmq_flush_interval_ms` - Timeout for flushing data from RabbitMQ. Default: [stream_flush_interval_ms](../../../operations/settings/settings.md#stream-flush-interval-ms). - `rabbitmq_queue_settings_list` - allows to set RabbitMQ settings when creating a queue. Available settings: `x-max-length`, `x-max-length-bytes`, `x-message-ttl`, `x-expires`, `x-priority`, `x-max-priority`, `x-overflow`, `x-dead-letter-exchange`, `x-queue-type`. The `durable` setting is enabled automatically for the queue. - `rabbitmq_address` - Address for connection. Use ether this setting or `rabbitmq_host_port`. diff --git a/docs/en/engines/table-engines/integrations/s3.md b/docs/en/engines/table-engines/integrations/s3.md index 3144bdd32fa..dfa06801d04 100644 --- a/docs/en/engines/table-engines/integrations/s3.md +++ b/docs/en/engines/table-engines/integrations/s3.md @@ -222,7 +222,7 @@ CREATE TABLE table_with_asterisk (name String, value UInt32) ## Storage Settings {#storage-settings} -- [s3_truncate_on_insert](/docs/en/operations/settings/settings.md#s3-truncate-on-insert) - allows to truncate file before insert into it. Disabled by default. +- [s3_truncate_on_insert](/docs/en/operations/settings/settings.md#s3_truncate_on_insert) - allows to truncate file before insert into it. Disabled by default. - [s3_create_multiple_files](/docs/en/operations/settings/settings.md#s3_allow_create_multiple_files) - allows to create a new file on each insert if format has suffix. Disabled by default. - [s3_skip_empty_files](/docs/en/operations/settings/settings.md#s3_skip_empty_files) - allows to skip empty files while reading. Disabled by default. diff --git a/docs/en/engines/table-engines/special/distributed.md b/docs/en/engines/table-engines/special/distributed.md index 6224c450ea2..de8ae0357dc 100644 --- a/docs/en/engines/table-engines/special/distributed.md +++ b/docs/en/engines/table-engines/special/distributed.md @@ -112,7 +112,7 @@ Specifying the `sharding_key` is necessary for the following: For **Insert limit settings** (`..._insert`) see also: - [distributed_foreground_insert](../../../operations/settings/settings.md#distributed_foreground_insert) setting -- [prefer_localhost_replica](../../../operations/settings/settings.md#settings-prefer-localhost-replica) setting +- [prefer_localhost_replica](../../../operations/settings/settings.md#prefer-localhost-replica) setting - `bytes_to_throw_insert` handled before `bytes_to_delay_insert`, so you should not set it to the value less then `bytes_to_delay_insert` ::: @@ -198,7 +198,7 @@ The parameters `host`, `port`, and optionally `user`, `password`, `secure`, `com - `secure` - Whether to use a secure SSL/TLS connection. Usually also requires specifying the port (the default secure port is `9440`). The server should listen on `9440` and be configured with correct certificates. - `compression` - Use data compression. Default value: `true`. -When specifying replicas, one of the available replicas will be selected for each of the shards when reading. You can configure the algorithm for load balancing (the preference for which replica to access) – see the [load_balancing](../../../operations/settings/settings.md#settings-load_balancing) setting. If the connection with the server is not established, there will be an attempt to connect with a short timeout. If the connection failed, the next replica will be selected, and so on for all the replicas. If the connection attempt failed for all the replicas, the attempt will be repeated the same way, several times. This works in favour of resiliency, but does not provide complete fault tolerance: a remote server might accept the connection, but might not work, or work poorly. +When specifying replicas, one of the available replicas will be selected for each of the shards when reading. You can configure the algorithm for load balancing (the preference for which replica to access) – see the [load_balancing](../../../operations/settings/settings.md#load_balancing) setting. If the connection with the server is not established, there will be an attempt to connect with a short timeout. If the connection failed, the next replica will be selected, and so on for all the replicas. If the connection attempt failed for all the replicas, the attempt will be repeated the same way, several times. This works in favour of resiliency, but does not provide complete fault tolerance: a remote server might accept the connection, but might not work, or work poorly. You can specify just one of the shards (in this case, query processing should be called remote, rather than distributed) or up to any number of shards. In each shard, you can specify from one to any number of replicas. You can specify a different number of replicas for each shard. @@ -243,7 +243,7 @@ If the server ceased to exist or had a rough restart (for example, due to a hard When querying a `Distributed` table, `SELECT` queries are sent to all shards and work regardless of how data is distributed across the shards (they can be distributed completely randomly). When you add a new shard, you do not have to transfer old data into it. Instead, you can write new data to it by using a heavier weight – the data will be distributed slightly unevenly, but queries will work correctly and efficiently. -When the `max_parallel_replicas` option is enabled, query processing is parallelized across all replicas within a single shard. For more information, see the section [max_parallel_replicas](../../../operations/settings/settings.md#settings-max_parallel_replicas). +When the `max_parallel_replicas` option is enabled, query processing is parallelized across all replicas within a single shard. For more information, see the section [max_parallel_replicas](../../../operations/settings/settings.md#max_parallel_replicas). To learn more about how distributed `in` and `global in` queries are processed, refer to [this](../../../sql-reference/operators/in.md#select-distributed-subqueries) documentation. diff --git a/docs/en/engines/table-engines/special/file.md b/docs/en/engines/table-engines/special/file.md index 6e3897398a5..fdf5242ba3b 100644 --- a/docs/en/engines/table-engines/special/file.md +++ b/docs/en/engines/table-engines/special/file.md @@ -101,8 +101,8 @@ For partitioning by month, use the `toYYYYMM(date_column)` expression, where `da ## Settings {#settings} -- [engine_file_empty_if_not_exists](/docs/en/operations/settings/settings.md#engine-file-emptyif-not-exists) - allows to select empty data from a file that doesn't exist. Disabled by default. +- [engine_file_empty_if_not_exists](/docs/en/operations/settings/settings.md#engine-file-empty_if-not-exists) - allows to select empty data from a file that doesn't exist. Disabled by default. - [engine_file_truncate_on_insert](/docs/en/operations/settings/settings.md#engine-file-truncate-on-insert) - allows to truncate file before insert into it. Disabled by default. - [engine_file_allow_create_multiple_files](/docs/en/operations/settings/settings.md#engine_file_allow_create_multiple_files) - allows to create a new file on each insert if format has suffix. Disabled by default. - [engine_file_skip_empty_files](/docs/en/operations/settings/settings.md#engine_file_skip_empty_files) - allows to skip empty files while reading. Disabled by default. -- [storage_file_read_method](/docs/en/operations/settings/settings.md#engine-file-emptyif-not-exists) - method of reading data from storage file, one of: `read`, `pread`, `mmap`. The mmap method does not apply to clickhouse-server (it's intended for clickhouse-local). Default value: `pread` for clickhouse-server, `mmap` for clickhouse-local. +- [storage_file_read_method](/docs/en/operations/settings/settings.md#engine-file-empty_if-not-exists) - method of reading data from storage file, one of: `read`, `pread`, `mmap`. The mmap method does not apply to clickhouse-server (it's intended for clickhouse-local). Default value: `pread` for clickhouse-server, `mmap` for clickhouse-local. diff --git a/docs/en/engines/table-engines/special/filelog.md b/docs/en/engines/table-engines/special/filelog.md index eef9a17444e..82201053bc5 100644 --- a/docs/en/engines/table-engines/special/filelog.md +++ b/docs/en/engines/table-engines/special/filelog.md @@ -41,7 +41,7 @@ Optional parameters: - `poll_timeout_ms` - Timeout for single poll from log file. Default: [stream_poll_timeout_ms](../../../operations/settings/settings.md#stream_poll_timeout_ms). - `poll_max_batch_size` — Maximum amount of records to be polled in a single poll. Default: [max_block_size](../../../operations/settings/settings.md#setting-max_block_size). -- `max_block_size` — The maximum batch size (in records) for poll. Default: [max_insert_block_size](../../../operations/settings/settings.md#setting-max_insert_block_size). +- `max_block_size` — The maximum batch size (in records) for poll. Default: [max_insert_block_size](../../../operations/settings/settings.md#max_insert_block_size). - `max_threads` - Number of max threads to parse files, default is 0, which means the number will be max(1, physical_cpu_cores / 4). - `poll_directory_watch_events_backoff_init` - The initial sleep value for watch directory thread. Default: `500`. - `poll_directory_watch_events_backoff_max` - The max sleep value for watch directory thread. Default: `32000`. diff --git a/docs/en/interfaces/http.md b/docs/en/interfaces/http.md index 63f75fb7830..4eeb19cefcf 100644 --- a/docs/en/interfaces/http.md +++ b/docs/en/interfaces/http.md @@ -167,7 +167,7 @@ For successful requests that do not return a data table, an empty response body You can use compression to reduce network traffic when transmitting a large amount of data or for creating dumps that are immediately compressed. -You can use the internal ClickHouse compression format when transmitting data. The compressed data has a non-standard format, and you need `clickhouse-compressor` program to work with it. It is installed with the `clickhouse-client` package. To increase the efficiency of data insertion, you can disable server-side checksum verification by using the [http_native_compression_disable_checksumming_on_decompress](../operations/settings/settings.md#settings-http_native_compression_disable_checksumming_on_decompress) setting. +You can use the internal ClickHouse compression format when transmitting data. The compressed data has a non-standard format, and you need `clickhouse-compressor` program to work with it. It is installed with the `clickhouse-client` package. To increase the efficiency of data insertion, you can disable server-side checksum verification by using the [http_native_compression_disable_checksumming_on_decompress](../operations/settings/settings.md#http_native_compression_disable_checksumming_on_decompress) setting. If you specify `compress=1` in the URL, the server will compress the data it sends to you. If you specify `decompress=1` in the URL, the server will decompress the data which you pass in the `POST` method. @@ -183,7 +183,7 @@ You can also choose to use [HTTP compression](https://en.wikipedia.org/wiki/HTTP - `snappy` To send a compressed `POST` request, append the request header `Content-Encoding: compression_method`. -In order for ClickHouse to compress the response, enable compression with [enable_http_compression](../operations/settings/settings.md#settings-enable_http_compression) setting and append `Accept-Encoding: compression_method` header to the request. You can configure the data compression level in the [http_zlib_compression_level](../operations/settings/settings.md#settings-http_zlib_compression_level) setting for all compression methods. +In order for ClickHouse to compress the response, enable compression with [enable_http_compression](../operations/settings/settings.md#enable_http_compression) setting and append `Accept-Encoding: compression_method` header to the request. You can configure the data compression level in the [http_zlib_compression_level](../operations/settings/settings.md#http_zlib_compression_level) setting for all compression methods. :::info Some HTTP clients might decompress data from the server by default (with `gzip` and `deflate`) and you might get decompressed data even if you use the compression settings correctly. @@ -285,7 +285,7 @@ For information about other parameters, see the section “SET”. Similarly, you can use ClickHouse sessions in the HTTP protocol. To do this, you need to add the `session_id` GET parameter to the request. You can use any string as the session ID. By default, the session is terminated after 60 seconds of inactivity. To change this timeout, modify the `default_session_timeout` setting in the server configuration, or add the `session_timeout` GET parameter to the request. To check the session status, use the `session_check=1` parameter. Only one query at a time can be executed within a single session. -You can receive information about the progress of a query in `X-ClickHouse-Progress` response headers. To do this, enable [send_progress_in_http_headers](../operations/settings/settings.md#settings-send_progress_in_http_headers). Example of the header sequence: +You can receive information about the progress of a query in `X-ClickHouse-Progress` response headers. To do this, enable [send_progress_in_http_headers](../operations/settings/settings.md#send_progress_in_http_headers). Example of the header sequence: ``` text X-ClickHouse-Progress: {"read_rows":"2752512","read_bytes":"240570816","total_rows_to_read":"8880128","elapsed_ns":"662334"} @@ -496,7 +496,7 @@ Next are the configuration methods for different `type`. `query` value is a predefined query of `predefined_query_handler`, which is executed by ClickHouse when an HTTP request is matched and the result of the query is returned. It is a must configuration. -The following example defines the values of [max_threads](../operations/settings/settings.md#settings-max_threads) and `max_final_threads` settings, then queries the system table to check whether these settings were set successfully. +The following example defines the values of [max_threads](../operations/settings/settings.md#max_threads) and `max_final_threads` settings, then queries the system table to check whether these settings were set successfully. :::note To keep the default `handlers` such as` query`, `play`,` ping`, add the `` rule. @@ -539,7 +539,7 @@ In `dynamic_query_handler`, the query is written in the form of parameter of the ClickHouse extracts and executes the value corresponding to the `query_param_name` value in the URL of the HTTP request. The default value of `query_param_name` is `/query` . It is an optional configuration. If there is no definition in the configuration file, the parameter is not passed in. -To experiment with this functionality, the example defines the values of [max_threads](../operations/settings/settings.md#settings-max_threads) and `max_final_threads` and `queries` whether the settings were set successfully. +To experiment with this functionality, the example defines the values of [max_threads](../operations/settings/settings.md#max_threads) and `max_final_threads` and `queries` whether the settings were set successfully. Example: diff --git a/docs/en/operations/monitoring.md b/docs/en/operations/monitoring.md index adc384e21ae..de61da6f5c4 100644 --- a/docs/en/operations/monitoring.md +++ b/docs/en/operations/monitoring.md @@ -64,4 +64,4 @@ You can configure ClickHouse to export metrics to [Prometheus](https://prometheu Additionally, you can monitor server availability through the HTTP API. Send the `HTTP GET` request to `/ping`. If the server is available, it responds with `200 OK`. -To monitor servers in a cluster configuration, you should set the [max_replica_delay_for_distributed_queries](../operations/settings/settings.md#settings-max_replica_delay_for_distributed_queries) parameter and use the HTTP resource `/replicas_status`. A request to `/replicas_status` returns `200 OK` if the replica is available and is not delayed behind the other replicas. If a replica is delayed, it returns `503 HTTP_SERVICE_UNAVAILABLE` with information about the gap. +To monitor servers in a cluster configuration, you should set the [max_replica_delay_for_distributed_queries](../operations/settings/settings.md#max_replica_delay_for_distributed_queries) parameter and use the HTTP resource `/replicas_status`. A request to `/replicas_status` returns `200 OK` if the replica is available and is not delayed behind the other replicas. If a replica is delayed, it returns `503 HTTP_SERVICE_UNAVAILABLE` with information about the gap. diff --git a/docs/en/operations/optimizing-performance/sampling-query-profiler.md b/docs/en/operations/optimizing-performance/sampling-query-profiler.md index 206f710734e..194d2714422 100644 --- a/docs/en/operations/optimizing-performance/sampling-query-profiler.md +++ b/docs/en/operations/optimizing-performance/sampling-query-profiler.md @@ -42,7 +42,7 @@ To analyze the `trace_log` system table: - Install the `clickhouse-common-static-dbg` package. See [Install from DEB Packages](../../getting-started/install.md#install-from-deb-packages). -- Allow introspection functions by the [allow_introspection_functions](../../operations/settings/settings.md#settings-allow_introspection_functions) setting. +- Allow introspection functions by the [allow_introspection_functions](../../operations/settings/settings.md#allow_introspection_functions) setting. For security reasons, introspection functions are disabled by default. diff --git a/docs/en/operations/query-cache.md b/docs/en/operations/query-cache.md index 781d1f9bcd5..0b858038caf 100644 --- a/docs/en/operations/query-cache.md +++ b/docs/en/operations/query-cache.md @@ -103,7 +103,7 @@ It is also possible to limit the cache usage of individual users using [settings constraints](settings/constraints-on-settings.md). More specifically, you can restrict the maximum amount of memory (in bytes) a user may allocate in the query cache and the maximum number of stored query results. For that, first provide configurations [query_cache_max_size_in_bytes](settings/settings.md#query-cache-max-size-in-bytes) and -[query_cache_max_entries](settings/settings.md#query-cache-size-max-entries) in a user profile in `users.xml`, then make both settings +[query_cache_max_entries](settings/settings.md#query-cache-max-entries) in a user profile in `users.xml`, then make both settings readonly: ``` xml @@ -144,7 +144,7 @@ value can be specified at session, profile or query level using setting [query_c Entries in the query cache are compressed by default. This reduces the overall memory consumption at the cost of slower writes into / reads from the query cache. To disable compression, use setting [query_cache_compress_entries](settings/settings.md#query-cache-compress-entries). -ClickHouse reads table data in blocks of [max_block_size](settings/settings.md#settings-max_block_size) rows. Due to filtering, aggregation, +ClickHouse reads table data in blocks of [max_block_size](settings/settings.md#setting-max_block_size) rows. Due to filtering, aggregation, etc., result blocks are typically much smaller than 'max_block_size' but there are also cases where they are much bigger. Setting [query_cache_squash_partial_results](settings/settings.md#query-cache-squash-partial-results) (enabled by default) controls if result blocks are squashed (if they are tiny) or split (if they are large) into blocks of 'max_block_size' size before insertion into the query result diff --git a/docs/en/operations/server-configuration-parameters/settings.md b/docs/en/operations/server-configuration-parameters/settings.md index 01e30c84526..48434d992e2 100644 --- a/docs/en/operations/server-configuration-parameters/settings.md +++ b/docs/en/operations/server-configuration-parameters/settings.md @@ -2009,7 +2009,7 @@ Data for the query cache is allocated in DRAM. If memory is scarce, make sure to ## query_thread_log {#query_thread_log} -Setting for logging threads of queries received with the [log_query_threads=1](../../operations/settings/settings.md#settings-log-query-threads) setting. +Setting for logging threads of queries received with the [log_query_threads=1](../../operations/settings/settings.md#log-query-threads) setting. Queries are logged in the [system.query_thread_log](../../operations/system-tables/query_thread_log.md#system_tables-query_thread_log) table, not in a separate file. You can change the name of the table in the `table` parameter (see below). @@ -2051,7 +2051,7 @@ If the table does not exist, ClickHouse will create it. If the structure of the ## query_views_log {#query_views_log} -Setting for logging views (live, materialized etc) dependant of queries received with the [log_query_views=1](../../operations/settings/settings.md#settings-log-query-views) setting. +Setting for logging views (live, materialized etc) dependant of queries received with the [log_query_views=1](../../operations/settings/settings.md#log-query-views) setting. Queries are logged in the [system.query_views_log](../../operations/system-tables/query_views_log.md#system_tables-query_views_log) table, not in a separate file. You can change the name of the table in the `table` parameter (see below). @@ -2331,7 +2331,7 @@ For the value of the `incl` attribute, see the section “[Configuration files]( **See Also** -- [skip_unavailable_shards](../../operations/settings/settings.md#settings-skip_unavailable_shards) +- [skip_unavailable_shards](../../operations/settings/settings.md#skip_unavailable_shards) - [Cluster Discovery](../../operations/cluster-discovery.md) - [Replicated database engine](../../engines/database-engines/replicated.md) diff --git a/docs/en/operations/settings/query-complexity.md b/docs/en/operations/settings/query-complexity.md index 9e36aa26946..1cb7ec9dced 100644 --- a/docs/en/operations/settings/query-complexity.md +++ b/docs/en/operations/settings/query-complexity.md @@ -139,7 +139,7 @@ Limit on the number of bytes in the result. The same as the previous setting. What to do if the volume of the result exceeds one of the limits: ‘throw’ or ‘break’. By default, throw. -Using ‘break’ is similar to using LIMIT. `Break` interrupts execution only at the block level. This means that amount of returned rows is greater than [max_result_rows](#setting-max_result_rows), multiple of [max_block_size](../../operations/settings/settings.md#setting-max_block_size) and depends on [max_threads](../../operations/settings/settings.md#settings-max_threads). +Using ‘break’ is similar to using LIMIT. `Break` interrupts execution only at the block level. This means that amount of returned rows is greater than [max_result_rows](#setting-max_result_rows), multiple of [max_block_size](../../operations/settings/settings.md#setting-max_block_size) and depends on [max_threads](../../operations/settings/settings.md#max_threads). Example: diff --git a/docs/en/operations/settings/settings.md b/docs/en/operations/settings/settings.md index b9e7c37485f..6e087467bb9 100644 --- a/docs/en/operations/settings/settings.md +++ b/docs/en/operations/settings/settings.md @@ -1716,7 +1716,7 @@ Default value: `1` ## query_cache_squash_partial_results {#query-cache-squash-partial-results} -Squash partial result blocks to blocks of size [max_block_size](#setting-max_block_size). Reduces performance of inserts into the [query cache](../query-cache.md) but improves the compressability of cache entries (see [query_cache_compress-entries](#query_cache_compress_entries)). +Squash partial result blocks to blocks of size [max_block_size](#setting-max_block_size). Reduces performance of inserts into the [query cache](../query-cache.md) but improves the compressability of cache entries (see [query_cache_compress-entries](#query-cache-compress-entries)). Possible values: @@ -2486,7 +2486,7 @@ See also: - [load_balancing](#load_balancing-round_robin) - [Table engine Distributed](../../engines/table-engines/special/distributed.md) - [distributed_replica_error_cap](#distributed_replica_error_cap) -- [distributed_replica_error_half_life](#settings-distributed_replica_error_half_life) +- [distributed_replica_error_half_life](#distributed_replica_error_half_life) ## distributed_background_insert_sleep_time_ms {#distributed_background_insert_sleep_time_ms} @@ -4715,7 +4715,7 @@ Possible values: Default value: `false`. -## rename_files_after_processing +## rename_files_after_processing {#rename_files_after_processing} - **Type:** String diff --git a/docs/en/operations/system-tables/clusters.md b/docs/en/operations/system-tables/clusters.md index 2659f80e338..63cc083e4bc 100644 --- a/docs/en/operations/system-tables/clusters.md +++ b/docs/en/operations/system-tables/clusters.md @@ -78,5 +78,5 @@ is_active: NULL **See Also** - [Table engine Distributed](../../engines/table-engines/special/distributed.md) -- [distributed_replica_error_cap setting](../../operations/settings/settings.md#settings-distributed_replica_error_cap) -- [distributed_replica_error_half_life setting](../../operations/settings/settings.md#settings-distributed_replica_error_half_life) +- [distributed_replica_error_cap setting](../../operations/settings/settings.md#distributed_replica_error_cap) +- [distributed_replica_error_half_life setting](../../operations/settings/settings.md#distributed_replica_error_half_life) diff --git a/docs/en/operations/system-tables/query_log.md b/docs/en/operations/system-tables/query_log.md index 4f5e214f1ce..7fcc4928355 100644 --- a/docs/en/operations/system-tables/query_log.md +++ b/docs/en/operations/system-tables/query_log.md @@ -11,7 +11,7 @@ This table does not contain the ingested data for `INSERT` queries. You can change settings of queries logging in the [query_log](../../operations/server-configuration-parameters/settings.md#server_configuration_parameters-query-log) section of the server configuration. -You can disable queries logging by setting [log_queries = 0](../../operations/settings/settings.md#settings-log-queries). We do not recommend to turn off logging because information in this table is important for solving issues. +You can disable queries logging by setting [log_queries = 0](../../operations/settings/settings.md#log-queries). We do not recommend to turn off logging because information in this table is important for solving issues. The flushing period of data is set in `flush_interval_milliseconds` parameter of the [query_log](../../operations/server-configuration-parameters/settings.md#server_configuration_parameters-query-log) server settings section. To force flushing, use the [SYSTEM FLUSH LOGS](../../sql-reference/statements/system.md#query_language-system-flush_logs) query. @@ -30,7 +30,7 @@ Each query creates one or two rows in the `query_log` table, depending on the st You can use the [log_queries_probability](../../operations/settings/settings.md#log-queries-probability) setting to reduce the number of queries, registered in the `query_log` table. -You can use the [log_formatted_queries](../../operations/settings/settings.md#settings-log-formatted-queries) setting to log formatted queries to the `formatted_query` column. +You can use the [log_formatted_queries](../../operations/settings/settings.md#log-formatted-queries) setting to log formatted queries to the `formatted_query` column. Columns: @@ -101,7 +101,7 @@ Columns: - `revision` ([UInt32](../../sql-reference/data-types/int-uint.md)) — ClickHouse revision. - `ProfileEvents` ([Map(String, UInt64)](../../sql-reference/data-types/map.md)) — ProfileEvents that measure different metrics. The description of them could be found in the table [system.events](../../operations/system-tables/events.md#system_tables-events) - `Settings` ([Map(String, String)](../../sql-reference/data-types/map.md)) — Settings that were changed when the client ran the query. To enable logging changes to settings, set the `log_query_settings` parameter to 1. -- `log_comment` ([String](../../sql-reference/data-types/string.md)) — Log comment. It can be set to arbitrary string no longer than [max_query_size](../../operations/settings/settings.md#settings-max_query_size). An empty string if it is not defined. +- `log_comment` ([String](../../sql-reference/data-types/string.md)) — Log comment. It can be set to arbitrary string no longer than [max_query_size](../../operations/settings/settings.md#max_query_size). An empty string if it is not defined. - `thread_ids` ([Array(UInt64)](../../sql-reference/data-types/array.md)) — Thread ids that are participating in query execution. These threads may not have run simultaneously. - `peak_threads_usage` ([UInt64)](../../sql-reference/data-types/int-uint.md)) — Maximum count of simultaneous threads executing the query. - `used_aggregate_functions` ([Array(String)](../../sql-reference/data-types/array.md)) — Canonical names of `aggregate functions`, which were used during query execution. diff --git a/docs/en/operations/system-tables/query_thread_log.md b/docs/en/operations/system-tables/query_thread_log.md index a198d7c304f..0420a0392f2 100644 --- a/docs/en/operations/system-tables/query_thread_log.md +++ b/docs/en/operations/system-tables/query_thread_log.md @@ -8,7 +8,7 @@ Contains information about threads that execute queries, for example, thread nam To start logging: 1. Configure parameters in the [query_thread_log](../../operations/server-configuration-parameters/settings.md#server_configuration_parameters-query_thread_log) section. -2. Set [log_query_threads](../../operations/settings/settings.md#settings-log-query-threads) to 1. +2. Set [log_query_threads](../../operations/settings/settings.md#log-query-threads) to 1. The flushing period of data is set in `flush_interval_milliseconds` parameter of the [query_thread_log](../../operations/server-configuration-parameters/settings.md#server_configuration_parameters-query_thread_log) server settings section. To force flushing, use the [SYSTEM FLUSH LOGS](../../sql-reference/statements/system.md#query_language-system-flush_logs) query. diff --git a/docs/en/operations/system-tables/query_views_log.md b/docs/en/operations/system-tables/query_views_log.md index 4dd8dd7420d..41a69da70aa 100644 --- a/docs/en/operations/system-tables/query_views_log.md +++ b/docs/en/operations/system-tables/query_views_log.md @@ -8,7 +8,7 @@ Contains information about the dependent views executed when running a query, fo To start logging: 1. Configure parameters in the [query_views_log](../../operations/server-configuration-parameters/settings.md#server_configuration_parameters-query_views_log) section. -2. Set [log_query_views](../../operations/settings/settings.md#settings-log-query-views) to 1. +2. Set [log_query_views](../../operations/settings/settings.md#log-query-views) to 1. The flushing period of data is set in `flush_interval_milliseconds` parameter of the [query_views_log](../../operations/server-configuration-parameters/settings.md#server_configuration_parameters-query_views_log) server settings section. To force flushing, use the [SYSTEM FLUSH LOGS](../../sql-reference/statements/system.md#query_language-system-flush_logs) query. diff --git a/docs/en/operations/system-tables/table_engines.md b/docs/en/operations/system-tables/table_engines.md index 08594739ecf..56668abae31 100644 --- a/docs/en/operations/system-tables/table_engines.md +++ b/docs/en/operations/system-tables/table_engines.md @@ -14,7 +14,7 @@ This table contains the following columns (the column type is shown in brackets) - `supports_sort_order` (UInt8) — Flag that indicates if table engine supports clauses `PARTITION_BY`, `PRIMARY_KEY`, `ORDER_BY` and `SAMPLE_BY`. - `supports_replication` (UInt8) — Flag that indicates if table engine supports [data replication](../../engines/table-engines/mergetree-family/replication.md). - `supports_duduplication` (UInt8) — Flag that indicates if table engine supports data deduplication. -- `supports_parallel_insert` (UInt8) — Flag that indicates if table engine supports parallel insert (see [`max_insert_threads`](../../operations/settings/settings.md#settings-max-insert-threads) setting). +- `supports_parallel_insert` (UInt8) — Flag that indicates if table engine supports parallel insert (see [`max_insert_threads`](../../operations/settings/settings.md#max-insert-threads) setting). Example: diff --git a/docs/en/sql-reference/aggregate-functions/reference/count.md b/docs/en/sql-reference/aggregate-functions/reference/count.md index a40108a331a..ca4067c8d8c 100644 --- a/docs/en/sql-reference/aggregate-functions/reference/count.md +++ b/docs/en/sql-reference/aggregate-functions/reference/count.md @@ -28,7 +28,7 @@ In both cases the type of the returned value is [UInt64](../../../sql-reference/ **Details** -ClickHouse supports the `COUNT(DISTINCT ...)` syntax. The behavior of this construction depends on the [count_distinct_implementation](../../../operations/settings/settings.md#settings-count_distinct_implementation) setting. It defines which of the [uniq\*](../../../sql-reference/aggregate-functions/reference/uniq.md#agg_function-uniq) functions is used to perform the operation. The default is the [uniqExact](../../../sql-reference/aggregate-functions/reference/uniqexact.md#agg_function-uniqexact) function. +ClickHouse supports the `COUNT(DISTINCT ...)` syntax. The behavior of this construction depends on the [count_distinct_implementation](../../../operations/settings/settings.md#count_distinct_implementation) setting. It defines which of the [uniq\*](../../../sql-reference/aggregate-functions/reference/uniq.md#agg_function-uniq) functions is used to perform the operation. The default is the [uniqExact](../../../sql-reference/aggregate-functions/reference/uniqexact.md#agg_function-uniqexact) function. The `SELECT count() FROM table` query is optimized by default using metadata from MergeTree. If you need to use row-level security, disable optimization using the [optimize_trivial_count_query](../../../operations/settings/settings.md#optimize-trivial-count-query) setting. diff --git a/docs/en/sql-reference/functions/array-functions.md b/docs/en/sql-reference/functions/array-functions.md index 00efa63c960..f5da00a8663 100644 --- a/docs/en/sql-reference/functions/array-functions.md +++ b/docs/en/sql-reference/functions/array-functions.md @@ -143,7 +143,7 @@ range([start, ] end [, step]) **Implementation details** - All arguments `start`, `end`, `step` must be below data types: `UInt8`, `UInt16`, `UInt32`, `UInt64`,`Int8`, `Int16`, `Int32`, `Int64`, as well as elements of the returned array, which's type is a super type of all arguments. -- An exception is thrown if query results in arrays with a total length of more than number of elements specified by the [function_range_max_elements_in_block](../../operations/settings/settings.md#settings-function_range_max_elements_in_block) setting. +- An exception is thrown if query results in arrays with a total length of more than number of elements specified by the [function_range_max_elements_in_block](../../operations/settings/settings.md#function_range_max_elements_in_block) setting. - Returns Null if any argument has Nullable(Nothing) type. An exception is thrown if any argument has Null value (Nullable(T) type). **Examples** diff --git a/docs/en/sql-reference/functions/introspection.md b/docs/en/sql-reference/functions/introspection.md index 8cb35483555..1025b8bdc3d 100644 --- a/docs/en/sql-reference/functions/introspection.md +++ b/docs/en/sql-reference/functions/introspection.md @@ -16,7 +16,7 @@ For proper operation of introspection functions: - Install the `clickhouse-common-static-dbg` package. -- Set the [allow_introspection_functions](../../operations/settings/settings.md#settings-allow_introspection_functions) setting to 1. +- Set the [allow_introspection_functions](../../operations/settings/settings.md#allow_introspection_functions) setting to 1. For security reasons introspection functions are disabled by default. diff --git a/docs/en/sql-reference/statements/select/join.md b/docs/en/sql-reference/statements/select/join.md index 281a1d0436c..5bec89f382e 100644 --- a/docs/en/sql-reference/statements/select/join.md +++ b/docs/en/sql-reference/statements/select/join.md @@ -45,20 +45,20 @@ Additional join types available in ClickHouse: - `ASOF JOIN` and `LEFT ASOF JOIN`, joining sequences with a non-exact match. `ASOF JOIN` usage is described below. :::note -When [join_algorithm](../../../operations/settings/settings.md#settings-join_algorithm) is set to `partial_merge`, `RIGHT JOIN` and `FULL JOIN` are supported only with `ALL` strictness (`SEMI`, `ANTI`, `ANY`, and `ASOF` are not supported). +When [join_algorithm](../../../operations/settings/settings.md#join_algorithm) is set to `partial_merge`, `RIGHT JOIN` and `FULL JOIN` are supported only with `ALL` strictness (`SEMI`, `ANTI`, `ANY`, and `ASOF` are not supported). ::: ## Settings -The default join type can be overridden using [join_default_strictness](../../../operations/settings/settings.md#settings-join_default_strictness) setting. +The default join type can be overridden using [join_default_strictness](../../../operations/settings/settings.md#join_default_strictness) setting. The behavior of ClickHouse server for `ANY JOIN` operations depends on the [any_join_distinct_right_table_keys](../../../operations/settings/settings.md#any_join_distinct_right_table_keys) setting. **See also** -- [join_algorithm](../../../operations/settings/settings.md#settings-join_algorithm) -- [join_any_take_last_row](../../../operations/settings/settings.md#settings-join_any_take_last_row) +- [join_algorithm](../../../operations/settings/settings.md#join_algorithm) +- [join_any_take_last_row](../../../operations/settings/settings.md#join_any_take_last_row) - [join_use_nulls](../../../operations/settings/settings.md#join_use_nulls) - [partial_merge_join_optimizations](../../../operations/settings/settings.md#partial_merge_join_optimizations) - [partial_merge_join_rows_in_right_blocks](../../../operations/settings/settings.md#partial_merge_join_rows_in_right_blocks) @@ -352,7 +352,7 @@ If you need a `JOIN` for joining with dimension tables (these are relatively sma ### Memory Limitations -By default, ClickHouse uses the [hash join](https://en.wikipedia.org/wiki/Hash_join) algorithm. ClickHouse takes the right_table and creates a hash table for it in RAM. If `join_algorithm = 'auto'` is enabled, then after some threshold of memory consumption, ClickHouse falls back to [merge](https://en.wikipedia.org/wiki/Sort-merge_join) join algorithm. For `JOIN` algorithms description see the [join_algorithm](../../../operations/settings/settings.md#settings-join_algorithm) setting. +By default, ClickHouse uses the [hash join](https://en.wikipedia.org/wiki/Hash_join) algorithm. ClickHouse takes the right_table and creates a hash table for it in RAM. If `join_algorithm = 'auto'` is enabled, then after some threshold of memory consumption, ClickHouse falls back to [merge](https://en.wikipedia.org/wiki/Sort-merge_join) join algorithm. For `JOIN` algorithms description see the [join_algorithm](../../../operations/settings/settings.md#join_algorithm) setting. If you need to restrict `JOIN` operation memory consumption use the following settings: diff --git a/docs/en/sql-reference/syntax.md b/docs/en/sql-reference/syntax.md index f5651c2dcb6..6dcb3e75e48 100644 --- a/docs/en/sql-reference/syntax.md +++ b/docs/en/sql-reference/syntax.md @@ -16,7 +16,7 @@ INSERT INTO t VALUES (1, 'Hello, world'), (2, 'abc'), (3, 'def') The `INSERT INTO t VALUES` fragment is parsed by the full parser, and the data `(1, 'Hello, world'), (2, 'abc'), (3, 'def')` is parsed by the fast stream parser. You can also turn on the full parser for the data by using the [input_format_values_interpret_expressions](../operations/settings/settings-formats.md#input_format_values_interpret_expressions) setting. When `input_format_values_interpret_expressions = 1`, ClickHouse first tries to parse values with the fast stream parser. If it fails, ClickHouse tries to use the full parser for the data, treating it like an SQL [expression](#expressions). -Data can have any format. When a query is received, the server calculates no more than [max_query_size](../operations/settings/settings.md#settings-max_query_size) bytes of the request in RAM (by default, 1 MB), and the rest is stream parsed. +Data can have any format. When a query is received, the server calculates no more than [max_query_size](../operations/settings/settings.md#max_query_size) bytes of the request in RAM (by default, 1 MB), and the rest is stream parsed. It allows for avoiding issues with large `INSERT` queries. When using the `Values` format in an `INSERT` query, it may seem that data is parsed the same as expressions in a `SELECT` query, but this is not true. The `Values` format is much more limited. diff --git a/docs/en/sql-reference/table-functions/cluster.md b/docs/en/sql-reference/table-functions/cluster.md index a083c6b89a6..ad92ab39183 100644 --- a/docs/en/sql-reference/table-functions/cluster.md +++ b/docs/en/sql-reference/table-functions/cluster.md @@ -55,5 +55,5 @@ Connection settings like `host`, `port`, `user`, `password`, `compression`, `sec **See Also** -- [skip_unavailable_shards](../../operations/settings/settings.md#settings-skip_unavailable_shards) -- [load_balancing](../../operations/settings/settings.md#settings-load_balancing) +- [skip_unavailable_shards](../../operations/settings/settings.md#skip_unavailable_shards) +- [load_balancing](../../operations/settings/settings.md#load_balancing) diff --git a/docs/en/sql-reference/table-functions/file.md b/docs/en/sql-reference/table-functions/file.md index f0de4a405a0..3a63811add6 100644 --- a/docs/en/sql-reference/table-functions/file.md +++ b/docs/en/sql-reference/table-functions/file.md @@ -199,11 +199,11 @@ SELECT count(*) FROM file('big_dir/**/file002', 'CSV', 'name String, value UInt3 ## Settings {#settings} -- [engine_file_empty_if_not_exists](/docs/en/operations/settings/settings.md#engine-file-emptyif-not-exists) - allows to select empty data from a file that doesn't exist. Disabled by default. +- [engine_file_empty_if_not_exists](/docs/en/operations/settings/settings.md#engine-file-empty_if-not-exists) - allows to select empty data from a file that doesn't exist. Disabled by default. - [engine_file_truncate_on_insert](/docs/en/operations/settings/settings.md#engine-file-truncate-on-insert) - allows to truncate file before insert into it. Disabled by default. - [engine_file_allow_create_multiple_files](/docs/en/operations/settings/settings.md#engine_file_allow_create_multiple_files) - allows to create a new file on each insert if format has suffix. Disabled by default. - [engine_file_skip_empty_files](/docs/en/operations/settings/settings.md#engine_file_skip_empty_files) - allows to skip empty files while reading. Disabled by default. -- [storage_file_read_method](/docs/en/operations/settings/settings.md#engine-file-emptyif-not-exists) - method of reading data from storage file, one of: read, pread, mmap (only for clickhouse-local). Default value: `pread` for clickhouse-server, `mmap` for clickhouse-local. +- [storage_file_read_method](/docs/en/operations/settings/settings.md#engine-file-empty_if-not-exists) - method of reading data from storage file, one of: read, pread, mmap (only for clickhouse-local). Default value: `pread` for clickhouse-server, `mmap` for clickhouse-local. **See Also** diff --git a/docs/en/sql-reference/table-functions/hdfs.md b/docs/en/sql-reference/table-functions/hdfs.md index 463632f4e07..92f904b8841 100644 --- a/docs/en/sql-reference/table-functions/hdfs.md +++ b/docs/en/sql-reference/table-functions/hdfs.md @@ -100,7 +100,7 @@ FROM hdfs('hdfs://hdfs1:9000/big_dir/file{0..9}{0..9}{0..9}', 'CSV', 'name Strin ## Storage Settings {#storage-settings} -- [hdfs_truncate_on_insert](/docs/en/operations/settings/settings.md#hdfs-truncate-on-insert) - allows to truncate file before insert into it. Disabled by default. +- [hdfs_truncate_on_insert](/docs/en/operations/settings/settings.md#hdfs_truncate_on_insert) - allows to truncate file before insert into it. Disabled by default. - [hdfs_create_multiple_files](/docs/en/operations/settings/settings.md#hdfs_allow_create_multiple_files) - allows to create a new file on each insert if format has suffix. Disabled by default. - [hdfs_skip_empty_files](/docs/en/operations/settings/settings.md#hdfs_skip_empty_files) - allows to skip empty files while reading. Disabled by default. - [ignore_access_denied_multidirectory_globs](/docs/en/operations/settings/settings.md#ignore_access_denied_multidirectory_globs) - allows to ignore permission denied errors for multi-directory globs. diff --git a/docs/en/sql-reference/table-functions/remote.md b/docs/en/sql-reference/table-functions/remote.md index 3ca177050d3..228f4a4c7e1 100644 --- a/docs/en/sql-reference/table-functions/remote.md +++ b/docs/en/sql-reference/table-functions/remote.md @@ -165,5 +165,5 @@ The following pattern types are supported. - `{0n..0m}` - A range of numbers with leading zeroes. This pattern preserves leading zeroes in indices. For instance, `example{01..03}-1` generates `example01-1`, `example02-1` and `example03-1`. - `{a|b}` - Any number of variants separated by a `|`. The pattern specifies replicas. For instance, `example01-{1|2}` generates replicas `example01-1` and `example01-2`. -The query will be sent to the first healthy replica. However, for `remote` the replicas are iterated in the order currently set in the [load_balancing](../../operations/settings/settings.md#settings-load_balancing) setting. +The query will be sent to the first healthy replica. However, for `remote` the replicas are iterated in the order currently set in the [load_balancing](../../operations/settings/settings.md#load_balancing) setting. The number of generated addresses is limited by [table_function_remote_max_addresses](../../operations/settings/settings.md#table_function_remote_max_addresses) setting. diff --git a/docs/en/sql-reference/table-functions/s3.md b/docs/en/sql-reference/table-functions/s3.md index dc11259c626..bcad284016d 100644 --- a/docs/en/sql-reference/table-functions/s3.md +++ b/docs/en/sql-reference/table-functions/s3.md @@ -236,7 +236,7 @@ LIMIT 5; ## Storage Settings {#storage-settings} -- [s3_truncate_on_insert](/docs/en/operations/settings/settings.md#s3-truncate-on-insert) - allows to truncate file before insert into it. Disabled by default. +- [s3_truncate_on_insert](/docs/en/operations/settings/settings.md#s3_truncate_on_insert) - allows to truncate file before insert into it. Disabled by default. - [s3_create_multiple_files](/docs/en/operations/settings/settings.md#s3_allow_create_multiple_files) - allows to create a new file on each insert if format has suffix. Disabled by default. - [s3_skip_empty_files](/docs/en/operations/settings/settings.md#s3_skip_empty_files) - allows to skip empty files while reading. Disabled by default. From 6fd32d92d0cadbdc96381b3e8b2d9869ecf6ffd5 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sun, 17 Dec 2023 09:29:22 +0100 Subject: [PATCH 153/253] Add max/peak RSS into system.asynchronous_metrics Signed-off-by: Azat Khuzhin --- src/Common/AsynchronousMetrics.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Common/AsynchronousMetrics.cpp b/src/Common/AsynchronousMetrics.cpp index 9df6d22df04..e8deb459b24 100644 --- a/src/Common/AsynchronousMetrics.cpp +++ b/src/Common/AsynchronousMetrics.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "config.h" @@ -655,6 +656,19 @@ void AsynchronousMetrics::update(TimePoint update_time) total_memory_tracker.setRSS(rss, free_memory_in_allocator_arenas); } } + + { + struct rusage rusage{}; + if (!getrusage(RUSAGE_SELF, &rusage)) + { + new_values["MemoryResidentMax"] = { rusage.ru_maxrss * 1024 /* KiB -> bytes */, + "Maximum amount of physical memory used by the server process, in bytes." }; + } + else + { + LOG_ERROR(log, "Cannot obtain resource usage: {}", errnoToString(errno)); + } + } #endif #if defined(OS_LINUX) From e0a790de1e9839168dbd281051092be3c4e9d897 Mon Sep 17 00:00:00 2001 From: Max K Date: Wed, 20 Dec 2023 21:28:54 +0100 Subject: [PATCH 154/253] Fix for nightly job for digest-ci (#58079) * Fix for run_always job - do not set done #no-merge-commit --- tests/ci/ci.py | 39 +++++++++++--------- tests/ci/pr_info.py | 89 ++++++++------------------------------------- 2 files changed, 36 insertions(+), 92 deletions(-) diff --git a/tests/ci/ci.py b/tests/ci/ci.py index 01317b49e1a..4378e4361f0 100644 --- a/tests/ci/ci.py +++ b/tests/ci/ci.py @@ -279,11 +279,11 @@ def _configure_docker_jobs( images_info = docker_images_helper.get_images_info() # a. check missing images - print("Start checking missing images in dockerhub") - # FIXME: we need login as docker manifest inspect goes directly to one of the *.docker.com hosts instead of "registry-mirrors" : ["http://dockerhub-proxy.dockerhub-proxy-zone:5000"] - # find if it's possible to use the setting of /etc/docker/daemon.json - docker_images_helper.docker_login() if not rebuild_all_dockers: + # FIXME: we need login as docker manifest inspect goes directly to one of the *.docker.com hosts instead of "registry-mirrors" : ["http://dockerhub-proxy.dockerhub-proxy-zone:5000"] + # find if it's possible to use the setting of /etc/docker/daemon.json + docker_images_helper.docker_login() + print("Start checking missing images in dockerhub") missing_multi_dict = check_missing_images_on_dockerhub(imagename_digest_dict) missing_multi = list(missing_multi_dict) missing_amd64 = [] @@ -305,6 +305,15 @@ def _configure_docker_jobs( "aarch64", ) ) + # FIXME: temporary hack, remove after transition to docker digest as tag + else: + if missing_multi: + print( + f"WARNING: Missing images {list(missing_multi)} - fallback to latest tag" + ) + for image in missing_multi: + imagename_digest_dict[image] = "latest" + print("...checking missing images in dockerhub - done") else: # add all images to missing missing_multi = list(imagename_digest_dict) @@ -315,16 +324,7 @@ def _configure_docker_jobs( for name in imagename_digest_dict if not images_info[name]["only_amd64"] ] - # FIXME: temporary hack, remove after transition to docker digest as tag - if docker_digest_or_latest: - if missing_multi: - print( - f"WARNING: Missing images {list(missing_multi)} - fallback to latest tag" - ) - for image in missing_multi: - imagename_digest_dict[image] = "latest" - print("...checking missing images in dockerhub - done") return { "images": imagename_digest_dict, "missing_aarch64": missing_aarch64, @@ -548,14 +548,14 @@ def main() -> int: if args.configure: GR = GitRunner() - pr_info = PRInfo(need_changed_files=True) + pr_info = PRInfo() docker_data = {} git_ref = GR.run(f"{GIT_PREFIX} rev-parse HEAD") # if '#no-merge-commit' is set in commit message - set git ref to PR branch head to avoid merge-commit tokens = [] - if pr_info.number != 0: + if pr_info.number != 0 and not args.skip_jobs: message = GR.run(f"{GIT_PREFIX} log {pr_info.sha} --format=%B -n 1") tokens = _fetch_commit_tokens(message) print(f"Found commit message tokens: [{tokens}]") @@ -689,7 +689,8 @@ def main() -> int: elif args.mark_success: assert indata, "Run config must be provided via --infile" job = args.job_name - num_batches = CI_CONFIG.get_job_config(job).num_batches + job_config = CI_CONFIG.get_job_config(job) + num_batches = job_config.num_batches assert ( num_batches <= 1 or 0 <= args.batch < num_batches ), f"--batch must be provided and in range [0, {num_batches}) for {job}" @@ -706,7 +707,7 @@ def main() -> int: if not CommitStatusData.is_present(): # apparently exit after rerun-helper check # do nothing, exit without failure - print("ERROR: no status file for job [{job}]") + print(f"ERROR: no status file for job [{job}]") job_status = CommitStatusData( status="dummy failure", description="dummy status", @@ -717,7 +718,9 @@ def main() -> int: job_status = CommitStatusData.load_status() # Storing job data (report_url) to restore OK GH status on job results reuse - if job_status.is_ok(): + if job_config.run_always: + print(f"Job [{job}] runs always in CI - do not mark as done") + elif job_status.is_ok(): success_flag_name = get_file_flag_name( job, indata["jobs_data"]["digests"][job], args.batch, num_batches ) diff --git a/tests/ci/pr_info.py b/tests/ci/pr_info.py index c023ca048d6..15558c81c7e 100644 --- a/tests/ci/pr_info.py +++ b/tests/ci/pr_info.py @@ -2,7 +2,7 @@ import json import logging import os -from typing import Dict, List, Set, Union, Literal +from typing import Dict, List, Set, Union from unidiff import PatchSet # type: ignore @@ -93,6 +93,7 @@ class PRInfo: github_event = PRInfo.default_event.copy() self.event = github_event self.changed_files = set() # type: Set[str] + self.changed_files_requested = False self.body = "" self.diff_urls = [] # type: List[str] # release_pr and merged_pr are used for docker images additional cache @@ -285,6 +286,7 @@ class PRInfo: response.raise_for_status() diff_object = PatchSet(response.text) self.changed_files.update({f.path for f in diff_object}) + self.changed_files_requested = True print(f"Fetched info about {len(self.changed_files)} changed files") def get_dict(self): @@ -297,9 +299,10 @@ class PRInfo: } def has_changes_in_documentation(self) -> bool: - # If the list wasn't built yet the best we can do is to - # assume that there were changes. - if self.changed_files is None or not self.changed_files: + if not self.changed_files_requested: + self.fetch_changed_files() + + if not self.changed_files: return True for f in self.changed_files: @@ -316,7 +319,11 @@ class PRInfo: checks if changes are docs related without other changes FIXME: avoid hardcoding filenames here """ + if not self.changed_files_requested: + self.fetch_changed_files() + if not self.changed_files: + # if no changes at all return False return False for f in self.changed_files: @@ -332,7 +339,10 @@ class PRInfo: return True def has_changes_in_submodules(self): - if self.changed_files is None or not self.changed_files: + if not self.changed_files_requested: + self.fetch_changed_files() + + if not self.changed_files: return True for f in self.changed_files: @@ -340,75 +350,6 @@ class PRInfo: return True return False - def can_skip_builds_and_use_version_from_master(self): - if FORCE_TESTS_LABEL in self.labels: - return False - - if self.changed_files is None or not self.changed_files: - return False - - return not any( - f.startswith("programs") - or f.startswith("src") - or f.startswith("base") - or f.startswith("cmake") - or f.startswith("rust") - or f == "CMakeLists.txt" - or f == "tests/ci/build_check.py" - for f in self.changed_files - ) - - def can_skip_integration_tests(self, versions: List[str]) -> bool: - if FORCE_TESTS_LABEL in self.labels: - return False - - # If docker image(s) relevant to integration tests are updated - if any(self.sha in version for version in versions): - return False - - if self.changed_files is None or not self.changed_files: - return False - - if not self.can_skip_builds_and_use_version_from_master(): - return False - - # Integration tests can be skipped if integration tests are not changed - return not any( - f.startswith("tests/integration/") - or f == "tests/ci/integration_test_check.py" - for f in self.changed_files - ) - - def can_skip_functional_tests( - self, version: str, test_type: Literal["stateless", "stateful"] - ) -> bool: - if FORCE_TESTS_LABEL in self.labels: - return False - - # If docker image(s) relevant to functional tests are updated - if self.sha in version: - return False - - if self.changed_files is None or not self.changed_files: - return False - - if not self.can_skip_builds_and_use_version_from_master(): - return False - - # Functional tests can be skipped if queries tests are not changed - if test_type == "stateless": - return not any( - f.startswith("tests/queries/0_stateless") - or f == "tests/ci/functional_test_check.py" - for f in self.changed_files - ) - else: # stateful - return not any( - f.startswith("tests/queries/1_stateful") - or f == "tests/ci/functional_test_check.py" - for f in self.changed_files - ) - class FakePRInfo: def __init__(self): From 7be39a27cc716b215d0e7e99449a35a970ce3856 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 21 Dec 2023 01:46:38 +0300 Subject: [PATCH 155/253] Revert "Consider lightweight deleted rows when selecting parts to merge" --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 96 ------------------- src/Storages/MergeTree/IMergeTreeDataPart.h | 14 --- .../MergeTree/MergeFromLogEntryTask.cpp | 2 +- .../MergeTree/MergeTreeDataMergerMutator.cpp | 11 +-- .../MergeTree/MergeTreeDataMergerMutator.h | 2 +- src/Storages/MergeTree/MergeTreeSettings.h | 1 - .../MergeTree/MutateFromLogEntryTask.cpp | 2 +- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 2 +- src/Storages/StorageMergeTree.cpp | 4 +- .../02942_consider_lwd_when_merge.reference | 3 - .../02942_consider_lwd_when_merge.sql | 23 ----- 11 files changed, 10 insertions(+), 150 deletions(-) delete mode 100644 tests/queries/0_stateless/02942_consider_lwd_when_merge.reference delete mode 100644 tests/queries/0_stateless/02942_consider_lwd_when_merge.sql diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 29189d20a93..87f23b0da2a 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -593,23 +593,6 @@ UInt64 IMergeTreeDataPart::getMarksCount() const return index_granularity.getMarksCount(); } -UInt64 IMergeTreeDataPart::getExistingBytesOnDisk() const -{ - if (!supportLightweightDeleteMutate() || !hasLightweightDelete() || !rows_count - || !storage.getSettings()->exclude_deleted_rows_for_part_size_in_merge) - return bytes_on_disk; - - /// Uninitialized existing_rows_count - /// (if existing_rows_count equals rows_count, it means that previously we failed to read existing_rows_count) - if (existing_rows_count > rows_count) - readExistingRowsCount(); - - if (existing_rows_count < rows_count) - return bytes_on_disk * existing_rows_count / rows_count; - else /// Load failed - return bytes_on_disk; -} - size_t IMergeTreeDataPart::getFileSizeOrZero(const String & file_name) const { auto checksum = checksums.files.find(file_name); @@ -1304,85 +1287,6 @@ void IMergeTreeDataPart::loadRowsCount() } } -void IMergeTreeDataPart::readExistingRowsCount() const -{ - if (!supportLightweightDeleteMutate() || !hasLightweightDelete() || !storage.getSettings()->exclude_deleted_rows_for_part_size_in_merge - || existing_rows_count < rows_count || !getMarksCount()) - return; - - std::lock_guard lock(existing_rows_count_mutex); - - /// Already read by another thread - if (existing_rows_count < rows_count) - return; - - NamesAndTypesList cols; - cols.push_back(LightweightDeleteDescription::FILTER_COLUMN); - - StorageMetadataPtr metadata_ptr = storage.getInMemoryMetadataPtr(); - StorageSnapshotPtr storage_snapshot_ptr = std::make_shared(storage, metadata_ptr); - - MergeTreeReaderPtr reader = getReader( - cols, - storage_snapshot_ptr, - MarkRanges{MarkRange(0, getMarksCount())}, - nullptr, - storage.getContext()->getMarkCache().get(), - std::make_shared(), - MergeTreeReaderSettings{}, - ValueSizeMap{}, - ReadBufferFromFileBase::ProfileCallback{}); - - if (!reader) - { - LOG_WARNING(storage.log, "Create reader failed while reading existing rows count"); - existing_rows_count = rows_count; - return; - } - - size_t current_mark = 0; - const size_t total_mark = getMarksCount(); - - bool continue_reading = false; - size_t current_row = 0; - size_t existing_count = 0; - - while (current_row < rows_count) - { - size_t rows_to_read = index_granularity.getMarkRows(current_mark); - continue_reading = (current_mark != 0); - - Columns result; - result.resize(1); - - size_t rows_read = reader->readRows(current_mark, total_mark, continue_reading, rows_to_read, result); - if (!rows_read) - { - LOG_WARNING(storage.log, "Part {} has lightweight delete, but _row_exists column not found", name); - existing_rows_count = rows_count; - return; - } - - current_row += rows_read; - current_mark += (rows_to_read == rows_read); - - const ColumnUInt8 * row_exists_col = typeid_cast(result[0].get()); - if (!row_exists_col) - { - LOG_WARNING(storage.log, "Part {} _row_exists column type is not UInt8", name); - existing_rows_count = rows_count; - return; - } - - for (UInt8 row_exists : row_exists_col->getData()) - if (row_exists) - existing_count++; - } - - existing_rows_count = existing_count; - LOG_DEBUG(storage.log, "Part {} existing_rows_count = {}", name, existing_rows_count); -} - void IMergeTreeDataPart::appendFilesOfRowsCount(Strings & files) { files.push_back("count.txt"); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index efc42788ebb..640a1f1d0a3 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -229,13 +229,6 @@ public: size_t rows_count = 0; - /// Existing rows count (excluding lightweight deleted rows) - /// UINT64_MAX -> uninitialized - /// 0 -> all rows were deleted - /// if reading failed, it will be set to rows_count - mutable size_t existing_rows_count = UINT64_MAX; - mutable std::mutex existing_rows_count_mutex; - time_t modification_time = 0; /// When the part is removed from the working set. Changes once. mutable std::atomic remove_time { std::numeric_limits::max() }; @@ -381,10 +374,6 @@ public: void setBytesOnDisk(UInt64 bytes_on_disk_) { bytes_on_disk = bytes_on_disk_; } void setBytesUncompressedOnDisk(UInt64 bytes_uncompressed_on_disk_) { bytes_uncompressed_on_disk = bytes_uncompressed_on_disk_; } - /// Returns estimated size of existing rows if setting exclude_deleted_rows_for_part_size_in_merge is true - /// Otherwise returns bytes_on_disk - UInt64 getExistingBytesOnDisk() const; - size_t getFileSizeOrZero(const String & file_name) const; auto getFilesChecksums() const { return checksums.files; } @@ -511,9 +500,6 @@ public: /// True if here is lightweight deleted mask file in part. bool hasLightweightDelete() const { return columns.contains(LightweightDeleteDescription::FILTER_COLUMN.name); } - /// Read existing rows count from _row_exists column - void readExistingRowsCount() const; - void writeChecksums(const MergeTreeDataPartChecksums & checksums_, const WriteSettings & settings); /// Checks the consistency of this data part. diff --git a/src/Storages/MergeTree/MergeFromLogEntryTask.cpp b/src/Storages/MergeTree/MergeFromLogEntryTask.cpp index 8cdbdac7821..9be31859a19 100644 --- a/src/Storages/MergeTree/MergeFromLogEntryTask.cpp +++ b/src/Storages/MergeTree/MergeFromLogEntryTask.cpp @@ -160,7 +160,7 @@ ReplicatedMergeMutateTaskBase::PrepareResult MergeFromLogEntryTask::prepare() } /// Start to make the main work - size_t estimated_space_for_merge = MergeTreeDataMergerMutator::estimateNeededDiskSpace(parts, true); + size_t estimated_space_for_merge = MergeTreeDataMergerMutator::estimateNeededDiskSpace(parts); /// Can throw an exception while reserving space. IMergeTreeDataPart::TTLInfos ttl_infos; diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index 722f00c75d5..42f480ed18a 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -405,7 +405,7 @@ MergeTreeDataMergerMutator::MergeSelectingInfo MergeTreeDataMergerMutator::getPo } IMergeSelector::Part part_info; - part_info.size = part->getExistingBytesOnDisk(); + part_info.size = part->getBytesOnDisk(); part_info.age = res.current_time - part->modification_time; part_info.level = part->info.level; part_info.data = ∂ @@ -611,7 +611,7 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectAllPartsToMergeWithinParti return SelectPartsDecision::CANNOT_SELECT; } - sum_bytes += (*it)->getExistingBytesOnDisk(); + sum_bytes += (*it)->getBytesOnDisk(); prev_it = it; ++it; @@ -791,7 +791,7 @@ MergeTreeData::DataPartPtr MergeTreeDataMergerMutator::renameMergedTemporaryPart } -size_t MergeTreeDataMergerMutator::estimateNeededDiskSpace(const MergeTreeData::DataPartsVector & source_parts, const bool & is_merge) +size_t MergeTreeDataMergerMutator::estimateNeededDiskSpace(const MergeTreeData::DataPartsVector & source_parts) { size_t res = 0; time_t current_time = std::time(nullptr); @@ -802,10 +802,7 @@ size_t MergeTreeDataMergerMutator::estimateNeededDiskSpace(const MergeTreeData:: if (part_max_ttl && part_max_ttl <= current_time) continue; - if (is_merge && part->storage.getSettings()->exclude_deleted_rows_for_part_size_in_merge) - res += part->getExistingBytesOnDisk(); - else - res += part->getBytesOnDisk(); + res += part->getBytesOnDisk(); } return static_cast(res * DISK_USAGE_COEFFICIENT_TO_RESERVE); diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.h b/src/Storages/MergeTree/MergeTreeDataMergerMutator.h index 8ff70d2776c..5e8a89c94a4 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.h +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.h @@ -192,7 +192,7 @@ public: /// The approximate amount of disk space needed for merge or mutation. With a surplus. - static size_t estimateNeededDiskSpace(const MergeTreeData::DataPartsVector & source_parts, const bool & is_merge); + static size_t estimateNeededDiskSpace(const MergeTreeData::DataPartsVector & source_parts); private: /** Select all parts belonging to the same partition. diff --git a/src/Storages/MergeTree/MergeTreeSettings.h b/src/Storages/MergeTree/MergeTreeSettings.h index aad1e684daa..a80ef5f81ad 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -78,7 +78,6 @@ struct Settings; M(UInt64, number_of_mutations_to_throw, 1000, "If table has at least that many unfinished mutations, throw 'Too many mutations' exception. Disabled if set to 0", 0) \ M(UInt64, min_delay_to_mutate_ms, 10, "Min delay of mutating MergeTree table in milliseconds, if there are a lot of unfinished mutations", 0) \ M(UInt64, max_delay_to_mutate_ms, 1000, "Max delay of mutating MergeTree table in milliseconds, if there are a lot of unfinished mutations", 0) \ - M(Bool, exclude_deleted_rows_for_part_size_in_merge, false, "Use an estimated source part size (excluding lightweight deleted rows) when selecting parts to merge", 0) \ \ /** Inserts settings. */ \ M(UInt64, parts_to_delay_insert, 1000, "If table contains at least that many active parts in single partition, artificially slow down insert into table. Disabled if set to 0", 0) \ diff --git a/src/Storages/MergeTree/MutateFromLogEntryTask.cpp b/src/Storages/MergeTree/MutateFromLogEntryTask.cpp index 620b0e34c6a..a9ff687fe4d 100644 --- a/src/Storages/MergeTree/MutateFromLogEntryTask.cpp +++ b/src/Storages/MergeTree/MutateFromLogEntryTask.cpp @@ -49,7 +49,7 @@ ReplicatedMergeMutateTaskBase::PrepareResult MutateFromLogEntryTask::prepare() } /// TODO - some better heuristic? - size_t estimated_space_for_result = MergeTreeDataMergerMutator::estimateNeededDiskSpace({source_part}, false); + size_t estimated_space_for_result = MergeTreeDataMergerMutator::estimateNeededDiskSpace({source_part}); if (entry.create_time + storage_settings_ptr->prefer_fetch_merged_part_time_threshold.totalSeconds() <= time(nullptr) && estimated_space_for_result >= storage_settings_ptr->prefer_fetch_merged_part_size_threshold) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index 632d14d22cb..2d0617e5826 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1349,7 +1349,7 @@ bool ReplicatedMergeTreeQueue::shouldExecuteLogEntry( if (auto part_in_memory = asInMemoryPart(part)) sum_parts_size_in_bytes += part_in_memory->block.bytes(); else - sum_parts_size_in_bytes += part->getExistingBytesOnDisk(); + sum_parts_size_in_bytes += part->getBytesOnDisk(); } } diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index b43a176d851..9378aaa1f6a 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1085,7 +1085,7 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMerge( if (isTTLMergeType(future_part->merge_type)) getContext()->getMergeList().bookMergeWithTTL(); - merging_tagger = std::make_unique(future_part, MergeTreeDataMergerMutator::estimateNeededDiskSpace(future_part->parts, true), *this, metadata_snapshot, false); + merging_tagger = std::make_unique(future_part, MergeTreeDataMergerMutator::estimateNeededDiskSpace(future_part->parts), *this, metadata_snapshot, false); return std::make_shared(future_part, std::move(merging_tagger), std::make_shared()); } @@ -1301,7 +1301,7 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMutate( future_part->name = part->getNewName(new_part_info); future_part->part_format = part->getFormat(); - tagger = std::make_unique(future_part, MergeTreeDataMergerMutator::estimateNeededDiskSpace({part}, false), *this, metadata_snapshot, true); + tagger = std::make_unique(future_part, MergeTreeDataMergerMutator::estimateNeededDiskSpace({part}), *this, metadata_snapshot, true); return std::make_shared(future_part, std::move(tagger), commands, txn); } } diff --git a/tests/queries/0_stateless/02942_consider_lwd_when_merge.reference b/tests/queries/0_stateless/02942_consider_lwd_when_merge.reference deleted file mode 100644 index 19920de3d3c..00000000000 --- a/tests/queries/0_stateless/02942_consider_lwd_when_merge.reference +++ /dev/null @@ -1,3 +0,0 @@ -2 -2 -1 diff --git a/tests/queries/0_stateless/02942_consider_lwd_when_merge.sql b/tests/queries/0_stateless/02942_consider_lwd_when_merge.sql deleted file mode 100644 index a65e8877020..00000000000 --- a/tests/queries/0_stateless/02942_consider_lwd_when_merge.sql +++ /dev/null @@ -1,23 +0,0 @@ -DROP TABLE IF EXISTS lwd_merge; - -CREATE TABLE lwd_merge (id UInt64 CODEC(NONE)) - ENGINE = MergeTree ORDER BY id -SETTINGS max_bytes_to_merge_at_max_space_in_pool = 80000, exclude_deleted_rows_for_part_size_in_merge = 0; - -INSERT INTO lwd_merge SELECT number FROM numbers(10000); -INSERT INTO lwd_merge SELECT number FROM numbers(10000, 10000); - -OPTIMIZE TABLE lwd_merge; -SELECT count() FROM system.parts WHERE database = currentDatabase() AND table = 'lwd_merge' AND active = 1; - -DELETE FROM lwd_merge WHERE id % 10 > 0; - -OPTIMIZE TABLE lwd_merge; -SELECT count() FROM system.parts WHERE database = currentDatabase() AND table = 'lwd_merge' AND active = 1; - -ALTER TABLE lwd_merge MODIFY SETTING exclude_deleted_rows_for_part_size_in_merge = 1; - -OPTIMIZE TABLE lwd_merge; -SELECT count() FROM system.parts WHERE database = currentDatabase() AND table = 'lwd_merge' AND active = 1; - -DROP TABLE IF EXISTS lwd_merge; From f0a51e5be506da124ec747bca034bcecd52ba086 Mon Sep 17 00:00:00 2001 From: zhanglistar Date: Thu, 21 Dec 2023 09:55:26 +0800 Subject: [PATCH 156/253] Modify comments and merge master. --- src/Functions/if.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Functions/if.cpp b/src/Functions/if.cpp index 2f9e7662469..cae3b720d8b 100644 --- a/src/Functions/if.cpp +++ b/src/Functions/if.cpp @@ -42,9 +42,8 @@ using namespace GatherUtils; /** Selection function by condition: if(cond, then, else). * cond - UInt8 * then, else - numeric types for which there is a general type, or dates, datetimes, or strings, or arrays of these types. - * For better performance, try to use branch free code for numeric types except floating point types because of inf or nan, besides the trick will not improve much on floating point types. eg. - * cond ? a : b -> !!cond * a + !cond * b - */ + * For better performance, try to use branch free code for numeric types(i.e. cond ? a : b --> !!cond * a + !cond * b), except floating point types because of Inf or NaN. +*/ template inline void fillVectorVector(const ArrayCond & cond, const ArrayA & a, const ArrayB & b, ArrayResult & res) From 411d1049ddac3a73fd31c57bcc8dfb945798e9ef Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 21 Dec 2023 06:02:46 +0100 Subject: [PATCH 157/253] Reintroduce OPTIMIZE CLEANUP as no-op --- src/Parsers/ParserOptimizeQuery.cpp | 4 ++++ .../0_stateless/02948_optimize_cleanup_as_noop.reference | 0 .../queries/0_stateless/02948_optimize_cleanup_as_noop.sql | 7 +++++++ 3 files changed, 11 insertions(+) create mode 100644 tests/queries/0_stateless/02948_optimize_cleanup_as_noop.reference create mode 100644 tests/queries/0_stateless/02948_optimize_cleanup_as_noop.sql diff --git a/src/Parsers/ParserOptimizeQuery.cpp b/src/Parsers/ParserOptimizeQuery.cpp index 826fbf38b36..5d3b196caf8 100644 --- a/src/Parsers/ParserOptimizeQuery.cpp +++ b/src/Parsers/ParserOptimizeQuery.cpp @@ -28,6 +28,7 @@ bool ParserOptimizeQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expecte ParserKeyword s_partition("PARTITION"); ParserKeyword s_final("FINAL"); ParserKeyword s_deduplicate("DEDUPLICATE"); + ParserKeyword s_cleanup("CLEANUP"); ParserKeyword s_by("BY"); ParserToken s_dot(TokenType::Dot); ParserIdentifier name_p(true); @@ -76,6 +77,9 @@ bool ParserOptimizeQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expecte return false; } + /// Obsolete feature, ignored for backward compatibility. + s_cleanup.ignore(pos, expected); + auto query = std::make_shared(); node = query; diff --git a/tests/queries/0_stateless/02948_optimize_cleanup_as_noop.reference b/tests/queries/0_stateless/02948_optimize_cleanup_as_noop.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02948_optimize_cleanup_as_noop.sql b/tests/queries/0_stateless/02948_optimize_cleanup_as_noop.sql new file mode 100644 index 00000000000..002d696e62f --- /dev/null +++ b/tests/queries/0_stateless/02948_optimize_cleanup_as_noop.sql @@ -0,0 +1,7 @@ +# There was a wrong, harmful feature, leading to bugs and data corruption. +# This feature is removed, but we take care to maintain compatibility on the syntax level, so now it works as a no-op. + +DROP TABLE IF EXISTS t; +CREATE TABLE t (x UInt8, PRIMARY KEY x) ENGINE = ReplacingMergeTree; +OPTIMIZE TABLE t CLEANUP; +DROP TABLE t; From ab6a712ec6b44771124094ad99b7c69d6c4988cd Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 21 Dec 2023 09:31:44 +0100 Subject: [PATCH 158/253] Add compatibility in the replication protocol for a removed feature --- src/Storages/MergeTree/ReplicatedMergeTreeLogEntry.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeLogEntry.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeLogEntry.cpp index 85f99e3f8c3..fc924d1f80c 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeLogEntry.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeLogEntry.cpp @@ -269,6 +269,12 @@ void ReplicatedMergeTreeLogEntryData::readText(ReadBuffer & in, MergeTreeDataFor deduplicate_by_columns = std::move(new_deduplicate_by_columns); } + else if (checkString("cleanup: ", in)) + { + /// Obsolete option, does nothing. + bool cleanup = false; + in >> cleanup; + } else trailing_newline_found = true; } From 8c7ab0c59842662aa9a8c38fd37271cf29a7b8d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Thu, 21 Dec 2023 09:38:59 +0100 Subject: [PATCH 159/253] Flaky 02922_analyzer_aggregate_nothing_type --- .../0_stateless/02922_analyzer_aggregate_nothing_type.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02922_analyzer_aggregate_nothing_type.sql b/tests/queries/0_stateless/02922_analyzer_aggregate_nothing_type.sql index 987515527f0..a064c091df0 100644 --- a/tests/queries/0_stateless/02922_analyzer_aggregate_nothing_type.sql +++ b/tests/queries/0_stateless/02922_analyzer_aggregate_nothing_type.sql @@ -11,7 +11,7 @@ SET allow_experimental_parallel_reading_from_replicas=1, max_parallel_replicas=2, use_hedged_requests=0, - cluster_for_parallel_replicas='parallel_replicas', + cluster_for_parallel_replicas='test_cluster_one_shard_three_replicas_localhost', parallel_replicas_for_non_replicated_merge_tree=1 ; From 547050d41b711e1c0a53272d00df002dd15de847 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 21 Dec 2023 08:44:28 +0000 Subject: [PATCH 160/253] Update version_date.tsv and changelogs after v23.11.3.23-stable --- docker/keeper/Dockerfile | 2 +- docker/server/Dockerfile.alpine | 2 +- docker/server/Dockerfile.ubuntu | 2 +- docs/changelogs/v23.11.3.23-stable.md | 26 ++++++++++++++++++++++++++ utils/list-versions/version_date.tsv | 1 + 5 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 docs/changelogs/v23.11.3.23-stable.md diff --git a/docker/keeper/Dockerfile b/docker/keeper/Dockerfile index 06bb3f2cdda..59170af8edf 100644 --- a/docker/keeper/Dockerfile +++ b/docker/keeper/Dockerfile @@ -34,7 +34,7 @@ RUN arch=${TARGETARCH:-amd64} \ # lts / testing / prestable / etc ARG REPO_CHANNEL="stable" ARG REPOSITORY="https://packages.clickhouse.com/tgz/${REPO_CHANNEL}" -ARG VERSION="23.11.2.11" +ARG VERSION="23.11.3.23" ARG PACKAGES="clickhouse-keeper" ARG DIRECT_DOWNLOAD_URLS="" diff --git a/docker/server/Dockerfile.alpine b/docker/server/Dockerfile.alpine index e7b0d4e15e5..b577775277e 100644 --- a/docker/server/Dockerfile.alpine +++ b/docker/server/Dockerfile.alpine @@ -32,7 +32,7 @@ RUN arch=${TARGETARCH:-amd64} \ # lts / testing / prestable / etc ARG REPO_CHANNEL="stable" ARG REPOSITORY="https://packages.clickhouse.com/tgz/${REPO_CHANNEL}" -ARG VERSION="23.11.2.11" +ARG VERSION="23.11.3.23" ARG PACKAGES="clickhouse-client clickhouse-server clickhouse-common-static" ARG DIRECT_DOWNLOAD_URLS="" diff --git a/docker/server/Dockerfile.ubuntu b/docker/server/Dockerfile.ubuntu index 8cb4bf94ac9..6dc764bd0b9 100644 --- a/docker/server/Dockerfile.ubuntu +++ b/docker/server/Dockerfile.ubuntu @@ -30,7 +30,7 @@ RUN sed -i "s|http://archive.ubuntu.com|${apt_archive}|g" /etc/apt/sources.list ARG REPO_CHANNEL="stable" ARG REPOSITORY="deb [signed-by=/usr/share/keyrings/clickhouse-keyring.gpg] https://packages.clickhouse.com/deb ${REPO_CHANNEL} main" -ARG VERSION="23.11.2.11" +ARG VERSION="23.11.3.23" ARG PACKAGES="clickhouse-client clickhouse-server clickhouse-common-static" # set non-empty deb_location_url url to create a docker image diff --git a/docs/changelogs/v23.11.3.23-stable.md b/docs/changelogs/v23.11.3.23-stable.md new file mode 100644 index 00000000000..7fcc65beb54 --- /dev/null +++ b/docs/changelogs/v23.11.3.23-stable.md @@ -0,0 +1,26 @@ +--- +sidebar_position: 1 +sidebar_label: 2023 +--- + +# 2023 Changelog + +### ClickHouse release v23.11.3.23-stable (a14ab450b0e) FIXME as compared to v23.11.2.11-stable (6e5411358c8) + +#### Bug Fix (user-visible misbehavior in an official stable release) + +* Fix invalid memory access in BLAKE3 (Rust) [#57876](https://github.com/ClickHouse/ClickHouse/pull/57876) ([Raúl Marín](https://github.com/Algunenano)). +* Normalize function names in CREATE INDEX [#57906](https://github.com/ClickHouse/ClickHouse/pull/57906) ([Alexander Tokmakov](https://github.com/tavplubix)). +* Fix handling of unavailable replicas before first request happened [#57933](https://github.com/ClickHouse/ClickHouse/pull/57933) ([Nikita Taranov](https://github.com/nickitat)). +* Revert "Fix bug window functions: revert [#39631](https://github.com/ClickHouse/ClickHouse/issues/39631)" [#58031](https://github.com/ClickHouse/ClickHouse/pull/58031) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). + +#### NO CL CATEGORY + +* Backported in [#57918](https://github.com/ClickHouse/ClickHouse/issues/57918):. [#57909](https://github.com/ClickHouse/ClickHouse/pull/57909) ([Alexey Milovidov](https://github.com/alexey-milovidov)). + +#### NOT FOR CHANGELOG / INSIGNIFICANT + +* Remove heavy rust stable toolchain [#57905](https://github.com/ClickHouse/ClickHouse/pull/57905) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). +* Fix docker image for integration tests (fixes CI) [#57952](https://github.com/ClickHouse/ClickHouse/pull/57952) ([Azat Khuzhin](https://github.com/azat)). +* Always use `pread` for reading cache segments [#57970](https://github.com/ClickHouse/ClickHouse/pull/57970) ([Nikita Taranov](https://github.com/nickitat)). + diff --git a/utils/list-versions/version_date.tsv b/utils/list-versions/version_date.tsv index f319f57e0b9..de3accea617 100644 --- a/utils/list-versions/version_date.tsv +++ b/utils/list-versions/version_date.tsv @@ -1,3 +1,4 @@ +v23.11.3.23-stable 2023-12-21 v23.11.2.11-stable 2023-12-13 v23.11.1.2711-stable 2023-12-06 v23.10.5.20-stable 2023-11-25 From ab2d44830c44899351e097fcf8fcd090e19b7759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Thu, 21 Dec 2023 10:08:27 +0100 Subject: [PATCH 161/253] Fix 02901_parallel_replicas_rollup too --- tests/queries/0_stateless/02901_parallel_replicas_rollup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/02901_parallel_replicas_rollup.sh b/tests/queries/0_stateless/02901_parallel_replicas_rollup.sh index 9c922ec4723..029b4d07ee2 100755 --- a/tests/queries/0_stateless/02901_parallel_replicas_rollup.sh +++ b/tests/queries/0_stateless/02901_parallel_replicas_rollup.sh @@ -29,7 +29,7 @@ $CLICKHOUSE_CLIENT \ --query_id "${query_id}" \ --max_parallel_replicas 3 \ --prefer_localhost_replica 1 \ - --cluster_for_parallel_replicas "parallel_replicas" \ + --cluster_for_parallel_replicas "test_cluster_one_shard_three_replicas_localhost" \ --allow_experimental_parallel_reading_from_replicas 1 \ --parallel_replicas_for_non_replicated_merge_tree 1 \ --parallel_replicas_min_number_of_rows_per_replica 0 \ @@ -62,7 +62,7 @@ $CLICKHOUSE_CLIENT \ --query_id "${query_id}" \ --max_parallel_replicas 3 \ --prefer_localhost_replica 1 \ - --cluster_for_parallel_replicas "parallel_replicas" \ + --cluster_for_parallel_replicas "test_cluster_one_shard_three_replicas_localhost" \ --allow_experimental_parallel_reading_from_replicas 1 \ --parallel_replicas_for_non_replicated_merge_tree 1 \ --parallel_replicas_min_number_of_rows_per_replica 0 \ From ced9407cef07946fc31c0e8583d28f4221980f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Thu, 21 Dec 2023 10:01:44 +0100 Subject: [PATCH 162/253] Improve error messages --- src/Backups/BackupImpl.cpp | 11 ++++++++--- src/Disks/DiskLocal.cpp | 2 +- src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Backups/BackupImpl.cpp b/src/Backups/BackupImpl.cpp index 61984d58889..511a0ce59a3 100644 --- a/src/Backups/BackupImpl.cpp +++ b/src/Backups/BackupImpl.cpp @@ -157,11 +157,16 @@ BackupImpl::~BackupImpl() void BackupImpl::open() { std::lock_guard lock{mutex}; - LOG_INFO(log, "{} backup: {}", ((open_mode == OpenMode::WRITE) ? "Writing" : "Reading"), backup_name_for_logging); - ProfileEvents::increment((open_mode == OpenMode::WRITE) ? ProfileEvents::BackupsOpenedForWrite : ProfileEvents::BackupsOpenedForRead); - if (open_mode == OpenMode::WRITE) + if (open_mode == OpenMode::READ) { + ProfileEvents::increment(ProfileEvents::BackupsOpenedForRead); + LOG_INFO(log, "Reading backup: {}", backup_name_for_logging); + } + else + { + ProfileEvents::increment(ProfileEvents::BackupsOpenedForWrite); + LOG_INFO(log, "Writing backup: {}", backup_name_for_logging); timestamp = std::time(nullptr); if (!uuid) uuid = UUIDHelpers::generateV4(); diff --git a/src/Disks/DiskLocal.cpp b/src/Disks/DiskLocal.cpp index b1f55e96967..8e21b95ce68 100644 --- a/src/Disks/DiskLocal.cpp +++ b/src/Disks/DiskLocal.cpp @@ -373,7 +373,7 @@ void DiskLocal::removeDirectory(const String & path) { auto fs_path = fs::path(disk_path) / path; if (0 != rmdir(fs_path.c_str())) - ErrnoException::throwFromPath(ErrorCodes::CANNOT_RMDIR, fs_path, "Cannot rmdir {}", fs_path); + ErrnoException::throwFromPath(ErrorCodes::CANNOT_RMDIR, fs_path, "Cannot remove directory {}", fs_path); } void DiskLocal::removeRecursive(const String & path) diff --git a/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp b/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp index 19ed5c94dfd..22ccbe03f0f 100644 --- a/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp @@ -740,7 +740,7 @@ void DWARFBlockInputFormat::parseFilenameTable(UnitState & unit, uint64_t offset auto error = prologue.parse(*debug_line_extractor, &offset, /*RecoverableErrorHandler*/ [&](auto e) { if (++seen_debug_line_warnings < 10) - LOG_INFO(&Poco::Logger::get("DWARF"), "{}", llvm::toString(std::move(e))); + LOG_INFO(&Poco::Logger::get("DWARF"), "Parsing error: {}", llvm::toString(std::move(e))); }, *dwarf_context, unit.dwarf_unit); if (error) From eff69940d0a5ef1c1e0b3a8d8baeec55629efe15 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 21 Dec 2023 11:42:04 +0100 Subject: [PATCH 163/253] Fix parallel replicas in presense of a scalar subquery with a big integer value --- src/Common/FieldVisitorToString.cpp | 39 ++++++++++++++----- ...icas_scalar_subquery_big_integer.reference | 1 + ...l_replicas_scalar_subquery_big_integer.sql | 9 +++++ 3 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 tests/queries/0_stateless/02949_parallel_replicas_scalar_subquery_big_integer.reference create mode 100644 tests/queries/0_stateless/02949_parallel_replicas_scalar_subquery_big_integer.sql diff --git a/src/Common/FieldVisitorToString.cpp b/src/Common/FieldVisitorToString.cpp index 60834afab35..c4cb4266418 100644 --- a/src/Common/FieldVisitorToString.cpp +++ b/src/Common/FieldVisitorToString.cpp @@ -18,16 +18,37 @@ template static inline String formatQuoted(T x) { WriteBufferFromOwnString wb; - writeQuoted(x, wb); - return wb.str(); -} -template -static inline void writeQuoted(const DecimalField & x, WriteBuffer & buf) -{ - writeChar('\'', buf); - writeText(x.getValue(), x.getScale(), buf, {}); - writeChar('\'', buf); + if constexpr (is_decimal_field) + { + writeChar('\'', wb); + writeText(x.getValue(), x.getScale(), wb, {}); + writeChar('\'', wb); + } + else if constexpr (is_big_int_v) + { + writeChar('\'', wb); + writeText(x, wb); + writeChar('\'', wb); + } + else + { + /// While `writeQuoted` sounds like it will always write the value in quotes, + /// in fact it means: write according to the rules of the quoted format, like VALUES, + /// where strings, dates, date-times, UUID are in quotes, and numbers are not. + + /// That's why we take extra care to put Decimal and big integers inside quotes + /// when formatting literals in SQL language, + /// because it is different from the quoted formats like VALUES. + + /// In fact, there are no Decimal and big integer literals in SQL, + /// but they can appear if we format the query from a modified AST. + + /// We can fix this idiosyncrasy later. + + writeQuoted(x, wb); + } + return wb.str(); } /** In contrast to writeFloatText (and writeQuoted), diff --git a/tests/queries/0_stateless/02949_parallel_replicas_scalar_subquery_big_integer.reference b/tests/queries/0_stateless/02949_parallel_replicas_scalar_subquery_big_integer.reference new file mode 100644 index 00000000000..97bd2c20556 --- /dev/null +++ b/tests/queries/0_stateless/02949_parallel_replicas_scalar_subquery_big_integer.reference @@ -0,0 +1 @@ +6 111111111111111111111111111111111111111 diff --git a/tests/queries/0_stateless/02949_parallel_replicas_scalar_subquery_big_integer.sql b/tests/queries/0_stateless/02949_parallel_replicas_scalar_subquery_big_integer.sql new file mode 100644 index 00000000000..26f87180ab2 --- /dev/null +++ b/tests/queries/0_stateless/02949_parallel_replicas_scalar_subquery_big_integer.sql @@ -0,0 +1,9 @@ +DROP TABLE IF EXISTS test; +CREATE TABLE test (x UInt8) ENGINE = MergeTree ORDER BY x; +INSERT INTO test VALUES (1), (2), (3); + +SET allow_experimental_parallel_reading_from_replicas = 1, max_parallel_replicas = 2, cluster_for_parallel_replicas = 'test_cluster_one_shard_three_replicas_localhost', prefer_localhost_replica = 0, parallel_replicas_for_non_replicated_merge_tree = 1; + +WITH (SELECT '111111111111111111111111111111111111111'::UInt128) AS v SELECT sum(x), max(v) FROM test; + +DROP TABLE test; From cb53ee63bee55f3795e5dc93e3a92ef025d8c46f Mon Sep 17 00:00:00 2001 From: Max K Date: Thu, 21 Dec 2023 11:53:01 +0100 Subject: [PATCH 164/253] Add GH status for PR formatting (#58050) * add GH status for PR formatting #no-merge-commit --- .gitmessage | 10 ++++ tests/ci/build_report_check.py | 2 +- tests/ci/ci_config.py | 1 + tests/ci/clickbench.py | 2 +- tests/ci/commit_status_helper.py | 25 +++++---- tests/ci/docs_check.py | 2 +- tests/ci/fast_test_check.py | 2 +- tests/ci/finish_check.py | 4 +- tests/ci/functional_test_check.py | 2 +- tests/ci/install_check.py | 2 +- tests/ci/libfuzzer_test_check.py | 2 +- tests/ci/run_check.py | 91 ++++++++++++++----------------- tests/ci/style_check.py | 2 +- tests/ci/unit_tests_check.py | 2 +- 14 files changed, 75 insertions(+), 74 deletions(-) create mode 100644 .gitmessage diff --git a/.gitmessage b/.gitmessage new file mode 100644 index 00000000000..f4a25a837bc --- /dev/null +++ b/.gitmessage @@ -0,0 +1,10 @@ + + +## To avoid merge commit in CI run (add a leading space to apply): +#no-merge-commit + +## Running specified job (add a leading space to apply): +#job_ +#job_stateless_tests_release +#job_package_debug +#job_integration_tests_asan diff --git a/tests/ci/build_report_check.py b/tests/ci/build_report_check.py index 755217f89b5..d9925725eec 100644 --- a/tests/ci/build_report_check.py +++ b/tests/ci/build_report_check.py @@ -78,7 +78,7 @@ def main(): pr_info = PRInfo() commit = get_commit(gh, pr_info.sha) - atexit.register(update_mergeable_check, gh, pr_info, build_check_name) + atexit.register(update_mergeable_check, commit, pr_info, build_check_name) rerun_helper = RerunHelper(commit, build_check_name) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index 8bf9c62a17b..d28e008ef50 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -847,6 +847,7 @@ CI_CONFIG.validate() # checks required by Mergeable Check REQUIRED_CHECKS = [ + "PR Check", "ClickHouse build check", "ClickHouse special build check", "Docs Check", diff --git a/tests/ci/clickbench.py b/tests/ci/clickbench.py index 26a826a19ad..f9fadae4e03 100644 --- a/tests/ci/clickbench.py +++ b/tests/ci/clickbench.py @@ -133,7 +133,7 @@ def main(): pr_info = PRInfo() commit = get_commit(gh, pr_info.sha) - atexit.register(update_mergeable_check, gh, pr_info, check_name) + atexit.register(update_mergeable_check, commit, pr_info, check_name) rerun_helper = RerunHelper(commit, check_name) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/commit_status_helper.py b/tests/ci/commit_status_helper.py index 2eac974858c..598eef9922e 100644 --- a/tests/ci/commit_status_helper.py +++ b/tests/ci/commit_status_helper.py @@ -15,11 +15,10 @@ from github.CommitStatus import CommitStatus from github.GithubException import GithubException from github.GithubObject import NotSet from github.IssueComment import IssueComment -from github.PullRequest import PullRequest from github.Repository import Repository from ci_config import CI_CONFIG, REQUIRED_CHECKS, CHECK_DESCRIPTIONS, CheckDescription -from env_helper import GITHUB_REPOSITORY, GITHUB_RUN_URL, TEMP_PATH +from env_helper import GITHUB_JOB_URL, GITHUB_REPOSITORY, TEMP_PATH from pr_info import PRInfo, SKIP_MERGEABLE_CHECK_LABEL from report import ( ERROR, @@ -437,11 +436,11 @@ def set_mergeable_check( context=MERGEABLE_NAME, description=description, state=state, - target_url=GITHUB_RUN_URL, + target_url=GITHUB_JOB_URL(), ) -def update_mergeable_check(gh: Github, pr_info: PRInfo, check_name: str) -> None: +def update_mergeable_check(commit: Commit, pr_info: PRInfo, check_name: str) -> None: not_run = ( pr_info.labels.intersection({SKIP_MERGEABLE_CHECK_LABEL, "release"}) or check_name not in REQUIRED_CHECKS @@ -454,7 +453,6 @@ def update_mergeable_check(gh: Github, pr_info: PRInfo, check_name: str) -> None logging.info("Update Mergeable Check by %s", check_name) - commit = get_commit(gh, pr_info.sha) statuses = get_commit_filtered_statuses(commit) required_checks = [ @@ -475,14 +473,17 @@ def update_mergeable_check(gh: Github, pr_info: PRInfo, check_name: str) -> None else: fail.append(status.context) + state: StatusType = SUCCESS + + if success: + description = ", ".join(success) + else: + description = "awaiting job statuses" + if fail: description = "failed: " + ", ".join(fail) - description = format_description(description) - if mergeable_status is None or mergeable_status.description != description: - set_mergeable_check(commit, description, FAILURE) - return - - description = ", ".join(success) + state = FAILURE description = format_description(description) + if mergeable_status is None or mergeable_status.description != description: - set_mergeable_check(commit, description) + set_mergeable_check(commit, description, state) diff --git a/tests/ci/docs_check.py b/tests/ci/docs_check.py index 6f68918e63c..1424ab8895d 100644 --- a/tests/ci/docs_check.py +++ b/tests/ci/docs_check.py @@ -67,7 +67,7 @@ def main(): if rerun_helper.is_already_finished_by_status(): logging.info("Check is already finished according to github status, exiting") sys.exit(0) - atexit.register(update_mergeable_check, gh, pr_info, NAME) + atexit.register(update_mergeable_check, commit, pr_info, NAME) if not pr_info.has_changes_in_documentation() and not args.force: logging.info("No changes in documentation") diff --git a/tests/ci/fast_test_check.py b/tests/ci/fast_test_check.py index 1ce6ab617ec..093537fdeb0 100644 --- a/tests/ci/fast_test_check.py +++ b/tests/ci/fast_test_check.py @@ -124,7 +124,7 @@ def main(): gh = Github(get_best_robot_token(), per_page=100) commit = get_commit(gh, pr_info.sha) - atexit.register(update_mergeable_check, gh, pr_info, NAME) + atexit.register(update_mergeable_check, commit, pr_info, NAME) rerun_helper = RerunHelper(commit, NAME) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/finish_check.py b/tests/ci/finish_check.py index 92d2fddef0f..6c615817164 100644 --- a/tests/ci/finish_check.py +++ b/tests/ci/finish_check.py @@ -18,9 +18,9 @@ def main(): pr_info = PRInfo(need_orgs=True) gh = Github(get_best_robot_token(), per_page=100) - # Update the Mergeable Check at the final step - update_mergeable_check(gh, pr_info, CI_STATUS_NAME) commit = get_commit(gh, pr_info.sha) + # Update the Mergeable Check at the final step + update_mergeable_check(commit, pr_info, CI_STATUS_NAME) statuses = [ status diff --git a/tests/ci/functional_test_check.py b/tests/ci/functional_test_check.py index c7ae91d88b2..4d81161b6de 100644 --- a/tests/ci/functional_test_check.py +++ b/tests/ci/functional_test_check.py @@ -254,7 +254,7 @@ def main(): ) commit = get_commit(gh, pr_info.sha) - atexit.register(update_mergeable_check, gh, pr_info, check_name) + atexit.register(update_mergeable_check, commit, pr_info, check_name) if validate_bugfix_check and "pr-bugfix" not in pr_info.labels: if args.post_commit_status == "file": diff --git a/tests/ci/install_check.py b/tests/ci/install_check.py index b8cfa0acd3d..5ef65f3f38b 100644 --- a/tests/ci/install_check.py +++ b/tests/ci/install_check.py @@ -279,7 +279,7 @@ def main(): if CI: gh = Github(get_best_robot_token(), per_page=100) commit = get_commit(gh, pr_info.sha) - atexit.register(update_mergeable_check, gh, pr_info, args.check_name) + atexit.register(update_mergeable_check, commit, pr_info, args.check_name) rerun_helper = RerunHelper(commit, args.check_name) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/libfuzzer_test_check.py b/tests/ci/libfuzzer_test_check.py index 58e78d571c5..6de0614541a 100644 --- a/tests/ci/libfuzzer_test_check.py +++ b/tests/ci/libfuzzer_test_check.py @@ -118,7 +118,7 @@ def main(): gh = Github(get_best_robot_token(), per_page=100) pr_info = PRInfo() commit = get_commit(gh, pr_info.sha) - atexit.register(update_mergeable_check, gh, pr_info, check_name) + atexit.register(update_mergeable_check, commit, pr_info, check_name) temp_path.mkdir(parents=True, exist_ok=True) diff --git a/tests/ci/run_check.py b/tests/ci/run_check.py index d1949a4da0b..fb56a65fe0b 100644 --- a/tests/ci/run_check.py +++ b/tests/ci/run_check.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import atexit import sys import logging from typing import Tuple @@ -13,9 +14,8 @@ from commit_status_helper import ( post_commit_status, post_labels, remove_labels, - set_mergeable_check, + update_mergeable_check, ) -from docs_check import NAME as DOCS_NAME from env_helper import GITHUB_REPOSITORY, GITHUB_SERVER_URL from get_robot_token import get_best_robot_token from pr_info import FORCE_TESTS_LABEL, PRInfo @@ -24,6 +24,7 @@ from lambda_shared_package.lambda_shared.pr import ( TRUSTED_CONTRIBUTORS, check_pr_description, ) +from report import FAILURE TRUSTED_ORG_IDS = { 54801242, # clickhouse @@ -34,6 +35,7 @@ CAN_BE_TESTED_LABEL = "can be tested" DO_NOT_TEST_LABEL = "do not test" FEATURE_LABEL = "pr-feature" SUBMODULE_CHANGED_LABEL = "submodule changed" +PR_CHECK = "PR Check" def pr_is_by_trusted_user(pr_user_login, pr_user_orgs): @@ -58,24 +60,20 @@ def pr_is_by_trusted_user(pr_user_login, pr_user_orgs): # Returns whether we should look into individual checks for this PR. If not, it # can be skipped entirely. -# Returns can_run, description, labels_state -def should_run_ci_for_pr(pr_info: PRInfo) -> Tuple[bool, str, str]: +# Returns can_run, description +def should_run_ci_for_pr(pr_info: PRInfo) -> Tuple[bool, str]: # Consider the labels and whether the user is trusted. print("Got labels", pr_info.labels) if FORCE_TESTS_LABEL in pr_info.labels: print(f"Label '{FORCE_TESTS_LABEL}' set, forcing remaining checks") - return True, f"Labeled '{FORCE_TESTS_LABEL}'", "pending" + return True, f"Labeled '{FORCE_TESTS_LABEL}'" if DO_NOT_TEST_LABEL in pr_info.labels: print(f"Label '{DO_NOT_TEST_LABEL}' set, skipping remaining checks") - return False, f"Labeled '{DO_NOT_TEST_LABEL}'", "success" + return False, f"Labeled '{DO_NOT_TEST_LABEL}'" if OK_SKIP_LABELS.intersection(pr_info.labels): - return ( - True, - "Don't try new checks for release/backports/cherry-picks", - "success", - ) + return True, "Don't try new checks for release/backports/cherry-picks" if CAN_BE_TESTED_LABEL not in pr_info.labels and not pr_is_by_trusted_user( pr_info.user_login, pr_info.user_orgs @@ -83,9 +81,9 @@ def should_run_ci_for_pr(pr_info: PRInfo) -> Tuple[bool, str, str]: print( f"PRs by untrusted users need the '{CAN_BE_TESTED_LABEL}' label - please contact a member of the core team" ) - return False, "Needs 'can be tested' label", "failure" + return False, "Needs 'can be tested' label" - return True, "No special conditions apply", "pending" + return True, "No special conditions apply" def main(): @@ -98,7 +96,7 @@ def main(): print("::notice ::Cannot run, no PR exists for the commit") sys.exit(1) - can_run, description, labels_state = should_run_ci_for_pr(pr_info) + can_run, description = should_run_ci_for_pr(pr_info) if can_run and OK_SKIP_LABELS.intersection(pr_info.labels): print("::notice :: Early finish the check, running in a special PR") sys.exit(0) @@ -106,6 +104,7 @@ def main(): description = format_description(description) gh = Github(get_best_robot_token(), per_page=100) commit = get_commit(gh, pr_info.sha) + atexit.register(update_mergeable_check, commit, pr_info, PR_CHECK) description_error, category = check_pr_description(pr_info.body, GITHUB_REPOSITORY) pr_labels_to_add = [] @@ -136,22 +135,6 @@ def main(): if pr_labels_to_remove: remove_labels(gh, pr_info, pr_labels_to_remove) - # FIXME: it should rather be in finish check. no reason to stop ci run. - if FEATURE_LABEL in pr_info.labels and not pr_info.has_changes_in_documentation(): - print( - f"The '{FEATURE_LABEL}' in the labels, " - "but there's no changed documentation" - ) - post_commit_status( # do not pass pr_info here intentionally - commit, - "failure", - "", - f"expect adding docs for {FEATURE_LABEL}", - DOCS_NAME, - pr_info, - ) - sys.exit(0) - if description_error: print( "::error ::Cannot run, PR description does not match the template: " @@ -171,34 +154,40 @@ def main(): "failure", url, format_description(description_error), - CI_STATUS_NAME, + PR_CHECK, pr_info, ) sys.exit(1) - set_mergeable_check(commit, "skipped") - ci_report_url = create_ci_report(pr_info, []) + if FEATURE_LABEL in pr_info.labels and not pr_info.has_changes_in_documentation(): + print( + f"The '{FEATURE_LABEL}' in the labels, " + "but there's no changed documentation" + ) + post_commit_status( + commit, + FAILURE, + "", + f"expect adding docs for {FEATURE_LABEL}", + PR_CHECK, + pr_info, + ) + # allow the workflow to continue + if not can_run: print("::notice ::Cannot run") - post_commit_status( - commit, - labels_state, - ci_report_url, - description, - CI_STATUS_NAME, - pr_info, - ) sys.exit(1) - else: - print("::notice ::Can run") - post_commit_status( - commit, - "pending", - ci_report_url, - description, - CI_STATUS_NAME, - pr_info, - ) + + ci_report_url = create_ci_report(pr_info, []) + print("::notice ::Can run") + post_commit_status( + commit, + "pending", + ci_report_url, + description, + CI_STATUS_NAME, + pr_info, + ) if __name__ == "__main__": diff --git a/tests/ci/style_check.py b/tests/ci/style_check.py index a5498fac393..b37dcb59237 100644 --- a/tests/ci/style_check.py +++ b/tests/ci/style_check.py @@ -145,7 +145,7 @@ def main(): gh = GitHub(get_best_robot_token(), create_cache_dir=False) commit = get_commit(gh, pr_info.sha) - atexit.register(update_mergeable_check, gh, pr_info, NAME) + atexit.register(update_mergeable_check, commit, pr_info, NAME) rerun_helper = RerunHelper(commit, NAME) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/unit_tests_check.py b/tests/ci/unit_tests_check.py index d6767cf8b7f..f1238a00bd4 100644 --- a/tests/ci/unit_tests_check.py +++ b/tests/ci/unit_tests_check.py @@ -187,7 +187,7 @@ def main(): gh = Github(get_best_robot_token(), per_page=100) commit = get_commit(gh, pr_info.sha) - atexit.register(update_mergeable_check, gh, pr_info, check_name) + atexit.register(update_mergeable_check, commit, pr_info, check_name) rerun_helper = RerunHelper(commit, check_name) if rerun_helper.is_already_finished_by_status(): From afdc0548e533fbd861c742bde2e4803f84bea0ec Mon Sep 17 00:00:00 2001 From: Max Kainov Date: Thu, 21 Dec 2023 11:33:22 +0000 Subject: [PATCH 165/253] limited ci on master for docs only --- tests/ci/ci.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/ci/ci.py b/tests/ci/ci.py index 4378e4361f0..84b244c6e08 100644 --- a/tests/ci/ci.py +++ b/tests/ci/ci.py @@ -135,7 +135,7 @@ def parse_args(parser: argparse.ArgumentParser) -> argparse.Namespace: "--skip-jobs", action="store_true", default=False, - help="skip fetching data about job runs, used in --configure action (for debugging)", + help="skip fetching data about job runs, used in --configure action (for debugging and nigthly ci)", ) parser.add_argument( "--rebuild-all-docker", @@ -377,6 +377,7 @@ def _configure_jobs( for batch in range(num_batches): # type: ignore batches_to_do.append(batch) elif job_config.run_always: + # always add to todo batches_to_do.append(batch) else: # this job controlled by digest, add to todo if it's not successfully done before @@ -607,8 +608,10 @@ def main() -> int: result["jobs_data"] = jobs_data result["docker_data"] = docker_data if pr_info.number != 0 and not args.docker_digest_or_latest: + #FIXME: it runs style check before docker build if possible (style-check images is not changed) + # find a way to do style check always before docker build and others _check_and_update_for_early_style_check(result) - if pr_info.number != 0 and pr_info.has_changes_in_documentation_only(): + if pr_info.has_changes_in_documentation_only(): _update_config_for_docs_only(result) elif args.update_gh_statuses: From a0a1189302a917b860c3aefceecc32378d39df29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Thu, 21 Dec 2023 13:47:49 +0100 Subject: [PATCH 166/253] More improvements --- src/Backups/RestorerFromBackup.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Backups/RestorerFromBackup.cpp b/src/Backups/RestorerFromBackup.cpp index 026671edd6a..4e580e493a7 100644 --- a/src/Backups/RestorerFromBackup.cpp +++ b/src/Backups/RestorerFromBackup.cpp @@ -43,14 +43,6 @@ namespace Stage = BackupCoordinationStage; namespace { - /// Uppercases the first character of a passed string. - String toUpperFirst(const String & str) - { - String res = str; - res[0] = std::toupper(res[0]); - return res; - } - /// Outputs "table " or "temporary table " String tableNameWithTypeToString(const String & database_name, const String & table_name, bool first_upper) { @@ -145,7 +137,7 @@ RestorerFromBackup::DataRestoreTasks RestorerFromBackup::run(Mode mode) void RestorerFromBackup::setStage(const String & new_stage, const String & message) { - LOG_TRACE(log, fmt::runtime(toUpperFirst(new_stage))); + LOG_TRACE(log, "Setting stage: {}", new_stage); current_stage = new_stage; if (restore_coordination) From 2dcffcc41c67c73725f7a59a71ab9edfc0c97d2a Mon Sep 17 00:00:00 2001 From: Max K Date: Thu, 21 Dec 2023 14:14:51 +0100 Subject: [PATCH 167/253] fix (#58125) --- tests/ci/ci.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/ci.py b/tests/ci/ci.py index 84b244c6e08..949a7a7e5bf 100644 --- a/tests/ci/ci.py +++ b/tests/ci/ci.py @@ -608,7 +608,7 @@ def main() -> int: result["jobs_data"] = jobs_data result["docker_data"] = docker_data if pr_info.number != 0 and not args.docker_digest_or_latest: - #FIXME: it runs style check before docker build if possible (style-check images is not changed) + # FIXME: it runs style check before docker build if possible (style-check images is not changed) # find a way to do style check always before docker build and others _check_and_update_for_early_style_check(result) if pr_info.has_changes_in_documentation_only(): From c08a490578aba298859448cc345a2c6ce79c7f32 Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 4 Dec 2023 10:20:01 +0000 Subject: [PATCH 168/253] Fix nullability after tryReplaceOrEqualsChainWithIn --- .../Passes/LogicalExpressionOptimizerPass.cpp | 19 +++++++++++++++++-- ...702_logical_optimizer_with_nulls.reference | 2 ++ .../02702_logical_optimizer_with_nulls.sql | 3 +++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp b/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp index 3bafa8bbd6d..371c0a07511 100644 --- a/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp +++ b/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp @@ -404,11 +404,14 @@ private: continue; } + bool is_any_nullable = false; Tuple args; args.reserve(equals_functions.size()); /// first we create tuple from RHS of equals functions for (const auto & equals : equals_functions) { + is_any_nullable |= equals->getResultType()->isNullable(); + const auto * equals_function = equals->as(); assert(equals_function && equals_function->getFunctionName() == "equals"); @@ -436,8 +439,20 @@ private: in_function->getArguments().getNodes() = std::move(in_arguments); in_function->resolveAsFunction(in_function_resolver); - - or_operands.push_back(std::move(in_function)); + /** For `k :: UInt8`, expression `k = 1 OR k = NULL` with result type Nullable(UInt8) + * is replaced with `k IN (1, NULL)` with result type UInt8. + * Convert it back to Nullable(UInt8). + */ + if (is_any_nullable && !in_function->getResultType()->isNullable()) + { + auto nullable_result_type = std::make_shared(in_function->getResultType()); + auto in_function_nullable = createCastFunction(std::move(in_function), std::move(nullable_result_type), getContext()); + or_operands.push_back(std::move(in_function_nullable)); + } + else + { + or_operands.push_back(std::move(in_function)); + } } if (or_operands.size() == function_node.getArguments().getNodes().size()) diff --git a/tests/queries/0_stateless/02702_logical_optimizer_with_nulls.reference b/tests/queries/0_stateless/02702_logical_optimizer_with_nulls.reference index eb79bbc842a..e7f46a974e6 100644 --- a/tests/queries/0_stateless/02702_logical_optimizer_with_nulls.reference +++ b/tests/queries/0_stateless/02702_logical_optimizer_with_nulls.reference @@ -75,3 +75,5 @@ QUERY id: 0 LIST id: 6, nodes: 2 COLUMN id: 7, column_name: a, result_type: Nullable(Int32), source_id: 3 CONSTANT id: 8, constant_value: Tuple_(UInt64_1, UInt64_3, UInt64_2), constant_value_type: Tuple(UInt8, UInt8, UInt8) +1 +1 diff --git a/tests/queries/0_stateless/02702_logical_optimizer_with_nulls.sql b/tests/queries/0_stateless/02702_logical_optimizer_with_nulls.sql index 07d0b170a02..72ab507f541 100644 --- a/tests/queries/0_stateless/02702_logical_optimizer_with_nulls.sql +++ b/tests/queries/0_stateless/02702_logical_optimizer_with_nulls.sql @@ -29,4 +29,7 @@ INSERT INTO 02702_logical_optimizer_with_null_column VALUES (1, 'test'), (2, 'te SELECT * FROM 02702_logical_optimizer_with_null_column WHERE a = 1 OR 3 = a OR 2 = a; EXPLAIN QUERY TREE SELECT * FROM 02702_logical_optimizer_with_null_column WHERE a = 1 OR 3 = a OR 2 = a; +SELECT materialize(1) AS k WHERE NULL OR (0 OR (k = 2) OR (k = CAST(1, 'Nullable(UInt8)') OR k = 3)); +SELECT (k = 2) OR (k = 1) OR ((NULL OR 1) = k) FROM (SELECT 1 AS k); + DROP TABLE 02702_logical_optimizer_with_null_column; From 177f8f7d0dbd0fc58d06aafa047fbf12ad6eda72 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 21 Dec 2023 16:09:56 +0100 Subject: [PATCH 169/253] Create a single point of true for integration tests images --- tests/ci/ci_config.py | 33 +++------------ tests/ci/integration_test_check.py | 33 +++------------ tests/ci/integration_test_images.py | 30 ++++++++++++++ tests/integration/ci-runner.py | 24 ++--------- tests/integration/integration_test_images.py | 1 + tests/integration/runner | 43 ++++++-------------- 6 files changed, 58 insertions(+), 106 deletions(-) create mode 100644 tests/ci/integration_test_images.py create mode 120000 tests/integration/integration_test_images.py diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index d28e008ef50..65233bf1d00 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 import logging - from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser from dataclasses import dataclass, field from pathlib import Path from typing import Callable, Dict, Iterable, List, Literal, Optional, Union +from integration_test_images import IMAGES + @dataclass class DigestConfig: @@ -131,20 +132,7 @@ upgrade_check_digest = DigestConfig( integration_check_digest = DigestConfig( include_paths=["./tests/ci/integration_test_check.py", "./tests/integration"], exclude_files=[".md"], - docker=[ - "clickhouse/dotnet-client", - "clickhouse/integration-helper", - "clickhouse/integration-test", - "clickhouse/integration-tests-runner", - "clickhouse/kerberized-hadoop", - "clickhouse/kerberos-kdc", - "clickhouse/mysql-golang-client", - "clickhouse/mysql-java-client", - "clickhouse/mysql-js-client", - "clickhouse/mysql-php-client", - "clickhouse/nginx-dav", - "clickhouse/postgresql-java-client", - ], + docker=IMAGES.copy(), ) ast_fuzzer_check_digest = DigestConfig( @@ -188,20 +176,9 @@ bugfix_validate_check = DigestConfig( "./tests/ci/bugfix_validate_check.py", ], exclude_files=[".md"], - docker=[ + docker=IMAGES.copy() + + [ "clickhouse/stateless-test", - "clickhouse/dotnet-client", - "clickhouse/integration-helper", - "clickhouse/integration-test", - "clickhouse/integration-tests-runner", - "clickhouse/kerberized-hadoop", - "clickhouse/kerberos-kdc", - "clickhouse/mysql-golang-client", - "clickhouse/mysql-java-client", - "clickhouse/mysql-js-client", - "clickhouse/mysql-php-client", - "clickhouse/nginx-dav", - "clickhouse/postgresql-java-client", ], ) # common test params diff --git a/tests/ci/integration_test_check.py b/tests/ci/integration_test_check.py index 0a8f166e53e..c65f162f770 100644 --- a/tests/ci/integration_test_check.py +++ b/tests/ci/integration_test_check.py @@ -10,13 +10,8 @@ import sys from pathlib import Path from typing import Dict, List, Tuple -from github import Github - from build_download_helper import download_all_deb_packages -from clickhouse_helper import ( - ClickHouseHelper, - prepare_tests_results_for_clickhouse, -) +from clickhouse_helper import ClickHouseHelper, prepare_tests_results_for_clickhouse from commit_status_helper import ( RerunHelper, get_commit, @@ -24,10 +19,12 @@ from commit_status_helper import ( post_commit_status, post_commit_status_to_file, ) -from docker_images_helper import DockerImage, pull_image, get_docker_image +from docker_images_helper import DockerImage, get_docker_image, pull_image from download_release_packages import download_last_release -from env_helper import REPORT_PATH, TEMP_PATH, REPO_COPY +from env_helper import REPO_COPY, REPORT_PATH, TEMP_PATH from get_robot_token import get_best_robot_token +from github_helper import GitHub +from integration_test_images import IMAGES from pr_info import PRInfo from report import ERROR, TestResult, TestResults, read_test_results from s3_helper import S3Helper @@ -36,24 +33,6 @@ from tee_popen import TeePopen from upload_result_helper import upload_results -# When update, update -# tests/integration/ci-runner.py:ClickhouseIntegrationTestsRunner.get_images_names too -IMAGES = [ - "clickhouse/dotnet-client", - "clickhouse/integration-helper", - "clickhouse/integration-test", - "clickhouse/integration-tests-runner", - "clickhouse/kerberized-hadoop", - "clickhouse/kerberos-kdc", - "clickhouse/mysql-golang-client", - "clickhouse/mysql-java-client", - "clickhouse/mysql-js-client", - "clickhouse/mysql-php-client", - "clickhouse/nginx-dav", - "clickhouse/postgresql-java-client", -] - - def get_json_params_dict( check_name: str, pr_info: PRInfo, @@ -210,7 +189,7 @@ def main(): logging.info("Skipping '%s' (no pr-bugfix in '%s')", check_name, pr_info.labels) sys.exit(0) - gh = Github(get_best_robot_token(), per_page=100) + gh = GitHub(get_best_robot_token()) commit = get_commit(gh, pr_info.sha) rerun_helper = RerunHelper(commit, check_name_with_group) diff --git a/tests/ci/integration_test_images.py b/tests/ci/integration_test_images.py new file mode 100644 index 00000000000..97fe4d878f0 --- /dev/null +++ b/tests/ci/integration_test_images.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +IMAGES_ENV = { + "clickhouse/dotnet-client": "DOCKER_DOTNET_CLIENT_TAG", + "clickhouse/integration-helper": "DOCKER_HELPER_TAG", + "clickhouse/integration-test": "DOCKER_BASE_TAG", + "clickhouse/integration-tests-runner": "", + "clickhouse/kerberized-hadoop": "DOCKER_KERBERIZED_HADOOP_TAG", + "clickhouse/kerberos-kdc": "DOCKER_KERBEROS_KDC_TAG", + "clickhouse/mysql-golang-client": "DOCKER_MYSQL_GOLANG_CLIENT_TAG", + "clickhouse/mysql-java-client": "DOCKER_MYSQL_JAVA_CLIENT_TAG", + "clickhouse/mysql-js-client": "DOCKER_MYSQL_JS_CLIENT_TAG", + "clickhouse/mysql-php-client": "DOCKER_MYSQL_PHP_CLIENT_TAG", + "clickhouse/nginx-dav": "DOCKER_NGINX_DAV_TAG", + "clickhouse/postgresql-java-client": "DOCKER_POSTGRESQL_JAVA_CLIENT_TAG", +} + +IMAGES = list(IMAGES_ENV.keys()) + + +def get_image_env(image: str) -> str: + return IMAGES_ENV.get(image, "") + + +def get_docker_env(image: str, tag: str) -> str: + "if image belongs to IMAGES_ENV, return `-e` argument for docker command" + env = get_image_env(image) + if not env: + return env + return f"-e {env}={tag} " diff --git a/tests/integration/ci-runner.py b/tests/integration/ci-runner.py index a3ad94a59ec..7c922e339fe 100755 --- a/tests/integration/ci-runner.py +++ b/tests/integration/ci-runner.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -from collections import defaultdict import csv import glob import json @@ -8,13 +7,15 @@ import logging import os import random import re +import shlex import shutil import string import subprocess import time -import shlex import zlib # for crc32 +from collections import defaultdict +from integration_test_images import IMAGES MAX_RETRY = 1 NUM_WORKERS = 5 @@ -301,23 +302,6 @@ class ClickhouseIntegrationTestsRunner: def shuffle_test_groups(self): return self.shuffle_groups != 0 - @staticmethod - def get_images_names(): - return [ - "clickhouse/dotnet-client", - "clickhouse/integration-helper", - "clickhouse/integration-test", - "clickhouse/integration-tests-runner", - "clickhouse/kerberized-hadoop", - "clickhouse/kerberos-kdc", - "clickhouse/mysql-golang-client", - "clickhouse/mysql-java-client", - "clickhouse/mysql-js-client", - "clickhouse/mysql-php-client", - "clickhouse/nginx-dav", - "clickhouse/postgresql-java-client", - ] - def _pre_pull_images(self, repo_path): image_cmd = self._get_runner_image_cmd(repo_path) @@ -523,7 +507,7 @@ class ClickhouseIntegrationTestsRunner: os.path.join(repo_path, "tests/integration", "runner"), "--docker-image-version", ): - for img in self.get_images_names(): + for img in IMAGES: if img == "clickhouse/integration-tests-runner": runner_version = self.get_image_version(img) logging.info( diff --git a/tests/integration/integration_test_images.py b/tests/integration/integration_test_images.py new file mode 120000 index 00000000000..1b344702aea --- /dev/null +++ b/tests/integration/integration_test_images.py @@ -0,0 +1 @@ +../ci/integration_test_images.py \ No newline at end of file diff --git a/tests/integration/runner b/tests/integration/runner index 3760bf16b84..b1193b5b471 100755 --- a/tests/integration/runner +++ b/tests/integration/runner @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import subprocess -import os -import getpass -import glob import argparse +import glob import logging -import signal -import subprocess -import sys -import string +import os import random import shlex +import signal +import string +import subprocess +import sys + +from integration_test_images import get_docker_env def random_str(length=6): @@ -335,30 +335,11 @@ if __name__ == "__main__": if args.docker_compose_images_tags is not None: for img_tag in args.docker_compose_images_tags: [image, tag] = img_tag.split(":") - if image == "clickhouse/dotnet-client": - env_tags += "-e {}={} ".format("DOCKER_DOTNET_CLIENT_TAG", tag) - elif image == "clickhouse/integration-helper": - env_tags += "-e {}={} ".format("DOCKER_HELPER_TAG", tag) - elif image == "clickhouse/integration-test": - env_tags += "-e {}={} ".format("DOCKER_BASE_TAG", tag) - elif image == "clickhouse/kerberized-hadoop": - env_tags += "-e {}={} ".format("DOCKER_KERBERIZED_HADOOP_TAG", tag) - elif image == "clickhouse/kerberos-kdc": - env_tags += "-e {}={} ".format("DOCKER_KERBEROS_KDC_TAG", tag) - elif image == "clickhouse/mysql-golang-client": - env_tags += "-e {}={} ".format("DOCKER_MYSQL_GOLANG_CLIENT_TAG", tag) - elif image == "clickhouse/mysql-java-client": - env_tags += "-e {}={} ".format("DOCKER_MYSQL_JAVA_CLIENT_TAG", tag) - elif image == "clickhouse/mysql-js-client": - env_tags += "-e {}={} ".format("DOCKER_MYSQL_JS_CLIENT_TAG", tag) - elif image == "clickhouse/mysql-php-client": - env_tags += "-e {}={} ".format("DOCKER_MYSQL_PHP_CLIENT_TAG", tag) - elif image == "clickhouse/nginx-dav": - env_tags += "-e {}={} ".format("DOCKER_NGINX_DAV_TAG", tag) - elif image == "clickhouse/postgresql-java-client": - env_tags += "-e {}={} ".format("DOCKER_POSTGRESQL_JAVA_CLIENT_TAG", tag) + env_tag = get_docker_env(image, tag) + if env_tag: + env_tags += env_tag else: - logging.info("Unknown image %s" % (image)) + logging.info("Unknown image %s", image) # create named volume which will be used inside to store images and other docker related files, # to avoid redownloading it every time From 4a04a1316fe9a6fadcaeaa9ca7a17819facd60bd Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 21 Dec 2023 16:11:54 +0100 Subject: [PATCH 170/253] Add a tag processing for python-bottle --- docker/test/integration/runner/compose/docker_compose_minio.yml | 2 +- tests/ci/integration_test_images.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/test/integration/runner/compose/docker_compose_minio.yml b/docker/test/integration/runner/compose/docker_compose_minio.yml index 45e55e7a79c..4255a529f6d 100644 --- a/docker/test/integration/runner/compose/docker_compose_minio.yml +++ b/docker/test/integration/runner/compose/docker_compose_minio.yml @@ -34,7 +34,7 @@ services: # Empty container to run proxy resolver. resolver: - image: clickhouse/python-bottle + image: clickhouse/python-bottle:${DOCKER_PYTHON_BOTTLE_TAG:-latest} expose: - "8080" tty: true diff --git a/tests/ci/integration_test_images.py b/tests/ci/integration_test_images.py index 97fe4d878f0..8148ac61181 100644 --- a/tests/ci/integration_test_images.py +++ b/tests/ci/integration_test_images.py @@ -13,6 +13,7 @@ IMAGES_ENV = { "clickhouse/mysql-php-client": "DOCKER_MYSQL_PHP_CLIENT_TAG", "clickhouse/nginx-dav": "DOCKER_NGINX_DAV_TAG", "clickhouse/postgresql-java-client": "DOCKER_POSTGRESQL_JAVA_CLIENT_TAG", + "clickhouse/python-bottle": "DOCKER_PYTHON_BOTTLE_TAG", } IMAGES = list(IMAGES_ENV.keys()) From ceed935b3025fcc92d11343535003fe4fd18a3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Thu, 21 Dec 2023 17:11:34 +0100 Subject: [PATCH 171/253] Remove debug comment and fix unconditional_retry logic --- src/Storages/MergeTree/ZooKeeperRetries.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/ZooKeeperRetries.h b/src/Storages/MergeTree/ZooKeeperRetries.h index f5b902cbd4f..eff68a14f24 100644 --- a/src/Storages/MergeTree/ZooKeeperRetries.h +++ b/src/Storages/MergeTree/ZooKeeperRetries.h @@ -210,8 +210,9 @@ private: { if (unconditional_retry) { - LOG_DEBUG(logger, "ZooKeeperRetriesControl: unconditional_retry"); unconditional_retry = false; + /// reset the flag, it will be set to false in case of error + iteration_succeeded = true; return true; } From f0933d2714fa2fa9bd77b6d043d66938f27c875d Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Thu, 21 Dec 2023 16:21:02 +0000 Subject: [PATCH 172/253] Disable parallel replicas with IN with subquery --- src/Interpreters/InterpreterSelectQuery.cpp | 11 ++++++++ src/Interpreters/PreparedSets.cpp | 2 +- src/Interpreters/PreparedSets.h | 3 ++- ...49_parallel_replicas_in_subquery.reference | 8 ++++++ .../02949_parallel_replicas_in_subquery.sql | 26 +++++++++++++++++++ 5 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 tests/queries/0_stateless/02949_parallel_replicas_in_subquery.reference create mode 100644 tests/queries/0_stateless/02949_parallel_replicas_in_subquery.sql diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 4dc462e38e7..297344a2643 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -882,6 +882,17 @@ bool InterpreterSelectQuery::adjustParallelReplicasAfterAnalysis() return true; } + if (query_analyzer->getPreparedSets()->hasSubqueries()) + { + if (settings.allow_experimental_parallel_reading_from_replicas == 2) + throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "IN with subquery is not supported with parallel replicas"); + + context->setSetting("allow_experimental_parallel_reading_from_replicas", Field(0)); + context->setSetting("max_parallel_replicas", UInt64{0}); + LOG_INFO(log, "Disabling parallel replicas to execute a query with IN with subquery"); + return true; + } + auto storage_merge_tree = std::dynamic_pointer_cast(storage); if (!storage_merge_tree || settings.parallel_replicas_min_number_of_rows_per_replica == 0) return false; diff --git a/src/Interpreters/PreparedSets.cpp b/src/Interpreters/PreparedSets.cpp index 955d8892284..7468bd1d519 100644 --- a/src/Interpreters/PreparedSets.cpp +++ b/src/Interpreters/PreparedSets.cpp @@ -327,7 +327,7 @@ std::shared_ptr PreparedSets::findStorage(const Hash & key return it->second; } -PreparedSets::Subqueries PreparedSets::getSubqueries() +PreparedSets::Subqueries PreparedSets::getSubqueries() const { PreparedSets::Subqueries res; res.reserve(sets_from_subqueries.size()); diff --git a/src/Interpreters/PreparedSets.h b/src/Interpreters/PreparedSets.h index e237789c63c..e6d499715b8 100644 --- a/src/Interpreters/PreparedSets.h +++ b/src/Interpreters/PreparedSets.h @@ -157,7 +157,8 @@ public: std::shared_ptr findSubquery(const Hash & key) const; using Subqueries = std::vector>; - Subqueries getSubqueries(); + Subqueries getSubqueries() const; + bool hasSubqueries() const { return !sets_from_subqueries.empty(); } const SetsFromTuple & getSetsFromTuple() const { return sets_from_tuple; } // const SetsFromStorage & getSetsFromStorage() const { return sets_from_storage; } diff --git a/tests/queries/0_stateless/02949_parallel_replicas_in_subquery.reference b/tests/queries/0_stateless/02949_parallel_replicas_in_subquery.reference new file mode 100644 index 00000000000..4d33751c699 --- /dev/null +++ b/tests/queries/0_stateless/02949_parallel_replicas_in_subquery.reference @@ -0,0 +1,8 @@ +--- +2 test2 8 +3 test3 8 +4 test4 1985 +--- +1 test1 42 +--- +3 test3 diff --git a/tests/queries/0_stateless/02949_parallel_replicas_in_subquery.sql b/tests/queries/0_stateless/02949_parallel_replicas_in_subquery.sql new file mode 100644 index 00000000000..53b8a761cda --- /dev/null +++ b/tests/queries/0_stateless/02949_parallel_replicas_in_subquery.sql @@ -0,0 +1,26 @@ +DROP TABLE IF EXISTS merge_tree_in_subqueries; +CREATE TABLE merge_tree_in_subqueries (id UInt64, name String, num UInt64) ENGINE = MergeTree ORDER BY (id, name); +INSERT INTO merge_tree_in_subqueries VALUES(1, 'test1', 42); +INSERT INTO merge_tree_in_subqueries VALUES(2, 'test2', 8); +INSERT INTO merge_tree_in_subqueries VALUES(3, 'test3', 8); +INSERT INTO merge_tree_in_subqueries VALUES(4, 'test4', 1985); +INSERT INTO merge_tree_in_subqueries VALUES(5, 'test5', 0); + +SET max_parallel_replicas=3, cluster_for_parallel_replicas='test_cluster_one_shard_three_replicas_localhost', parallel_replicas_for_non_replicated_merge_tree=1; + +SELECT * FROM merge_tree_in_subqueries WHERE id IN (SELECT * FROM system.numbers LIMIT 0) SETTINGS allow_experimental_parallel_reading_from_replicas=2; -- { serverError SUPPORT_IS_DISABLED } +SELECT * FROM merge_tree_in_subqueries WHERE id IN (SELECT * FROM system.numbers LIMIT 0) SETTINGS allow_experimental_parallel_reading_from_replicas=1; + +SELECT '---'; +SELECT * FROM merge_tree_in_subqueries WHERE id IN (SELECT * FROM system.numbers LIMIT 2, 3) ORDER BY id SETTINGS allow_experimental_parallel_reading_from_replicas=2; -- { serverError SUPPORT_IS_DISABLED }; +SELECT * FROM merge_tree_in_subqueries WHERE id IN (SELECT * FROM system.numbers LIMIT 2, 3) ORDER BY id SETTINGS allow_experimental_parallel_reading_from_replicas=1; + +SELECT '---'; +SELECT * FROM merge_tree_in_subqueries WHERE id IN (SELECT 1) ORDER BY id SETTINGS allow_experimental_parallel_reading_from_replicas=2; -- { serverError SUPPORT_IS_DISABLED }; +SELECT * FROM merge_tree_in_subqueries WHERE id IN (SELECT 1) ORDER BY id SETTINGS allow_experimental_parallel_reading_from_replicas=1; + +-- IN with tuples is allowed +SELECT '---'; +SELECT id, name FROM merge_tree_in_subqueries WHERE (id, name) IN (3, 'test3') SETTINGS allow_experimental_parallel_reading_from_replicas=2; + +DROP TABLE IF EXISTS merge_tree_in_subqueries; From 58d94c0ce0b24a770ac95152af94de5a0edcda1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Thu, 21 Dec 2023 17:48:57 +0100 Subject: [PATCH 173/253] Fix clang-tidy --- src/Functions/FunctionsStringHashFixedString.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Functions/FunctionsStringHashFixedString.cpp b/src/Functions/FunctionsStringHashFixedString.cpp index 9b613b4026e..fd42a84fa26 100644 --- a/src/Functions/FunctionsStringHashFixedString.cpp +++ b/src/Functions/FunctionsStringHashFixedString.cpp @@ -172,7 +172,7 @@ struct SHA512Impl256 /// SSL library that we use, for S390X architecture only OpenSSL is supported. But the SHA512-256, SHA512_256_Init, /// SHA512_256_Update, SHA512_256_Final methods to calculate hash (similar to the other SHA functions) aren't available /// in the current version of OpenSSL that we use which necessitates the use of the EVP interface. - auto md_ctx = EVP_MD_CTX_create(); + auto * md_ctx = EVP_MD_CTX_create(); EVP_DigestInit_ex(md_ctx, EVP_sha512_256(), nullptr /*engine*/); EVP_DigestUpdate(md_ctx, begin, size); EVP_DigestFinal_ex(md_ctx, out_char_data, nullptr /*size*/); From 81b8507cc18dd82eb101998f4bbe7c1fc4dcd8ac Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Thu, 21 Dec 2023 17:04:03 +0000 Subject: [PATCH 174/253] New analyzer support --- src/Planner/Planner.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Planner/Planner.cpp b/src/Planner/Planner.cpp index 123a4269292..74ae3028928 100644 --- a/src/Planner/Planner.cpp +++ b/src/Planner/Planner.cpp @@ -1333,14 +1333,27 @@ void Planner::buildPlanForQueryNode() } collectSets(query_tree, *planner_context); + + const auto & settings = query_context->getSettingsRef(); + if (query_context->canUseTaskBasedParallelReplicas()) + { + if (planner_context->getPreparedSets().hasSubqueries()) + { + if (settings.allow_experimental_parallel_reading_from_replicas == 2) + throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "IN with subquery is not supported with parallel replicas"); + + auto & mutable_context = planner_context->getMutableQueryContext(); + mutable_context->setSetting("allow_experimental_parallel_reading_from_replicas", Field(0)); + LOG_DEBUG(&Poco::Logger::get("Planner"), "Disabling parallel replicas to execute a query with IN with subquery"); + } + } + collectTableExpressionData(query_tree, planner_context); checkStoragesSupportTransactions(planner_context); if (!select_query_options.only_analyze) collectFiltersForAnalysis(query_tree, planner_context); - const auto & settings = query_context->getSettingsRef(); - if (query_context->canUseTaskBasedParallelReplicas()) { const auto & table_expression_nodes = planner_context->getTableExpressionNodeToData(); From 8ac13b3e2673ce6c9ac38f6cc869ad1714a5be74 Mon Sep 17 00:00:00 2001 From: Max K Date: Thu, 21 Dec 2023 18:08:25 +0100 Subject: [PATCH 175/253] Support "do not test" label with ci.py (#58128) * support "do not test" label with ci.py Co-authored-by: robot-clickhouse --- tests/ci/ci.py | 17 ++++++++++++++++- tests/ci/ci_config.py | 26 +++++++++++++++++++++++++- tests/ci/run_check.py | 5 ----- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/tests/ci/ci.py b/tests/ci/ci.py index 949a7a7e5bf..701f66b9a6a 100644 --- a/tests/ci/ci.py +++ b/tests/ci/ci.py @@ -397,6 +397,21 @@ def _configure_jobs( else: jobs_to_skip += (job,) + if pr_labels: + jobs_requested_by_label = [] # type: List[str] + ci_controlling_labels = [] # type: List[str] + for label in pr_labels: + label_config = CI_CONFIG.get_label_config(label) + if label_config: + jobs_requested_by_label += label_config.run_jobs + ci_controlling_labels += [label] + if ci_controlling_labels: + print(f"NOTE: CI controlling labels are set: [{ci_controlling_labels}]") + print( + f" : following jobs will be executed: [{jobs_requested_by_label}]" + ) + jobs_to_do = jobs_requested_by_label + if commit_tokens: requested_jobs = [ token[len("#job_") :] @@ -416,7 +431,7 @@ def _configure_jobs( if parent in jobs_to_do and parent not in jobs_to_do_requested: jobs_to_do_requested.append(parent) print( - f"NOTE: Only specific job(s) were requested: [{jobs_to_do_requested}]" + f"NOTE: Only specific job(s) were requested by commit message tokens: [{jobs_to_do_requested}]" ) jobs_to_do = jobs_to_do_requested diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index d28e008ef50..ee9ada88a9d 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from enum import Enum import logging from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser @@ -8,6 +9,10 @@ from pathlib import Path from typing import Callable, Dict, Iterable, List, Literal, Optional, Union +class Labels(Enum): + DO_NOT_TEST_LABEL = "do not test" + + @dataclass class DigestConfig: # all files, dirs to include into digest, glob supported @@ -22,6 +27,15 @@ class DigestConfig: git_submodules: bool = False +@dataclass +class LabelConfig: + """ + class to configure different CI scenarious per GH label + """ + + run_jobs: Iterable[str] = frozenset() + + @dataclass class JobConfig: """ @@ -95,7 +109,7 @@ class TestConfig: BuildConfigs = Dict[str, BuildConfig] BuildsReportConfig = Dict[str, BuildReportConfig] TestConfigs = Dict[str, TestConfig] - +LabelConfigs = Dict[str, LabelConfig] # common digests configs compatibility_check_digest = DigestConfig( @@ -268,6 +282,13 @@ class CiConfig: builds_report_config: BuildsReportConfig test_configs: TestConfigs other_jobs_configs: TestConfigs + label_configs: LabelConfigs + + def get_label_config(self, label_name: str) -> Optional[LabelConfig]: + for label, config in self.label_configs.items(): + if label_name == label: + return config + return None def get_job_config(self, check_name: str) -> JobConfig: res = None @@ -417,6 +438,9 @@ class CiConfig: CI_CONFIG = CiConfig( + label_configs={ + Labels.DO_NOT_TEST_LABEL.value: LabelConfig(run_jobs=["Style check"]), + }, build_config={ "package_release": BuildConfig( name="package_release", diff --git a/tests/ci/run_check.py b/tests/ci/run_check.py index fb56a65fe0b..108aa7d1946 100644 --- a/tests/ci/run_check.py +++ b/tests/ci/run_check.py @@ -32,7 +32,6 @@ TRUSTED_ORG_IDS = { OK_SKIP_LABELS = {"release", "pr-backport", "pr-cherrypick"} CAN_BE_TESTED_LABEL = "can be tested" -DO_NOT_TEST_LABEL = "do not test" FEATURE_LABEL = "pr-feature" SUBMODULE_CHANGED_LABEL = "submodule changed" PR_CHECK = "PR Check" @@ -68,10 +67,6 @@ def should_run_ci_for_pr(pr_info: PRInfo) -> Tuple[bool, str]: print(f"Label '{FORCE_TESTS_LABEL}' set, forcing remaining checks") return True, f"Labeled '{FORCE_TESTS_LABEL}'" - if DO_NOT_TEST_LABEL in pr_info.labels: - print(f"Label '{DO_NOT_TEST_LABEL}' set, skipping remaining checks") - return False, f"Labeled '{DO_NOT_TEST_LABEL}'" - if OK_SKIP_LABELS.intersection(pr_info.labels): return True, "Don't try new checks for release/backports/cherry-picks" From b9f1039ac421875d95dc5ff6773cd0f6cc35e19e Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Thu, 21 Dec 2023 17:09:49 +0000 Subject: [PATCH 176/253] Fix log message --- src/Interpreters/InterpreterSelectQuery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 297344a2643..19deb16db7c 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -889,7 +889,7 @@ bool InterpreterSelectQuery::adjustParallelReplicasAfterAnalysis() context->setSetting("allow_experimental_parallel_reading_from_replicas", Field(0)); context->setSetting("max_parallel_replicas", UInt64{0}); - LOG_INFO(log, "Disabling parallel replicas to execute a query with IN with subquery"); + LOG_DEBUG(log, "Disabling parallel replicas to execute a query with IN with subquery"); return true; } From 2e522b9405d9b750dd4860790a98a33ba31f4875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Thu, 21 Dec 2023 19:18:57 +0100 Subject: [PATCH 177/253] Remove requestUnconditionalRetry It's confusing, only me used it and I used it wrong --- .../MergeTree/ReplicatedMergeTreeSink.cpp | 7 +------ src/Storages/MergeTree/ZooKeeperRetries.h | 17 +++-------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp index 5913904ae65..8b22c61e012 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeSink.cpp @@ -912,12 +912,8 @@ std::pair, bool> ReplicatedMergeTreeSinkImpl:: part->name, multi_code, MAX_AGE_OF_LOCAL_PART_THAT_WASNT_ADDED_TO_ZOOKEEPER); }); - /// Independently of how many retries we had left we want to do at least one check of this inner retry - /// so a) we try to verify at least once if metadata was written and b) we set the proper final error - /// (UNKNOWN_STATUS_OF_INSERT) if we fail to reconnect to keeper - new_retry_controller.requestUnconditionalRetry(); - bool node_exists = false; + /// The loop will be executed at least once new_retry_controller.retryLoop([&] { fiu_do_on(FailPoints::replicated_merge_tree_commit_zk_fail_when_recovering_from_hw_fault, { zookeeper->forceFailureBeforeOperation(); }); @@ -1076,7 +1072,6 @@ std::pair, bool> ReplicatedMergeTreeSinkImpl:: quorum_info.status_path = storage.zookeeper_path + "/quorum/parallel/" + retry_context.actual_part_name; ZooKeeperRetriesControl new_retry_controller = retries_ctl; - new_retry_controller.requestUnconditionalRetry(); new_retry_controller.actionAfterLastFailedRetry([&] { /// We do not know whether or not data has been inserted in other replicas diff --git a/src/Storages/MergeTree/ZooKeeperRetries.h b/src/Storages/MergeTree/ZooKeeperRetries.h index eff68a14f24..15874b8f675 100644 --- a/src/Storages/MergeTree/ZooKeeperRetries.h +++ b/src/Storages/MergeTree/ZooKeeperRetries.h @@ -61,6 +61,7 @@ public: /// then it can provide feedback to retries controller via user errors /// /// It is possible to use it multiple times (it will share nº of errors over the total amount of calls) + /// Each retryLoop is independent and it will execute f at least once void retryLoop(auto && f, auto && iteration_cleanup) { current_iteration = 0; @@ -68,6 +69,8 @@ public: while (current_iteration == 0 || canTry()) { + /// reset the flag, it will be set to false in case of error + iteration_succeeded = true; try { f(); @@ -175,8 +178,6 @@ public: void stopRetries() { stop_retries = true; } - void requestUnconditionalRetry() { unconditional_retry = true; } - bool isLastRetry() const { return total_failures >= retries_info.max_retries; } bool isRetry() const { return current_iteration > 1; } @@ -208,14 +209,6 @@ private: bool canTry() { - if (unconditional_retry) - { - unconditional_retry = false; - /// reset the flag, it will be set to false in case of error - iteration_succeeded = true; - return true; - } - if (iteration_succeeded) { if (logger && total_failures > 0) @@ -253,9 +246,6 @@ private: sleepForMilliseconds(current_backoff_ms); current_backoff_ms = std::min(current_backoff_ms * 2, retries_info.max_backoff_ms); - /// reset the flag, it will be set to false in case of error - iteration_succeeded = true; - return true; } @@ -308,7 +298,6 @@ private: UserError user_error; KeeperError keeper_error; std::function action_after_last_failed_retry = []() {}; - bool unconditional_retry = false; bool iteration_succeeded = true; bool stop_retries = false; QueryStatusPtr process_list_element; From aa5a6449f08583f9bcbe27cd7c5258026fe36871 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 15 Dec 2023 17:03:53 +0100 Subject: [PATCH 178/253] Fix system.stack_trace for threads with blocked SIGRTMIN Some third-party libraries (i.e. librdkafka) could block it, and in this case system.stack_trace will return stacktrace for the main process (usually, basically it could be any thread with non blocked signal). By replacing sigqueue() with more precise rt_tgsigqueueinfo(), other threads will not respond to the signal. Signed-off-by: Azat Khuzhin (cherry picked from commit 106042cf4162d261364d39cfa1ec85e940c93b5b) --- .../System/StorageSystemStackTrace.cpp | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Storages/System/StorageSystemStackTrace.cpp b/src/Storages/System/StorageSystemStackTrace.cpp index 1baaf5144e8..3361827a259 100644 --- a/src/Storages/System/StorageSystemStackTrace.cpp +++ b/src/Storages/System/StorageSystemStackTrace.cpp @@ -1,4 +1,4 @@ -#ifdef OS_LINUX /// Because of 'sigqueue' functions and RT signals. +#ifdef OS_LINUX /// Because of 'rt_tgsigqueueinfo' functions and RT signals. #include #include @@ -28,6 +28,12 @@ #include #include #include +#include + +int rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info) +{ + return static_cast(syscall(__NR_rt_tgsigqueueinfo, tgid, tid, sig, info)); +} namespace DB @@ -48,7 +54,7 @@ namespace { // Initialized in StorageSystemStackTrace's ctor and used in signalHandler. -std::atomic expected_pid; +std::atomic server_pid; const int sig = SIGRTMIN; std::atomic sequence_num = 0; /// For messages sent via pipe. @@ -80,7 +86,7 @@ void signalHandler(int, siginfo_t * info, void * context) /// In case malicious user is sending signals manually (for unknown reason). /// If we don't check - it may break our synchronization. - if (info->si_pid != expected_pid) + if (info->si_pid != server_pid) return; /// Signal received too late. @@ -287,20 +293,22 @@ protected: Stopwatch watch; SCOPE_EXIT({ signals_sent_ms += watch.elapsedMilliseconds(); }); - sigval sig_value{}; + siginfo_t sig_info{}; + sig_info.si_code = SI_QUEUE; /// sigqueue() + sig_info.si_pid = server_pid; + sig_info.si_value.sival_int = sequence_num.load(std::memory_order_acquire); - sig_value.sival_int = sequence_num.load(std::memory_order_acquire); - if (0 != ::sigqueue(static_cast(tid), sig, sig_value)) + if (0 != ::rt_tgsigqueueinfo(server_pid, static_cast(tid), sig, &sig_info)) { /// The thread may has been already finished. if (ESRCH == errno) continue; - throw ErrnoException(ErrorCodes::CANNOT_SIGQUEUE, "Cannot send signal with sigqueue"); + throw ErrnoException(ErrorCodes::CANNOT_SIGQUEUE, "Cannot queue a signal"); } /// Just in case we will wait for pipe with timeout. In case signal didn't get processed. - if (wait(pipe_read_timeout_ms) && sig_value.sival_int == data_ready_num.load(std::memory_order_acquire)) + if (wait(pipe_read_timeout_ms) && sig_info.si_value.sival_int == data_ready_num.load(std::memory_order_acquire)) { size_t stack_trace_size = stack_trace.getSize(); size_t stack_trace_offset = stack_trace.getOffset(); @@ -317,7 +325,7 @@ protected: } else { - LOG_DEBUG(log, "Cannot obtain a stack trace for thread {}", tid); + LOG_DEBUG(log, "Cannot obtain a stack trace for thread {} ({})", tid, thread_name); res_columns[res_index++]->insert(thread_name); res_columns[res_index++]->insert(tid); @@ -396,7 +404,7 @@ StorageSystemStackTrace::StorageSystemStackTrace(const StorageID & table_id_) notification_pipe.open(); /// Setup signal handler. - expected_pid = getpid(); + server_pid = getpid(); struct sigaction sa{}; sa.sa_sigaction = signalHandler; sa.sa_flags = SA_SIGINFO; From 6e0e4d154dba78e009ada751d7ecd753c2f1db61 Mon Sep 17 00:00:00 2001 From: Thom O'Connor Date: Thu, 21 Dec 2023 12:01:00 -0700 Subject: [PATCH 179/253] Update from.md Updated the SELECT...FROM...FINAL documentation to add better guidance to the use of FINAL, rather than saying "avoid using it" --- docs/en/sql-reference/statements/select/from.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/sql-reference/statements/select/from.md b/docs/en/sql-reference/statements/select/from.md index a4f449ad321..9b29e61448f 100644 --- a/docs/en/sql-reference/statements/select/from.md +++ b/docs/en/sql-reference/statements/select/from.md @@ -34,7 +34,7 @@ Queries that use `FINAL` are executed slightly slower than similar queries that - Data is merged during query execution. - Queries with `FINAL` read primary key columns in addition to the columns specified in the query. -**In most cases, avoid using `FINAL`.** The common approach is to use different queries that assume the background processes of the `MergeTree` engine haven’t happened yet and deal with it by applying aggregation (for example, to discard duplicates). +`FINAL` requires additional compute and memory resources, as the processing that normally would occur at merge time must occur in memory at the time of the query. However, using FINAL is sometimes necessary in order to produce accurate results, and is less expensive than running `OPTIMZIE` to force a merge. It is also sometimes possible to use different queries that assume the background processes of the `MergeTree` engine haven’t happened yet and deal with it by applying aggregation (for example, to discard duplicates). If you need to use FINAL in your queries in order to get the required results, then it is okay to do so but be aware of the additional processing required. `FINAL` can be applied automatically using [FINAL](../../../operations/settings/settings.md#final) setting to all tables in a query using a session or a user profile. From 8774f8097f3977d9136dbb8a3ceb3f4f40fe0d5d Mon Sep 17 00:00:00 2001 From: Max K Date: Thu, 21 Dec 2023 21:30:40 +0100 Subject: [PATCH 180/253] Run build report check job on build failures, fix (#58135) * run build report check on build failures #no-merge-commit * Always run post action in reusable_build #no-merge-commit --- .github/workflows/backport_branches.yml | 6 ++++-- .github/workflows/master.yml | 6 ++++-- .github/workflows/pull_request.yml | 6 ++++-- .github/workflows/release_branches.yml | 6 ++++-- .github/workflows/reusable_build.yml | 2 ++ 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/workflows/backport_branches.yml b/.github/workflows/backport_branches.yml index c3e74390646..ef554a1b0ff 100644 --- a/.github/workflows/backport_branches.yml +++ b/.github/workflows/backport_branches.yml @@ -157,7 +157,8 @@ jobs: ##################################### BUILD REPORTER ####################################### ############################################################################################ BuilderReport: - if: ${{ !failure() && !cancelled() }} + # run report check for failed builds to indicate the CI error + if: ${{ !cancelled() }} needs: - RunConfig - BuilderDebAarch64 @@ -177,7 +178,8 @@ jobs: run_command: | python3 build_report_check.py "$CHECK_NAME" BuilderSpecialReport: - if: ${{ !failure() && !cancelled() }} + # run report check for failed builds to indicate the CI error + if: ${{ !cancelled() }} needs: - RunConfig - BuilderBinDarwin diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 432a9df5369..d2865eb737d 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -262,6 +262,8 @@ jobs: ##################################### BUILD REPORTER ####################################### ############################################################################################ BuilderReport: + # run report check for failed builds to indicate the CI error + if: ${{ !cancelled() }} needs: - RunConfig - BuilderBinRelease @@ -272,7 +274,6 @@ jobs: - BuilderDebRelease - BuilderDebTsan - BuilderDebUBsan - if: ${{ !failure() && !cancelled() }} uses: ./.github/workflows/reusable_test.yml with: test_name: ClickHouse build check @@ -285,7 +286,8 @@ jobs: run_command: | python3 build_report_check.py "$CHECK_NAME" BuilderSpecialReport: - if: ${{ !failure() && !cancelled() }} + # run report check for failed builds to indicate the CI error + if: ${{ !cancelled() }} needs: - RunConfig - BuilderBinAarch64 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 08a4ab99520..bd2b2b60904 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -291,6 +291,8 @@ jobs: ##################################### BUILD REPORTER ####################################### ############################################################################################ BuilderReport: + # run report check for failed builds to indicate the CI error + if: ${{ !cancelled() }} needs: - RunConfig - BuilderBinRelease @@ -301,7 +303,6 @@ jobs: - BuilderDebRelease - BuilderDebTsan - BuilderDebUBsan - if: ${{ !failure() && !cancelled() }} uses: ./.github/workflows/reusable_test.yml with: test_name: ClickHouse build check @@ -314,7 +315,8 @@ jobs: run_command: | python3 build_report_check.py "$CHECK_NAME" BuilderSpecialReport: - if: ${{ !failure() && !cancelled() }} + # run report check for failed builds to indicate the CI error + if: ${{ !cancelled() }} needs: - RunConfig - BuilderBinAarch64 diff --git a/.github/workflows/release_branches.yml b/.github/workflows/release_branches.yml index fa8e93369b3..69229ef75df 100644 --- a/.github/workflows/release_branches.yml +++ b/.github/workflows/release_branches.yml @@ -172,6 +172,8 @@ jobs: ##################################### BUILD REPORTER ####################################### ############################################################################################ BuilderReport: + # run report check for failed builds to indicate the CI error + if: ${{ !cancelled() }} needs: - RunConfig - BuilderDebRelease @@ -181,7 +183,6 @@ jobs: - BuilderDebUBsan - BuilderDebMsan - BuilderDebDebug - if: ${{ !failure() && !cancelled() }} uses: ./.github/workflows/reusable_test.yml with: test_name: ClickHouse build check @@ -194,7 +195,8 @@ jobs: run_command: | python3 build_report_check.py "$CHECK_NAME" BuilderSpecialReport: - if: ${{ !failure() && !cancelled() }} + # run report check for failed builds to indicate the CI error + if: ${{ !cancelled() }} needs: - RunConfig - BuilderDebRelease diff --git a/.github/workflows/reusable_build.yml b/.github/workflows/reusable_build.yml index b1bc64c1f69..e6aa04a3569 100644 --- a/.github/workflows/reusable_build.yml +++ b/.github/workflows/reusable_build.yml @@ -76,6 +76,8 @@ jobs: run: | python3 "$GITHUB_WORKSPACE/tests/ci/build_check.py" "$BUILD_NAME" - name: Post + # it still be build report to upload for failed build job + if: always() run: | python3 "$GITHUB_WORKSPACE/tests/ci/ci.py" --infile ${{ toJson(inputs.data) }} --post --job-name '${{inputs.build_name}}' - name: Mark as done From c1705bc0c70d8b03eeca4fd91cf62e86a63cb41a Mon Sep 17 00:00:00 2001 From: zvonand Date: Thu, 21 Dec 2023 22:30:51 +0100 Subject: [PATCH 181/253] fix accurateCastOrNull --- src/Functions/FunctionsConversion.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index bef1e7b420a..5a06c363065 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -1413,10 +1413,10 @@ inline bool tryParseImpl(DataTypeDate32::FieldType & x, ReadBuff template <> inline bool tryParseImpl(DataTypeDateTime::FieldType & x, ReadBuffer & rb, const DateLUTImpl * time_zone, bool) { - time_t tmp = 0; - if (!tryReadDateTimeText(tmp, rb, *time_zone)) + time_t time = 0; + if (!tryReadDateTimeText(time, rb, *time_zone)) return false; - x = static_cast(tmp); + convertFromTime(x, time); return true; } @@ -1697,7 +1697,6 @@ struct ConvertThroughParsing break; } } - parseImpl(vec_to[i], read_buffer, local_time_zone, precise_float_parsing); } while (false); } @@ -3291,7 +3290,6 @@ private: { /// In case when converting to Nullable type, we apply different parsing rule, /// that will not throw an exception but return NULL in case of malformed input. - FunctionPtr function = FunctionConvertFromString::create(); return createFunctionAdaptor(function, from_type); } From a634fdf89fcc32ee98e4583f98b5d752dabdb42d Mon Sep 17 00:00:00 2001 From: zvonand Date: Thu, 21 Dec 2023 22:38:50 +0100 Subject: [PATCH 182/253] add test --- .../0_stateless/01556_accurate_cast_or_null.reference | 2 ++ tests/queries/0_stateless/01556_accurate_cast_or_null.sql | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01556_accurate_cast_or_null.reference b/tests/queries/0_stateless/01556_accurate_cast_or_null.reference index a2ccd5af868..5187a19cc72 100644 --- a/tests/queries/0_stateless/01556_accurate_cast_or_null.reference +++ b/tests/queries/0_stateless/01556_accurate_cast_or_null.reference @@ -36,6 +36,8 @@ 2023-05-30 14:38:20 1970-01-01 00:00:19 1970-01-01 19:26:40 +1970-01-01 00:00:00 +2106-02-07 06:28:15 \N \N \N diff --git a/tests/queries/0_stateless/01556_accurate_cast_or_null.sql b/tests/queries/0_stateless/01556_accurate_cast_or_null.sql index 2fb7b1177e6..15ac71dea93 100644 --- a/tests/queries/0_stateless/01556_accurate_cast_or_null.sql +++ b/tests/queries/0_stateless/01556_accurate_cast_or_null.sql @@ -39,9 +39,12 @@ SELECT accurateCastOrNull(number + 127, 'Int8') AS x FROM numbers (2) ORDER BY x SELECT accurateCastOrNull(-1, 'DateTime'); SELECT accurateCastOrNull(5000000000, 'DateTime'); SELECT accurateCastOrNull('1xxx', 'DateTime'); -select toString(accurateCastOrNull('2023-05-30 14:38:20', 'DateTime'), timezone()); +SELECT toString(accurateCastOrNull('2023-05-30 14:38:20', 'DateTime'), timezone()); SELECT toString(accurateCastOrNull(19, 'DateTime'), 'UTC'); SELECT toString(accurateCastOrNull(70000, 'DateTime'), 'UTC'); +-- need fixed timezone in these two lines +SELECT toString(accurateCastOrNull('1965-05-30 14:38:20', 'DateTime'), timezone()) SETTINGS session_timezone = 'UTC'; +SELECT toString(accurateCastOrNull('2223-05-30 14:38:20', 'DateTime'), timezone()) SETTINGS session_timezone = 'UTC'; SELECT accurateCastOrNull(-1, 'Date'); SELECT accurateCastOrNull(5000000000, 'Date'); From 5d8e5ee5d27af58dbb35c2c838f9bc6a2b9d65ad Mon Sep 17 00:00:00 2001 From: Christoph Wurm Date: Fri, 22 Dec 2023 11:40:46 +0000 Subject: [PATCH 183/253] Update insert-into.md: Clarify position of SETTINGS clause I've recently stumbled several times trying to figure out where to put the `SETTINGS` when inserting `VALUES` and `FROM INFILE`, so I'm clarifying it here in the docs. --- docs/en/sql-reference/statements/insert-into.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/sql-reference/statements/insert-into.md b/docs/en/sql-reference/statements/insert-into.md index e0cc98c2351..f9d93305071 100644 --- a/docs/en/sql-reference/statements/insert-into.md +++ b/docs/en/sql-reference/statements/insert-into.md @@ -11,7 +11,7 @@ Inserts data into a table. **Syntax** ``` sql -INSERT INTO [TABLE] [db.]table [(c1, c2, c3)] VALUES (v11, v12, v13), (v21, v22, v23), ... +INSERT INTO [TABLE] [db.]table [(c1, c2, c3)] [SETTINGS ...] VALUES (v11, v12, v13), (v21, v22, v23), ... ``` You can specify a list of columns to insert using the `(c1, c2, c3)`. You can also use an expression with column [matcher](../../sql-reference/statements/select/index.md#asterisk) such as `*` and/or [modifiers](../../sql-reference/statements/select/index.md#select-modifiers) such as [APPLY](../../sql-reference/statements/select/index.md#apply-modifier), [EXCEPT](../../sql-reference/statements/select/index.md#except-modifier), [REPLACE](../../sql-reference/statements/select/index.md#replace-modifier). @@ -126,7 +126,7 @@ To insert a default value instead of `NULL` into a column with not nullable data **Syntax** ``` sql -INSERT INTO [TABLE] [db.]table [(c1, c2, c3)] FROM INFILE file_name [COMPRESSION type] FORMAT format_name +INSERT INTO [TABLE] [db.]table [(c1, c2, c3)] FROM INFILE file_name [COMPRESSION type] [SETTINGS ...] [FORMAT format_name] ``` Use the syntax above to insert data from a file, or files, stored on the **client** side. `file_name` and `type` are string literals. Input file [format](../../interfaces/formats.md) must be set in the `FORMAT` clause. From d29762f19f13b14dd2002aa4ee5f21ffe546f752 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 22 Dec 2023 12:23:07 +0100 Subject: [PATCH 184/253] Do not send signals to threads that blocks SIGRTMIN for system.stack_trace That way we can avoid superior timeouts during reading from system.stack_trace. Two known cases of such threads are: - rdk: -- librdkafka threads - iou-wrk -- io_uring threads Signed-off-by: Azat Khuzhin --- .../System/StorageSystemStackTrace.cpp | 145 ++++++++++++------ 1 file changed, 94 insertions(+), 51 deletions(-) diff --git a/src/Storages/System/StorageSystemStackTrace.cpp b/src/Storages/System/StorageSystemStackTrace.cpp index 3361827a259..a3b953aeea1 100644 --- a/src/Storages/System/StorageSystemStackTrace.cpp +++ b/src/Storages/System/StorageSystemStackTrace.cpp @@ -30,12 +30,6 @@ #include #include -int rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info) -{ - return static_cast(syscall(__NR_rt_tgsigqueueinfo, tgid, tid, sig, info)); -} - - namespace DB { @@ -55,7 +49,7 @@ namespace // Initialized in StorageSystemStackTrace's ctor and used in signalHandler. std::atomic server_pid; -const int sig = SIGRTMIN; +const int STACK_TRACE_SERVICE_SIGNAL = SIGRTMIN; std::atomic sequence_num = 0; /// For messages sent via pipe. std::atomic data_ready_num = 0; @@ -79,6 +73,11 @@ size_t query_id_size = 0; LazyPipeFDs notification_pipe; +int rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info) +{ + return static_cast(syscall(__NR_rt_tgsigqueueinfo, tgid, tid, sig, info)); +} + void signalHandler(int, siginfo_t * info, void * context) { DENY_ALLOCATIONS_IN_SCOPE; @@ -220,6 +219,39 @@ ThreadIdToName getFilteredThreadNames(ASTPtr query, ContextPtr context, const Pa return tid_to_name; } +bool parseHexNumber(std::string_view sv, UInt64 & res) +{ + errno = 0; /// Functions strto* don't clear errno. + char * pos_integer = const_cast(sv.begin()); + res = std::strtoull(sv.begin(), &pos_integer, 16); + return (pos_integer == sv.begin() + sv.size() && errno != ERANGE); +} +bool isSignalBlocked(UInt64 tid, int signal) +{ + String buffer; + + ReadBufferFromFile status(fmt::format("/proc/{}/status", tid)); + while (!status.eof()) + { + readEscapedStringUntilEOL(buffer, status); + if (!status.eof()) + ++status.position(); + if (buffer.starts_with("SigBlk:")) + break; + } + status.close(); + + std::string_view line(buffer); + line = line.substr(strlen("SigBlk:")); + line = line.substr(0, line.rend() - std::find_if_not(line.rbegin(), line.rend(), ::isspace)); + + UInt64 sig_blk; + if (parseHexNumber(line, sig_blk)) + return sig_blk & signal; + else + return false; +} + /// Send a signal to every thread and wait for result. /// We must wait for every thread one by one sequentially, /// because there is a limit on number of queued signals in OS and otherwise signals may get lost. @@ -238,6 +270,7 @@ public: , proc_it("/proc/self/task") /// It shouldn't be possible to do concurrent reads from this table. , lock(mutex) + , signal_str(strsignal(STACK_TRACE_SERVICE_SIGNAL)) /// NOLINT(concurrency-mt-unsafe) // not thread-safe but ok in this context { /// Create a mask of what columns are needed in the result. NameSet names_set(column_names.begin(), column_names.end()); @@ -289,55 +322,64 @@ protected: } else { - ++signals_sent; - Stopwatch watch; - SCOPE_EXIT({ signals_sent_ms += watch.elapsedMilliseconds(); }); - - siginfo_t sig_info{}; - sig_info.si_code = SI_QUEUE; /// sigqueue() - sig_info.si_pid = server_pid; - sig_info.si_value.sival_int = sequence_num.load(std::memory_order_acquire); - - if (0 != ::rt_tgsigqueueinfo(server_pid, static_cast(tid), sig, &sig_info)) + bool signal_blocked = isSignalBlocked(tid, STACK_TRACE_SERVICE_SIGNAL); + if (!signal_blocked) { - /// The thread may has been already finished. - if (ESRCH == errno) + ++signals_sent; + Stopwatch watch; + SCOPE_EXIT({ + signals_sent_ms += watch.elapsedMilliseconds(); + + /// Signed integer overflow is undefined behavior in both C and C++. However, according to + /// C++ standard, Atomic signed integer arithmetic is defined to use two's complement; there + /// are no undefined results. See https://en.cppreference.com/w/cpp/atomic/atomic and + /// http://eel.is/c++draft/atomics.types.generic#atomics.types.int-8 + ++sequence_num; + }); + + siginfo_t sig_info{}; + sig_info.si_code = SI_QUEUE; /// sigqueue() + sig_info.si_pid = server_pid; + sig_info.si_value.sival_int = sequence_num.load(std::memory_order_acquire); + + if (0 != rt_tgsigqueueinfo(server_pid, static_cast(tid), STACK_TRACE_SERVICE_SIGNAL, &sig_info)) + { + /// The thread may has been already finished. + if (ESRCH == errno) + continue; + + throw ErrnoException(ErrorCodes::CANNOT_SIGQUEUE, "Cannot queue a signal"); + } + + /// Just in case we will wait for pipe with timeout. In case signal didn't get processed. + if (wait(pipe_read_timeout_ms) && sig_info.si_value.sival_int == data_ready_num.load(std::memory_order_acquire)) + { + size_t stack_trace_size = stack_trace.getSize(); + size_t stack_trace_offset = stack_trace.getOffset(); + + Array arr; + arr.reserve(stack_trace_size - stack_trace_offset); + for (size_t i = stack_trace_offset; i < stack_trace_size; ++i) + arr.emplace_back(reinterpret_cast(stack_trace.getFramePointers()[i])); + + res_columns[res_index++]->insert(thread_name); + res_columns[res_index++]->insert(tid); + res_columns[res_index++]->insertData(query_id_data, query_id_size); + res_columns[res_index++]->insert(arr); + continue; - - throw ErrnoException(ErrorCodes::CANNOT_SIGQUEUE, "Cannot queue a signal"); + } } - /// Just in case we will wait for pipe with timeout. In case signal didn't get processed. - if (wait(pipe_read_timeout_ms) && sig_info.si_value.sival_int == data_ready_num.load(std::memory_order_acquire)) - { - size_t stack_trace_size = stack_trace.getSize(); - size_t stack_trace_offset = stack_trace.getOffset(); - - Array arr; - arr.reserve(stack_trace_size - stack_trace_offset); - for (size_t i = stack_trace_offset; i < stack_trace_size; ++i) - arr.emplace_back(reinterpret_cast(stack_trace.getFramePointers()[i])); - - res_columns[res_index++]->insert(thread_name); - res_columns[res_index++]->insert(tid); - res_columns[res_index++]->insertData(query_id_data, query_id_size); - res_columns[res_index++]->insert(arr); - } + if (signal_blocked) + LOG_DEBUG(log, "Thread {} ({}) blocks SIG{} signal", tid, thread_name, signal_str); else - { LOG_DEBUG(log, "Cannot obtain a stack trace for thread {} ({})", tid, thread_name); - res_columns[res_index++]->insert(thread_name); - res_columns[res_index++]->insert(tid); - res_columns[res_index++]->insertDefault(); - res_columns[res_index++]->insertDefault(); - } - - /// Signed integer overflow is undefined behavior in both C and C++. However, according to - /// C++ standard, Atomic signed integer arithmetic is defined to use two's complement; there - /// are no undefined results. See https://en.cppreference.com/w/cpp/atomic/atomic and - /// http://eel.is/c++draft/atomics.types.generic#atomics.types.int-8 - ++sequence_num; + res_columns[res_index++]->insert(thread_name); + res_columns[res_index++]->insert(tid); + res_columns[res_index++]->insertDefault(); + res_columns[res_index++]->insertDefault(); } } LOG_TRACE(log, "Send signal to {} threads (total), took {} ms", signals_sent, signals_sent_ms); @@ -366,6 +408,7 @@ private: size_t signals_sent_ms = 0; std::unique_lock lock; + const char * signal_str; ColumnPtr getFilteredThreadIds() { @@ -412,10 +455,10 @@ StorageSystemStackTrace::StorageSystemStackTrace(const StorageID & table_id_) if (sigemptyset(&sa.sa_mask)) throw ErrnoException(ErrorCodes::CANNOT_MANIPULATE_SIGSET, "Cannot set signal handler"); - if (sigaddset(&sa.sa_mask, sig)) + if (sigaddset(&sa.sa_mask, STACK_TRACE_SERVICE_SIGNAL)) throw ErrnoException(ErrorCodes::CANNOT_MANIPULATE_SIGSET, "Cannot set signal handler"); - if (sigaction(sig, &sa, nullptr)) + if (sigaction(STACK_TRACE_SERVICE_SIGNAL, &sa, nullptr)) throw ErrnoException(ErrorCodes::CANNOT_SET_SIGNAL_HANDLER, "Cannot set signal handler"); } From 823abaa73e20bbdf02633956cf19099f33c3b75a Mon Sep 17 00:00:00 2001 From: yariks5s Date: Fri, 22 Dec 2023 13:27:03 +0000 Subject: [PATCH 185/253] init --- src/IO/S3/Client.cpp | 3 +++ src/IO/S3/Requests.h | 5 +++++ src/IO/S3/URI.cpp | 6 ++++++ src/IO/S3/URI.h | 1 + 4 files changed, 15 insertions(+) diff --git a/src/IO/S3/Client.cpp b/src/IO/S3/Client.cpp index 7658ea5941c..e22b7734cef 100644 --- a/src/IO/S3/Client.cpp +++ b/src/IO/S3/Client.cpp @@ -573,6 +573,9 @@ Client::doRequest(RequestType & request, RequestFn request_fn) const auto new_uri = getURIFromError(error); if (!new_uri) return result; + + if (initial_endpoint.substr(11) == "amazonaws.com") // Check if user didn't mention any region + new_uri->addRegionToURI(request.getRegion()); const auto & current_uri_override = request.getURIOverride(); /// we already tried with this URI diff --git a/src/IO/S3/Requests.h b/src/IO/S3/Requests.h index eae45491fe6..3db82b7252d 100644 --- a/src/IO/S3/Requests.h +++ b/src/IO/S3/Requests.h @@ -58,6 +58,11 @@ public: return BaseRequest::GetChecksumAlgorithmName(); } + std::string getRegion() const + { + return region_override; + } + void overrideRegion(std::string region) const { region_override = std::move(region); diff --git a/src/IO/S3/URI.cpp b/src/IO/S3/URI.cpp index e05e0882329..b2f80869972 100644 --- a/src/IO/S3/URI.cpp +++ b/src/IO/S3/URI.cpp @@ -146,6 +146,12 @@ URI::URI(const std::string & uri_) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Bucket or key name are invalid in S3 URI."); } +void URI::addRegionToURI(const std::string ®ion) +{ + auto pos = endpoint.find("amazonaws.com"); + endpoint = endpoint.substr(0, pos) + region + "." + endpoint.substr(pos); +} + void URI::validateBucket(const String & bucket, const Poco::URI & uri) { /// S3 specification requires at least 3 and at most 63 characters in bucket name. diff --git a/src/IO/S3/URI.h b/src/IO/S3/URI.h index f8f40cf9108..2873728bc78 100644 --- a/src/IO/S3/URI.h +++ b/src/IO/S3/URI.h @@ -32,6 +32,7 @@ struct URI URI() = default; explicit URI(const std::string & uri_); + void addRegionToURI(const std::string & region); static void validateBucket(const std::string & bucket, const Poco::URI & uri); }; From ab6cc0c9f0e85c6c44f14f5113db458c672e8d3b Mon Sep 17 00:00:00 2001 From: Yarik Briukhovetskyi <114298166+yariks5s@users.noreply.github.com> Date: Fri, 22 Dec 2023 14:59:21 +0100 Subject: [PATCH 186/253] Update Client.cpp --- src/IO/S3/Client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IO/S3/Client.cpp b/src/IO/S3/Client.cpp index e22b7734cef..102f7371910 100644 --- a/src/IO/S3/Client.cpp +++ b/src/IO/S3/Client.cpp @@ -573,7 +573,7 @@ Client::doRequest(RequestType & request, RequestFn request_fn) const auto new_uri = getURIFromError(error); if (!new_uri) return result; - + if (initial_endpoint.substr(11) == "amazonaws.com") // Check if user didn't mention any region new_uri->addRegionToURI(request.getRegion()); From 8693e1535a1c2c5d777d4ab51afe5f4ff5393860 Mon Sep 17 00:00:00 2001 From: serxa Date: Fri, 22 Dec 2023 14:39:44 +0000 Subject: [PATCH 187/253] Fix dashboard legend sorting and rows number --- programs/server/dashboard.html | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/programs/server/dashboard.html b/programs/server/dashboard.html index 79ca983ae7f..04fdfb2d3ca 100644 --- a/programs/server/dashboard.html +++ b/programs/server/dashboard.html @@ -965,12 +965,10 @@ document.getElementById('mass-editor-textarea').addEventListener('input', e => { function legendAsTooltipPlugin({ className, style = { background: "var(--legend-background)" } } = {}) { let legendEl; - let showTop = false; - const showLimit = 5; + let multiline; function init(u, opts) { legendEl = u.root.querySelector(".u-legend"); - legendEl.classList.remove("u-inline"); className && legendEl.classList.add(className); @@ -986,18 +984,19 @@ function legendAsTooltipPlugin({ className, style = { background: "var(--legend- ...style }); + const nodes = legendEl.querySelectorAll("th"); + for (let i = 0; i < nodes.length; i++) + nodes[i]._order = i; + if (opts.series.length == 2) { - const nodes = legendEl.querySelectorAll("th"); + multiline = false; for (let i = 0; i < nodes.length; i++) nodes[i].style.display = "none"; } else { + multiline = true; legendEl.querySelector("th").remove(); legendEl.querySelector("td").setAttribute('colspan', '2'); legendEl.querySelector("td").style.textAlign = 'center'; - } - - if (opts.series.length - 1 > showLimit) { - showTop = true; let footer = legendEl.insertRow().insertCell(); footer.setAttribute('colspan', '2'); footer.style.textAlign = 'center'; @@ -1024,18 +1023,20 @@ function legendAsTooltipPlugin({ className, style = { background: "var(--legend- left -= legendEl.clientWidth / 2; top -= legendEl.clientHeight / 2; legendEl.style.transform = "translate(" + left + "px, " + top + "px)"; - if (showTop) { + + if (multiline) { let nodes = nodeListToArray(legendEl.querySelectorAll("tr")); let header = nodes.shift(); let footer = nodes.pop(); - nodes.forEach(function (node) { node._sort_key = +node.querySelector("td").textContent; }); - nodes.sort((a, b) => +b._sort_key - +a._sort_key); + let showLimit = Math.floor(u.height / 30); + nodes.forEach(function (node) { node._sort_key = nodes.length > showLimit ? +node.querySelector("td").textContent.replace(/,/g,'') : node._order; }); + nodes.sort((a, b) => b._sort_key - a._sort_key); nodes.forEach(function (node) { node.parentNode.appendChild(node); }); for (let i = 0; i < nodes.length; i++) { nodes[i].style.display = i < showLimit ? null : "none"; - delete nodes[i]._sort_key; } footer.parentNode.appendChild(footer); + footer.style.display = nodes.length > showLimit ? null : "none"; } } From a54dfabf0e6a93789b1ac6db94abb5234de9c124 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Tue, 19 Dec 2023 12:51:00 +0100 Subject: [PATCH 188/253] Add full repo name to the PR cache name --- tests/ci/github_helper.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/ci/github_helper.py b/tests/ci/github_helper.py index 15ee7dc9620..0addafb7fbe 100644 --- a/tests/ci/github_helper.py +++ b/tests/ci/github_helper.py @@ -1,9 +1,10 @@ #!/usr/bin/env python """Helper for GitHub API requests""" import logging +import re from datetime import date, datetime, timedelta -from pathlib import Path from os import path as p +from pathlib import Path from time import sleep from typing import List, Optional, Tuple, Union @@ -143,7 +144,9 @@ class GitHub(github.Github): def get_pull_cached( self, repo: Repository, number: int, obj_updated_at: Optional[datetime] = None ) -> PullRequest: - cache_file = self.cache_path / f"pr-{number}.pickle" + # clean any special symbol from the repo name, especially '/' + repo_name = re.sub(r"\W", "_", repo.full_name) + cache_file = self.cache_path / f"pr-{repo_name}-{number}.pickle" if cache_file.is_file(): is_updated, cached_pr = self._is_cache_updated(cache_file, obj_updated_at) From 6f68696c14f26dc2124e658b6cc8a48d2ca32217 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Fri, 22 Dec 2023 17:26:31 +0000 Subject: [PATCH 189/253] Fix: for joins with old analyzer --- src/Interpreters/ActionsVisitor.cpp | 6 ++- src/Interpreters/InterpreterSelectQuery.cpp | 44 ++++++++++++++----- src/Interpreters/PreparedSets.cpp | 23 ++++++++-- src/Interpreters/PreparedSets.h | 12 +++-- .../02731_parallel_replicas_join_subquery.sql | 4 ++ 5 files changed, 69 insertions(+), 20 deletions(-) diff --git a/src/Interpreters/ActionsVisitor.cpp b/src/Interpreters/ActionsVisitor.cpp index 6be9f6c803f..6ffe04d0c95 100644 --- a/src/Interpreters/ActionsVisitor.cpp +++ b/src/Interpreters/ActionsVisitor.cpp @@ -1414,7 +1414,10 @@ FutureSetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool set_key = right_in_operand->getTreeHash(/*ignore_aliases=*/ true); if (auto set = data.prepared_sets->findSubquery(set_key)) + { + set->markAsINSubquery(); return set; + } FutureSetPtr external_table_set; @@ -1460,7 +1463,8 @@ FutureSetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool interpreter->buildQueryPlan(*source); } - return data.prepared_sets->addFromSubquery(set_key, std::move(source), nullptr, std::move(external_table_set), data.getContext()->getSettingsRef()); + return data.prepared_sets->addFromSubquery( + set_key, std::move(source), nullptr, std::move(external_table_set), data.getContext()->getSettingsRef(), true); } else { diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 19deb16db7c..42a0d60fc8a 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -870,7 +870,38 @@ bool InterpreterSelectQuery::adjustParallelReplicasAfterAnalysis() ASTSelectQuery & query = getSelectQuery(); /// While only_analyze we don't know anything about parts, so any decision about how many parallel replicas to use would be wrong - if (!storage || options.only_analyze || !context->canUseParallelReplicasOnInitiator()) + if (!storage || !context->canUseParallelReplicasOnInitiator()) + return false; + + if (query_analyzer->getPreparedSets()->hasSubqueries()) + { + bool in_subqueries = false; + const auto & sets = query_analyzer->getPreparedSets(); + const auto subqueries = sets->getSubqueries(); + for(const auto & subquery : subqueries) + { + if (subquery->isINSubquery()) + { + in_subqueries = true; + break; + } + } + + // LOG_DEBUG(log, "Prepared sets: subqueries={} in_subqueries={}\n{}", subqueries.size(), in_subqueries, StackTrace().toString()); + + if (in_subqueries) + { + if (settings.allow_experimental_parallel_reading_from_replicas == 2) + throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "IN with subquery is not supported with parallel replicas"); + + context->setSetting("allow_experimental_parallel_reading_from_replicas", Field(0)); + context->setSetting("max_parallel_replicas", UInt64{0}); + LOG_DEBUG(log, "Disabling parallel replicas to execute a query with IN with subquery"); + return true; + } + } + + if (options.only_analyze) return false; if (getTrivialCount(0).has_value()) @@ -882,17 +913,6 @@ bool InterpreterSelectQuery::adjustParallelReplicasAfterAnalysis() return true; } - if (query_analyzer->getPreparedSets()->hasSubqueries()) - { - if (settings.allow_experimental_parallel_reading_from_replicas == 2) - throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "IN with subquery is not supported with parallel replicas"); - - context->setSetting("allow_experimental_parallel_reading_from_replicas", Field(0)); - context->setSetting("max_parallel_replicas", UInt64{0}); - LOG_DEBUG(log, "Disabling parallel replicas to execute a query with IN with subquery"); - return true; - } - auto storage_merge_tree = std::dynamic_pointer_cast(storage); if (!storage_merge_tree || settings.parallel_replicas_min_number_of_rows_per_replica == 0) return false; diff --git a/src/Interpreters/PreparedSets.cpp b/src/Interpreters/PreparedSets.cpp index 7468bd1d519..8e7c493d6a5 100644 --- a/src/Interpreters/PreparedSets.cpp +++ b/src/Interpreters/PreparedSets.cpp @@ -98,10 +98,12 @@ FutureSetFromSubquery::FutureSetFromSubquery( std::unique_ptr source_, StoragePtr external_table_, FutureSetPtr external_table_set_, - const Settings & settings) + const Settings & settings, + bool in_subquery_) : external_table(std::move(external_table_)) , external_table_set(std::move(external_table_set_)) , source(std::move(source_)) + , in_subquery(in_subquery_) { set_and_key = std::make_shared(); set_and_key->key = std::move(key); @@ -261,14 +263,18 @@ FutureSetPtr PreparedSets::addFromSubquery( std::unique_ptr source, StoragePtr external_table, FutureSetPtr external_table_set, - const Settings & settings) + const Settings & settings, + bool in_subquery) { + // LOG_DEBUG(&Poco::Logger::get(__PRETTY_FUNCTION__), "in_subquery={}\n{}", in_subquery, StackTrace().toString()); + auto from_subquery = std::make_shared( toString(key, {}), std::move(source), std::move(external_table), std::move(external_table_set), - settings); + settings, + in_subquery); auto [it, inserted] = sets_from_subqueries.emplace(key, from_subquery); @@ -318,6 +324,15 @@ std::shared_ptr PreparedSets::findSubquery(const Hash & k return it->second; } +void PreparedSets::markAsINSubquery(const Hash & key) +{ + auto it = sets_from_subqueries.find(key); + if (it == sets_from_subqueries.end()) + return; + + it->second->markAsINSubquery(); +} + std::shared_ptr PreparedSets::findStorage(const Hash & key) const { auto it = sets_from_storage.find(key); @@ -331,7 +346,7 @@ PreparedSets::Subqueries PreparedSets::getSubqueries() const { PreparedSets::Subqueries res; res.reserve(sets_from_subqueries.size()); - for (auto & [_, set] : sets_from_subqueries) + for (const auto & [_, set] : sets_from_subqueries) res.push_back(set); return res; diff --git a/src/Interpreters/PreparedSets.h b/src/Interpreters/PreparedSets.h index e6d499715b8..65072caf0a9 100644 --- a/src/Interpreters/PreparedSets.h +++ b/src/Interpreters/PreparedSets.h @@ -59,7 +59,7 @@ using FutureSetPtr = std::shared_ptr; class FutureSetFromStorage final : public FutureSet { public: - FutureSetFromStorage(SetPtr set_); + explicit FutureSetFromStorage(SetPtr set_); SetPtr get() const override; DataTypes getTypes() const override; @@ -97,7 +97,8 @@ public: std::unique_ptr source_, StoragePtr external_table_, FutureSetPtr external_table_set_, - const Settings & settings); + const Settings & settings, + bool in_subquery_); FutureSetFromSubquery( String key, @@ -112,6 +113,8 @@ public: QueryTreeNodePtr detachQueryTree() { return std::move(query_tree); } void setQueryPlan(std::unique_ptr source_); + void markAsINSubquery() { in_subquery = true; } + bool isINSubquery() const { return in_subquery; } private: SetAndKeyPtr set_and_key; @@ -120,6 +123,7 @@ private: std::unique_ptr source; QueryTreeNodePtr query_tree; + bool in_subquery = false; // subquery used in IN operator }; /// Container for all the sets used in query. @@ -145,7 +149,8 @@ public: std::unique_ptr source, StoragePtr external_table, FutureSetPtr external_table_set, - const Settings & settings); + const Settings & settings, + bool in_subquery = false); FutureSetPtr addFromSubquery( const Hash & key, @@ -155,6 +160,7 @@ public: FutureSetPtr findTuple(const Hash & key, const DataTypes & types) const; std::shared_ptr findStorage(const Hash & key) const; std::shared_ptr findSubquery(const Hash & key) const; + void markAsINSubquery(const Hash & key); using Subqueries = std::vector>; Subqueries getSubqueries() const; diff --git a/tests/queries/0_stateless/02731_parallel_replicas_join_subquery.sql b/tests/queries/0_stateless/02731_parallel_replicas_join_subquery.sql index fa40c96048c..a117378b0bf 100644 --- a/tests/queries/0_stateless/02731_parallel_replicas_join_subquery.sql +++ b/tests/queries/0_stateless/02731_parallel_replicas_join_subquery.sql @@ -1,5 +1,7 @@ -- Tags: zookeeper +DROP TABLE IF EXISTS join_inner_table SYNC; + CREATE TABLE join_inner_table ( id UUID, @@ -77,6 +79,8 @@ ORDER BY is_initial_query, c, query; ---- Query with JOIN +DROP TABLE IF EXISTS join_outer_table SYNC; + CREATE TABLE join_outer_table ( id UUID, From a543048b5f9883e39a73954d6aa2149e932e6ea7 Mon Sep 17 00:00:00 2001 From: yariks5s Date: Fri, 22 Dec 2023 17:28:09 +0000 Subject: [PATCH 190/253] review fix --- src/IO/S3/Client.cpp | 2 +- src/IO/S3/Requests.h | 2 +- src/IO/S3/URI.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/IO/S3/Client.cpp b/src/IO/S3/Client.cpp index e22b7734cef..c63d31e2ea6 100644 --- a/src/IO/S3/Client.cpp +++ b/src/IO/S3/Client.cpp @@ -575,7 +575,7 @@ Client::doRequest(RequestType & request, RequestFn request_fn) const return result; if (initial_endpoint.substr(11) == "amazonaws.com") // Check if user didn't mention any region - new_uri->addRegionToURI(request.getRegion()); + new_uri->addRegionToURI(request.getRegionOverride()); const auto & current_uri_override = request.getURIOverride(); /// we already tried with this URI diff --git a/src/IO/S3/Requests.h b/src/IO/S3/Requests.h index 3db82b7252d..bfb94a5a67e 100644 --- a/src/IO/S3/Requests.h +++ b/src/IO/S3/Requests.h @@ -58,7 +58,7 @@ public: return BaseRequest::GetChecksumAlgorithmName(); } - std::string getRegion() const + std::string getRegionOverride() const { return region_override; } diff --git a/src/IO/S3/URI.cpp b/src/IO/S3/URI.cpp index b2f80869972..e990875dd2f 100644 --- a/src/IO/S3/URI.cpp +++ b/src/IO/S3/URI.cpp @@ -148,8 +148,8 @@ URI::URI(const std::string & uri_) void URI::addRegionToURI(const std::string ®ion) { - auto pos = endpoint.find("amazonaws.com"); - endpoint = endpoint.substr(0, pos) + region + "." + endpoint.substr(pos); + if (auto pos = endpoint.find("amazonaws.com"); pos != std::string::npos) + endpoint = endpoint.substr(0, pos) + region + "." + endpoint.substr(pos); } void URI::validateBucket(const String & bucket, const Poco::URI & uri) From f066f3151484851759ebf39820ceefdcaec3d8a7 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 22 Dec 2023 18:32:26 +0100 Subject: [PATCH 191/253] Adjust all std::ios implementations in poco to set failbit/badbit by default Signed-off-by: Azat Khuzhin --- base/poco/Foundation/include/Poco/StreamUtil.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/base/poco/Foundation/include/Poco/StreamUtil.h b/base/poco/Foundation/include/Poco/StreamUtil.h index fa1814a0f2e..ed0a4fb5154 100644 --- a/base/poco/Foundation/include/Poco/StreamUtil.h +++ b/base/poco/Foundation/include/Poco/StreamUtil.h @@ -69,6 +69,9 @@ // init() is called in the MyIOS constructor. // Therefore we replace each call to init() with // the poco_ios_init macro defined below. +// +// Also this macro will adjust exceptions() flags, since by default std::ios +// will hide exceptions, while in ClickHouse it is better to pass them through. #if !defined(POCO_IOS_INIT_HACK) @@ -79,7 +82,10 @@ #if defined(POCO_IOS_INIT_HACK) # define poco_ios_init(buf) #else -# define poco_ios_init(buf) init(buf) +# define poco_ios_init(buf) do { \ + init(buf); \ + this->exceptions(std::ios::failbit | std::ios::badbit); \ +} while (0) #endif From b4c3969d3a79fb931c1eff095686494b79556dcc Mon Sep 17 00:00:00 2001 From: Jordi Villar Date: Fri, 22 Dec 2023 18:30:44 +0100 Subject: [PATCH 192/253] Add bytes_uncompressed to system.part_log --- src/Interpreters/PartLog.cpp | 1 + src/Storages/MergeTree/MergeTreeData.cpp | 3 ++- ...2950_part_log_bytes_uncompressed.reference | 4 ++++ .../02950_part_log_bytes_uncompressed.sql | 21 +++++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference create mode 100644 tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql diff --git a/src/Interpreters/PartLog.cpp b/src/Interpreters/PartLog.cpp index 973c5260ea1..338775bfb0c 100644 --- a/src/Interpreters/PartLog.cpp +++ b/src/Interpreters/PartLog.cpp @@ -245,6 +245,7 @@ bool PartLog::addNewParts( elem.part_type = part->getType(); elem.bytes_compressed_on_disk = part->getBytesOnDisk(); + elem.bytes_uncompressed = part->getBytesUncompressedOnDisk(); elem.rows = part->rows_count; elem.error = static_cast(execution_status.code); diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 0ddeb0a6828..86fda17d863 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -2325,6 +2325,7 @@ void MergeTreeData::removePartsFinally(const MergeTreeData::DataPartsVector & pa part_log_elem.partition_id = part->info.partition_id; part_log_elem.part_name = part->name; part_log_elem.bytes_compressed_on_disk = part->getBytesOnDisk(); + part_log_elem.bytes_uncompressed = part->getBytesUncompressedOnDisk(); part_log_elem.rows = part->rows_count; part_log_elem.part_type = part->getType(); @@ -7509,6 +7510,7 @@ try part_log_elem.disk_name = result_part->getDataPartStorage().getDiskName(); part_log_elem.path_on_disk = result_part->getDataPartStorage().getFullPath(); part_log_elem.bytes_compressed_on_disk = result_part->getBytesOnDisk(); + part_log_elem.bytes_uncompressed = result_part->getBytesUncompressedOnDisk(); part_log_elem.rows = result_part->rows_count; part_log_elem.part_type = result_part->getType(); } @@ -7523,7 +7525,6 @@ try part_log_elem.bytes_read_uncompressed = (*merge_entry)->bytes_read_uncompressed; part_log_elem.rows = (*merge_entry)->rows_written; - part_log_elem.bytes_uncompressed = (*merge_entry)->bytes_written_uncompressed; part_log_elem.peak_memory_usage = (*merge_entry)->getMemoryTracker().getPeak(); } diff --git a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference new file mode 100644 index 00000000000..ee43a2dd1a3 --- /dev/null +++ b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference @@ -0,0 +1,4 @@ +NewPart part_log_bytes_uncompressed all_1_1_0 2 +NewPart part_log_bytes_uncompressed all_2_2_0 2 +MergeParts part_log_bytes_uncompressed all_1_2_1 4 +MutatePart part_log_bytes_uncompressed all_1_2_1_3 4 diff --git a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql new file mode 100644 index 00000000000..197f963477b --- /dev/null +++ b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql @@ -0,0 +1,21 @@ +CREATE TABLE part_log_bytes_uncompressed ( + key UInt8, + value UInt8 +) +Engine=MergeTree() +ORDER BY key; + +INSERT INTO part_log_bytes_uncompressed VALUES (1, 1); +INSERT INTO part_log_bytes_uncompressed VALUES (2, 1); + +OPTIMIZE TABLE part_log_bytes_uncompressed FINAL; + +ALTER TABLE part_log_bytes_uncompressed UPDATE value = 3 WHERE 1 = 1 SETTINGS mutations_sync=2; + +SYSTEM FLUSH LOGS; + +SELECT event_type, table, part_name, bytes_uncompressed FROM system.part_log +WHERE event_date >= yesterday() AND database = currentDatabase() AND table = 'part_log_bytes_uncompressed' +ORDER BY event_time_microseconds; + +DROP TABLE part_log_bytes_uncompressed; From 74ea601dc244b456e6d83702ae6f6f6e7049820b Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 22 Dec 2023 18:21:54 +0100 Subject: [PATCH 193/253] Add documentation for MemoryResidentMax metric in system.asynchronous_metrics Signed-off-by: Azat Khuzhin --- docs/en/operations/system-tables/asynchronous_metrics.md | 4 ++++ utils/check-style/aspell-ignore/en/aspell-dict.txt | 1 + 2 files changed, 5 insertions(+) diff --git a/docs/en/operations/system-tables/asynchronous_metrics.md b/docs/en/operations/system-tables/asynchronous_metrics.md index e46b495239c..90c5a7d2e7a 100644 --- a/docs/en/operations/system-tables/asynchronous_metrics.md +++ b/docs/en/operations/system-tables/asynchronous_metrics.md @@ -239,6 +239,10 @@ The amount of virtual memory mapped for the pages of machine code of the server The amount of virtual memory mapped for the use of stack and for the allocated memory, in bytes. It is unspecified whether it includes the per-thread stacks and most of the allocated memory, that is allocated with the 'mmap' system call. This metric exists only for completeness reasons. I recommend to use the `MemoryResident` metric for monitoring. +### MemoryResidentMax + +Maximum amount of physical memory used by the server process, in bytes. + ### MemoryResident The amount of physical memory used by the server process, in bytes. diff --git a/utils/check-style/aspell-ignore/en/aspell-dict.txt b/utils/check-style/aspell-ignore/en/aspell-dict.txt index 804f7200032..35afb8185fb 100644 --- a/utils/check-style/aspell-ignore/en/aspell-dict.txt +++ b/utils/check-style/aspell-ignore/en/aspell-dict.txt @@ -502,6 +502,7 @@ Memcheck MemoryCode MemoryDataAndStack MemoryResident +MemoryResidentMax MemorySanitizer MemoryShared MemoryTracking From 1e19d84811a41987afd0548eb07dced16de1c8ad Mon Sep 17 00:00:00 2001 From: kssenii Date: Fri, 22 Dec 2023 18:39:59 +0100 Subject: [PATCH 194/253] Update stateful/run.sh --- docker/test/stateful/run.sh | 82 ++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/docker/test/stateful/run.sh b/docker/test/stateful/run.sh index 806b57c4616..c9ce5697182 100755 --- a/docker/test/stateful/run.sh +++ b/docker/test/stateful/run.sh @@ -40,6 +40,12 @@ if [ "$cache_policy" = "SLRU" ]; then mv /etc/clickhouse-server/config.d/storage_conf.xml.tmp /etc/clickhouse-server/config.d/storage_conf.xml fi +if [[ -n "$USE_S3_STORAGE_FOR_MERGE_TREE" ]] && [[ "$USE_S3_STORAGE_FOR_MERGE_TREE" -eq 1 ]]; then + # It is not needed, we will explicitly create tables on s3. + # We do not have statefull tests with s3 storage run in public repository, but this is needed for another repository. + rm /etc/clickhouse-server/config.d/s3_storage_policy_for_merge_tree_by_default.xml +fi + function start() { if [[ -n "$USE_DATABASE_REPLICATED" ]] && [[ "$USE_DATABASE_REPLICATED" -eq 1 ]]; then @@ -123,8 +129,76 @@ if [[ -n "$USE_DATABASE_REPLICATED" ]] && [[ "$USE_DATABASE_REPLICATED" -eq 1 ]] else clickhouse-client --query "CREATE DATABASE test" clickhouse-client --query "SHOW TABLES FROM test" - clickhouse-client --query "RENAME TABLE datasets.hits_v1 TO test.hits" - clickhouse-client --query "RENAME TABLE datasets.visits_v1 TO test.visits" + if [[ -n "$USE_S3_STORAGE_FOR_MERGE_TREE" ]] && [[ "$USE_S3_STORAGE_FOR_MERGE_TREE" -eq 1 ]]; then + clickhouse-client --query "CREATE TABLE test.hits (WatchID UInt64, JavaEnable UInt8, Title String, GoodEvent Int16, + EventTime DateTime, EventDate Date, CounterID UInt32, ClientIP UInt32, ClientIP6 FixedString(16), RegionID UInt32, + UserID UInt64, CounterClass Int8, OS UInt8, UserAgent UInt8, URL String, Referer String, URLDomain String, + RefererDomain String, Refresh UInt8, IsRobot UInt8, RefererCategories Array(UInt16), URLCategories Array(UInt16), + URLRegions Array(UInt32), RefererRegions Array(UInt32), ResolutionWidth UInt16, ResolutionHeight UInt16, ResolutionDepth UInt8, + FlashMajor UInt8, FlashMinor UInt8, FlashMinor2 String, NetMajor UInt8, NetMinor UInt8, UserAgentMajor UInt16, + UserAgentMinor FixedString(2), CookieEnable UInt8, JavascriptEnable UInt8, IsMobile UInt8, MobilePhone UInt8, + MobilePhoneModel String, Params String, IPNetworkID UInt32, TraficSourceID Int8, SearchEngineID UInt16, + SearchPhrase String, AdvEngineID UInt8, IsArtifical UInt8, WindowClientWidth UInt16, WindowClientHeight UInt16, + ClientTimeZone Int16, ClientEventTime DateTime, SilverlightVersion1 UInt8, SilverlightVersion2 UInt8, SilverlightVersion3 UInt32, + SilverlightVersion4 UInt16, PageCharset String, CodeVersion UInt32, IsLink UInt8, IsDownload UInt8, IsNotBounce UInt8, + FUniqID UInt64, HID UInt32, IsOldCounter UInt8, IsEvent UInt8, IsParameter UInt8, DontCountHits UInt8, WithHash UInt8, + HitColor FixedString(1), UTCEventTime DateTime, Age UInt8, Sex UInt8, Income UInt8, Interests UInt16, Robotness UInt8, + GeneralInterests Array(UInt16), RemoteIP UInt32, RemoteIP6 FixedString(16), WindowName Int32, OpenerName Int32, + HistoryLength Int16, BrowserLanguage FixedString(2), BrowserCountry FixedString(2), SocialNetwork String, SocialAction String, + HTTPError UInt16, SendTiming Int32, DNSTiming Int32, ConnectTiming Int32, ResponseStartTiming Int32, ResponseEndTiming Int32, + FetchTiming Int32, RedirectTiming Int32, DOMInteractiveTiming Int32, DOMContentLoadedTiming Int32, DOMCompleteTiming Int32, + LoadEventStartTiming Int32, LoadEventEndTiming Int32, NSToDOMContentLoadedTiming Int32, FirstPaintTiming Int32, + RedirectCount Int8, SocialSourceNetworkID UInt8, SocialSourcePage String, ParamPrice Int64, ParamOrderID String, + ParamCurrency FixedString(3), ParamCurrencyID UInt16, GoalsReached Array(UInt32), OpenstatServiceName String, + OpenstatCampaignID String, OpenstatAdID String, OpenstatSourceID String, UTMSource String, UTMMedium String, + UTMCampaign String, UTMContent String, UTMTerm String, FromTag String, HasGCLID UInt8, RefererHash UInt64, + URLHash UInt64, CLID UInt32, YCLID UInt64, ShareService String, ShareURL String, ShareTitle String, + ParsedParams Nested(Key1 String, Key2 String, Key3 String, Key4 String, Key5 String, ValueDouble Float64), + IslandID FixedString(16), RequestNum UInt32, RequestTry UInt8) ENGINE = MergeTree() PARTITION BY toYYYYMM(EventDate) + ORDER BY (CounterID, EventDate, intHash32(UserID)) SAMPLE BY intHash32(UserID) SETTINGS index_granularity = 8192, storage_policy='s3_cache'" + clickhouse-client --query "CREATE TABLE test.visits (CounterID UInt32, StartDate Date, Sign Int8, IsNew UInt8, + VisitID UInt64, UserID UInt64, StartTime DateTime, Duration UInt32, UTCStartTime DateTime, PageViews Int32, + Hits Int32, IsBounce UInt8, Referer String, StartURL String, RefererDomain String, StartURLDomain String, + EndURL String, LinkURL String, IsDownload UInt8, TraficSourceID Int8, SearchEngineID UInt16, SearchPhrase String, + AdvEngineID UInt8, PlaceID Int32, RefererCategories Array(UInt16), URLCategories Array(UInt16), URLRegions Array(UInt32), + RefererRegions Array(UInt32), IsYandex UInt8, GoalReachesDepth Int32, GoalReachesURL Int32, GoalReachesAny Int32, + SocialSourceNetworkID UInt8, SocialSourcePage String, MobilePhoneModel String, ClientEventTime DateTime, RegionID UInt32, + ClientIP UInt32, ClientIP6 FixedString(16), RemoteIP UInt32, RemoteIP6 FixedString(16), IPNetworkID UInt32, + SilverlightVersion3 UInt32, CodeVersion UInt32, ResolutionWidth UInt16, ResolutionHeight UInt16, UserAgentMajor UInt16, + UserAgentMinor UInt16, WindowClientWidth UInt16, WindowClientHeight UInt16, SilverlightVersion2 UInt8, SilverlightVersion4 UInt16, + FlashVersion3 UInt16, FlashVersion4 UInt16, ClientTimeZone Int16, OS UInt8, UserAgent UInt8, ResolutionDepth UInt8, + FlashMajor UInt8, FlashMinor UInt8, NetMajor UInt8, NetMinor UInt8, MobilePhone UInt8, SilverlightVersion1 UInt8, + Age UInt8, Sex UInt8, Income UInt8, JavaEnable UInt8, CookieEnable UInt8, JavascriptEnable UInt8, IsMobile UInt8, + BrowserLanguage UInt16, BrowserCountry UInt16, Interests UInt16, Robotness UInt8, GeneralInterests Array(UInt16), + Params Array(String), Goals Nested(ID UInt32, Serial UInt32, EventTime DateTime, Price Int64, OrderID String, CurrencyID UInt32), + WatchIDs Array(UInt64), ParamSumPrice Int64, ParamCurrency FixedString(3), ParamCurrencyID UInt16, ClickLogID UInt64, + ClickEventID Int32, ClickGoodEvent Int32, ClickEventTime DateTime, ClickPriorityID Int32, ClickPhraseID Int32, ClickPageID Int32, + ClickPlaceID Int32, ClickTypeID Int32, ClickResourceID Int32, ClickCost UInt32, ClickClientIP UInt32, ClickDomainID UInt32, + ClickURL String, ClickAttempt UInt8, ClickOrderID UInt32, ClickBannerID UInt32, ClickMarketCategoryID UInt32, ClickMarketPP UInt32, + ClickMarketCategoryName String, ClickMarketPPName String, ClickAWAPSCampaignName String, ClickPageName String, ClickTargetType UInt16, + ClickTargetPhraseID UInt64, ClickContextType UInt8, ClickSelectType Int8, ClickOptions String, ClickGroupBannerID Int32, + OpenstatServiceName String, OpenstatCampaignID String, OpenstatAdID String, OpenstatSourceID String, UTMSource String, + UTMMedium String, UTMCampaign String, UTMContent String, UTMTerm String, FromTag String, HasGCLID UInt8, FirstVisit DateTime, + PredLastVisit Date, LastVisit Date, TotalVisits UInt32, TraficSource Nested(ID Int8, SearchEngineID UInt16, AdvEngineID UInt8, + PlaceID UInt16, SocialSourceNetworkID UInt8, Domain String, SearchPhrase String, SocialSourcePage String), Attendance FixedString(16), + CLID UInt32, YCLID UInt64, NormalizedRefererHash UInt64, SearchPhraseHash UInt64, RefererDomainHash UInt64, NormalizedStartURLHash UInt64, + StartURLDomainHash UInt64, NormalizedEndURLHash UInt64, TopLevelDomain UInt64, URLScheme UInt64, OpenstatServiceNameHash UInt64, + OpenstatCampaignIDHash UInt64, OpenstatAdIDHash UInt64, OpenstatSourceIDHash UInt64, UTMSourceHash UInt64, UTMMediumHash UInt64, + UTMCampaignHash UInt64, UTMContentHash UInt64, UTMTermHash UInt64, FromHash UInt64, WebVisorEnabled UInt8, WebVisorActivity UInt32, + ParsedParams Nested(Key1 String, Key2 String, Key3 String, Key4 String, Key5 String, ValueDouble Float64), + Market Nested(Type UInt8, GoalID UInt32, OrderID String, OrderPrice Int64, PP UInt32, DirectPlaceID UInt32, DirectOrderID UInt32, + DirectBannerID UInt32, GoodID String, GoodName String, GoodQuantity Int32, GoodPrice Int64), IslandID FixedString(16)) + ENGINE = CollapsingMergeTree(Sign) PARTITION BY toYYYYMM(StartDate) ORDER BY (CounterID, StartDate, intHash32(UserID), VisitID) + SAMPLE BY intHash32(UserID) SETTINGS index_granularity = 8192, storage_policy='s3_cache'" + + clickhouse-client --query "INSERT INTO test.hits SELECT * FROM datasets.hits_v1 SETTINGS enable_filesystem_cache_on_write_operations=0" + clickhouse-client --query "INSERT INTO test.visits SELECT * FROM datasets.visits_v1 SETTINGS enable_filesystem_cache_on_write_operations=0" + clickhouse-client --query "DROP TABLE datasets.visits_v1 SYNC" + clickhouse-client --query "DROP TABLE datasets.hits_v1 SYNC" + else + clickhouse-client --query "RENAME TABLE datasets.hits_v1 TO test.hits" + clickhouse-client --query "RENAME TABLE datasets.visits_v1 TO test.visits" + fi clickhouse-client --query "CREATE TABLE test.hits_s3 (WatchID UInt64, JavaEnable UInt8, Title String, GoodEvent Int16, EventTime DateTime, EventDate Date, CounterID UInt32, ClientIP UInt32, ClientIP6 FixedString(16), RegionID UInt32, UserID UInt64, CounterClass Int8, OS UInt8, UserAgent UInt8, URL String, Referer String, URLDomain String, RefererDomain String, Refresh UInt8, IsRobot UInt8, RefererCategories Array(UInt16), URLCategories Array(UInt16), URLRegions Array(UInt32), RefererRegions Array(UInt32), ResolutionWidth UInt16, ResolutionHeight UInt16, ResolutionDepth UInt8, FlashMajor UInt8, FlashMinor UInt8, FlashMinor2 String, NetMajor UInt8, NetMinor UInt8, UserAgentMajor UInt16, UserAgentMinor FixedString(2), CookieEnable UInt8, JavascriptEnable UInt8, IsMobile UInt8, MobilePhone UInt8, MobilePhoneModel String, Params String, IPNetworkID UInt32, TraficSourceID Int8, SearchEngineID UInt16, SearchPhrase String, AdvEngineID UInt8, IsArtifical UInt8, WindowClientWidth UInt16, WindowClientHeight UInt16, ClientTimeZone Int16, ClientEventTime DateTime, SilverlightVersion1 UInt8, SilverlightVersion2 UInt8, SilverlightVersion3 UInt32, SilverlightVersion4 UInt16, PageCharset String, CodeVersion UInt32, IsLink UInt8, IsDownload UInt8, IsNotBounce UInt8, FUniqID UInt64, HID UInt32, IsOldCounter UInt8, IsEvent UInt8, IsParameter UInt8, DontCountHits UInt8, WithHash UInt8, HitColor FixedString(1), UTCEventTime DateTime, Age UInt8, Sex UInt8, Income UInt8, Interests UInt16, Robotness UInt8, GeneralInterests Array(UInt16), RemoteIP UInt32, RemoteIP6 FixedString(16), WindowName Int32, OpenerName Int32, HistoryLength Int16, BrowserLanguage FixedString(2), BrowserCountry FixedString(2), SocialNetwork String, SocialAction String, HTTPError UInt16, SendTiming Int32, DNSTiming Int32, ConnectTiming Int32, ResponseStartTiming Int32, ResponseEndTiming Int32, FetchTiming Int32, RedirectTiming Int32, DOMInteractiveTiming Int32, DOMContentLoadedTiming Int32, DOMCompleteTiming Int32, LoadEventStartTiming Int32, LoadEventEndTiming Int32, NSToDOMContentLoadedTiming Int32, FirstPaintTiming Int32, RedirectCount Int8, SocialSourceNetworkID UInt8, SocialSourcePage String, ParamPrice Int64, ParamOrderID String, ParamCurrency FixedString(3), ParamCurrencyID UInt16, GoalsReached Array(UInt32), OpenstatServiceName String, OpenstatCampaignID String, OpenstatAdID String, OpenstatSourceID String, UTMSource String, UTMMedium String, UTMCampaign String, UTMContent String, UTMTerm String, FromTag String, HasGCLID UInt8, RefererHash UInt64, URLHash UInt64, CLID UInt32, YCLID UInt64, ShareService String, ShareURL String, ShareTitle String, ParsedParams Nested(Key1 String, Key2 String, Key3 String, Key4 String, Key5 String, ValueDouble Float64), IslandID FixedString(16), RequestNum UInt32, RequestTry UInt8) ENGINE = MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SAMPLE BY intHash32(UserID) SETTINGS index_granularity = 8192, storage_policy='s3_cache'" clickhouse-client --query "INSERT INTO test.hits_s3 SELECT * FROM test.hits SETTINGS enable_filesystem_cache_on_write_operations=0" fi @@ -144,6 +218,10 @@ function run_tests() ADDITIONAL_OPTIONS+=('--replicated-database') fi + if [[ -n "$USE_S3_STORAGE_FOR_MERGE_TREE" ]] && [[ "$USE_S3_STORAGE_FOR_MERGE_TREE" -eq 1 ]]; then + ADDITIONAL_OPTIONS+=('--s3-storage') + fi + if [[ -n "$USE_DATABASE_ORDINARY" ]] && [[ "$USE_DATABASE_ORDINARY" -eq 1 ]]; then ADDITIONAL_OPTIONS+=('--db-engine=Ordinary') fi From ca1ddf0608352d861cd0e5d844a65c14568e001b Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Fri, 22 Dec 2023 17:54:21 +0000 Subject: [PATCH 195/253] Fix style --- src/Interpreters/InterpreterSelectQuery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 42a0d60fc8a..7426bc9837c 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -878,7 +878,7 @@ bool InterpreterSelectQuery::adjustParallelReplicasAfterAnalysis() bool in_subqueries = false; const auto & sets = query_analyzer->getPreparedSets(); const auto subqueries = sets->getSubqueries(); - for(const auto & subquery : subqueries) + for (const auto & subquery : subqueries) { if (subquery->isINSubquery()) { From 026ba566e394a3e3b73f02fe42363b5d57b9452c Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 22 Dec 2023 19:35:05 +0100 Subject: [PATCH 196/253] Update 00165_jit_aggregate_functions.sql --- tests/queries/1_stateful/00165_jit_aggregate_functions.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/1_stateful/00165_jit_aggregate_functions.sql b/tests/queries/1_stateful/00165_jit_aggregate_functions.sql index c826a129b2a..157d5892ad8 100644 --- a/tests/queries/1_stateful/00165_jit_aggregate_functions.sql +++ b/tests/queries/1_stateful/00165_jit_aggregate_functions.sql @@ -1,5 +1,6 @@ SET compile_aggregate_expressions = 1; SET min_count_to_compile_aggregate_expression = 0; +SET max_bytes_before_external_group_by='200M'; -- might be randomized to 1 leading to timeout SELECT 'Aggregation using JIT compilation'; From 8f9dff8c88c389806a1e6cc1828487ae73e0d784 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 22 Dec 2023 19:54:15 +0100 Subject: [PATCH 197/253] Update clickhouse-test --- tests/clickhouse-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 57e7491ccd9..9f66f49837f 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -2307,7 +2307,7 @@ def reportLogStats(args): 'Attempt to read after eof', 'String size is too big ({}), maximum: {}' ) AS known_short_messages SELECT count() AS c, message_format_string, substr(any(message), 1, 120), - min(if(notEmpty(regexpExtract(message, '(.*)\\([A-Z0-9_]+\\)') as prefix), prefix, length(message)) - 26 AS length_without_exception_boilerplate) AS min_length_without_exception_boilerplate + min(if(length(regexpExtract(message, '(.*)\\([A-Z0-9_]+\\)')) as prefix_len > 0, prefix_len, length(message)) - 26 AS length_without_exception_boilerplate) AS min_length_without_exception_boilerplate FROM system.text_log WHERE (now() - toIntervalMinute(240)) < event_time AND (length(message_format_string) < 16 From 1deafa1a00ce8587e80b83e73cf9d17848cae21c Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Fri, 22 Dec 2023 20:54:52 +0000 Subject: [PATCH 198/253] Profile event 'ParallelReplicasUsedCount' --- src/Common/ProfileEvents.cpp | 2 ++ .../ParallelReplicasReadingCoordinator.cpp | 14 +++++++++- .../ParallelReplicasReadingCoordinator.h | 1 + ...lel_replicas_used_replicas_count.reference | 8 ++++++ ..._parallel_replicas_used_replicas_count.sql | 27 +++++++++++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.reference create mode 100644 tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.sql diff --git a/src/Common/ProfileEvents.cpp b/src/Common/ProfileEvents.cpp index f342a19b2aa..413a997a7bf 100644 --- a/src/Common/ProfileEvents.cpp +++ b/src/Common/ProfileEvents.cpp @@ -586,6 +586,8 @@ The server successfully detected this situation and will download merged part fr M(LogWarning, "Number of log messages with level Warning") \ M(LogError, "Number of log messages with level Error") \ M(LogFatal, "Number of log messages with level Fatal") \ + \ + M(ParallelReplicasUsedCount, "Number of replicas used to execute query with task-based parallel replicas") \ #ifdef APPLY_FOR_EXTERNAL_EVENTS #define APPLY_FOR_EVENTS(M) APPLY_FOR_BUILTIN_EVENTS(M) APPLY_FOR_EXTERNAL_EVENTS(M) diff --git a/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp b/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp index 95313654c0e..3e5368d8a49 100644 --- a/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp +++ b/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp @@ -23,6 +23,11 @@ #include #include +namespace ProfileEvents +{ + extern const Event ParallelReplicasUsedCount; +} + namespace DB { struct Part @@ -573,7 +578,14 @@ ParallelReadResponse ParallelReplicasReadingCoordinator::handleRequest(ParallelR initialize(); } - return pimpl->handleRequest(std::move(request)); + auto response = pimpl->handleRequest(std::move(request)); + if (!response.finish) + { + if (replicas_used.insert(request.replica_num).second) + ProfileEvents::increment(ProfileEvents::ParallelReplicasUsedCount); + } + + return response; } void ParallelReplicasReadingCoordinator::markReplicaAsUnavailable(size_t replica_number) diff --git a/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.h b/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.h index 795d7462278..acc265c124f 100644 --- a/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.h +++ b/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.h @@ -39,6 +39,7 @@ private: std::atomic initialized{false}; std::unique_ptr pimpl; ProgressCallback progress_callback; // store the callback only to bypass it to coordinator implementation + std::set replicas_used; /// To initialize `pimpl` we need to know the coordinator mode. We can know it only from initial announcement or regular request. /// The problem is `markReplicaAsUnavailable` might be called before any of these requests happened. diff --git a/tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.reference b/tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.reference new file mode 100644 index 00000000000..21b7b527b7a --- /dev/null +++ b/tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.reference @@ -0,0 +1,8 @@ +100 4950 +1 +89 +90 +91 +92 +93 +1 diff --git a/tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.sql b/tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.sql new file mode 100644 index 00000000000..f993d662809 --- /dev/null +++ b/tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.sql @@ -0,0 +1,27 @@ +DROP TABLE IF EXISTS test; + +CREATE TABLE test (k UInt64, v String) +ENGINE = MergeTree +ORDER BY k; + +SYSTEM STOP MERGES test; + +INSERT INTO test SELECT number, toString(number) FROM numbers(100); + +SET allow_experimental_parallel_reading_from_replicas = 2, max_parallel_replicas = 3, prefer_localhost_replica = 0, parallel_replicas_for_non_replicated_merge_tree=1, cluster_for_parallel_replicas='test_cluster_one_shard_three_replicas_localhost'; + +-- default coordinator +SELECT count(), sum(k) +FROM test +SETTINGS log_comment = '02950_parallel_replicas_used_replicas_count'; + +SYSTEM FLUSH LOGS; +SELECT ProfileEvents['ParallelReplicasUsedCount'] FROM system.query_log WHERE type = 'QueryFinish' AND query_id IN (SELECT query_id FROM system.query_log WHERE current_database = currentDatabase() AND log_comment = '02950_parallel_replicas_used_replicas_count' AND type = 'QueryFinish' AND initial_query_id = query_id); + +-- In order coordinator +SELECT k FROM test order by k limit 5 offset 89 SETTINGS optimize_read_in_order=1, log_comment='02950_parallel_replicas_used_replicas_count_2'; + +SYSTEM FLUSH LOGS; +SELECT ProfileEvents['ParallelReplicasUsedCount'] FROM system.query_log WHERE type = 'QueryFinish' AND query_id IN (SELECT query_id FROM system.query_log WHERE current_database = currentDatabase() AND log_comment = '02950_parallel_replicas_used_replicas_count_2' AND type = 'QueryFinish' AND initial_query_id = query_id); + +DROP TABLE test; From b386674ede4ec8d1a03e4548d37ff4e2ccd2e5cf Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 20 Dec 2023 16:35:43 +0100 Subject: [PATCH 199/253] Add GitHubCache class, generalize Cache class --- tests/ci/ccache_utils.py | 142 +++++++++++++++++++++++++++------------ 1 file changed, 99 insertions(+), 43 deletions(-) diff --git a/tests/ci/ccache_utils.py b/tests/ci/ccache_utils.py index 6ccaa8c80e0..0906b1d14e5 100644 --- a/tests/ci/ccache_utils.py +++ b/tests/ci/ccache_utils.py @@ -5,12 +5,10 @@ import os import shutil from pathlib import Path -import requests # type: ignore - -from build_download_helper import download_build_with_progress, DownloadException -from compress_files import decompress_fast, compress_fast +from build_download_helper import DownloadException, download_build_with_progress +from compress_files import compress_fast, decompress_fast from digest_helper import digest_path -from env_helper import S3_DOWNLOAD, S3_BUILDS_BUCKET +from env_helper import S3_BUILDS_BUCKET, S3_DOWNLOAD from git_helper import git_runner from s3_helper import S3Helper @@ -98,7 +96,67 @@ def upload_ccache( logging.info("Upload finished") -class CargoCache: +class CacheError(Exception): + pass + + +class Cache: + """a generic class for all caches""" + + def __init__( + self, + directory: Path, + temp_path: Path, + archive_name: str, + s3_helper: S3Helper, + ): + self.directory = directory + self.temp_path = temp_path + self.archive_name = archive_name + self.s3_helper = s3_helper + + def _download(self, url: str, ignore_error: bool = False) -> None: + compressed_cache = self.temp_path / self.archive_name + try: + download_build_with_progress(url, compressed_cache) + except DownloadException as e: + if not ignore_error: + raise CacheError(f"Failed to download {url}") from e + logging.warning("Unable downloading cache, creating empty directory") + self.directory.mkdir(parents=True, exist_ok=True) + return + + # decompress the cache and check if the necessary directory is there + self.directory.parent.mkdir(parents=True, exist_ok=True) + decompress_fast(compressed_cache, self.directory.parent) + if not self.directory.exists(): + if not ignore_error: + raise CacheError( + "The cache is downloaded and uncompressed, but directory " + f"{self.directory} does not exist" + ) + logging.warning( + "The cache archive was successfully downloaded and " + "decompressed, but %s does not exitst. Creating empty one", + self.directory, + ) + self.directory.mkdir(parents=True, exist_ok=True) + + def _upload(self, s3_path: str, force_upload: bool = False) -> None: + if not force_upload: + existing_cache = self.s3_helper.list_prefix_non_recursive(s3_path) + if existing_cache: + logging.info("Remote cache %s already exist, won't reupload", s3_path) + return + + logging.info("Compressing cargo cache") + archive_path = self.temp_path / self.archive_name + compress_fast(self.directory, archive_path) + logging.info("Uploading %s to S3 path %s", archive_path, s3_path) + self.s3_helper.upload_build_file_to_s3(archive_path, s3_path) + + +class CargoCache(Cache): PREFIX = "ccache/cargo_cache" def __init__( @@ -107,51 +165,49 @@ class CargoCache: temp_path: Path, s3_helper: S3Helper, ): - self._cargo_lock_file = Path(git_runner.cwd) / "rust" / "Cargo.lock" - self.lock_hash = digest_path(self._cargo_lock_file).hexdigest() - self.directory = directory - self.archive_name = f"Cargo_cache_{self.lock_hash}.tar.zst" - self.temp_path = temp_path - self.s3_helper = s3_helper - self._url = ( - f"{S3_DOWNLOAD}/{S3_BUILDS_BUCKET}/{self.PREFIX}/{self.archive_name}" - ) + cargo_lock_file = Path(git_runner.cwd) / "rust" / "Cargo.lock" + self.lock_hash = digest_path(cargo_lock_file).hexdigest() self._force_upload_cache = False + super().__init__( + directory, temp_path, f"Cargo_cache_{self.lock_hash}.tar.zst", s3_helper + ) + self._url = self.s3_helper.get_url( + S3_BUILDS_BUCKET, f"{self.PREFIX}/{self.archive_name}" + ) def download(self): logging.info("Searching rust cache for Cargo.lock md5 %s", self.lock_hash) - compressed_cache = self.temp_path / self.archive_name try: - download_build_with_progress(self._url, compressed_cache) - except DownloadException: + self._download(self._url, False) + except CacheError: logging.warning("Unable downloading cargo cache, creating empty directory") + logging.info("Cache for Cargo.lock md5 %s will be uploaded", self.lock_hash) + self._force_upload_cache = True self.directory.mkdir(parents=True, exist_ok=True) return - # decompress the cache and check if the necessary directory is there - self.directory.parent.mkdir(parents=True, exist_ok=True) - decompress_fast(compressed_cache, self.directory.parent) - if not self.directory.exists(): - logging.warning( - "The cargo cache archive was successfully downloaded and " - "decompressed, but %s does not exitst. Creating empty one", - self.directory, - ) - logging.info("Cache for Cargo.lock md5 %s will be uploaded", self.lock_hash) - self.directory.mkdir(parents=True, exist_ok=True) + def upload(self): + self._upload(f"{self.PREFIX}/{self.archive_name}", self._force_upload_cache) + + +class GitHubCache(Cache): + PREFIX = "ccache/github" + + def __init__( + self, + directory: Path, + temp_path: Path, + s3_helper: S3Helper, + ): + self.force_upload = True + super().__init__(directory, temp_path, "GitHub.tar.zst", s3_helper) + self._url = self.s3_helper.get_url( + S3_BUILDS_BUCKET, f"{self.PREFIX}/{self.archive_name}" + ) + + def download(self): + logging.info("Searching cache for GitHub class") + self._download(self._url, True) def upload(self): - if not self._force_upload_cache: - cache_response = requests.head(self._url) - if cache_response.status_code == 200: - logging.info( - "Remote cargo cache %s already exist, won't reupload", self._url - ) - return - - logging.info("Compressing cargo cache") - archive_path = self.directory.parent / self.archive_name - compress_fast(self.directory, archive_path) - s3_path = f"{self.PREFIX}/{self.archive_name}" - logging.info("Uploading %s to S3 path %s", archive_path, s3_path) - self.s3_helper.upload_build_file_to_s3(archive_path, s3_path) + self._upload(f"{self.PREFIX}/{self.archive_name}", True) From e7c22fef7b87e280a884f39f737e8cb73f6617ac Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 20 Dec 2023 16:37:37 +0100 Subject: [PATCH 200/253] Rename ccache_utils to cache_utils --- tests/ci/build_check.py | 2 +- tests/ci/{ccache_utils.py => cache_utils.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/ci/{ccache_utils.py => cache_utils.py} (100%) diff --git a/tests/ci/build_check.py b/tests/ci/build_check.py index f5181f4c843..27243aac4f1 100644 --- a/tests/ci/build_check.py +++ b/tests/ci/build_check.py @@ -9,7 +9,7 @@ import sys import time from ci_config import CI_CONFIG, BuildConfig -from ccache_utils import CargoCache +from cache_utils import CargoCache from env_helper import ( GITHUB_JOB_API_URL, diff --git a/tests/ci/ccache_utils.py b/tests/ci/cache_utils.py similarity index 100% rename from tests/ci/ccache_utils.py rename to tests/ci/cache_utils.py From 74dd63f956fdf83747422ac6f977f877764dedcc Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Fri, 22 Dec 2023 17:45:30 +0100 Subject: [PATCH 201/253] Add a hack to un-/mark PR as a draft #job_package_debug --- tests/ci/github_helper.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/ci/github_helper.py b/tests/ci/github_helper.py index 0addafb7fbe..ae1eaf4c06a 100644 --- a/tests/ci/github_helper.py +++ b/tests/ci/github_helper.py @@ -195,6 +195,32 @@ class GitHub(github.Github): with open(path, "rb") as ob_fd: return self.load(ob_fd) # type: ignore + # pylint: disable=protected-access + @staticmethod + def toggle_pr_draft(pr: PullRequest) -> None: + """GH rest API does not provide a way to toggle the draft status for PR""" + node_id = pr._rawData["node_id"] + if pr.draft: + action = ( + "mutation PullRequestReadyForReview($input:MarkPullRequestReadyForReviewInput!)" + "{markPullRequestReadyForReview(input: $input){pullRequest{id}}}" + ) + else: + action = ( + "mutation ConvertPullRequestToDraft($input:ConvertPullRequestToDraftInput!)" + "{convertPullRequestToDraft(input: $input){pullRequest{id}}}" + ) + query = { + "query": action, + "variables": {"input": {"pullRequestId": node_id}}, + } + url = f"{pr._requester.base_url}/graphql" + _, data = pr._requester.requestJsonAndCheck("POST", url, input=query) + if data.get("data"): + pr._draft = pr._makeBoolAttribute(not pr.draft) + + # pylint: enable=protected-access + def _is_cache_updated( self, cache_file: Path, obj_updated_at: Optional[datetime] ) -> Tuple[bool, object]: From bff0b9c7902f2be852338483afeffe07a8968bc0 Mon Sep 17 00:00:00 2001 From: Jordi Villar Date: Fri, 22 Dec 2023 22:33:58 +0100 Subject: [PATCH 202/253] Fix mutations new part uncompressed bytes --- src/Storages/MergeTree/MutateTask.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index b2f48f682d4..8c896edab14 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -879,6 +879,7 @@ void finalizeMutatedPart( /// All information about sizes is stored in checksums. /// It doesn't make sense to touch filesystem for sizes. new_data_part->setBytesOnDisk(new_data_part->checksums.getTotalSizeOnDisk()); + new_data_part->setBytesUncompressedOnDisk(new_data_part->checksums.getTotalSizeUncompressedOnDisk()); /// Also use information from checksums new_data_part->calculateColumnsAndSecondaryIndicesSizesOnDisk(); From 66ea6be827148c0490146f549019aaa91652f68d Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Fri, 22 Dec 2023 21:36:02 +0000 Subject: [PATCH 203/253] Fix test --- ...ference => 02950_parallel_replicas_used_count.reference} | 0 ...cas_count.sql => 02950_parallel_replicas_used_count.sql} | 6 ++---- 2 files changed, 2 insertions(+), 4 deletions(-) rename tests/queries/0_stateless/{02950_parallel_replicas_used_replicas_count.reference => 02950_parallel_replicas_used_count.reference} (100%) rename tests/queries/0_stateless/{02950_parallel_replicas_used_replicas_count.sql => 02950_parallel_replicas_used_count.sql} (87%) diff --git a/tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.reference b/tests/queries/0_stateless/02950_parallel_replicas_used_count.reference similarity index 100% rename from tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.reference rename to tests/queries/0_stateless/02950_parallel_replicas_used_count.reference diff --git a/tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.sql b/tests/queries/0_stateless/02950_parallel_replicas_used_count.sql similarity index 87% rename from tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.sql rename to tests/queries/0_stateless/02950_parallel_replicas_used_count.sql index f993d662809..22f55acd365 100644 --- a/tests/queries/0_stateless/02950_parallel_replicas_used_replicas_count.sql +++ b/tests/queries/0_stateless/02950_parallel_replicas_used_count.sql @@ -4,8 +4,6 @@ CREATE TABLE test (k UInt64, v String) ENGINE = MergeTree ORDER BY k; -SYSTEM STOP MERGES test; - INSERT INTO test SELECT number, toString(number) FROM numbers(100); SET allow_experimental_parallel_reading_from_replicas = 2, max_parallel_replicas = 3, prefer_localhost_replica = 0, parallel_replicas_for_non_replicated_merge_tree=1, cluster_for_parallel_replicas='test_cluster_one_shard_three_replicas_localhost'; @@ -16,12 +14,12 @@ FROM test SETTINGS log_comment = '02950_parallel_replicas_used_replicas_count'; SYSTEM FLUSH LOGS; -SELECT ProfileEvents['ParallelReplicasUsedCount'] FROM system.query_log WHERE type = 'QueryFinish' AND query_id IN (SELECT query_id FROM system.query_log WHERE current_database = currentDatabase() AND log_comment = '02950_parallel_replicas_used_replicas_count' AND type = 'QueryFinish' AND initial_query_id = query_id); +SELECT ProfileEvents['ParallelReplicasUsedCount'] FROM system.query_log WHERE type = 'QueryFinish' AND query_id IN (SELECT query_id FROM system.query_log WHERE current_database = currentDatabase() AND log_comment = '02950_parallel_replicas_used_replicas_count' AND type = 'QueryFinish' AND initial_query_id = query_id) SETTINGS allow_experimental_parallel_reading_from_replicas=0; -- In order coordinator SELECT k FROM test order by k limit 5 offset 89 SETTINGS optimize_read_in_order=1, log_comment='02950_parallel_replicas_used_replicas_count_2'; SYSTEM FLUSH LOGS; -SELECT ProfileEvents['ParallelReplicasUsedCount'] FROM system.query_log WHERE type = 'QueryFinish' AND query_id IN (SELECT query_id FROM system.query_log WHERE current_database = currentDatabase() AND log_comment = '02950_parallel_replicas_used_replicas_count_2' AND type = 'QueryFinish' AND initial_query_id = query_id); +SELECT ProfileEvents['ParallelReplicasUsedCount'] FROM system.query_log WHERE type = 'QueryFinish' AND query_id IN (SELECT query_id FROM system.query_log WHERE current_database = currentDatabase() AND log_comment = '02950_parallel_replicas_used_replicas_count_2' AND type = 'QueryFinish' AND initial_query_id = query_id) SETTINGS allow_experimental_parallel_reading_from_replicas=0; DROP TABLE test; From 93b4d51d9f48a194b06ffcaf222d220900bfd1c8 Mon Sep 17 00:00:00 2001 From: Jordi Villar Date: Fri, 22 Dec 2023 22:36:21 +0100 Subject: [PATCH 204/253] Add RemovePart events to tests --- .../0_stateless/02950_part_log_bytes_uncompressed.reference | 3 +++ .../queries/0_stateless/02950_part_log_bytes_uncompressed.sql | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference index ee43a2dd1a3..d8444ce9aa6 100644 --- a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference +++ b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference @@ -2,3 +2,6 @@ NewPart part_log_bytes_uncompressed all_1_1_0 2 NewPart part_log_bytes_uncompressed all_2_2_0 2 MergeParts part_log_bytes_uncompressed all_1_2_1 4 MutatePart part_log_bytes_uncompressed all_1_2_1_3 4 +NewPart part_log_bytes_uncompressed all_4_4_0 2 +NewPart part_log_bytes_uncompressed all_4_4_1 0 +RemovePart part_log_bytes_uncompressed all_4_4_0 2 diff --git a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql index 197f963477b..154f3bdd773 100644 --- a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql +++ b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql @@ -12,6 +12,9 @@ OPTIMIZE TABLE part_log_bytes_uncompressed FINAL; ALTER TABLE part_log_bytes_uncompressed UPDATE value = 3 WHERE 1 = 1 SETTINGS mutations_sync=2; +INSERT INTO part_log_bytes_uncompressed VALUES (3, 1); +ALTER TABLE part_log_bytes_uncompressed DROP PART 'all_4_4_0' SETTINGS mutations_sync=2; + SYSTEM FLUSH LOGS; SELECT event_type, table, part_name, bytes_uncompressed FROM system.part_log From 17e21aa21e47d95fbc2f34bedb710d83b6e4de71 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 22 Dec 2023 18:06:50 +0100 Subject: [PATCH 205/253] Pass throught exceptions for reading from S3 Reading from S3 has retries on almost any exception, except for CANNOT_ALLOCATE_MEMORY, but the problem is that this exception may not be even delivered to ReadBufferFromS3::processException(), due to std::istream will hide it (see [1]). [1]: https://github.com/ClickHouse/llvm-project/blob/1834e42289c58402c804a87be4d489892b88f3ec/libcxx/include/istream#L1059 So let's pass throught them to see at least some information instead of just: Message: Cannot read from istream at offset 0 And here as an example of exception:

(lldb) bt * thread 244, name = 'MergeMutate', stop reason = breakpoint 6.1 * frame 0: 0x000000001aa9c791 clickhouse`::__cxa_throw(thrown_object=0x00007fc3b68fdb00, tinfo=0x000000000548af50, dest=(clickhouse`Poco::Net::NetException::~NetException() at NetException.cpp:26))(void *)) at cxa_exception.cpp:258:33 frame 1: 0x0000000017c55b2b clickhouse`Poco::Net::SocketImpl::error(code=-1232086272, arg=0x00007fc3be4f4d30) at SocketImpl.cpp:985:3 frame 2: 0x0000000017c1f7ff clickhouse`Poco::Net::SecureSocketImpl::handleError(int) [inlined] Poco::Net::SocketImpl::error(code=) at SocketImpl.cpp:923:2 frame 3: 0x0000000017c1f7e4 clickhouse`Poco::Net::SecureSocketImpl::handleError(this=0x00007fc35ae42b30, rc=-1) at SecureSocketImpl.cpp:531 frame 4: 0x0000000017c208f9 clickhouse`Poco::Net::SecureSocketImpl::receiveBytes(this=0x00007fc35ae42b30, buffer=0x00007fc346e402c4, length=131068, flags=) at SecureSocketImpl.cpp:353:10 frame 5: 0x0000000017c3b388 clickhouse`Poco::Net::HTTPSession::read(char*, long) [inlined] Poco::Net::StreamSocket::receiveBytes(buffer=, length=, flags=0) at StreamSocket.cpp:130:17 frame 6: 0x0000000017c3b379 clickhouse`Poco::Net::HTTPSession::read(char*, long) [inlined] Poco::Net::HTTPSession::receive(this=, buffer=, length=) at HTTPSession.cpp:166 frame 7: 0x0000000017c3b379 clickhouse`Poco::Net::HTTPSession::read(this=0x00007fc3973e9098, buffer=, length=) at HTTPSession.cpp:144 frame 8: 0x0000000017c0f706 clickhouse`Poco::Net::HTTPSClientSession::read(this=, buffer=, length=) at HTTPSClientSession.cpp:195:23 frame 9: 0x0000000017c32eb4 clickhouse`Poco::Net::HTTPFixedLengthStreamBuf::readFromDevice(this=0x00007fc361bd6708, buffer=, length=) at HTTPFixedLengthStream.cpp:53:16 frame 10: 0x0000000017c2a368 clickhouse`Poco::BasicBufferedStreamBuf, Poco::Net::HTTPBufferAllocator>::underflow(this=0x00007fc361bd6708) at BufferedStreamBuf.h:97:17 frame 11: 0x0000000008b2abca clickhouse`std::__1::basic_streambuf >::uflow(this=0x00007fc361bd6708) at streambuf:445:9 frame 12: 0x0000000008b2ab5a clickhouse`std::__1::basic_streambuf >::xsgetn(this=0x00007fc361bd6708, __s="P, __n=1048576) at streambuf:422:25 frame 13: 0x0000000008b2ca0e clickhouse`std::__1::basic_istream >::read(char*, long) [inlined] std::__1::basic_streambuf >::sgetn[abi:v15000](this=, __s="!P, __n=1048576) at streambuf:204:14 frame 14: 0x0000000008b2ca02 clickhouse`std::__1::basic_istream >::read(this=0x00007fc35ae42d40, __s="!P, __n=1048576) at istream:1052 frame 15: 0x0000000010de0b9f clickhouse`DB::ReadBufferFromIStream::nextImpl(this=0x00007fc3663d2380) at ReadBufferFromIStream.cpp:15:10
Signed-off-by: Azat Khuzhin --- src/IO/ReadBufferFromIStream.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/IO/ReadBufferFromIStream.cpp b/src/IO/ReadBufferFromIStream.cpp index e0c966fb700..3b3bdb5c564 100644 --- a/src/IO/ReadBufferFromIStream.cpp +++ b/src/IO/ReadBufferFromIStream.cpp @@ -34,6 +34,11 @@ bool ReadBufferFromIStream::nextImpl() ReadBufferFromIStream::ReadBufferFromIStream(std::istream & istr_, size_t size) : BufferWithOwnMemory(size), istr(istr_) { + /// - badbit will be set if some exception will be throw from ios implementation + /// - failbit can be set when for instance read() reads less data, so we + /// cannot set it, since we are requesting to read more data, then the + /// buffer has now. + istr.exceptions(std::ios::badbit); } } From aa69611781aa65b3163a1cb6d1dc781fe51dd769 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 23 Dec 2023 03:36:08 +0300 Subject: [PATCH 206/253] Update ProfileEvents.cpp --- src/Common/ProfileEvents.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Common/ProfileEvents.cpp b/src/Common/ProfileEvents.cpp index 413a997a7bf..4bdf6288a1c 100644 --- a/src/Common/ProfileEvents.cpp +++ b/src/Common/ProfileEvents.cpp @@ -250,9 +250,9 @@ Number of times data after merge is not byte-identical to the data on another re 7. Manual modification of source data after server startup. 8. Manual modification of checksums stored in ZooKeeper. 9. Part format related settings like 'enable_mixed_granularity_parts' are different on different replicas. -The server successfully detected this situation and will download merged part from replica to force byte-identical result. +The server successfully detected this situation and will download merged part from the replica to force the byte-identical result. )") \ - M(DataAfterMutationDiffersFromReplica, "Number of times data after mutation is not byte-identical to the data on another replicas. In addition to the reasons described in 'DataAfterMergeDiffersFromReplica', it is also possible due to non-deterministic mutation.") \ + M(DataAfterMutationDiffersFromReplica, "Number of times data after mutation is not byte-identical to the data on other replicas. In addition to the reasons described in 'DataAfterMergeDiffersFromReplica', it is also possible due to non-deterministic mutation.") \ M(PolygonsAddedToPool, "A polygon has been added to the cache (pool) for the 'pointInPolygon' function.") \ M(PolygonsInPoolAllocatedBytes, "The number of bytes for polygons added to the cache (pool) for the 'pointInPolygon' function.") \ \ @@ -272,12 +272,12 @@ The server successfully detected this situation and will download merged part fr M(PartsLockWaitMicroseconds, "Total time spent waiting for data parts lock in MergeTree tables") \ \ M(RealTimeMicroseconds, "Total (wall clock) time spent in processing (queries and other tasks) threads (note that this is a sum).") \ - M(UserTimeMicroseconds, "Total time spent in processing (queries and other tasks) threads executing CPU instructions in user mode. This include time CPU pipeline was stalled due to main memory access, cache misses, branch mispredictions, hyper-threading, etc.") \ + M(UserTimeMicroseconds, "Total time spent in processing (queries and other tasks) threads executing CPU instructions in user mode. This includes time CPU pipeline was stalled due to main memory access, cache misses, branch mispredictions, hyper-threading, etc.") \ M(SystemTimeMicroseconds, "Total time spent in processing (queries and other tasks) threads executing CPU instructions in OS kernel mode. This is time spent in syscalls, excluding waiting time during blocking syscalls.") \ M(MemoryOvercommitWaitTimeMicroseconds, "Total time spent in waiting for memory to be freed in OvercommitTracker.") \ M(MemoryAllocatorPurge, "Total number of times memory allocator purge was requested") \ M(MemoryAllocatorPurgeTimeMicroseconds, "Total number of times memory allocator purge was requested") \ - M(SoftPageFaults, "The number of soft page faults in query execution threads. Soft page fault usually means a miss in the memory allocator cache which required a new memory mapping from the OS and subsequent allocation of a page of physical memory.") \ + M(SoftPageFaults, "The number of soft page faults in query execution threads. Soft page fault usually means a miss in the memory allocator cache, which requires a new memory mapping from the OS and subsequent allocation of a page of physical memory.") \ M(HardPageFaults, "The number of hard page faults in query execution threads. High values indicate either that you forgot to turn off swap on your server, or eviction of memory pages of the ClickHouse binary during very high memory pressure, or successful usage of the 'mmap' read method for the tables data.") \ \ M(OSIOWaitMicroseconds, "Total time a thread spent waiting for a result of IO operation, from the OS point of view. This is real IO that doesn't include page cache.") \ @@ -290,8 +290,8 @@ The server successfully detected this situation and will download merged part fr \ M(PerfCpuCycles, "Total cycles. Be wary of what happens during CPU frequency scaling.") \ M(PerfInstructions, "Retired instructions. Be careful, these can be affected by various issues, most notably hardware interrupt counts.") \ - M(PerfCacheReferences, "Cache accesses. Usually this indicates Last Level Cache accesses but this may vary depending on your CPU. This may include prefetches and coherency messages; again this depends on the design of your CPU.") \ - M(PerfCacheMisses, "Cache misses. Usually this indicates Last Level Cache misses; this is intended to be used in con‐junction with the PERFCOUNTHWCACHEREFERENCES event to calculate cache miss rates.") \ + M(PerfCacheReferences, "Cache accesses. Usually, this indicates Last Level Cache accesses, but this may vary depending on your CPU. This may include prefetches and coherency messages; again this depends on the design of your CPU.") \ + M(PerfCacheMisses, "Cache misses. Usually this indicates Last Level Cache misses; this is intended to be used in conjunction with the PERFCOUNTHWCACHEREFERENCES event to calculate cache miss rates.") \ M(PerfBranchInstructions, "Retired branch instructions. Prior to Linux 2.6.35, this used the wrong event on AMD processors.") \ M(PerfBranchMisses, "Mispredicted branch instructions.") \ M(PerfBusCycles, "Bus cycles, which can be different from total cycles.") \ @@ -457,7 +457,7 @@ The server successfully detected this situation and will download merged part fr M(FileSegmentWaitReadBufferMicroseconds, "Metric per file segment. Time spend waiting for internal read buffer (includes cache waiting)") \ M(FileSegmentReadMicroseconds, "Metric per file segment. Time spend reading from file") \ M(FileSegmentCacheWriteMicroseconds, "Metric per file segment. Time spend writing data to cache") \ - M(FileSegmentPredownloadMicroseconds, "Metric per file segment. Time spent predownloading data to cache (predownloading - finishing file segment download (after someone who failed to do that) up to the point current thread was requested to do)") \ + M(FileSegmentPredownloadMicroseconds, "Metric per file segment. Time spent pre-downloading data to cache (pre-downloading - finishing file segment download (after someone who failed to do that) up to the point current thread was requested to do)") \ M(FileSegmentUsedBytes, "Metric per file segment. How many bytes were actually used from current file segment") \ \ M(ReadBufferSeekCancelConnection, "Number of seeks which lead to new connection (s3, http)") \ @@ -466,12 +466,12 @@ The server successfully detected this situation and will download merged part fr M(SleepFunctionMicroseconds, "Time set to sleep in a sleep function (sleep, sleepEachRow).") \ M(SleepFunctionElapsedMicroseconds, "Time spent sleeping in a sleep function (sleep, sleepEachRow).") \ \ - M(ThreadPoolReaderPageCacheHit, "Number of times the read inside ThreadPoolReader was done from page cache.") \ - M(ThreadPoolReaderPageCacheHitBytes, "Number of bytes read inside ThreadPoolReader when it was done from page cache.") \ + M(ThreadPoolReaderPageCacheHit, "Number of times the read inside ThreadPoolReader was done from the page cache.") \ + M(ThreadPoolReaderPageCacheHitBytes, "Number of bytes read inside ThreadPoolReader when it was done from the page cache.") \ M(ThreadPoolReaderPageCacheHitElapsedMicroseconds, "Time spent reading data from page cache in ThreadPoolReader.") \ M(ThreadPoolReaderPageCacheMiss, "Number of times the read inside ThreadPoolReader was not done from page cache and was hand off to thread pool.") \ M(ThreadPoolReaderPageCacheMissBytes, "Number of bytes read inside ThreadPoolReader when read was not done from page cache and was hand off to thread pool.") \ - M(ThreadPoolReaderPageCacheMissElapsedMicroseconds, "Time spent reading data inside the asynchronous job in ThreadPoolReader - when read was not done from page cache.") \ + M(ThreadPoolReaderPageCacheMissElapsedMicroseconds, "Time spent reading data inside the asynchronous job in ThreadPoolReader - when read was not done from the page cache.") \ \ M(AsynchronousReadWaitMicroseconds, "Time spent in waiting for asynchronous reads in asynchronous local read.") \ M(SynchronousReadWaitMicroseconds, "Time spent in waiting for synchronous reads in asynchronous local read.") \ @@ -512,7 +512,7 @@ The server successfully detected this situation and will download merged part fr M(SchemaInferenceCacheSchemaHits, "Number of times the schema is found in schema cache during schema inference") \ M(SchemaInferenceCacheNumRowsHits, "Number of times the number of rows is found in schema cache during count from files") \ M(SchemaInferenceCacheMisses, "Number of times the requested source is not in schema cache") \ - M(SchemaInferenceCacheSchemaMisses, "Number of times the requested source is in cache but the schema is not in cache while schema inference") \ + M(SchemaInferenceCacheSchemaMisses, "Number of times the requested source is in cache but the schema is not in cache during schema inference") \ M(SchemaInferenceCacheNumRowsMisses, "Number of times the requested source is in cache but the number of rows is not in cache while count from files") \ M(SchemaInferenceCacheEvictions, "Number of times a schema from cache was evicted due to overflow") \ M(SchemaInferenceCacheInvalidations, "Number of times a schema in cache became invalid due to changes in data") \ @@ -570,7 +570,7 @@ The server successfully detected this situation and will download merged part fr \ M(ReadTaskRequestsSent, "The number of callbacks requested from the remote server back to the initiator server to choose the read task (for s3Cluster table function and similar). Measured on the remote server side.") \ M(MergeTreeReadTaskRequestsSent, "The number of callbacks requested from the remote server back to the initiator server to choose the read task (for MergeTree tables). Measured on the remote server side.") \ - M(MergeTreeAllRangesAnnouncementsSent, "The number of announcement sent from the remote server to the initiator server about the set of data parts (for MergeTree tables). Measured on the remote server side.") \ + M(MergeTreeAllRangesAnnouncementsSent, "The number of announcements sent from the remote server to the initiator server about the set of data parts (for MergeTree tables). Measured on the remote server side.") \ M(ReadTaskRequestsSentElapsedMicroseconds, "Time spent in callbacks requested from the remote server back to the initiator server to choose the read task (for s3Cluster table function and similar). Measured on the remote server side.") \ M(MergeTreeReadTaskRequestsSentElapsedMicroseconds, "Time spent in callbacks requested from the remote server back to the initiator server to choose the read task (for MergeTree tables). Measured on the remote server side.") \ M(MergeTreeAllRangesAnnouncementsSentElapsedMicroseconds, "Time spent in sending the announcement from the remote server to the initiator server about the set of data parts (for MergeTree tables). Measured on the remote server side.") \ @@ -587,7 +587,7 @@ The server successfully detected this situation and will download merged part fr M(LogError, "Number of log messages with level Error") \ M(LogFatal, "Number of log messages with level Fatal") \ \ - M(ParallelReplicasUsedCount, "Number of replicas used to execute query with task-based parallel replicas") \ + M(ParallelReplicasUsedCount, "Number of replicas used to execute a query with task-based parallel replicas") \ #ifdef APPLY_FOR_EXTERNAL_EVENTS #define APPLY_FOR_EVENTS(M) APPLY_FOR_BUILTIN_EVENTS(M) APPLY_FOR_EXTERNAL_EVENTS(M) From 5dbc7d3184457c28158aae5ebfa6c57847065bc9 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 23 Dec 2023 01:42:29 +0100 Subject: [PATCH 207/253] Fix flaky test --- .../0_stateless/02719_aggregate_with_empty_string_key.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02719_aggregate_with_empty_string_key.sql b/tests/queries/0_stateless/02719_aggregate_with_empty_string_key.sql index 7930b2ca0cc..12572982ddd 100644 --- a/tests/queries/0_stateless/02719_aggregate_with_empty_string_key.sql +++ b/tests/queries/0_stateless/02719_aggregate_with_empty_string_key.sql @@ -2,6 +2,6 @@ drop table if exists test ; create table test(str Nullable(String), i Int64) engine=Memory(); insert into test values(null, 1),('', 2),('s', 1); select '-----------String------------'; -select str ,max(i) from test group by str; +select str, max(i) from test group by str order by str nulls first; drop table test; From ffca9bc564f5ddfbdc4e92b261af1fe00cb9d08d Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 23 Dec 2023 02:04:35 +0100 Subject: [PATCH 208/253] Fix #58171 --- src/IO/SharedThreadPools.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/IO/SharedThreadPools.cpp b/src/IO/SharedThreadPools.cpp index 6af5aab7a38..c8506663bc8 100644 --- a/src/IO/SharedThreadPools.cpp +++ b/src/IO/SharedThreadPools.cpp @@ -66,6 +66,8 @@ void StaticThreadPool::reloadConfiguration(size_t max_threads, size_t max_free_t if (!instance) throw Exception(ErrorCodes::LOGICAL_ERROR, "The {} is not initialized", name); + std::lock_guard lock(mutex); + instance->setMaxThreads(turbo_mode_enabled > 0 ? max_threads_turbo : max_threads); instance->setMaxFreeThreads(max_free_threads); instance->setQueueSize(queue_size); From b5c8c4050b5f67d31f588f55a4de656421a3015c Mon Sep 17 00:00:00 2001 From: Pradeep Chhetri Date: Sat, 23 Dec 2023 11:08:50 +0800 Subject: [PATCH 209/253] Add base backup name to system.backups and system.backup_log tables --- src/Backups/BackupOperationInfo.h | 3 +++ src/Backups/BackupsWorker.cpp | 14 +++++++++++--- src/Backups/BackupsWorker.h | 2 +- src/Interpreters/BackupLog.cpp | 2 ++ src/Storages/System/StorageSystemBackups.cpp | 3 +++ 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Backups/BackupOperationInfo.h b/src/Backups/BackupOperationInfo.h index 54f5e5e9965..e57b57d75f1 100644 --- a/src/Backups/BackupOperationInfo.h +++ b/src/Backups/BackupOperationInfo.h @@ -17,6 +17,9 @@ struct BackupOperationInfo /// Operation name, a string like "Disk('backups', 'my_backup')" String name; + /// Base Backup Operation name, a string like "Disk('backups', 'my_base_backup')" + String base_backup_name; + /// This operation is internal and should not be shown in system.backups bool internal = false; diff --git a/src/Backups/BackupsWorker.cpp b/src/Backups/BackupsWorker.cpp index a1f619af0a4..a87f8520138 100644 --- a/src/Backups/BackupsWorker.cpp +++ b/src/Backups/BackupsWorker.cpp @@ -394,9 +394,13 @@ OperationID BackupsWorker::startMakingBackup(const ASTPtr & query, const Context auto backup_info = BackupInfo::fromAST(*backup_query->backup_name); String backup_name_for_logging = backup_info.toStringForLogging(); + String base_backup_name; + if (backup_settings.base_backup_info) + base_backup_name = backup_settings.base_backup_info->toString(); + try { - addInfo(backup_id, backup_name_for_logging, backup_settings.internal, BackupStatus::CREATING_BACKUP); + addInfo(backup_id, backup_name_for_logging, base_backup_name, backup_settings.internal, BackupStatus::CREATING_BACKUP); /// Prepare context to use. ContextPtr context_in_use = context; @@ -745,8 +749,11 @@ OperationID BackupsWorker::startRestoring(const ASTPtr & query, ContextMutablePt { auto backup_info = BackupInfo::fromAST(*restore_query->backup_name); String backup_name_for_logging = backup_info.toStringForLogging(); + String base_backup_name; + if (restore_settings.base_backup_info) + base_backup_name = restore_settings.base_backup_info->toString(); - addInfo(restore_id, backup_name_for_logging, restore_settings.internal, BackupStatus::RESTORING); + addInfo(restore_id, backup_name_for_logging, base_backup_name, restore_settings.internal, BackupStatus::RESTORING); /// Prepare context to use. ContextMutablePtr context_in_use = context; @@ -1005,11 +1012,12 @@ void BackupsWorker::restoreTablesData(const OperationID & restore_id, BackupPtr } -void BackupsWorker::addInfo(const OperationID & id, const String & name, bool internal, BackupStatus status) +void BackupsWorker::addInfo(const OperationID & id, const String & name, const String & base_backup_name, bool internal, BackupStatus status) { BackupOperationInfo info; info.id = id; info.name = name; + info.base_backup_name = base_backup_name; info.internal = internal; info.status = status; info.start_time = std::chrono::system_clock::now(); diff --git a/src/Backups/BackupsWorker.h b/src/Backups/BackupsWorker.h index b0a76eb0fa8..e2bd076314f 100644 --- a/src/Backups/BackupsWorker.h +++ b/src/Backups/BackupsWorker.h @@ -83,7 +83,7 @@ private: /// Run data restoring tasks which insert data to tables. void restoreTablesData(const BackupOperationID & restore_id, BackupPtr backup, DataRestoreTasks && tasks, ThreadPool & thread_pool); - void addInfo(const BackupOperationID & id, const String & name, bool internal, BackupStatus status); + void addInfo(const BackupOperationID & id, const String & name, const String & base_backup_name, bool internal, BackupStatus status); void setStatus(const BackupOperationID & id, BackupStatus status, bool throw_if_error = true); void setStatusSafe(const String & id, BackupStatus status) { setStatus(id, status, false); } void setNumFilesAndSize(const BackupOperationID & id, size_t num_files, UInt64 total_size, size_t num_entries, diff --git a/src/Interpreters/BackupLog.cpp b/src/Interpreters/BackupLog.cpp index 4953a2140ea..e49bb28bd45 100644 --- a/src/Interpreters/BackupLog.cpp +++ b/src/Interpreters/BackupLog.cpp @@ -27,6 +27,7 @@ NamesAndTypesList BackupLogElement::getNamesAndTypes() {"event_time_microseconds", std::make_shared(6)}, {"id", std::make_shared()}, {"name", std::make_shared()}, + {"base_backup_name", std::make_shared()}, {"status", std::make_shared(getBackupStatusEnumValues())}, {"error", std::make_shared()}, {"start_time", std::make_shared()}, @@ -49,6 +50,7 @@ void BackupLogElement::appendToBlock(MutableColumns & columns) const columns[i++]->insert(event_time_usec); columns[i++]->insert(info.id); columns[i++]->insert(info.name); + columns[i++]->insert(info.base_backup_name); columns[i++]->insert(static_cast(info.status)); columns[i++]->insert(info.error_message); columns[i++]->insert(static_cast(std::chrono::system_clock::to_time_t(info.start_time))); diff --git a/src/Storages/System/StorageSystemBackups.cpp b/src/Storages/System/StorageSystemBackups.cpp index 46ab70ff04a..a5dd7ea6e0b 100644 --- a/src/Storages/System/StorageSystemBackups.cpp +++ b/src/Storages/System/StorageSystemBackups.cpp @@ -20,6 +20,7 @@ NamesAndTypesList StorageSystemBackups::getNamesAndTypes() NamesAndTypesList names_and_types{ {"id", std::make_shared()}, {"name", std::make_shared()}, + {"base_backup_name", std::make_shared()}, {"status", std::make_shared(getBackupStatusEnumValues())}, {"error", std::make_shared()}, {"start_time", std::make_shared()}, @@ -42,6 +43,7 @@ void StorageSystemBackups::fillData(MutableColumns & res_columns, ContextPtr con size_t column_index = 0; auto & column_id = assert_cast(*res_columns[column_index++]); auto & column_name = assert_cast(*res_columns[column_index++]); + auto & column_base_backup_name = assert_cast(*res_columns[column_index++]); auto & column_status = assert_cast(*res_columns[column_index++]); auto & column_error = assert_cast(*res_columns[column_index++]); auto & column_start_time = assert_cast(*res_columns[column_index++]); @@ -59,6 +61,7 @@ void StorageSystemBackups::fillData(MutableColumns & res_columns, ContextPtr con { column_id.insertData(info.id.data(), info.id.size()); column_name.insertData(info.name.data(), info.name.size()); + column_base_backup_name.insertData(info.base_backup_name.data(), info.base_backup_name.size()); column_status.insertValue(static_cast(info.status)); column_error.insertData(info.error_message.data(), info.error_message.size()); column_start_time.insertValue(static_cast(std::chrono::system_clock::to_time_t(info.start_time))); From dc4b9a1013e4f110362ad380feb6fae6624cacb7 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 23 Dec 2023 04:55:55 +0100 Subject: [PATCH 210/253] Obfuscator: keep settings and timezones --- programs/format/Format.cpp | 20 +++++++++++++++++++ src/Core/Settings.cpp | 2 -- src/Parsers/obfuscateQueries.cpp | 19 ++++++++++-------- src/Storages/MergeTree/MergeTreeSettings.cpp | 10 ++++++++++ src/Storages/MergeTree/MergeTreeSettings.h | 5 ++++- .../02950_obfuscator_keywords_more.reference | 1 + .../02950_obfuscator_keywords_more.sh | 9 +++++++++ .../00096_obfuscator_save_load.reference | 2 +- ...0175_obfuscator_schema_inference.reference | 2 +- 9 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 tests/queries/0_stateless/02950_obfuscator_keywords_more.reference create mode 100755 tests/queries/0_stateless/02950_obfuscator_keywords_more.sh diff --git a/programs/format/Format.cpp b/programs/format/Format.cpp index d7d61bbcd3b..8e7f38b6a1e 100644 --- a/programs/format/Format.cpp +++ b/programs/format/Format.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,9 @@ #pragma GCC diagnostic ignored "-Wunused-function" #pragma GCC diagnostic ignored "-Wmissing-declarations" +extern const char * auto_time_zones[]; + + namespace DB { namespace ErrorCodes @@ -133,9 +137,25 @@ int mainEntryClickHouseFormat(int argc, char ** argv) auto all_known_storage_names = StorageFactory::instance().getAllRegisteredNames(); auto all_known_data_type_names = DataTypeFactory::instance().getAllRegisteredNames(); + auto all_known_settings = Settings().getAllRegisteredNames(); + auto all_known_merge_tree_settings = MergeTreeSettings().getAllRegisteredNames(); additional_names.insert(all_known_storage_names.begin(), all_known_storage_names.end()); additional_names.insert(all_known_data_type_names.begin(), all_known_data_type_names.end()); + additional_names.insert(all_known_settings.begin(), all_known_settings.end()); + additional_names.insert(all_known_merge_tree_settings.begin(), all_known_merge_tree_settings.end()); + + for (auto * it = auto_time_zones; *it; ++it) + { + String time_zone_name = *it; + + /// Example: Europe/Amsterdam + Strings split; + boost::split(split, time_zone_name, [](char c){ return c == '/'; }); + for (const auto & word : split) + if (!word.empty()) + additional_names.insert(word); + } KnownIdentifierFunc is_known_identifier = [&](std::string_view name) { diff --git a/src/Core/Settings.cpp b/src/Core/Settings.cpp index 5e5194eeb68..a38197b9eeb 100644 --- a/src/Core/Settings.cpp +++ b/src/Core/Settings.cpp @@ -107,9 +107,7 @@ std::vector Settings::getAllRegisteredNames() const { std::vector all_settings; for (const auto & setting_field : all()) - { all_settings.push_back(setting_field.getName()); - } return all_settings; } diff --git a/src/Parsers/obfuscateQueries.cpp b/src/Parsers/obfuscateQueries.cpp index a6806a628bf..8012dbb37c6 100644 --- a/src/Parsers/obfuscateQueries.cpp +++ b/src/Parsers/obfuscateQueries.cpp @@ -1095,7 +1095,11 @@ void obfuscateIdentifier(std::string_view src, WriteBuffer & result, WordMap & o } -void obfuscateLiteral(std::string_view src, WriteBuffer & result, SipHash hash_func) +void obfuscateLiteral( + std::string_view src, + WriteBuffer & result, + SipHash hash_func, + KnownIdentifierFunc known_identifier_func) { const char * src_pos = src.data(); const char * src_end = src_pos + src.size(); @@ -1208,15 +1212,15 @@ void obfuscateLiteral(std::string_view src, WriteBuffer & result, SipHash hash_f } else if (isAlphaASCII(src_pos[0])) { - /// Alphabetial characters + /// Alphabetical characters const char * alpha_end = src_pos + 1; while (alpha_end < src_end && isAlphaASCII(*alpha_end)) ++alpha_end; - String wordcopy(src_pos, alpha_end); - Poco::toUpperInPlace(wordcopy); - if (keep_words.contains(wordcopy)) + String word(src_pos, alpha_end); + String wordcopy = Poco::toUpper(word); + if (keep_words.contains(wordcopy) || known_identifier_func(word)) { result.write(src_pos, alpha_end - src_pos); src_pos = alpha_end; @@ -1337,14 +1341,14 @@ void obfuscateQueries( } else if (token.type == TokenType::Number) { - obfuscateLiteral(whole_token, result, hash_func); + obfuscateLiteral(whole_token, result, hash_func, known_identifier_func); } else if (token.type == TokenType::StringLiteral) { assert(token.size() >= 2); result.write(*token.begin); - obfuscateLiteral({token.begin + 1, token.size() - 2}, result, hash_func); + obfuscateLiteral({token.begin + 1, token.size() - 2}, result, hash_func, known_identifier_func); result.write(token.end[-1]); } else if (token.type == TokenType::Comment) @@ -1360,4 +1364,3 @@ void obfuscateQueries( } } - diff --git a/src/Storages/MergeTree/MergeTreeSettings.cpp b/src/Storages/MergeTree/MergeTreeSettings.cpp index 1906f130101..e0015cdeb40 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.cpp +++ b/src/Storages/MergeTree/MergeTreeSettings.cpp @@ -212,4 +212,14 @@ void MergeTreeSettings::sanityCheck(size_t background_pool_tasks) const merge_selecting_sleep_slowdown_factor); } } + + +std::vector MergeTreeSettings::getAllRegisteredNames() const +{ + std::vector all_settings; + for (const auto & setting_field : all()) + all_settings.push_back(setting_field.getName()); + return all_settings; +} + } diff --git a/src/Storages/MergeTree/MergeTreeSettings.h b/src/Storages/MergeTree/MergeTreeSettings.h index a80ef5f81ad..106e66d8a99 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -248,7 +249,7 @@ DECLARE_SETTINGS_TRAITS(MergeTreeSettingsTraits, LIST_OF_MERGE_TREE_SETTINGS) /** Settings for the MergeTree family of engines. * Could be loaded from config or from a CREATE TABLE query (SETTINGS clause). */ -struct MergeTreeSettings : public BaseSettings +struct MergeTreeSettings : public BaseSettings, public IHints<2> { void loadFromConfig(const String & config_elem, const Poco::Util::AbstractConfiguration & config); @@ -269,6 +270,8 @@ struct MergeTreeSettings : public BaseSettings /// Check that the values are sane taking also query-level settings into account. void sanityCheck(size_t background_pool_tasks) const; + + std::vector getAllRegisteredNames() const override; }; using MergeTreeSettingsPtr = std::shared_ptr; diff --git a/tests/queries/0_stateless/02950_obfuscator_keywords_more.reference b/tests/queries/0_stateless/02950_obfuscator_keywords_more.reference new file mode 100644 index 00000000000..7c3fcea85ea --- /dev/null +++ b/tests/queries/0_stateless/02950_obfuscator_keywords_more.reference @@ -0,0 +1 @@ +CREATE TABLE test (pill DateTime('UTC'), tart DateTime('Europe/Amsterdam')) ENGINE = ReplicatedVersionedCollapsingMergeTree ORDER BY pill SETTINGS index_granularity = 15414; diff --git a/tests/queries/0_stateless/02950_obfuscator_keywords_more.sh b/tests/queries/0_stateless/02950_obfuscator_keywords_more.sh new file mode 100755 index 00000000000..fb0e7c178e2 --- /dev/null +++ b/tests/queries/0_stateless/02950_obfuscator_keywords_more.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +obf="$CLICKHOUSE_FORMAT --obfuscate" + +echo "CREATE TABLE test (secret1 DateTime('UTC'), secret2 DateTime('Europe/Amsterdam')) ENGINE = ReplicatedVersionedCollapsingMergeTree ORDER BY secret1 SETTINGS index_granularity = 8192;" | $obf diff --git a/tests/queries/1_stateful/00096_obfuscator_save_load.reference b/tests/queries/1_stateful/00096_obfuscator_save_load.reference index af0e6eade55..5ad3431c0f0 100644 --- a/tests/queries/1_stateful/00096_obfuscator_save_load.reference +++ b/tests/queries/1_stateful/00096_obfuscator_save_load.reference @@ -1,3 +1,3 @@ -403499 +403484 1000 320 171 23 2500 569 354 13 diff --git a/tests/queries/1_stateful/00175_obfuscator_schema_inference.reference b/tests/queries/1_stateful/00175_obfuscator_schema_inference.reference index bd7f726bffd..395c2b2d4ef 100644 --- a/tests/queries/1_stateful/00175_obfuscator_schema_inference.reference +++ b/tests/queries/1_stateful/00175_obfuscator_schema_inference.reference @@ -1,4 +1,4 @@ -403489 +403471 1000 320 171 23 2500 597 332 14 2500 597 332 14 From bcda9b9d6f7774d9e10fdd32bfe315bfce668b11 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 23 Dec 2023 07:21:43 +0300 Subject: [PATCH 211/253] Update from.md --- docs/en/sql-reference/statements/select/from.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/sql-reference/statements/select/from.md b/docs/en/sql-reference/statements/select/from.md index 9b29e61448f..06742ff74e2 100644 --- a/docs/en/sql-reference/statements/select/from.md +++ b/docs/en/sql-reference/statements/select/from.md @@ -34,7 +34,7 @@ Queries that use `FINAL` are executed slightly slower than similar queries that - Data is merged during query execution. - Queries with `FINAL` read primary key columns in addition to the columns specified in the query. -`FINAL` requires additional compute and memory resources, as the processing that normally would occur at merge time must occur in memory at the time of the query. However, using FINAL is sometimes necessary in order to produce accurate results, and is less expensive than running `OPTIMZIE` to force a merge. It is also sometimes possible to use different queries that assume the background processes of the `MergeTree` engine haven’t happened yet and deal with it by applying aggregation (for example, to discard duplicates). If you need to use FINAL in your queries in order to get the required results, then it is okay to do so but be aware of the additional processing required. +`FINAL` requires additional compute and memory resources, as the processing that normally would occur at merge time must occur in memory at the time of the query. However, using FINAL is sometimes necessary in order to produce accurate results, and is less expensive than running `OPTIMIZE` to force a merge. It is also sometimes possible to use different queries that assume the background processes of the `MergeTree` engine haven’t happened yet and deal with it by applying aggregation (for example, to discard duplicates). If you need to use FINAL in your queries in order to get the required results, then it is okay to do so but be aware of the additional processing required. `FINAL` can be applied automatically using [FINAL](../../../operations/settings/settings.md#final) setting to all tables in a query using a session or a user profile. From 6a23fe034fd4c0e516a9ef1c8ff5666c7647590d Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 23 Dec 2023 06:17:47 +0100 Subject: [PATCH 212/253] Remove parallel parsing for JSONCompactEachRow --- src/Formats/registerFormats.cpp | 3 - .../Impl/JSONCompactEachRowRowInputFormat.cpp | 19 ------ .../queries/0_stateless/02951_data.jsonl.zst | Bin 0 -> 1034789 bytes ...el_parsing_json_compact_each_row.reference | 1 + ..._parallel_parsing_json_compact_each_row.sh | 63 ++++++++++++++++++ 5 files changed, 64 insertions(+), 22 deletions(-) create mode 100644 tests/queries/0_stateless/02951_data.jsonl.zst create mode 100644 tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.reference create mode 100755 tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.sh diff --git a/src/Formats/registerFormats.cpp b/src/Formats/registerFormats.cpp index 6c9f1a94022..7c7ccac8b01 100644 --- a/src/Formats/registerFormats.cpp +++ b/src/Formats/registerFormats.cpp @@ -14,7 +14,6 @@ void registerFileSegmentationEngineJSONEachRow(FormatFactory & factory); void registerFileSegmentationEngineRegexp(FormatFactory & factory); void registerFileSegmentationEngineJSONAsString(FormatFactory & factory); void registerFileSegmentationEngineJSONAsObject(FormatFactory & factory); -void registerFileSegmentationEngineJSONCompactEachRow(FormatFactory & factory); #if USE_HIVE void registerFileSegmentationEngineHiveText(FormatFactory & factory); #endif @@ -161,7 +160,6 @@ void registerFormats() registerFileSegmentationEngineJSONEachRow(factory); registerFileSegmentationEngineJSONAsString(factory); registerFileSegmentationEngineJSONAsObject(factory); - registerFileSegmentationEngineJSONCompactEachRow(factory); #if USE_HIVE registerFileSegmentationEngineHiveText(factory); #endif @@ -294,4 +292,3 @@ void registerFormats() } } - diff --git a/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.cpp b/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.cpp index b301b9527c1..08dc9e2d511 100644 --- a/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.cpp @@ -284,23 +284,4 @@ void registerJSONCompactEachRowSchemaReader(FormatFactory & factory) } } -void registerFileSegmentationEngineJSONCompactEachRow(FormatFactory & factory) -{ - auto register_func = [&](const String & format_name, bool with_names, bool with_types) - { - /// In case when we have names and/or types in the first two/one rows, - /// we need to read at least one more row of actual data. So, set - /// the minimum of rows for segmentation engine according to - /// parameters with_names and with_types. - size_t min_rows = 1 + int(with_names) + int(with_types); - factory.registerFileSegmentationEngine(format_name, [min_rows](ReadBuffer & in, DB::Memory<> & memory, size_t min_bytes, size_t max_rows) - { - return JSONUtils::fileSegmentationEngineJSONCompactEachRow(in, memory, min_bytes, min_rows, max_rows); - }); - }; - - registerWithNamesAndTypes("JSONCompactEachRow", register_func); - registerWithNamesAndTypes("JSONCompactStringsEachRow", register_func); -} - } diff --git a/tests/queries/0_stateless/02951_data.jsonl.zst b/tests/queries/0_stateless/02951_data.jsonl.zst new file mode 100644 index 0000000000000000000000000000000000000000..9701cdd5f6e803cc6a977adc8a6074a1313634b5 GIT binary patch literal 1034789 zcmV(zK<2+FwJ-gIXe0;e03?9{3e5^sB|vLaC^kC}qv1ImU5|;@zHQ#Vn{bu@CO2(v zNfYubR9JOaU;?5A4Fweiax_sai#oD;Q8&Cp`O$@MQhU8w-PMJ@dp9sjmz5NuyCNlB z>%bE^6T0nAqQ09XQ`v~>)VgWw8>S%5ZRxoo zrz%EG+%0CBOTNQc6=|skPf{_AR0*TW&82gah=vpQjcBJE=h|3h!w`$|$Q;dT!sZns zpNCYKzOf@qa}95qxLL(KsFopf$AT)x<&@a{D%Rb@=h~4^zW~eQjpz6uvr0#Jd1H+0&Vl!ij$s)t?&(1|-CzafN(wYTXz<>+pkC8>| zcg&&;+sHdnC4HsQcIZ(oh))#TZdiTsdSWJRb0(=omh<=@fW

Fxv8}kvbkeYO;DAnQofOh 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 !k;gM{)Zf!n>d{d86 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

bV=EMeNQ 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 z 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 z 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

f9 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%0A4MI{D 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

g7+enMw8K4 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

Z@xn9e99%M_ov0=?hhls=y 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 pNSbxR@t=Gq=q8ikOVV?tNYcs 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�az0$uWb(#=}Ms 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 literal 0 HcmV?d00001 diff --git a/tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.reference b/tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.reference new file mode 100644 index 00000000000..0953b633db6 --- /dev/null +++ b/tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.reference @@ -0,0 +1 @@ +15021837090950060251 diff --git a/tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.sh b/tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.sh new file mode 100755 index 00000000000..4d63c68a854 --- /dev/null +++ b/tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Tags: no-parallel + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +${CLICKHOUSE_LOCAL} --input-format-parallel-parsing 1 --query " + SELECT sum(cityHash64(*)) FROM file('02951_data.jsonl.zst', JSONCompactEachRow, ' + time_offset Decimal64(3), + lat Float64, + lon Float64, + altitude String, + ground_speed Float32, + track_degrees Float32, + flags UInt32, + vertical_rate Int32, + aircraft Tuple( + alert Int64, + alt_geom Int64, + gva Int64, + nac_p Int64, + nac_v Int64, + nic Int64, + nic_baro Int64, + rc Int64, + sda Int64, + sil Int64, + sil_type String, + spi Int64, + track Float64, + type String, + version Int64, + category String, + emergency String, + flight String, + squawk String, + baro_rate Int64, + nav_altitude_fms Int64, + nav_altitude_mcp Int64, + nav_modes Array(String), + nav_qnh Float64, + geom_rate Int64, + ias Int64, + mach Float64, + mag_heading Float64, + oat Int64, + roll Float64, + tas Int64, + tat Int64, + true_heading Float64, + wd Int64, + ws Int64, + track_rate Float64, + nav_heading Float64 + ), + source LowCardinality(String), + geometric_altitude Int32, + geometric_vertical_rate Int32, + indicated_airspeed Int32, + roll_angle Float32, + hex String + ')" From 1c26bae241b31952dd2d3e6e01416419fc2f0c81 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 23 Dec 2023 06:39:16 +0100 Subject: [PATCH 213/253] Revert wrong changes --- tests/queries/1_stateful/00096_obfuscator_save_load.reference | 2 +- .../1_stateful/00175_obfuscator_schema_inference.reference | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/1_stateful/00096_obfuscator_save_load.reference b/tests/queries/1_stateful/00096_obfuscator_save_load.reference index 5ad3431c0f0..af0e6eade55 100644 --- a/tests/queries/1_stateful/00096_obfuscator_save_load.reference +++ b/tests/queries/1_stateful/00096_obfuscator_save_load.reference @@ -1,3 +1,3 @@ -403484 +403499 1000 320 171 23 2500 569 354 13 diff --git a/tests/queries/1_stateful/00175_obfuscator_schema_inference.reference b/tests/queries/1_stateful/00175_obfuscator_schema_inference.reference index 395c2b2d4ef..bd7f726bffd 100644 --- a/tests/queries/1_stateful/00175_obfuscator_schema_inference.reference +++ b/tests/queries/1_stateful/00175_obfuscator_schema_inference.reference @@ -1,4 +1,4 @@ -403471 +403489 1000 320 171 23 2500 597 332 14 2500 597 332 14 From 468b5e28133df8ace28be55f7d908ac1cc60c4ad Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 23 Dec 2023 08:23:15 +0100 Subject: [PATCH 214/253] Fix use-after-move --- src/Formats/JSONUtils.cpp | 7 ++++--- src/Functions/concat.cpp | 10 +++++----- src/Functions/format.cpp | 10 +++++----- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Formats/JSONUtils.cpp b/src/Formats/JSONUtils.cpp index 7ddfdb6b572..c018a2e12cc 100644 --- a/src/Formats/JSONUtils.cpp +++ b/src/Formats/JSONUtils.cpp @@ -115,13 +115,14 @@ namespace JSONUtils return {loadAtPosition(in, memory, pos), number_of_rows}; } - std::pair fileSegmentationEngineJSONEachRow(ReadBuffer & in, DB::Memory<> & memory, size_t min_bytes, size_t max_rows) + std::pair fileSegmentationEngineJSONEachRow( + ReadBuffer & in, DB::Memory<> & memory, size_t min_bytes, size_t max_rows) { return fileSegmentationEngineJSONEachRowImpl<'{', '}'>(in, memory, min_bytes, 1, max_rows); } - std::pair - fileSegmentationEngineJSONCompactEachRow(ReadBuffer & in, DB::Memory<> & memory, size_t min_bytes, size_t min_rows, size_t max_rows) + std::pair fileSegmentationEngineJSONCompactEachRow( + ReadBuffer & in, DB::Memory<> & memory, size_t min_bytes, size_t min_rows, size_t max_rows) { return fileSegmentationEngineJSONEachRowImpl<'[', ']'>(in, memory, min_bytes, min_rows, max_rows); } diff --git a/src/Functions/concat.cpp b/src/Functions/concat.cpp index 4d7d9ffb56c..b057e7fede5 100644 --- a/src/Functions/concat.cpp +++ b/src/Functions/concat.cpp @@ -145,13 +145,13 @@ private: } write_helper.finalize(); - /// Same as the normal `ColumnString` branch - has_column_string = true; - data[i] = &converted_col_str->getChars(); - offsets[i] = &converted_col_str->getOffsets(); - /// Keep the pointer alive converted_col_ptrs[i] = std::move(converted_col_str); + + /// Same as the normal `ColumnString` branch + has_column_string = true; + data[i] = &converted_col_ptrs[i]->getChars(); + offsets[i] = &converted_col_ptrs[i]->getOffsets(); } } diff --git a/src/Functions/format.cpp b/src/Functions/format.cpp index f1f73cfe438..41b6d65023b 100644 --- a/src/Functions/format.cpp +++ b/src/Functions/format.cpp @@ -108,13 +108,13 @@ public: } write_helper.finalize(); - /// Same as the normal `ColumnString` branch - has_column_string = true; - data[i - 1] = &converted_col_str->getChars(); - offsets[i - 1] = &converted_col_str->getOffsets(); - /// Keep the pointer alive converted_col_ptrs[i - 1] = std::move(converted_col_str); + + /// Same as the normal `ColumnString` branch + has_column_string = true; + data[i - 1] = &converted_col_ptrs[i - 1]->getChars(); + offsets[i - 1] = &converted_col_ptrs[i - 1]->getOffsets(); } } From e4ea1f89d1747e708ad4b6a276179768599dfb43 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 23 Dec 2023 08:28:19 +0100 Subject: [PATCH 215/253] Fix FastTest --- .../0_stateless/02951_parallel_parsing_json_compact_each_row.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.sh b/tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.sh index 4d63c68a854..bdaac0e0c50 100755 --- a/tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.sh +++ b/tests/queries/0_stateless/02951_parallel_parsing_json_compact_each_row.sh @@ -6,7 +6,7 @@ CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) . "$CUR_DIR"/../shell_config.sh ${CLICKHOUSE_LOCAL} --input-format-parallel-parsing 1 --query " - SELECT sum(cityHash64(*)) FROM file('02951_data.jsonl.zst', JSONCompactEachRow, ' + SELECT sum(cityHash64(*)) FROM file('$CUR_DIR/02951_data.jsonl.zst', JSONCompactEachRow, ' time_offset Decimal64(3), lat Float64, lon Float64, From 896cbb6a75691dd8fc9eae7e553cc71ed9d5c6fe Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 23 Dec 2023 09:07:06 +0100 Subject: [PATCH 216/253] Retry on poco exceptions while reading from S3 as well Signed-off-by: Azat Khuzhin --- src/IO/ReadBufferFromS3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IO/ReadBufferFromS3.cpp b/src/IO/ReadBufferFromS3.cpp index 36cac929e3f..619fd40edc3 100644 --- a/src/IO/ReadBufferFromS3.cpp +++ b/src/IO/ReadBufferFromS3.cpp @@ -196,7 +196,7 @@ bool ReadBufferFromS3::nextImpl() next_result = impl->next(); break; } - catch (Exception & e) + catch (Poco::Exception & e) { if (!processException(e, getPosition(), attempt) || last_attempt) throw; From 3a485a8bbf4d17b0f9d8df15c051f82f11c0dc6c Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Sat, 23 Dec 2023 11:00:29 +0000 Subject: [PATCH 217/253] Fix:moved request object was used --- src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp b/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp index 3e5368d8a49..333a0590d6b 100644 --- a/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp +++ b/src/Storages/MergeTree/ParallelReplicasReadingCoordinator.cpp @@ -578,10 +578,11 @@ ParallelReadResponse ParallelReplicasReadingCoordinator::handleRequest(ParallelR initialize(); } + const auto replica_num = request.replica_num; auto response = pimpl->handleRequest(std::move(request)); if (!response.finish) { - if (replicas_used.insert(request.replica_num).second) + if (replicas_used.insert(replica_num).second) ProfileEvents::increment(ProfileEvents::ParallelReplicasUsedCount); } From 701c8ac9ba916317cc87df25568cac901af1236f Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Sat, 23 Dec 2023 11:44:02 +0000 Subject: [PATCH 218/253] Cleanup & comments --- src/Interpreters/ActionsVisitor.cpp | 2 +- src/Interpreters/InterpreterSelectQuery.cpp | 4 ++-- src/Interpreters/PreparedSets.cpp | 2 -- src/Interpreters/PreparedSets.h | 4 ++++ 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Interpreters/ActionsVisitor.cpp b/src/Interpreters/ActionsVisitor.cpp index 6ffe04d0c95..827914eaefe 100644 --- a/src/Interpreters/ActionsVisitor.cpp +++ b/src/Interpreters/ActionsVisitor.cpp @@ -1464,7 +1464,7 @@ FutureSetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool } return data.prepared_sets->addFromSubquery( - set_key, std::move(source), nullptr, std::move(external_table_set), data.getContext()->getSettingsRef(), true); + set_key, std::move(source), nullptr, std::move(external_table_set), data.getContext()->getSettingsRef(), /*in_subquery=*/true); } else { diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 5f1e5dad660..8543b5ca552 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -873,6 +873,8 @@ bool InterpreterSelectQuery::adjustParallelReplicasAfterAnalysis() if (!storage || !context->canUseParallelReplicasOnInitiator()) return false; + /// check if IN operator with subquery is present in the query + /// if so, disable parallel replicas if (query_analyzer->getPreparedSets()->hasSubqueries()) { bool in_subqueries = false; @@ -887,8 +889,6 @@ bool InterpreterSelectQuery::adjustParallelReplicasAfterAnalysis() } } - // LOG_DEBUG(log, "Prepared sets: subqueries={} in_subqueries={}\n{}", subqueries.size(), in_subqueries, StackTrace().toString()); - if (in_subqueries) { if (settings.allow_experimental_parallel_reading_from_replicas == 2) diff --git a/src/Interpreters/PreparedSets.cpp b/src/Interpreters/PreparedSets.cpp index 8e7c493d6a5..18a25482b7f 100644 --- a/src/Interpreters/PreparedSets.cpp +++ b/src/Interpreters/PreparedSets.cpp @@ -266,8 +266,6 @@ FutureSetPtr PreparedSets::addFromSubquery( const Settings & settings, bool in_subquery) { - // LOG_DEBUG(&Poco::Logger::get(__PRETTY_FUNCTION__), "in_subquery={}\n{}", in_subquery, StackTrace().toString()); - auto from_subquery = std::make_shared( toString(key, {}), std::move(source), diff --git a/src/Interpreters/PreparedSets.h b/src/Interpreters/PreparedSets.h index 65072caf0a9..a166336faf7 100644 --- a/src/Interpreters/PreparedSets.h +++ b/src/Interpreters/PreparedSets.h @@ -124,6 +124,10 @@ private: std::unique_ptr source; QueryTreeNodePtr query_tree; bool in_subquery = false; // subquery used in IN operator + // the flag can be removed after enabling new analyzer and removing interpreter + // or after enabling support IN operator with subqueries in parallel replicas + // Note: it's necessary with interpreter since prepaired sets used also for GLOBAL JOINs, + // with new analyzer it's not a case }; /// Container for all the sets used in query. From 3c8504267934d2addaa5adc16256e9e26a844e44 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Sat, 23 Dec 2023 12:05:02 +0000 Subject: [PATCH 219/253] Fix typo --- src/Interpreters/PreparedSets.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Interpreters/PreparedSets.h b/src/Interpreters/PreparedSets.h index a166336faf7..9f8bac9f71c 100644 --- a/src/Interpreters/PreparedSets.h +++ b/src/Interpreters/PreparedSets.h @@ -126,7 +126,7 @@ private: bool in_subquery = false; // subquery used in IN operator // the flag can be removed after enabling new analyzer and removing interpreter // or after enabling support IN operator with subqueries in parallel replicas - // Note: it's necessary with interpreter since prepaired sets used also for GLOBAL JOINs, + // Note: it's necessary with interpreter since prepared sets used also for GLOBAL JOINs, // with new analyzer it's not a case }; From 6bb181ce55b10b1dcca265edea2d761e3e9e133b Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 23 Dec 2023 13:06:34 +0100 Subject: [PATCH 220/253] Looking at strange code --- programs/benchmark/Benchmark.cpp | 1 - src/Formats/JSONUtils.cpp | 11 ++--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/programs/benchmark/Benchmark.cpp b/programs/benchmark/Benchmark.cpp index ef24eeaa6d7..59fc6c0c17f 100644 --- a/programs/benchmark/Benchmark.cpp +++ b/programs/benchmark/Benchmark.cpp @@ -35,7 +35,6 @@ #include #include #include -#include /** A tool for evaluating ClickHouse performance. diff --git a/src/Formats/JSONUtils.cpp b/src/Formats/JSONUtils.cpp index 7ddfdb6b572..0f46299f946 100644 --- a/src/Formats/JSONUtils.cpp +++ b/src/Formats/JSONUtils.cpp @@ -24,7 +24,6 @@ namespace ErrorCodes namespace JSONUtils { - template static std::pair fileSegmentationEngineJSONEachRowImpl(ReadBuffer & in, DB::Memory<> & memory, size_t min_bytes, size_t min_rows, size_t max_rows) @@ -72,7 +71,7 @@ namespace JSONUtils } else { - pos = find_first_symbols(pos, in.buffer().end()); + pos = find_first_symbols(pos, in.buffer().end()); if (pos > in.buffer().end()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Position in buffer is out of bounds. There must be a bug."); @@ -89,19 +88,13 @@ namespace JSONUtils --balance; ++pos; } - else if (*pos == '\\') - { - ++pos; - if (loadAtPosition(in, memory, pos)) - ++pos; - } else if (*pos == '"') { quotes = true; ++pos; } - if (balance == 0) + if (!quotes && balance == 0) { ++number_of_rows; if ((number_of_rows >= min_rows) From ac542199c55a3cb86b11770ea7de0cad5e00907f Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 23 Dec 2023 13:42:26 +0100 Subject: [PATCH 221/253] Add some comments about racy code for system.stack_trace Signed-off-by: Azat Khuzhin --- src/Storages/System/StorageSystemStackTrace.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Storages/System/StorageSystemStackTrace.cpp b/src/Storages/System/StorageSystemStackTrace.cpp index a3b953aeea1..4c0efade93f 100644 --- a/src/Storages/System/StorageSystemStackTrace.cpp +++ b/src/Storages/System/StorageSystemStackTrace.cpp @@ -296,6 +296,7 @@ protected: const auto & thread_ids_data = assert_cast(*thread_ids).getData(); + /// NOTE: This is racy, so you may get incorrect thread_name. ThreadIdToName thread_names; if (read_thread_names) thread_names = getFilteredThreadNames(query, context, thread_ids_data, log); @@ -322,6 +323,13 @@ protected: } else { + /// NOTE: This check is racy (thread can be + /// destroyed/replaced/...), but it is OK, since only the + /// following could happen: + /// - it will incorrectly detect that the signal is blocked and + /// will not send it this time + /// - it will incorrectly detect that the signal is not blocked + /// then it will wait storage_system_stack_trace_pipe_read_timeout_ms bool signal_blocked = isSignalBlocked(tid, STACK_TRACE_SERVICE_SIGNAL); if (!signal_blocked) { From 2f6c0487ada3f929016de99d87dd936afec52fa5 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 23 Dec 2023 13:43:35 +0100 Subject: [PATCH 222/253] Ignore ENOENT for SigBlk check for system.stack_trace Signed-off-by: Azat Khuzhin --- .../System/StorageSystemStackTrace.cpp | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/Storages/System/StorageSystemStackTrace.cpp b/src/Storages/System/StorageSystemStackTrace.cpp index 4c0efade93f..85e7ba81617 100644 --- a/src/Storages/System/StorageSystemStackTrace.cpp +++ b/src/Storages/System/StorageSystemStackTrace.cpp @@ -230,26 +230,35 @@ bool isSignalBlocked(UInt64 tid, int signal) { String buffer; - ReadBufferFromFile status(fmt::format("/proc/{}/status", tid)); - while (!status.eof()) + try { - readEscapedStringUntilEOL(buffer, status); - if (!status.eof()) - ++status.position(); - if (buffer.starts_with("SigBlk:")) - break; + ReadBufferFromFile status(fmt::format("/proc/{}/status", tid)); + while (!status.eof()) + { + readEscapedStringUntilEOL(buffer, status); + if (!status.eof()) + ++status.position(); + if (buffer.starts_with("SigBlk:")) + break; + } + status.close(); + + std::string_view line(buffer); + line = line.substr(strlen("SigBlk:")); + line = line.substr(0, line.rend() - std::find_if_not(line.rbegin(), line.rend(), ::isspace)); + + UInt64 sig_blk; + if (parseHexNumber(line, sig_blk)) + return sig_blk & signal; + } + catch (const Exception & e) + { + /// Ignore TOCTOU error + if (e.code() != ErrorCodes::FILE_DOESNT_EXIST) + throw; } - status.close(); - std::string_view line(buffer); - line = line.substr(strlen("SigBlk:")); - line = line.substr(0, line.rend() - std::find_if_not(line.rbegin(), line.rend(), ::isspace)); - - UInt64 sig_blk; - if (parseHexNumber(line, sig_blk)) - return sig_blk & signal; - else - return false; + return false; } /// Send a signal to every thread and wait for result. From 56e7965d8d4ab63259a0a2f3538c3137df06275f Mon Sep 17 00:00:00 2001 From: Jordi Villar Date: Sat, 23 Dec 2023 09:39:49 +0100 Subject: [PATCH 223/253] Try to fix tests for the CI --- .../02950_part_log_bytes_uncompressed.reference | 14 +++++++------- .../02950_part_log_bytes_uncompressed.sql | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference index d8444ce9aa6..6bc0a1f3417 100644 --- a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference +++ b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference @@ -1,7 +1,7 @@ -NewPart part_log_bytes_uncompressed all_1_1_0 2 -NewPart part_log_bytes_uncompressed all_2_2_0 2 -MergeParts part_log_bytes_uncompressed all_1_2_1 4 -MutatePart part_log_bytes_uncompressed all_1_2_1_3 4 -NewPart part_log_bytes_uncompressed all_4_4_0 2 -NewPart part_log_bytes_uncompressed all_4_4_1 0 -RemovePart part_log_bytes_uncompressed all_4_4_0 2 +NewPart part_log_bytes_uncompressed all_1_1_0 1 1 +NewPart part_log_bytes_uncompressed all_2_2_0 1 1 +MergeParts part_log_bytes_uncompressed all_1_2_1 1 1 +MutatePart part_log_bytes_uncompressed all_1_2_1_3 1 1 +NewPart part_log_bytes_uncompressed all_4_4_0 1 1 +NewPart part_log_bytes_uncompressed all_4_4_1 0 0 +RemovePart part_log_bytes_uncompressed all_4_4_0 1 1 diff --git a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql index 154f3bdd773..c80df01f8bf 100644 --- a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql +++ b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql @@ -5,19 +5,19 @@ CREATE TABLE part_log_bytes_uncompressed ( Engine=MergeTree() ORDER BY key; -INSERT INTO part_log_bytes_uncompressed VALUES (1, 1); -INSERT INTO part_log_bytes_uncompressed VALUES (2, 1); +INSERT INTO part_log_bytes_uncompressed SELECT 1, 1 FROM numbers(1000); +INSERT INTO part_log_bytes_uncompressed SELECT 2, 1 FROM numbers(1000); OPTIMIZE TABLE part_log_bytes_uncompressed FINAL; ALTER TABLE part_log_bytes_uncompressed UPDATE value = 3 WHERE 1 = 1 SETTINGS mutations_sync=2; -INSERT INTO part_log_bytes_uncompressed VALUES (3, 1); +INSERT INTO part_log_bytes_uncompressed SELECT 3, 1 FROM numbers(1000); ALTER TABLE part_log_bytes_uncompressed DROP PART 'all_4_4_0' SETTINGS mutations_sync=2; SYSTEM FLUSH LOGS; -SELECT event_type, table, part_name, bytes_uncompressed FROM system.part_log +SELECT event_type, table, part_name, bytes_uncompressed > 0, size_in_bytes < bytes_uncompressed FROM system.part_log WHERE event_date >= yesterday() AND database = currentDatabase() AND table = 'part_log_bytes_uncompressed' ORDER BY event_time_microseconds; From 72fa58e19217cb38abe799db793c21b113fd0b77 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 23 Dec 2023 15:42:49 +0100 Subject: [PATCH 224/253] Fix leftover processes/hangs in tests One of such cases is 02479_race_condition_between_insert_and_droppin_mv [1], yes it can be fixed (by using fixed number of iterations, or with some bash trickery), but it is better to fix them completelly, eventually such tests will be submitted and pass review anyway. By allocating process group for each test we can kill all the processes in this process group, and this what this patch does. This will also fix some test hangs (like in [1]) as well as some possible issues in stress tests. [1]: https://s3.amazonaws.com/clickhouse-test-reports/0/e2c1230b00386c4d0096a245396ab3be7ce60950/stateless_tests__release__analyzer_/run.log Signed-off-by: Azat Khuzhin --- tests/clickhouse-test | 4 ++-- tests/queries/shell_config.sh | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 9f66f49837f..7bf1fcee390 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1031,7 +1031,7 @@ class TestCase: if proc: if proc.returncode is None: try: - proc.kill() + os.killpg(os.getpgid(proc.pid), signal.SIGKILL) except OSError as e: if e.errno != ESRCH: raise @@ -1307,7 +1307,7 @@ class TestCase: command = pattern.format(**params) - proc = Popen(command, shell=True, env=os.environ) + proc = Popen(command, shell=True, env=os.environ, start_new_session=True) while ( datetime.now() - start_time diff --git a/tests/queries/shell_config.sh b/tests/queries/shell_config.sh index c687a63623f..63f6ba9a914 100644 --- a/tests/queries/shell_config.sh +++ b/tests/queries/shell_config.sh @@ -185,3 +185,11 @@ function query_with_retry done echo "Query '$query' failed with '$result'" } + +# Add --foreground to avoid running setpgid() in timeout, otherwise +# clickhouse-test will not kill those processes in case of timeout +function timeout() +{ + command timeout --foreground "$@" +} +export -f timeout From 01289411da35a6ee3c03c80c9f5864bd5ac45e42 Mon Sep 17 00:00:00 2001 From: Jordi Villar Date: Sat, 23 Dec 2023 17:33:35 +0100 Subject: [PATCH 225/253] Let's order by part name to avoid flakiness --- .../0_stateless/02950_part_log_bytes_uncompressed.reference | 4 ++-- .../queries/0_stateless/02950_part_log_bytes_uncompressed.sql | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference index 6bc0a1f3417..abdcc960be3 100644 --- a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference +++ b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.reference @@ -1,7 +1,7 @@ NewPart part_log_bytes_uncompressed all_1_1_0 1 1 -NewPart part_log_bytes_uncompressed all_2_2_0 1 1 MergeParts part_log_bytes_uncompressed all_1_2_1 1 1 MutatePart part_log_bytes_uncompressed all_1_2_1_3 1 1 +NewPart part_log_bytes_uncompressed all_2_2_0 1 1 NewPart part_log_bytes_uncompressed all_4_4_0 1 1 -NewPart part_log_bytes_uncompressed all_4_4_1 0 0 RemovePart part_log_bytes_uncompressed all_4_4_0 1 1 +NewPart part_log_bytes_uncompressed all_4_4_1 0 0 diff --git a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql index c80df01f8bf..a45a504af1e 100644 --- a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql +++ b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql @@ -19,6 +19,6 @@ SYSTEM FLUSH LOGS; SELECT event_type, table, part_name, bytes_uncompressed > 0, size_in_bytes < bytes_uncompressed FROM system.part_log WHERE event_date >= yesterday() AND database = currentDatabase() AND table = 'part_log_bytes_uncompressed' -ORDER BY event_time_microseconds; +ORDER BY part_name; DROP TABLE part_log_bytes_uncompressed; From f713e284389e8e30010ae8ff4dbf3c569f18d01e Mon Sep 17 00:00:00 2001 From: Jordi Villar Date: Sat, 23 Dec 2023 19:07:17 +0100 Subject: [PATCH 226/253] Sorting is difficult --- tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql index a45a504af1e..0c2cef6e004 100644 --- a/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql +++ b/tests/queries/0_stateless/02950_part_log_bytes_uncompressed.sql @@ -19,6 +19,6 @@ SYSTEM FLUSH LOGS; SELECT event_type, table, part_name, bytes_uncompressed > 0, size_in_bytes < bytes_uncompressed FROM system.part_log WHERE event_date >= yesterday() AND database = currentDatabase() AND table = 'part_log_bytes_uncompressed' -ORDER BY part_name; +ORDER BY part_name, event_type; DROP TABLE part_log_bytes_uncompressed; From 172687f53a09dbb27e2951db6747ace5cf7dec08 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Sat, 23 Dec 2023 21:43:32 +0000 Subject: [PATCH 227/253] Fix DWARFBlockInputFormat failing on DWARF 5 unit address ranges --- .../Formats/Impl/DWARFBlockInputFormat.cpp | 41 +++++++++++++++---- .../Formats/Impl/DWARFBlockInputFormat.h | 1 + 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp b/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp index 22ccbe03f0f..b9f65dd49b0 100644 --- a/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp @@ -390,6 +390,40 @@ Chunk DWARFBlockInputFormat::parseEntries(UnitState & unit) if (need[COL_TAG]) col_tag->insertValue(tag); + if (tag == llvm::dwarf::DW_TAG_compile_unit) + { + /// Pre-parse DW_AT_addr_base and DW_AT_rnglists_base because other attributes may + /// rely on them. (Why couldn't DWARF just promise that these attributes must appear + /// before any attributes that depend on them?) + uint64_t offset = unit.offset; + for (auto attr : abbrev->attributes()) + { + if (attr.Attr == llvm::dwarf::DW_AT_addr_base || + attr.Attr == llvm::dwarf::DW_AT_rnglists_base) + { + auto val = llvm::DWARFFormValue::createFromSValue( + attr.Form, attr.isImplicitConst() ? attr.getImplicitConstValue() : 0); + if (!val.extractValue(*extractor, &offset, form_params, unit.dwarf_unit)) + throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, + "Failed to parse attribute {} of form {} at offset {}", + llvm::dwarf::AttributeString(attr.Attr), attr.Form, unit.offset); + uint64_t v = val.getRawUValue(); + if (attr.Attr == llvm::dwarf::DW_AT_addr_base) + unit.addr_base = v; + else + unit.rnglists_base = v; + } + else + { + if (!llvm::DWARFFormValue::skipValue( + attr.Form, *extractor, &offset, form_params)) + throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, + "Failed to skip attribute {} of form {} at offset {}", + llvm::dwarf::AttributeString(attr.Attr), attr.Form, offset); + } + } + } + bool need_name = need[COL_NAME]; bool need_linkage_name = need[COL_LINKAGE_NAME]; bool need_decl_file = need[COL_DECL_FILE]; @@ -452,13 +486,6 @@ Chunk DWARFBlockInputFormat::parseEntries(UnitState & unit) if (attr.Attr == llvm::dwarf::DW_AT_decl_line && std::exchange(need_decl_line, false)) col_decl_line->insertValue(static_cast(val.getRawUValue())); - /// Starting offset of this unit's data in .debug_addr section. - if (attr.Attr == llvm::dwarf::DW_AT_addr_base) - unit.addr_base = val.getRawUValue(); - /// Same for .debug_rnglists section. - if (attr.Attr == llvm::dwarf::DW_AT_rnglists_base) - unit.rnglists_base = val.getRawUValue(); - if (attr.Attr == llvm::dwarf::DW_AT_high_pc) { high_pc = val.getRawUValue(); diff --git a/src/Processors/Formats/Impl/DWARFBlockInputFormat.h b/src/Processors/Formats/Impl/DWARFBlockInputFormat.h index 07c00656e4d..366671210bb 100644 --- a/src/Processors/Formats/Impl/DWARFBlockInputFormat.h +++ b/src/Processors/Formats/Impl/DWARFBlockInputFormat.h @@ -53,6 +53,7 @@ private: std::string unit_name; ColumnPtr filename_table; // from .debug_line size_t filename_table_size = 0; + /// Starting offset of this unit's data in .debug_addr and .debug_rnglists sections. uint64_t addr_base = UINT64_MAX; uint64_t rnglists_base = UINT64_MAX; From e1a136b79105bf305be75ebfe0c3428dd42ec531 Mon Sep 17 00:00:00 2001 From: Vitaly Baranov Date: Sat, 23 Dec 2023 17:51:49 +0100 Subject: [PATCH 228/253] Explicit finalize() function in ZipArchiveWriter. Simplify too complicated code in ZipArchiveWriter. --- src/Backups/BackupImpl.cpp | 9 +- src/Backups/BackupImpl.h | 2 +- src/IO/Archives/IArchiveWriter.h | 6 +- src/IO/Archives/ZipArchiveWriter.cpp | 382 +++++++++--------- src/IO/Archives/ZipArchiveWriter.h | 34 +- .../tests/gtest_archive_reader_and_writer.cpp | 13 +- .../test_backup_restore_s3/test.py | 7 + 7 files changed, 253 insertions(+), 200 deletions(-) diff --git a/src/Backups/BackupImpl.cpp b/src/Backups/BackupImpl.cpp index 511a0ce59a3..9ac68bc2437 100644 --- a/src/Backups/BackupImpl.cpp +++ b/src/Backups/BackupImpl.cpp @@ -194,7 +194,7 @@ void BackupImpl::open() void BackupImpl::close() { std::lock_guard lock{mutex}; - closeArchive(); + closeArchive(/* finalize= */ false); if (!is_internal_backup && writer && !writing_finalized) removeAllFilesAfterFailure(); @@ -227,8 +227,11 @@ void BackupImpl::openArchive() } } -void BackupImpl::closeArchive() +void BackupImpl::closeArchive(bool finalize) { + if (finalize && archive_writer) + archive_writer->finalize(); + archive_reader.reset(); archive_writer.reset(); } @@ -983,7 +986,7 @@ void BackupImpl::finalizeWriting() { LOG_TRACE(log, "Finalizing backup {}", backup_name_for_logging); writeBackupMetadata(); - closeArchive(); + closeArchive(/* finalize= */ true); setCompressedSize(); removeLockFile(); LOG_TRACE(log, "Finalized backup {}", backup_name_for_logging); diff --git a/src/Backups/BackupImpl.h b/src/Backups/BackupImpl.h index 6070db79aa6..b369fe00171 100644 --- a/src/Backups/BackupImpl.h +++ b/src/Backups/BackupImpl.h @@ -89,7 +89,7 @@ private: void close(); void openArchive(); - void closeArchive(); + void closeArchive(bool finalize); /// Writes the file ".backup" containing backup's metadata. void writeBackupMetadata() TSA_REQUIRES(mutex); diff --git a/src/IO/Archives/IArchiveWriter.h b/src/IO/Archives/IArchiveWriter.h index d7ff038e7bc..cccc6dc953b 100644 --- a/src/IO/Archives/IArchiveWriter.h +++ b/src/IO/Archives/IArchiveWriter.h @@ -13,7 +13,7 @@ class WriteBufferFromFileBase; class IArchiveWriter : public std::enable_shared_from_this, boost::noncopyable { public: - /// Destructors finalizes writing the archive. + /// Call finalize() before destructing IArchiveWriter. virtual ~IArchiveWriter() = default; /// Starts writing a file to the archive. The function returns a write buffer, @@ -26,6 +26,10 @@ public: /// This function should be used mostly for debugging purposes. virtual bool isWritingFile() const = 0; + /// Finalizes writing of the archive. This function must be always called at the end of writing. + /// (Unless an error appeared and the archive is in fact no longer needed.) + virtual void finalize() = 0; + static constexpr const int kDefaultCompressionLevel = -1; /// Sets compression method and level. diff --git a/src/IO/Archives/ZipArchiveWriter.cpp b/src/IO/Archives/ZipArchiveWriter.cpp index b9a696ee2e2..af6c87e8c88 100644 --- a/src/IO/Archives/ZipArchiveWriter.cpp +++ b/src/IO/Archives/ZipArchiveWriter.cpp @@ -15,86 +15,56 @@ namespace ErrorCodes extern const int CANNOT_PACK_ARCHIVE; extern const int SUPPORT_IS_DISABLED; extern const int LOGICAL_ERROR; + extern const int NOT_IMPLEMENTED; } -using RawHandle = zipFile; - -/// Holds a raw handle, calls acquireRawHandle() in the constructor and releaseRawHandle() in the destructor. -class ZipArchiveWriter::HandleHolder +namespace { -public: - HandleHolder() = default; - - explicit HandleHolder(const std::shared_ptr & writer_) : writer(writer_), raw_handle(writer->acquireRawHandle()) { } - - ~HandleHolder() + void checkResultCodeImpl(int code, const String & file_name) { - if (raw_handle) + if (code >= ZIP_OK) + return; + + String message = "Code = "; + switch (code) { - try - { - int err = zipCloseFileInZip(raw_handle); - /// If err == ZIP_PARAMERROR the file is already closed. - if (err != ZIP_PARAMERROR) - checkResult(err); - } - catch (...) - { - tryLogCurrentException("ZipArchiveWriter"); - } - writer->releaseRawHandle(raw_handle); + case ZIP_ERRNO: message += "ERRNO, errno = " + errnoToString(); break; + case ZIP_PARAMERROR: message += "PARAMERROR"; break; + case ZIP_BADZIPFILE: message += "BADZIPFILE"; break; + case ZIP_INTERNALERROR: message += "INTERNALERROR"; break; + default: message += std::to_string(code); break; } + throw Exception(ErrorCodes::CANNOT_PACK_ARCHIVE, "Couldn't pack zip archive: {}, filename={}", message, quoteString(file_name)); } - - HandleHolder(HandleHolder && src) noexcept - { - *this = std::move(src); - } - - HandleHolder & operator=(HandleHolder && src) noexcept - { - writer = std::exchange(src.writer, nullptr); - raw_handle = std::exchange(src.raw_handle, nullptr); - return *this; - } - - RawHandle getRawHandle() const { return raw_handle; } - std::shared_ptr getWriter() const { return writer; } - - void checkResult(int code) const { writer->checkResult(code); } - -private: - std::shared_ptr writer; - RawHandle raw_handle = nullptr; -}; +} /// This class represents a WriteBuffer actually returned by writeFile(). class ZipArchiveWriter::WriteBufferFromZipArchive : public WriteBufferFromFileBase { public: - WriteBufferFromZipArchive(HandleHolder && handle_, const String & filename_) + WriteBufferFromZipArchive(std::shared_ptr archive_writer_, const String & filename_) : WriteBufferFromFileBase(DBMS_DEFAULT_BUFFER_SIZE, nullptr, 0) - , handle(std::move(handle_)) , filename(filename_) { - auto compress_method = handle.getWriter()->compression_method; - auto compress_level = handle.getWriter()->compression_level; + zip_handle = archive_writer_->startWritingFile(); + archive_writer = archive_writer_; + + auto compress_method = archive_writer_->getCompressionMethod(); + auto compress_level = archive_writer_->getCompressionLevel(); checkCompressionMethodIsEnabled(compress_method); const char * password_cstr = nullptr; - const String & password_str = handle.getWriter()->password; - if (!password_str.empty()) + String current_password = archive_writer_->getPassword(); + if (!current_password.empty()) { checkEncryptionIsEnabled(); - password_cstr = password_str.c_str(); + password_cstr = current_password.c_str(); } - RawHandle raw_handle = handle.getRawHandle(); - - checkResult(zipOpenNewFileInZip3_64( - raw_handle, + int code = zipOpenNewFileInZip3_64( + zip_handle, filename_.c_str(), /* zipfi= */ nullptr, /* extrafield_local= */ nullptr, @@ -110,21 +80,30 @@ public: /* strategy= */ 0, password_cstr, /* crc_for_crypting= */ 0, - /* zip64= */ true)); + /* zip64= */ true); + checkResultCode(code); } ~WriteBufferFromZipArchive() override { try { - finalize(); + closeFile(/* throw_if_error= */ false); + endWritingFile(); } catch (...) { - tryLogCurrentException("ZipArchiveWriter"); + tryLogCurrentException("WriteBufferFromZipArchive"); } } + void finalizeImpl() override + { + next(); + closeFile(/* throw_if_error= */ true); + endWritingFile(); + } + void sync() override { next(); } std::string getFileName() const override { return filename; } @@ -133,110 +112,106 @@ private: { if (!offset()) return; - RawHandle raw_handle = handle.getRawHandle(); - int code = zipWriteInFileInZip(raw_handle, working_buffer.begin(), static_cast(offset())); - checkResult(code); + chassert(zip_handle); + int code = zipWriteInFileInZip(zip_handle, working_buffer.begin(), static_cast(offset())); + checkResultCode(code); } - void checkResult(int code) const { handle.checkResult(code); } + void closeFile(bool throw_if_error) + { + if (zip_handle) + { + int code = zipCloseFileInZip(zip_handle); + zip_handle = nullptr; + if (throw_if_error) + checkResultCode(code); + } + } - HandleHolder handle; - String filename; + void endWritingFile() + { + if (auto archive_writer_ptr = archive_writer.lock()) + { + archive_writer_ptr->endWritingFile(); + archive_writer.reset(); + } + } + + void checkResultCode(int code) const { checkResultCodeImpl(code, filename); } + + std::weak_ptr archive_writer; + const String filename; + ZipHandle zip_handle; }; -namespace +/// Provides a set of functions allowing the minizip library to write its output +/// to a WriteBuffer instead of an ordinary file in the local filesystem. +class ZipArchiveWriter::StreamInfo { - /// Provides a set of functions allowing the minizip library to write its output - /// to a WriteBuffer instead of an ordinary file in the local filesystem. - class StreamFromWriteBuffer +public: + explicit StreamInfo(std::unique_ptr write_buffer_) + : write_buffer(std::move(write_buffer_)), start_offset(write_buffer->count()) { - public: - static RawHandle open(std::unique_ptr archive_write_buffer) - { - Opaque opaque{std::move(archive_write_buffer)}; + } - zlib_filefunc64_def func_def; - func_def.zopen64_file = &StreamFromWriteBuffer::openFileFunc; - func_def.zclose_file = &StreamFromWriteBuffer::closeFileFunc; - func_def.zread_file = &StreamFromWriteBuffer::readFileFunc; - func_def.zwrite_file = &StreamFromWriteBuffer::writeFileFunc; - func_def.zseek64_file = &StreamFromWriteBuffer::seekFunc; - func_def.ztell64_file = &StreamFromWriteBuffer::tellFunc; - func_def.zerror_file = &StreamFromWriteBuffer::testErrorFunc; - func_def.opaque = &opaque; + ~StreamInfo() = default; - return zipOpen2_64( - /* path= */ nullptr, - /* append= */ false, - /* globalcomment= */ nullptr, - &func_def); - } + ZipHandle makeZipHandle() + { + zlib_filefunc64_def func_def; + func_def.zopen64_file = &StreamInfo::openFileFunc; + func_def.zclose_file = &StreamInfo::closeFileFunc; + func_def.zread_file = &StreamInfo::readFileFunc; + func_def.zwrite_file = &StreamInfo::writeFileFunc; + func_def.zseek64_file = &StreamInfo::seekFunc; + func_def.ztell64_file = &StreamInfo::tellFunc; + func_def.zerror_file = &StreamInfo::testErrorFunc; + func_def.opaque = this; - private: - std::unique_ptr write_buffer; - UInt64 start_offset = 0; + return zipOpen2_64( + /* path= */ nullptr, + /* append= */ false, + /* globalcomment= */ nullptr, + &func_def); + } - struct Opaque - { - std::unique_ptr write_buffer; - }; + WriteBuffer & getWriteBuffer() { return *write_buffer; } - static void * openFileFunc(void * opaque, const void *, int) - { - Opaque & opq = *reinterpret_cast(opaque); - return new StreamFromWriteBuffer(std::move(opq.write_buffer)); - } +private: + /// We do nothing in openFileFunc() and in closeFileFunc() because we already have `write_buffer` (file is already opened). + static void * openFileFunc(void * opaque, const void *, int) { return opaque; } + static int closeFileFunc(void *, void *) { return ZIP_OK; } - explicit StreamFromWriteBuffer(std::unique_ptr write_buffer_) - : write_buffer(std::move(write_buffer_)), start_offset(write_buffer->count()) {} + static unsigned long writeFileFunc(void * opaque, void *, const void * buf, unsigned long size) // NOLINT(google-runtime-int) + { + auto * stream_info = reinterpret_cast(opaque); + stream_info->write_buffer->write(reinterpret_cast(buf), size); + return size; + } - ~StreamFromWriteBuffer() - { - write_buffer->finalize(); - } + static int testErrorFunc(void *, void *) { return ZIP_OK; } - static int closeFileFunc(void *, void * stream) - { - delete reinterpret_cast(stream); - return ZIP_OK; - } + static ZPOS64_T tellFunc(void * opaque, void *) + { + auto * stream_info = reinterpret_cast(opaque); + auto pos = stream_info->write_buffer->count() - stream_info->start_offset; + return pos; + } - static StreamFromWriteBuffer & get(void * ptr) - { - return *reinterpret_cast(ptr); - } + static long seekFunc(void *, void *, ZPOS64_T, int) // NOLINT(google-runtime-int) + { + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "StreamInfo::seek() is not implemented"); + } - static unsigned long writeFileFunc(void *, void * stream, const void * buf, unsigned long size) // NOLINT(google-runtime-int) - { - auto & strm = get(stream); - strm.write_buffer->write(reinterpret_cast(buf), size); - return size; - } + static unsigned long readFileFunc(void *, void *, void *, unsigned long) // NOLINT(google-runtime-int) + { + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "StreamInfo::readFile() is not implemented"); + } - static int testErrorFunc(void *, void *) - { - return ZIP_OK; - } - - static ZPOS64_T tellFunc(void *, void * stream) - { - auto & strm = get(stream); - auto pos = strm.write_buffer->count() - strm.start_offset; - return pos; - } - - static long seekFunc(void *, void *, ZPOS64_T, int) // NOLINT(google-runtime-int) - { - throw Exception(ErrorCodes::LOGICAL_ERROR, "StreamFromWriteBuffer::seek must not be called"); - } - - static unsigned long readFileFunc(void *, void *, void *, unsigned long) // NOLINT(google-runtime-int) - { - throw Exception(ErrorCodes::LOGICAL_ERROR, "StreamFromWriteBuffer::readFile must not be called"); - } - }; -} + std::unique_ptr write_buffer; + UInt64 start_offset; +}; ZipArchiveWriter::ZipArchiveWriter(const String & path_to_archive_) @@ -248,21 +223,42 @@ ZipArchiveWriter::ZipArchiveWriter(const String & path_to_archive_, std::unique_ : path_to_archive(path_to_archive_), compression_method(MZ_COMPRESS_METHOD_DEFLATE) { if (archive_write_buffer_) - handle = StreamFromWriteBuffer::open(std::move(archive_write_buffer_)); + { + stream_info = std::make_unique(std::move(archive_write_buffer_)); + zip_handle = stream_info->makeZipHandle(); + } else - handle = zipOpen64(path_to_archive.c_str(), /* append= */ false); - if (!handle) - throw Exception(ErrorCodes::CANNOT_PACK_ARCHIVE, "Couldn't create zip archive {}", quoteString(path_to_archive)); + { + zip_handle = zipOpen64(path_to_archive.c_str(), /* append= */ false); + } + if (!zip_handle) + throw Exception(ErrorCodes::CANNOT_PACK_ARCHIVE, "Couldn't create zip archive {}", quoteString(path_to_archive)); } ZipArchiveWriter::~ZipArchiveWriter() { - if (handle) + if (!finalized) + { + /// It is totally OK to destroy instance without finalization when an exception occurs. + /// However it is suspicious to destroy instance without finalization at the green path. + if (!std::uncaught_exceptions() && std::current_exception() == nullptr) + { + Poco::Logger * log = &Poco::Logger::get("ZipArchiveWriter"); + LOG_ERROR(log, + "ZipArchiveWriter is not finalized when destructor is called. " + "The zip archive might not be written at all or might be truncated. " + "Stack trace: {}", StackTrace().toString()); + chassert(false && "ZipArchiveWriter is not finalized in destructor."); + } + } + + if (zip_handle) { try { - checkResult(zipClose(handle, /* global_comment= */ nullptr)); + zipCloseFileInZip(zip_handle); + zipClose(zip_handle, /* global_comment= */ nullptr); } catch (...) { @@ -273,13 +269,38 @@ ZipArchiveWriter::~ZipArchiveWriter() std::unique_ptr ZipArchiveWriter::writeFile(const String & filename) { - return std::make_unique(acquireHandle(), filename); + return std::make_unique(std::static_pointer_cast(shared_from_this()), filename); } bool ZipArchiveWriter::isWritingFile() const { std::lock_guard lock{mutex}; - return !handle; + return is_writing_file; +} + +void ZipArchiveWriter::finalize() +{ + std::lock_guard lock{mutex}; + if (finalized) + return; + + if (is_writing_file) + throw Exception(ErrorCodes::LOGICAL_ERROR, "ZipArchiveWriter::finalize() is called in the middle of writing a file into the zip archive. That's not allowed"); + + if (zip_handle) + { + int code = zipClose(zip_handle, /* global_comment= */ nullptr); + zip_handle = nullptr; + checkResultCode(code); + } + + if (stream_info) + { + stream_info->getWriteBuffer().finalize(); + stream_info.reset(); + } + + finalized = true; } void ZipArchiveWriter::setCompression(const String & compression_method_, int compression_level_) @@ -289,12 +310,30 @@ void ZipArchiveWriter::setCompression(const String & compression_method_, int co compression_level = compression_level_; } +int ZipArchiveWriter::getCompressionMethod() const +{ + std::lock_guard lock{mutex}; + return compression_method; +} + +int ZipArchiveWriter::getCompressionLevel() const +{ + std::lock_guard lock{mutex}; + return compression_level; +} + void ZipArchiveWriter::setPassword(const String & password_) { std::lock_guard lock{mutex}; password = password_; } +String ZipArchiveWriter::getPassword() const +{ + std::lock_guard lock{mutex}; + return password; +} + int ZipArchiveWriter::compressionMethodToInt(const String & compression_method_) { if (compression_method_.empty()) @@ -361,45 +400,24 @@ void ZipArchiveWriter::checkEncryptionIsEnabled() #endif } -ZipArchiveWriter::HandleHolder ZipArchiveWriter::acquireHandle() -{ - return HandleHolder{std::static_pointer_cast(shared_from_this())}; -} - -RawHandle ZipArchiveWriter::acquireRawHandle() +ZipArchiveWriter::ZipHandle ZipArchiveWriter::startWritingFile() { std::lock_guard lock{mutex}; - if (!handle) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot have more than one write buffer while writing a zip archive"); - return std::exchange(handle, nullptr); + if (is_writing_file) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Cannot write two files to a zip archive in parallel"); + is_writing_file = true; + return zip_handle; } -void ZipArchiveWriter::releaseRawHandle(RawHandle raw_handle_) +void ZipArchiveWriter::endWritingFile() { std::lock_guard lock{mutex}; - handle = raw_handle_; + is_writing_file = false; } -void ZipArchiveWriter::checkResult(int code) const +void ZipArchiveWriter::checkResultCode(int code) const { - if (code >= ZIP_OK) - return; - - String message = "Code = "; - switch (code) - { - case ZIP_ERRNO: message += "ERRNO, errno = " + errnoToString(); break; - case ZIP_PARAMERROR: message += "PARAMERROR"; break; - case ZIP_BADZIPFILE: message += "BADZIPFILE"; break; - case ZIP_INTERNALERROR: message += "INTERNALERROR"; break; - default: message += std::to_string(code); break; - } - showError(message); -} - -void ZipArchiveWriter::showError(const String & message) const -{ - throw Exception(ErrorCodes::CANNOT_PACK_ARCHIVE, "Couldn't pack zip archive {}: {}", quoteString(path_to_archive), message); + checkResultCodeImpl(code, path_to_archive); } } diff --git a/src/IO/Archives/ZipArchiveWriter.h b/src/IO/Archives/ZipArchiveWriter.h index a54130556b3..891da1a2e75 100644 --- a/src/IO/Archives/ZipArchiveWriter.h +++ b/src/IO/Archives/ZipArchiveWriter.h @@ -4,6 +4,7 @@ #if USE_MINIZIP #include +#include #include @@ -22,7 +23,7 @@ public: /// Constructs an archive that will be written by using a specified `archive_write_buffer_`. ZipArchiveWriter(const String & path_to_archive_, std::unique_ptr archive_write_buffer_); - /// Destructors finalizes writing the archive. + /// Call finalize() before destructing IArchiveWriter. ~ZipArchiveWriter() override; /// Starts writing a file to the archive. The function returns a write buffer, @@ -35,6 +36,10 @@ public: /// This function should be used mostly for debugging purposes. bool isWritingFile() const override; + /// Finalizes writing of the archive. This function must be always called at the end of writing. + /// (Unless an error appeared and the archive is in fact no longer needed.) + void finalize() override; + /// Supported compression methods. static constexpr const char kStore[] = "store"; static constexpr const char kDeflate[] = "deflate"; @@ -68,22 +73,27 @@ public: static void checkEncryptionIsEnabled(); private: + class StreamInfo; + using ZipHandle = void *; class WriteBufferFromZipArchive; - class HandleHolder; - using RawHandle = void *; - HandleHolder acquireHandle(); - RawHandle acquireRawHandle(); - void releaseRawHandle(RawHandle raw_handle_); + int getCompressionMethod() const; + int getCompressionLevel() const; + String getPassword() const; - void checkResult(int code) const; - [[noreturn]] void showError(const String & message) const; + ZipHandle startWritingFile(); + void endWritingFile(); + + void checkResultCode(int code) const; const String path_to_archive; - int compression_method; /// By default the compression method is "deflate". - int compression_level = kDefaultCompressionLevel; - String password; - RawHandle handle = nullptr; + std::unique_ptr TSA_GUARDED_BY(mutex) stream_info; + int compression_method TSA_GUARDED_BY(mutex); /// By default the compression method is "deflate". + int compression_level TSA_GUARDED_BY(mutex) = kDefaultCompressionLevel; + String password TSA_GUARDED_BY(mutex); + ZipHandle zip_handle TSA_GUARDED_BY(mutex) = nullptr; + bool is_writing_file TSA_GUARDED_BY(mutex) = false; + bool finalized TSA_GUARDED_BY(mutex) = false; mutable std::mutex mutex; }; diff --git a/src/IO/tests/gtest_archive_reader_and_writer.cpp b/src/IO/tests/gtest_archive_reader_and_writer.cpp index b48955c25e7..37fbdff901a 100644 --- a/src/IO/tests/gtest_archive_reader_and_writer.cpp +++ b/src/IO/tests/gtest_archive_reader_and_writer.cpp @@ -102,7 +102,8 @@ TEST_P(ArchiveReaderAndWriterTest, EmptyArchive) { /// Make an archive. { - createArchiveWriter(getPathToArchive()); + auto writer = createArchiveWriter(getPathToArchive()); + writer->finalize(); } /// The created archive can be found in the local filesystem. @@ -132,7 +133,9 @@ TEST_P(ArchiveReaderAndWriterTest, SingleFileInArchive) { auto out = writer->writeFile("a.txt"); writeString(contents, *out); + out->finalize(); } + writer->finalize(); } /// Read the archive. @@ -198,11 +201,14 @@ TEST_P(ArchiveReaderAndWriterTest, TwoFilesInArchive) { auto out = writer->writeFile("a.txt"); writeString(a_contents, *out); + out->finalize(); } { auto out = writer->writeFile("b/c.txt"); writeString(c_contents, *out); + out->finalize(); } + writer->finalize(); } /// Read the archive. @@ -281,11 +287,14 @@ TEST_P(ArchiveReaderAndWriterTest, InMemory) { auto out = writer->writeFile("a.txt"); writeString(a_contents, *out); + out->finalize(); } { auto out = writer->writeFile("b.txt"); writeString(b_contents, *out); + out->finalize(); } + writer->finalize(); } /// The created archive is really in memory. @@ -335,7 +344,9 @@ TEST_P(ArchiveReaderAndWriterTest, Password) { auto out = writer->writeFile("a.txt"); writeString(contents, *out); + out->finalize(); } + writer->finalize(); } /// Read the archive. diff --git a/tests/integration/test_backup_restore_s3/test.py b/tests/integration/test_backup_restore_s3/test.py index 478124ad41b..cd8f70b3239 100644 --- a/tests/integration/test_backup_restore_s3/test.py +++ b/tests/integration/test_backup_restore_s3/test.py @@ -445,3 +445,10 @@ def test_backup_with_fs_cache( # see MergeTreeData::initializeDirectoriesAndFormatVersion() if "CachedWriteBufferCacheWriteBytes" in restore_events: assert restore_events["CachedWriteBufferCacheWriteBytes"] <= 1 + + +def test_backup_to_zip(): + storage_policy = "default" + backup_name = new_backup_name() + backup_destination = f"S3('http://minio1:9001/root/data/backups/{backup_name}.zip', 'minio', 'minio123')" + check_backup_and_restore(storage_policy, backup_destination) From e98c49a58fb87e08c8807c28a9c38344d6d9c627 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 24 Dec 2023 05:44:24 +0100 Subject: [PATCH 229/253] Fix a benign error in archive reader --- src/Storages/StorageFile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/StorageFile.cpp b/src/Storages/StorageFile.cpp index bc4db8f1998..25bb6691ff6 100644 --- a/src/Storages/StorageFile.cpp +++ b/src/Storages/StorageFile.cpp @@ -1935,7 +1935,7 @@ void StorageFile::parseFileSource(String source, String & filename, String & pat } std::string_view path_to_archive_view = std::string_view{source}.substr(0, pos); - while (path_to_archive_view.back() == ' ') + while (path_to_archive_view.ends_with(' ')) path_to_archive_view.remove_suffix(1); if (path_to_archive_view.empty()) From f582a9b39c8ba141f3bbca0945ce6418cd6126ba Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 24 Dec 2023 05:55:53 +0100 Subject: [PATCH 230/253] Add a test --- tests/queries/0_stateless/02952_archive_parsing.reference | 0 tests/queries/0_stateless/02952_archive_parsing.sql | 1 + 2 files changed, 1 insertion(+) create mode 100644 tests/queries/0_stateless/02952_archive_parsing.reference create mode 100644 tests/queries/0_stateless/02952_archive_parsing.sql diff --git a/tests/queries/0_stateless/02952_archive_parsing.reference b/tests/queries/0_stateless/02952_archive_parsing.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02952_archive_parsing.sql b/tests/queries/0_stateless/02952_archive_parsing.sql new file mode 100644 index 00000000000..49b0223e6ec --- /dev/null +++ b/tests/queries/0_stateless/02952_archive_parsing.sql @@ -0,0 +1 @@ +SELECT * FROM file('::a'); -- { serverError BAD_ARGUMENTS } From 71c5ae548ffe3484e770e97c0c8b2009042a808c Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 24 Dec 2023 07:57:43 +0300 Subject: [PATCH 231/253] Revert "Fix leftover processes/hangs in tests" --- tests/clickhouse-test | 4 ++-- tests/queries/shell_config.sh | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 7bf1fcee390..9f66f49837f 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1031,7 +1031,7 @@ class TestCase: if proc: if proc.returncode is None: try: - os.killpg(os.getpgid(proc.pid), signal.SIGKILL) + proc.kill() except OSError as e: if e.errno != ESRCH: raise @@ -1307,7 +1307,7 @@ class TestCase: command = pattern.format(**params) - proc = Popen(command, shell=True, env=os.environ, start_new_session=True) + proc = Popen(command, shell=True, env=os.environ) while ( datetime.now() - start_time diff --git a/tests/queries/shell_config.sh b/tests/queries/shell_config.sh index 63f6ba9a914..c687a63623f 100644 --- a/tests/queries/shell_config.sh +++ b/tests/queries/shell_config.sh @@ -185,11 +185,3 @@ function query_with_retry done echo "Query '$query' failed with '$result'" } - -# Add --foreground to avoid running setpgid() in timeout, otherwise -# clickhouse-test will not kill those processes in case of timeout -function timeout() -{ - command timeout --foreground "$@" -} -export -f timeout From a3f204cb7ef78cbf81e6aaa4423ede99714516f4 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Sun, 24 Dec 2023 02:58:34 +0000 Subject: [PATCH 232/253] Fix base address for ranges --- .../Formats/Impl/DWARFBlockInputFormat.cpp | 89 +++++++++++-------- .../Formats/Impl/DWARFBlockInputFormat.h | 13 ++- 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp b/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp index b9f65dd49b0..4c3bb219415 100644 --- a/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/DWARFBlockInputFormat.cpp @@ -308,6 +308,41 @@ static inline void throwIfError(llvm::Error & e, const char * what) throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, "Failed to parse {}: {}", what, llvm::toString(std::move(e))); } +llvm::DWARFFormValue DWARFBlockInputFormat::parseAttribute( + const llvm::DWARFAbbreviationDeclaration::AttributeSpec & attr, uint64_t * offset, + const UnitState & unit) const +{ + auto val = llvm::DWARFFormValue::createFromSValue( + attr.Form, attr.isImplicitConst() ? attr.getImplicitConstValue() : 0); + if (!val.extractValue(*extractor, offset, unit.dwarf_unit->getFormParams(), unit.dwarf_unit)) + throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, + "Failed to parse attribute {} of form {} at offset {}", + llvm::dwarf::AttributeString(attr.Attr), attr.Form, *offset); + return val; +} + +void DWARFBlockInputFormat::skipAttribute( + const llvm::DWARFAbbreviationDeclaration::AttributeSpec & attr, uint64_t * offset, + const UnitState & unit) const +{ + if (!llvm::DWARFFormValue::skipValue( + attr.Form, *extractor, offset, unit.dwarf_unit->getFormParams())) + throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, + "Failed to skip attribute {} of form {} at offset {}", + llvm::dwarf::AttributeString(attr.Attr), attr.Form, *offset); +} + +uint64_t DWARFBlockInputFormat::parseAddress(llvm::dwarf::Attribute attr, const llvm::DWARFFormValue & val, const UnitState & unit) +{ + if (val.getForm() == llvm::dwarf::DW_FORM_addr) + return val.getRawUValue(); + if (val.getForm() == llvm::dwarf::DW_FORM_addrx || + (val.getForm() >= llvm::dwarf::DW_FORM_addrx1 && + val.getForm() <= llvm::dwarf::DW_FORM_addrx4)) + return fetchFromDebugAddr(unit.debug_addr_base, val.getRawUValue()); + throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, "Form {} for {} is not supported", llvm::dwarf::FormEncodingString(val.getForm()), llvm::dwarf::AttributeString(attr)); +} + Chunk DWARFBlockInputFormat::parseEntries(UnitState & unit) { const auto & header = getPort().getHeader(); @@ -315,7 +350,6 @@ Chunk DWARFBlockInputFormat::parseEntries(UnitState & unit) std::array need{}; for (const std::string & name : header.getNames()) need[column_name_to_idx.at(name)] = true; - auto form_params = unit.dwarf_unit->getFormParams(); /// For parallel arrays, we nominate one of them to be responsible for populating the offsets vector. need[COL_ATTR_NAME] = need[COL_ATTR_NAME] || need[COL_ATTR_FORM] || need[COL_ATTR_INT] || need[COL_ATTR_STR]; @@ -396,32 +430,26 @@ Chunk DWARFBlockInputFormat::parseEntries(UnitState & unit) /// rely on them. (Why couldn't DWARF just promise that these attributes must appear /// before any attributes that depend on them?) uint64_t offset = unit.offset; + std::optional low_pc; for (auto attr : abbrev->attributes()) { if (attr.Attr == llvm::dwarf::DW_AT_addr_base || attr.Attr == llvm::dwarf::DW_AT_rnglists_base) { - auto val = llvm::DWARFFormValue::createFromSValue( - attr.Form, attr.isImplicitConst() ? attr.getImplicitConstValue() : 0); - if (!val.extractValue(*extractor, &offset, form_params, unit.dwarf_unit)) - throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, - "Failed to parse attribute {} of form {} at offset {}", - llvm::dwarf::AttributeString(attr.Attr), attr.Form, unit.offset); - uint64_t v = val.getRawUValue(); + auto val = parseAttribute(attr, &offset, unit); if (attr.Attr == llvm::dwarf::DW_AT_addr_base) - unit.addr_base = v; + unit.debug_addr_base = val.getRawUValue(); else - unit.rnglists_base = v; + unit.rnglists_base = val.getRawUValue(); } + else if (attr.Attr == llvm::dwarf::DW_AT_low_pc) + low_pc = parseAttribute(attr, &offset, unit); else - { - if (!llvm::DWARFFormValue::skipValue( - attr.Form, *extractor, &offset, form_params)) - throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, - "Failed to skip attribute {} of form {} at offset {}", - llvm::dwarf::AttributeString(attr.Attr), attr.Form, offset); - } + skipAttribute(attr, &offset, unit); } + /// May use addr_base. + if (low_pc.has_value()) + unit.base_address = parseAddress(llvm::dwarf::DW_AT_low_pc, *low_pc, unit); } bool need_name = need[COL_NAME]; @@ -444,11 +472,7 @@ Chunk DWARFBlockInputFormat::parseEntries(UnitState & unit) for (auto attr : abbrev->attributes()) { - auto val = llvm::DWARFFormValue::createFromSValue(attr.Form, attr.isImplicitConst() ? attr.getImplicitConstValue() : 0); - /// This is relatively slow, maybe we should reimplement it. - if (!val.extractValue(*extractor, &unit.offset, form_params, unit.dwarf_unit)) - throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, "Failed to parse attribute {} of form {} at offset {}", - llvm::dwarf::AttributeString(attr.Attr), attr.Form, unit.offset); + auto val = parseAttribute(attr, &unit.offset, unit); if (need[COL_ATTR_NAME]) col_attr_name->insertValue(attr.Attr); @@ -542,16 +566,7 @@ Chunk DWARFBlockInputFormat::parseEntries(UnitState & unit) if (need_ranges && (attr.Attr == llvm::dwarf::DW_AT_low_pc || attr.Attr == llvm::dwarf::DW_AT_high_pc)) { - UInt64 addr; - if (val.getForm() == llvm::dwarf::DW_FORM_addr) - addr = val.getRawUValue(); - else if (val.getForm() == llvm::dwarf::DW_FORM_addrx || - (val.getForm() >= llvm::dwarf::DW_FORM_addrx1 && - val.getForm() <= llvm::dwarf::DW_FORM_addrx4)) - addr = fetchFromDebugAddr(unit.addr_base, val.getRawUValue()); - else - throw Exception(ErrorCodes::CANNOT_PARSE_DWARF, "Form {} for {} is not supported", llvm::dwarf::FormEncodingString(val.getForm()), llvm::dwarf::AttributeString(attr.Attr)); - + UInt64 addr = parseAddress(attr.Attr, val, unit); if (attr.Attr == llvm::dwarf::DW_AT_low_pc) low_pc = addr; else @@ -645,7 +660,7 @@ Chunk DWARFBlockInputFormat::parseEntries(UnitState & unit) if (need_ranges) { if (ranges.has_value()) - parseRanges(*ranges, ranges_rnglistx, low_pc, unit, col_ranges_start, col_ranges_end); + parseRanges(*ranges, ranges_rnglistx, unit, col_ranges_start, col_ranges_end); else if (low_pc.has_value()) { UInt64 high; @@ -810,12 +825,12 @@ uint64_t DWARFBlockInputFormat::fetchFromDebugAddr(uint64_t addr_base, uint64_t } void DWARFBlockInputFormat::parseRanges( - uint64_t offset, bool form_rnglistx, std::optional low_pc, const UnitState & unit, const ColumnVector::MutablePtr & col_ranges_start, + uint64_t offset, bool form_rnglistx, const UnitState & unit, const ColumnVector::MutablePtr & col_ranges_start, const ColumnVector::MutablePtr & col_ranges_end) const { llvm::Optional base_addr; - if (low_pc.has_value()) - base_addr = llvm::object::SectionedAddress{.Address = *low_pc}; + if (unit.base_address != UINT64_MAX) + base_addr = llvm::object::SectionedAddress{.Address = unit.base_address}; llvm::DWARFAddressRangesVector ranges; @@ -860,7 +875,7 @@ void DWARFBlockInputFormat::parseRanges( auto lookup_addr = [&](uint32_t idx) -> llvm::Optional { - uint64_t addr = fetchFromDebugAddr(unit.addr_base, idx); + uint64_t addr = fetchFromDebugAddr(unit.debug_addr_base, idx); return llvm::object::SectionedAddress{.Address = addr}; }; ranges = list.getAbsoluteRanges(base_addr, /*AddressByteSize*/ 8, lookup_addr); diff --git a/src/Processors/Formats/Impl/DWARFBlockInputFormat.h b/src/Processors/Formats/Impl/DWARFBlockInputFormat.h index 366671210bb..e1409dd3373 100644 --- a/src/Processors/Formats/Impl/DWARFBlockInputFormat.h +++ b/src/Processors/Formats/Impl/DWARFBlockInputFormat.h @@ -54,8 +54,10 @@ private: ColumnPtr filename_table; // from .debug_line size_t filename_table_size = 0; /// Starting offset of this unit's data in .debug_addr and .debug_rnglists sections. - uint64_t addr_base = UINT64_MAX; + uint64_t debug_addr_base = UINT64_MAX; uint64_t rnglists_base = UINT64_MAX; + /// "Base address" for parsing range lists. Not to be confused with "addr base". + uint64_t base_address = UINT64_MAX; uint64_t offset = 0; std::vector stack; @@ -103,11 +105,18 @@ private: void parseFilenameTable(UnitState & unit, uint64_t offset); Chunk parseEntries(UnitState & unit); + llvm::DWARFFormValue parseAttribute( + const llvm::DWARFAbbreviationDeclaration::AttributeSpec & attr, uint64_t * offset, + const UnitState & unit) const; + void skipAttribute( + const llvm::DWARFAbbreviationDeclaration::AttributeSpec & attr, uint64_t * offset, + const UnitState & unit) const; + uint64_t parseAddress(llvm::dwarf::Attribute attr, const llvm::DWARFFormValue & val, const UnitState & unit); /// Parse .debug_addr entry. uint64_t fetchFromDebugAddr(uint64_t addr_base, uint64_t idx) const; /// Parse .debug_ranges (DWARF4) or .debug_rnglists (DWARF5) entry. void parseRanges( - uint64_t offset, bool form_rnglistx, std::optional low_pc, const UnitState & unit, + uint64_t offset, bool form_rnglistx, const UnitState & unit, const ColumnVector::MutablePtr & col_ranges_start, const ColumnVector::MutablePtr & col_ranges_end) const; }; From 1fdee81ad503a666eea04eb438e369a4c429c109 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 23 Dec 2023 15:42:49 +0100 Subject: [PATCH 233/253] Fix leftover processes/hangs in tests One of such cases is 02479_race_condition_between_insert_and_droppin_mv [1], yes it can be fixed (by using fixed number of iterations, or with some bash trickery), but it is better to fix them completelly, eventually such tests will be submitted and pass review anyway. By allocating process group for each test we can kill all the processes in this process group, and this what this patch does. This will also fix some test hangs (like in [1]) as well as some possible issues in stress tests. [1]: https://s3.amazonaws.com/clickhouse-test-reports/0/e2c1230b00386c4d0096a245396ab3be7ce60950/stateless_tests__release__analyzer_/run.log Signed-off-by: Azat Khuzhin (cherry picked from commit 72fa58e19217cb38abe799db793c21b113fd0b77) --- tests/clickhouse-test | 4 ++-- tests/queries/shell_config.sh | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 9f66f49837f..7bf1fcee390 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1031,7 +1031,7 @@ class TestCase: if proc: if proc.returncode is None: try: - proc.kill() + os.killpg(os.getpgid(proc.pid), signal.SIGKILL) except OSError as e: if e.errno != ESRCH: raise @@ -1307,7 +1307,7 @@ class TestCase: command = pattern.format(**params) - proc = Popen(command, shell=True, env=os.environ) + proc = Popen(command, shell=True, env=os.environ, start_new_session=True) while ( datetime.now() - start_time diff --git a/tests/queries/shell_config.sh b/tests/queries/shell_config.sh index c687a63623f..63f6ba9a914 100644 --- a/tests/queries/shell_config.sh +++ b/tests/queries/shell_config.sh @@ -185,3 +185,11 @@ function query_with_retry done echo "Query '$query' failed with '$result'" } + +# Add --foreground to avoid running setpgid() in timeout, otherwise +# clickhouse-test will not kill those processes in case of timeout +function timeout() +{ + command timeout --foreground "$@" +} +export -f timeout From 5899ee99bd30fcd1b1156aa8e465be910654eaf8 Mon Sep 17 00:00:00 2001 From: Pradeep Chhetri Date: Sun, 24 Dec 2023 18:07:26 +0800 Subject: [PATCH 234/253] Add support for specifying query parameters in the command line in clickhouse-local --- programs/local/LocalServer.cpp | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index 7357c239e6b..b425258b2d3 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -776,6 +776,7 @@ void LocalServer::processConfig() global_context->setQueryKindInitial(); global_context->setQueryKind(query_kind); + global_context->setQueryParameters(query_parameters); } @@ -822,6 +823,7 @@ void LocalServer::printHelpMessage([[maybe_unused]] const OptionsDescription & o std::cout << getHelpHeader() << "\n"; std::cout << options_description.main_description.value() << "\n"; std::cout << getHelpFooter() << "\n"; + std::cout << "In addition, --param_name=value can be specified for substitution of parameters for parametrized queries.\n"; #endif } @@ -898,7 +900,31 @@ void LocalServer::readArguments(int argc, char ** argv, Arguments & common_argum for (int arg_num = 1; arg_num < argc; ++arg_num) { std::string_view arg = argv[arg_num]; - if (arg == "--multiquery" && (arg_num + 1) < argc && !std::string_view(argv[arg_num + 1]).starts_with('-')) + /// Parameter arg after underline. + if (arg.starts_with("--param_")) + { + auto param_continuation = arg.substr(strlen("--param_")); + auto equal_pos = param_continuation.find_first_of('='); + + if (equal_pos == std::string::npos) + { + /// param_name value + ++arg_num; + if (arg_num >= argc) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Parameter requires value"); + arg = argv[arg_num]; + query_parameters.emplace(String(param_continuation), String(arg)); + } + else + { + if (equal_pos == 0) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Parameter name cannot be empty"); + + /// param_name=value + query_parameters.emplace(param_continuation.substr(0, equal_pos), param_continuation.substr(equal_pos + 1)); + } + } + else if (arg == "--multiquery" && (arg_num + 1) < argc && !std::string_view(argv[arg_num + 1]).starts_with('-')) { /// Transform the abbreviated syntax '--multiquery ' into the full syntax '--multiquery -q ' ++arg_num; From 0e89d01b94afaf40a0beeb5280dad4b4eabc473a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 24 Dec 2023 12:14:45 +0100 Subject: [PATCH 235/253] Binary (symbols) viewer --- programs/server/binary.html | 267 +++++++++++++++++++++++++++++ src/Server/HTTPHandlerFactory.cpp | 7 +- src/Server/WebUIRequestHandler.cpp | 7 +- 3 files changed, 279 insertions(+), 2 deletions(-) create mode 100644 programs/server/binary.html diff --git a/programs/server/binary.html b/programs/server/binary.html new file mode 100644 index 00000000000..988dd33a72a --- /dev/null +++ b/programs/server/binary.html @@ -0,0 +1,267 @@ + + + + + + ClickHouse Binary Viewer + + + + + +
+
+ + + + + diff --git a/src/Server/HTTPHandlerFactory.cpp b/src/Server/HTTPHandlerFactory.cpp index e1ee9586f83..66b55f68217 100644 --- a/src/Server/HTTPHandlerFactory.cpp +++ b/src/Server/HTTPHandlerFactory.cpp @@ -7,7 +7,6 @@ #include #include "HTTPHandler.h" -#include "NotFoundHandler.h" #include "StaticRequestHandler.h" #include "ReplicasStatusHandler.h" #include "InterserverIOHTTPHandler.h" @@ -161,6 +160,12 @@ void addCommonDefaultHandlersFactory(HTTPRequestHandlerFactoryMain & factory, IS factory.addPathToHints("/dashboard"); factory.addHandler(dashboard_handler); + auto binary_handler = std::make_shared>(server); + binary_handler->attachNonStrictPath("/binary"); + binary_handler->allowGetAndHeadRequest(); + factory.addPathToHints("/binary"); + factory.addHandler(binary_handler); + auto js_handler = std::make_shared>(server); js_handler->attachNonStrictPath("/js/"); js_handler->allowGetAndHeadRequest(); diff --git a/src/Server/WebUIRequestHandler.cpp b/src/Server/WebUIRequestHandler.cpp index 6277131fd5c..ad48b38b91a 100644 --- a/src/Server/WebUIRequestHandler.cpp +++ b/src/Server/WebUIRequestHandler.cpp @@ -1,7 +1,6 @@ #include "WebUIRequestHandler.h" #include "IServer.h" -#include #include #include @@ -25,6 +24,7 @@ INCBIN(resource_play_html, SOURCE_DIR "/programs/server/play.html"); INCBIN(resource_dashboard_html, SOURCE_DIR "/programs/server/dashboard.html"); INCBIN(resource_uplot_js, SOURCE_DIR "/programs/server/js/uplot.js"); +INCBIN(resource_binary_html, SOURCE_DIR "/programs/server/binary.html"); namespace DB @@ -68,6 +68,11 @@ void WebUIRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServerR *response.send() << html; } + else if (request.getURI().starts_with("/binary")) + { + response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_OK); + *response.send() << std::string_view(reinterpret_cast(gresource_binary_htmlData), gresource_binary_htmlSize); + } else if (request.getURI() == "/js/uplot.js") { response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_OK); From 7c18530a8ceb7b8b4153c25ed40ebcfc59e02baf Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 24 Dec 2023 12:19:31 +0100 Subject: [PATCH 236/253] Add a test --- tests/queries/0_stateless/02952_binary.reference | 1 + tests/queries/0_stateless/02952_binary.sh | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 tests/queries/0_stateless/02952_binary.reference create mode 100755 tests/queries/0_stateless/02952_binary.sh diff --git a/tests/queries/0_stateless/02952_binary.reference b/tests/queries/0_stateless/02952_binary.reference new file mode 100644 index 00000000000..8205460df96 --- /dev/null +++ b/tests/queries/0_stateless/02952_binary.reference @@ -0,0 +1 @@ +addressToSymbol diff --git a/tests/queries/0_stateless/02952_binary.sh b/tests/queries/0_stateless/02952_binary.sh new file mode 100755 index 00000000000..7c66c656841 --- /dev/null +++ b/tests/queries/0_stateless/02952_binary.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_PORT_HTTP_PROTO}://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_HTTP}/binary" | grep -oF --max-count 1 'addressToSymbol' From 658336f6745109e00ea579d205e001b9fb5cdcca Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 24 Dec 2023 12:22:31 +0100 Subject: [PATCH 237/253] Add documentation --- docs/en/interfaces/overview.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/interfaces/overview.md b/docs/en/interfaces/overview.md index e60aff927c4..0e09ab6a0b7 100644 --- a/docs/en/interfaces/overview.md +++ b/docs/en/interfaces/overview.md @@ -25,6 +25,7 @@ ClickHouse server provides embedded visual interfaces for power users: - Play UI: open `/play` in the browser; - Advanced Dashboard: open `/dashboard` in the browser; +- Binary symbols viewer for ClickHouse engineers: open `/binary` in the browser; There are also a wide range of third-party libraries for working with ClickHouse: From 1ed19fc8b10ab61e190d03a2b61b92e913d7ff75 Mon Sep 17 00:00:00 2001 From: Pradeep Chhetri Date: Sun, 24 Dec 2023 19:59:14 +0800 Subject: [PATCH 238/253] Add test --- .../02952_clickhouse_local_query_parameters_cli.reference | 1 + .../02952_clickhouse_local_query_parameters_cli.sh | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 tests/queries/0_stateless/02952_clickhouse_local_query_parameters_cli.reference create mode 100755 tests/queries/0_stateless/02952_clickhouse_local_query_parameters_cli.sh diff --git a/tests/queries/0_stateless/02952_clickhouse_local_query_parameters_cli.reference b/tests/queries/0_stateless/02952_clickhouse_local_query_parameters_cli.reference new file mode 100644 index 00000000000..9972842f982 --- /dev/null +++ b/tests/queries/0_stateless/02952_clickhouse_local_query_parameters_cli.reference @@ -0,0 +1 @@ +1 1 diff --git a/tests/queries/0_stateless/02952_clickhouse_local_query_parameters_cli.sh b/tests/queries/0_stateless/02952_clickhouse_local_query_parameters_cli.sh new file mode 100755 index 00000000000..5e9efbbf3ad --- /dev/null +++ b/tests/queries/0_stateless/02952_clickhouse_local_query_parameters_cli.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Tags: no-fasttest + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_LOCAL --param_x 1 -q "SELECT {x:UInt64}, {x:String};" From c5dbde8407d8e2fb2b1a9145ae3e0aec8a293fd5 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sun, 24 Dec 2023 13:14:13 +0100 Subject: [PATCH 239/253] Replace timeout --foreground with one workaround The problem with --foreground option is that it send the signal only to the process that had been spawned by timeout(1), while it can create lots of children, and when you killing parent you are closing pipes and childrens will get EPIPE, like in [1]. [1]: https://s3.amazonaws.com/clickhouse-test-reports/0/069f8bbb2f48541cc736903e1da5459fa2c27da0/stateless_tests__debug__%5B2_5%5D.html Another problem is that now child process will finish correctly, which may also print some errors like QUERY_WAS_CANCELLED (see [2]). [2]: https://s3.amazonaws.com/clickhouse-test-reports/0/ef66714bf20042ba9cb5d59b7839befe26110b93/stateless_tests__release__analyzer_.html In general this is not required actually, since all timeout invocations uses timeout value less then the default test limit (10min). But it may leave some processes in case of overriding this limit, i.e. `clickhouse-test --timeout 1` So to workaround this at least somehow, let's send SIGTERM and only after some timeout (here I use 0.1), SIGKILL. This will give at least some ability to terminate all childrens that had been spawned by timeout(1). Signed-off-by: Azat Khuzhin --- tests/clickhouse-test | 19 ++++++++++++++++++- tests/queries/shell_config.sh | 8 -------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 7bf1fcee390..1175d8342b1 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1031,7 +1031,24 @@ class TestCase: if proc: if proc.returncode is None: try: - os.killpg(os.getpgid(proc.pid), signal.SIGKILL) + pgid = os.getpgid(proc.pid) + # NOTE: this still may leave some processes, that had been + # created by timeout(1), since it also creates new process + # group. But this should not be a problem with default + # options, since the default time for each test is 10min, + # and this is way more bigger then the timeout for each + # timeout(1) invocation. + # + # But as a workaround we are sending SIGTERM first, and + # only after SIGKILL, that way timeout(1) will have an + # ability to terminate childrens (though not always since + # signals are asynchronous). + os.killpg(pgid, signal.SIGTERM) + # This may not be enough, but this is at least something + # (and anyway it is OK to spend 0.1 second more in case of + # test timeout). + sleep(0.1) + os.killpg(pgid, signal.SIGKILL) except OSError as e: if e.errno != ESRCH: raise diff --git a/tests/queries/shell_config.sh b/tests/queries/shell_config.sh index 63f6ba9a914..c687a63623f 100644 --- a/tests/queries/shell_config.sh +++ b/tests/queries/shell_config.sh @@ -185,11 +185,3 @@ function query_with_retry done echo "Query '$query' failed with '$result'" } - -# Add --foreground to avoid running setpgid() in timeout, otherwise -# clickhouse-test will not kill those processes in case of timeout -function timeout() -{ - command timeout --foreground "$@" -} -export -f timeout From 435e1de7b0f1bf62e4bfe516eff1e744720776be Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 23 Dec 2023 13:36:20 +0100 Subject: [PATCH 240/253] Remove Exception's ctor to create it from a simple string-like object This may cause troubles, like forgetting to pass arguments, and there are few places in the code (see the upcomming patch). I doubt that this will make any performance changes, since the check should be compile time. And anyway Exception is an exceptional situation which should be rare (there is no such code with single argument for logging, while logging is more common). Signed-off-by: Azat Khuzhin --- src/Common/Exception.h | 9 --------- src/Common/ZooKeeper/IKeeper.h | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/Common/Exception.h b/src/Common/Exception.h index 9b2507794bb..aabc848b230 100644 --- a/src/Common/Exception.h +++ b/src/Common/Exception.h @@ -93,15 +93,6 @@ public: return Exception(msg, code, remote_); } - /// Message must be a compile-time constant - template - requires std::is_convertible_v - Exception(int code, T && message) : Exception(message, code) - { - capture_thread_frame_pointers = thread_frame_pointers; - message_format_string = tryGetStaticFormatString(message); - } - /// These creators are for messages that were received by network or generated by a third-party library in runtime. /// Please use a constructor for all other cases. static Exception createRuntime(int code, const String & message) { return Exception(message, code); } diff --git a/src/Common/ZooKeeper/IKeeper.h b/src/Common/ZooKeeper/IKeeper.h index 80dee2b5c81..76cdfe9f230 100644 --- a/src/Common/ZooKeeper/IKeeper.h +++ b/src/Common/ZooKeeper/IKeeper.h @@ -471,7 +471,7 @@ private: /// Message must be a compile-time constant template requires std::is_convertible_v - Exception(T && message, const Error code_) : DB::Exception(DB::ErrorCodes::KEEPER_EXCEPTION, std::forward(message)), code(code_) + Exception(T && message, const Error code_) : DB::Exception(std::forward(message), DB::ErrorCodes::KEEPER_EXCEPTION, /* remote_= */ false), code(code_) { incrementErrorMetrics(code); } From 3be3b0a280793b7de0dacca59876e93999f721f0 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 23 Dec 2023 13:37:57 +0100 Subject: [PATCH 241/253] Fix incorrect Exceptions Signed-off-by: Azat Khuzhin --- src/AggregateFunctions/AggregateFunctionFactory.cpp | 4 ++-- src/AggregateFunctions/IAggregateFunction.h | 2 +- src/Analyzer/Passes/CNF.cpp | 3 ++- src/Backups/BackupIO_S3.cpp | 4 ++-- src/Client/LocalConnection.cpp | 4 ++-- src/Common/Macros.cpp | 2 +- src/Coordination/KeeperStorage.cpp | 2 +- src/Databases/DatabaseS3.cpp | 6 +++--- src/Dictionaries/DictionaryStructure.cpp | 2 +- src/Dictionaries/RangeHashedDictionary.h | 2 +- src/Functions/FunctionsExternalDictionaries.h | 2 +- src/Functions/GregorianDate.cpp | 2 +- src/Interpreters/Context.cpp | 4 ++-- src/Interpreters/DatabaseCatalog.cpp | 2 +- src/Interpreters/InterpreterCreateQuery.cpp | 2 +- src/Interpreters/TreeCNFConverter.cpp | 4 +++- src/Planner/TableExpressionData.h | 2 +- src/Processors/Formats/Impl/AvroRowInputFormat.cpp | 2 +- src/Processors/Formats/Impl/JSONAsStringRowInputFormat.cpp | 2 +- src/Server/TCPHandler.cpp | 2 +- src/Storages/MergeTree/MergeTreeIndexUSearch.cpp | 4 ++-- 21 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/AggregateFunctions/AggregateFunctionFactory.cpp b/src/AggregateFunctions/AggregateFunctionFactory.cpp index 5c101888140..b6ba562045d 100644 --- a/src/AggregateFunctions/AggregateFunctionFactory.cpp +++ b/src/AggregateFunctions/AggregateFunctionFactory.cpp @@ -51,10 +51,10 @@ void AggregateFunctionFactory::registerFunction(const String & name, Value creat void AggregateFunctionFactory::registerNullsActionTransformation(const String & source_ignores_nulls, const String & target_respect_nulls) { if (!aggregate_functions.contains(source_ignores_nulls)) - throw Exception(ErrorCodes::LOGICAL_ERROR, "registerNullsActionTransformation: Source aggregation '{}' not found"); + throw Exception(ErrorCodes::LOGICAL_ERROR, "registerNullsActionTransformation: Source aggregation '{}' not found", source_ignores_nulls); if (!aggregate_functions.contains(target_respect_nulls)) - throw Exception(ErrorCodes::LOGICAL_ERROR, "registerNullsActionTransformation: Target aggregation '{}' not found"); + throw Exception(ErrorCodes::LOGICAL_ERROR, "registerNullsActionTransformation: Target aggregation '{}' not found", target_respect_nulls); if (!respect_nulls.emplace(source_ignores_nulls, target_respect_nulls).second) throw Exception( diff --git a/src/AggregateFunctions/IAggregateFunction.h b/src/AggregateFunctions/IAggregateFunction.h index a8254baac3a..94bb121893d 100644 --- a/src/AggregateFunctions/IAggregateFunction.h +++ b/src/AggregateFunctions/IAggregateFunction.h @@ -197,7 +197,7 @@ public: virtual void insertMergeResultInto(AggregateDataPtr __restrict place, IColumn & to, Arena * arena) const { if (isState()) - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Function {} is marked as State but method insertMergeResultInto is not implemented"); + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Function {} is marked as State but method insertMergeResultInto is not implemented", getName()); insertResultInto(place, to, arena); } diff --git a/src/Analyzer/Passes/CNF.cpp b/src/Analyzer/Passes/CNF.cpp index 91e973c7573..aa6ee539934 100644 --- a/src/Analyzer/Passes/CNF.cpp +++ b/src/Analyzer/Passes/CNF.cpp @@ -536,7 +536,8 @@ CNF CNF::toCNF(const QueryTreeNodePtr & node, ContextPtr context, size_t max_gro if (!cnf) throw Exception(ErrorCodes::TOO_MANY_TEMPORARY_COLUMNS, "Cannot convert expression '{}' to CNF, because it produces to many clauses." - "Size of boolean formula in CNF can be exponential of size of source formula."); + "Size of boolean formula in CNF can be exponential of size of source formula.", + node->formatConvertedASTForErrorMessage()); return *cnf; } diff --git a/src/Backups/BackupIO_S3.cpp b/src/Backups/BackupIO_S3.cpp index 74195a93072..967abe09b3f 100644 --- a/src/Backups/BackupIO_S3.cpp +++ b/src/Backups/BackupIO_S3.cpp @@ -146,7 +146,7 @@ UInt64 BackupReaderS3::getFileSize(const String & file_name) { auto objects = listObjects(*client, s3_uri, file_name); if (objects.empty()) - throw Exception(ErrorCodes::S3_ERROR, "Object {} must exist"); + throw Exception(ErrorCodes::S3_ERROR, "Object {} must exist", file_name); return objects[0].GetSize(); } @@ -299,7 +299,7 @@ UInt64 BackupWriterS3::getFileSize(const String & file_name) { auto objects = listObjects(*client, s3_uri, file_name); if (objects.empty()) - throw Exception(ErrorCodes::S3_ERROR, "Object {} must exist"); + throw Exception(ErrorCodes::S3_ERROR, "Object {} must exist", file_name); return objects[0].GetSize(); } diff --git a/src/Client/LocalConnection.cpp b/src/Client/LocalConnection.cpp index 849308155b0..dbb115f44ef 100644 --- a/src/Client/LocalConnection.cpp +++ b/src/Client/LocalConnection.cpp @@ -201,7 +201,7 @@ void LocalConnection::sendQuery( catch (...) { state->io.onException(); - state->exception = std::make_unique(ErrorCodes::UNKNOWN_EXCEPTION, "Unknown exception"); + state->exception = std::make_unique(Exception(ErrorCodes::UNKNOWN_EXCEPTION, "Unknown exception")); } } @@ -311,7 +311,7 @@ bool LocalConnection::poll(size_t) catch (...) { state->io.onException(); - state->exception = std::make_unique(ErrorCodes::UNKNOWN_EXCEPTION, "Unknown exception"); + state->exception = std::make_unique(Exception(ErrorCodes::UNKNOWN_EXCEPTION, "Unknown exception")); } } diff --git a/src/Common/Macros.cpp b/src/Common/Macros.cpp index 891aa53c061..0035e7abfe8 100644 --- a/src/Common/Macros.cpp +++ b/src/Common/Macros.cpp @@ -120,7 +120,7 @@ String Macros::expand(const String & s, auto uuid = ServerUUID::get(); if (UUIDHelpers::Nil == uuid) throw Exception(ErrorCodes::BAD_ARGUMENTS, - "Macro {server_uuid} expanded to zero, which means the UUID is not initialized (most likely it's not a server application)"); + "Macro {{server_uuid}} expanded to zero, which means the UUID is not initialized (most likely it's not a server application)"); res += toString(uuid); info.expanded_other = true; } diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 0d1d07ec7c5..41e6f5b5e2b 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -914,7 +914,7 @@ void KeeperStorage::unregisterEphemeralPath(int64_t session_id, const std::strin { auto ephemerals_it = ephemerals.find(session_id); if (ephemerals_it == ephemerals.end()) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Session {} is missing ephemeral path"); + throw Exception(ErrorCodes::LOGICAL_ERROR, "Session {} is missing ephemeral path", session_id); ephemerals_it->second.erase(path); if (ephemerals_it->second.empty()) diff --git a/src/Databases/DatabaseS3.cpp b/src/Databases/DatabaseS3.cpp index 11655f5f100..b92b4a971c1 100644 --- a/src/Databases/DatabaseS3.cpp +++ b/src/Databases/DatabaseS3.cpp @@ -255,7 +255,7 @@ DatabaseS3::Configuration DatabaseS3::parseArguments(ASTs engine_args, ContextPt arg = evaluateConstantExpressionOrIdentifierAsLiteral(arg, context_); if (engine_args.size() > 3) - throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, error_message.c_str()); + throw Exception::createRuntime(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, error_message.c_str()); if (engine_args.empty()) return result; @@ -269,7 +269,7 @@ DatabaseS3::Configuration DatabaseS3::parseArguments(ASTs engine_args, ContextPt if (boost::iequals(second_arg, "NOSIGN")) result.no_sign_request = true; else - throw Exception(ErrorCodes::BAD_ARGUMENTS, error_message.c_str()); + throw Exception::createRuntime(ErrorCodes::BAD_ARGUMENTS, error_message.c_str()); } // url, access_key_id, secret_access_key @@ -279,7 +279,7 @@ DatabaseS3::Configuration DatabaseS3::parseArguments(ASTs engine_args, ContextPt auto secret_key = checkAndGetLiteralArgument(engine_args[2], "secret_access_key"); if (key_id.empty() || secret_key.empty() || boost::iequals(key_id, "NOSIGN")) - throw Exception(ErrorCodes::BAD_ARGUMENTS, error_message.c_str()); + throw Exception::createRuntime(ErrorCodes::BAD_ARGUMENTS, error_message.c_str()); result.access_key_id = key_id; result.secret_access_key = secret_key; diff --git a/src/Dictionaries/DictionaryStructure.cpp b/src/Dictionaries/DictionaryStructure.cpp index 76cd36bf76a..0b6bdea60a3 100644 --- a/src/Dictionaries/DictionaryStructure.cpp +++ b/src/Dictionaries/DictionaryStructure.cpp @@ -37,7 +37,7 @@ DictionaryTypedSpecialAttribute makeDictionaryTypedSpecialAttribute( auto expression = config.getString(config_prefix + ".expression", ""); if (name.empty() && !expression.empty()) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "Element {}.name is empty"); + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Element {}.name is empty", config_prefix); const auto type_name = config.getString(config_prefix + ".type", default_type); return DictionaryTypedSpecialAttribute{std::move(name), std::move(expression), DataTypeFactory::instance().get(type_name)}; diff --git a/src/Dictionaries/RangeHashedDictionary.h b/src/Dictionaries/RangeHashedDictionary.h index 9be9fa1d0d4..c44bffe42e1 100644 --- a/src/Dictionaries/RangeHashedDictionary.h +++ b/src/Dictionaries/RangeHashedDictionary.h @@ -683,7 +683,7 @@ void RangeHashedDictionary::loadData() if (configuration.require_nonempty && 0 == element_count) throw Exception(ErrorCodes::DICTIONARY_IS_EMPTY, - "{}: dictionary source is empty and 'require_nonempty' property is set."); + "{}: dictionary source is empty and 'require_nonempty' property is set.", getFullName()); } template diff --git a/src/Functions/FunctionsExternalDictionaries.h b/src/Functions/FunctionsExternalDictionaries.h index db6529da73c..37ddfd6168e 100644 --- a/src/Functions/FunctionsExternalDictionaries.h +++ b/src/Functions/FunctionsExternalDictionaries.h @@ -654,7 +654,7 @@ private: if (tuple_size < 1) throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, - "Tuple second argument of function {} must contain multiple constant string columns"); + "Tuple second argument of function {} must contain multiple constant string columns", getName()); for (size_t i = 0; i < tuple_col.tupleSize(); ++i) { diff --git a/src/Functions/GregorianDate.cpp b/src/Functions/GregorianDate.cpp index f28194781c2..eb7ef4abe56 100644 --- a/src/Functions/GregorianDate.cpp +++ b/src/Functions/GregorianDate.cpp @@ -125,7 +125,7 @@ void GregorianDate::init(ReadBuffer & in) assertEOF(in); if (month_ < 1 || month_ > 12 || day_of_month_ < 1 || day_of_month_ > monthLength(is_leap_year(year_), month_)) - throw Exception(ErrorCodes::CANNOT_PARSE_DATE, "Invalid date, out of range (year: {}, month: {}, day_of_month: {})."); + throw Exception(ErrorCodes::CANNOT_PARSE_DATE, "Invalid date, out of range (year: {}, month: {}, day_of_month: {}).", year_, month_, day_of_month_); } bool GregorianDate::tryInit(ReadBuffer & in) diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 589d03cc074..25146ebc10d 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -4539,7 +4539,7 @@ StorageID Context::resolveStorageIDImpl(StorageID storage_id, StorageNamespace w if (!storage_id) { if (exception) - exception->emplace(ErrorCodes::UNKNOWN_TABLE, "Both table name and UUID are empty"); + exception->emplace(Exception(ErrorCodes::UNKNOWN_TABLE, "Both table name and UUID are empty")); return storage_id; } @@ -4600,7 +4600,7 @@ StorageID Context::resolveStorageIDImpl(StorageID storage_id, StorageNamespace w if (current_database.empty()) { if (exception) - exception->emplace(ErrorCodes::UNKNOWN_DATABASE, "Default database is not selected"); + exception->emplace(Exception(ErrorCodes::UNKNOWN_DATABASE, "Default database is not selected")); return StorageID::createEmpty(); } storage_id.database_name = current_database; diff --git a/src/Interpreters/DatabaseCatalog.cpp b/src/Interpreters/DatabaseCatalog.cpp index c388ade9062..fc1975e8c86 100644 --- a/src/Interpreters/DatabaseCatalog.cpp +++ b/src/Interpreters/DatabaseCatalog.cpp @@ -331,7 +331,7 @@ DatabaseAndTable DatabaseCatalog::getTableImpl( if (!table_id) { if (exception) - exception->emplace(ErrorCodes::UNKNOWN_TABLE, "Cannot find table: StorageID is empty"); + exception->emplace(Exception(ErrorCodes::UNKNOWN_TABLE, "Cannot find table: StorageID is empty")); return {}; } diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index bf07f4ed3ee..bd22e3b411a 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -1730,7 +1730,7 @@ void InterpreterCreateQuery::prepareOnClusterQuery(ASTCreateQuery & create, Cont throw Exception(ErrorCodes::INCORRECT_QUERY, "Seems like cluster is configured for cross-replication, " - "but zookeeper_path for ReplicatedMergeTree is not specified or contains {uuid} macro. " + "but zookeeper_path for ReplicatedMergeTree is not specified or contains {{uuid}} macro. " "It's not supported for cross replication, because tables must have different UUIDs. " "Please specify unique zookeeper_path explicitly."); } diff --git a/src/Interpreters/TreeCNFConverter.cpp b/src/Interpreters/TreeCNFConverter.cpp index 1613b09ee48..d2c7300c80c 100644 --- a/src/Interpreters/TreeCNFConverter.cpp +++ b/src/Interpreters/TreeCNFConverter.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -239,7 +240,8 @@ CNFQuery TreeCNFConverter::toCNF( if (!cnf) throw Exception(ErrorCodes::TOO_MANY_TEMPORARY_COLUMNS, "Cannot convert expression '{}' to CNF, because it produces to many clauses." - "Size of boolean formula in CNF can be exponential of size of source formula."); + "Size of boolean formula in CNF can be exponential of size of source formula.", + queryToString(query)); return *cnf; } diff --git a/src/Planner/TableExpressionData.h b/src/Planner/TableExpressionData.h index ee5a05fe7da..9f963dc182a 100644 --- a/src/Planner/TableExpressionData.h +++ b/src/Planner/TableExpressionData.h @@ -63,7 +63,7 @@ public: void addColumn(const NameAndTypePair & column, const ColumnIdentifier & column_identifier) { if (hasColumn(column.name)) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Column with name {} already exists"); + throw Exception(ErrorCodes::LOGICAL_ERROR, "Column with name {} already exists", column.name); addColumnImpl(column, column_identifier); } diff --git a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp index b4d2605e871..9841b5e70c6 100644 --- a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp @@ -1289,7 +1289,7 @@ DataTypePtr AvroSchemaReader::avroNodeToDataType(avro::NodePtr node) case avro::Type::AVRO_MAP: return std::make_shared(avroNodeToDataType(node->leafAt(0)), avroNodeToDataType(node->leafAt(1))); default: - throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Avro column {} is not supported for inserting."); + throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Avro column {} is not supported for inserting.", nodeName(node)); } } diff --git a/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.cpp b/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.cpp index f6bd4f51289..6db0ee61e32 100644 --- a/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.cpp @@ -91,7 +91,7 @@ void JSONAsStringRowInputFormat::readJSONObject(IColumn & column) bool quotes = false; if (*buf->position() != '{') - throw Exception(ErrorCodes::INCORRECT_DATA, "JSON object must begin with '{'."); + throw Exception(ErrorCodes::INCORRECT_DATA, "JSON object must begin with '{{'."); ++buf->position(); ++balance; diff --git a/src/Server/TCPHandler.cpp b/src/Server/TCPHandler.cpp index f2d7a58119f..a563e0e0004 100644 --- a/src/Server/TCPHandler.cpp +++ b/src/Server/TCPHandler.cpp @@ -657,7 +657,7 @@ void TCPHandler::runImpl() catch (...) { state.io.onException(); - exception = std::make_unique(ErrorCodes::UNKNOWN_EXCEPTION, "Unknown exception"); + exception = std::make_unique(Exception(ErrorCodes::UNKNOWN_EXCEPTION, "Unknown exception")); } try diff --git a/src/Storages/MergeTree/MergeTreeIndexUSearch.cpp b/src/Storages/MergeTree/MergeTreeIndexUSearch.cpp index a889e58bfec..dc8ed368011 100644 --- a/src/Storages/MergeTree/MergeTreeIndexUSearch.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexUSearch.cpp @@ -212,7 +212,7 @@ void MergeTreeIndexAggregatorUSearch::update(const Block & block, size_t { auto rc = index->add(static_cast(index->size()), &column_array_data_float_data[column_array_offsets[current_row - 1]]); if (!rc) - throw Exception(ErrorCodes::INCORRECT_DATA, rc.error.release()); + throw Exception::createRuntime(ErrorCodes::INCORRECT_DATA, rc.error.release()); ProfileEvents::increment(ProfileEvents::USearchAddCount); ProfileEvents::increment(ProfileEvents::USearchAddVisitedMembers, rc.visited_members); @@ -243,7 +243,7 @@ void MergeTreeIndexAggregatorUSearch::update(const Block & block, size_t { auto rc = index->add(static_cast(index->size()), item.data()); if (!rc) - throw Exception(ErrorCodes::INCORRECT_DATA, rc.error.release()); + throw Exception::createRuntime(ErrorCodes::INCORRECT_DATA, rc.error.release()); ProfileEvents::increment(ProfileEvents::USearchAddCount); ProfileEvents::increment(ProfileEvents::USearchAddVisitedMembers, rc.visited_members); From 98a6d67ae31b388c6e0ea02963d1e8050ca29a83 Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Sun, 24 Dec 2023 23:49:26 +0000 Subject: [PATCH 242/253] Disable tests with CREATE AS SELECT for database Replicated --- tests/queries/0_stateless/01162_strange_mutations.sh | 2 ++ .../02783_parallel_replicas_trivial_count_optimization.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/queries/0_stateless/01162_strange_mutations.sh b/tests/queries/0_stateless/01162_strange_mutations.sh index eea9ea5f7e5..f2428141264 100755 --- a/tests/queries/0_stateless/01162_strange_mutations.sh +++ b/tests/queries/0_stateless/01162_strange_mutations.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# Tags: no-replicated-database +# Tag no-replicated-database: CREATE AS SELECT is disabled CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/02783_parallel_replicas_trivial_count_optimization.sh b/tests/queries/0_stateless/02783_parallel_replicas_trivial_count_optimization.sh index 6c697095b57..bafab249b47 100755 --- a/tests/queries/0_stateless/02783_parallel_replicas_trivial_count_optimization.sh +++ b/tests/queries/0_stateless/02783_parallel_replicas_trivial_count_optimization.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# Tags: no-replicated-database +# Tag no-replicated-database: CREATE AS SELECT is disabled CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From 9196c2b994475db5273f50d9695a055b82a98ae3 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 25 Dec 2023 04:54:54 +0100 Subject: [PATCH 243/253] Follow-up --- tests/queries/0_stateless/02952_binary.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02952_binary.sh b/tests/queries/0_stateless/02952_binary.sh index 7c66c656841..c55df1a80b1 100755 --- a/tests/queries/0_stateless/02952_binary.sh +++ b/tests/queries/0_stateless/02952_binary.sh @@ -4,4 +4,4 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh -${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_PORT_HTTP_PROTO}://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_HTTP}/binary" | grep -oF --max-count 1 'addressToSymbol' +${CLICKHOUSE_CURL} -s "${CLICKHOUSE_PORT_HTTP_PROTO}://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_HTTP}/binary" 2>/dev/null | grep -oF --max-count 1 'addressToSymbol' From 7ad278eefb6f462635d1f97b8a3bfb43e54457e4 Mon Sep 17 00:00:00 2001 From: "wangtao.2077" Date: Tue, 26 Dec 2023 13:31:45 +0800 Subject: [PATCH 244/253] fix AddDefaultDatabaseVisitor bad performance --- src/Interpreters/AddDefaultDatabaseVisitor.h | 7 --- .../02953_slow_create_view.reference | 0 .../0_stateless/02953_slow_create_view.sql | 44 +++++++++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 tests/queries/0_stateless/02953_slow_create_view.reference create mode 100644 tests/queries/0_stateless/02953_slow_create_view.sql diff --git a/src/Interpreters/AddDefaultDatabaseVisitor.h b/src/Interpreters/AddDefaultDatabaseVisitor.h index 08d159b42ca..27639c4b813 100644 --- a/src/Interpreters/AddDefaultDatabaseVisitor.h +++ b/src/Interpreters/AddDefaultDatabaseVisitor.h @@ -148,8 +148,6 @@ private: { if (table_expression.database_and_table_name) tryVisit(table_expression.database_and_table_name); - else if (table_expression.subquery) - tryVisit(table_expression.subquery); } void visit(const ASTTableIdentifier & identifier, ASTPtr & ast) const @@ -167,11 +165,6 @@ private: ast = qualified_identifier; } - void visit(ASTSubquery & subquery, ASTPtr &) const - { - tryVisit(subquery.children[0]); - } - void visit(ASTFunction & function, ASTPtr &) const { bool is_operator_in = functionIsInOrGlobalInOperator(function.name); diff --git a/tests/queries/0_stateless/02953_slow_create_view.reference b/tests/queries/0_stateless/02953_slow_create_view.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02953_slow_create_view.sql b/tests/queries/0_stateless/02953_slow_create_view.sql new file mode 100644 index 00000000000..7824bd97b92 --- /dev/null +++ b/tests/queries/0_stateless/02953_slow_create_view.sql @@ -0,0 +1,44 @@ +drop view if exists slow_view1; + +create view slow_view1 as +with c1 as (select 1 as a), + c2 as (select a from c1), + c3 as (select a from c2), + c4 as (select a from c3), + c5 as (select a from c4), + c6 as (select a from c5), + c7 as (select a from c6), + c8 as (select a from c7), + c9 as (select a from c8), + c10 as (select a from c9), + c11 as (select a from c10), + c12 as (select a from c11), + c13 as (select a from c12), + c14 as (select a from c13), + c15 as (select a from c14), + c16 as (select a from c15), + c17 as (select a from c16), + c18 as (select a from c17), + c19 as (select a from c18), + c20 as (select a from c19), + c21 as (select a from c20), + c22 as (select a from c21), + c23 as (select a from c22), + c24 as (select a from c23), + c25 as (select a from c24), + c26 as (select a from c25), + c27 as (select a from c26), + c28 as (select a from c27), + c29 as (select a from c28), + c30 as (select a from c29), + c31 as (select a from c30), + c32 as (select a from c31), + c33 as (select a from c32), + c34 as (select a from c33), + c35 as (select a from c34), + c36 as (select a from c35), + c37 as (select a from c36), + c38 as (select a from c37), + c39 as (select a from c38), + c40 as (select a from c39) +select a from c21; From a59d874bf93f667af298fbfb8b0a200b5e9dd3ec Mon Sep 17 00:00:00 2001 From: santrancisco Date: Tue, 26 Dec 2023 16:56:58 +1100 Subject: [PATCH 245/253] fix syntax --- src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 8485f3f3e1d..afef8ffa7e6 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -928,7 +928,7 @@ RangesInDataParts MergeTreeDataSelectExecutor::filterPartsByPrimaryKeyAndSkipInd Strings forced_indices; { - Tokens tokens(indices.data(), &indices[indices.size()], settings.max_query_size); + Tokens tokens(indices.data(), indices.data() + indices.size(), settings.max_query_size); IParser::Pos pos(tokens, static_cast(settings.max_parser_depth)); Expected expected; if (!parseIdentifiersOrStringLiterals(pos, expected, forced_indices)) From 91835256aa7b2d99712670c9b4133efc6a27ddbb Mon Sep 17 00:00:00 2001 From: santrancisco Date: Tue, 26 Dec 2023 16:57:18 +1100 Subject: [PATCH 246/253] fix doc --- docs/en/operations/query-cache.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/operations/query-cache.md b/docs/en/operations/query-cache.md index 0b858038caf..50c5ff4457f 100644 --- a/docs/en/operations/query-cache.md +++ b/docs/en/operations/query-cache.md @@ -29,8 +29,8 @@ Transactionally inconsistent caching is traditionally provided by client tools o the same caching logic and configuration is often duplicated. With ClickHouse's query cache, the caching logic moves to the server side. This reduces maintenance effort and avoids redundancy. -:::security consideration -The cached query result is tied to the user executing it. Authorization checks are performed when the query is executed. This means that if there are any alterations to the user's role or permissions between the time the query is cached and when the cache is accessed, the result will not reflect these changes. We recommend using different users to distinguish between different levels of access, instead of actively toggling roles for a single user between queries, as this practice may lead to unexpected query results. +:::note +Security consideration: The cached query result is tied to the user executing it. Authorization checks are performed when the query is executed. This means that if there are any alterations to the user's role or permissions between the time the query is cached and when the cache is accessed, the result will not reflect these changes. We recommend using different users to distinguish between different levels of access, instead of actively toggling roles for a single user between queries, as this practice may lead to unexpected query results. ::: ## Configuration Settings and Usage From bfcccf9fa3e7aaa86ba83a47610162121db5e5ad Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Tue, 26 Dec 2023 16:56:52 +0800 Subject: [PATCH 247/253] Primary key analysis for _part_offset --- .../QueryPlan/ReadFromMergeTree.cpp | 10 +- src/Processors/QueryPlan/ReadFromMergeTree.h | 1 + .../MergeTree/MergeTreeDataSelectExecutor.cpp | 104 +++++++++++++++--- .../MergeTree/MergeTreeDataSelectExecutor.h | 5 + .../MergeTree/MergeTreeSequentialSource.cpp | 2 +- ...02950_part_offset_as_primary_key.reference | 14 +++ .../02950_part_offset_as_primary_key.sql | 40 +++++++ 7 files changed, 155 insertions(+), 21 deletions(-) create mode 100644 tests/queries/0_stateless/02950_part_offset_as_primary_key.reference create mode 100644 tests/queries/0_stateless/02950_part_offset_as_primary_key.sql diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index 875b0d9bdbc..d47d17ccd26 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -1338,7 +1338,7 @@ static void buildIndexes( context, primary_key_column_names, primary_key.expression, - array_join_name_set}, {}, {}, {}, false, {}}); + array_join_name_set}, {}, {}, {}, {}, false, {}}); } else { @@ -1346,7 +1346,7 @@ static void buildIndexes( query_info, context, primary_key_column_names, - primary_key.expression}, {}, {}, {}, false, {}}); + primary_key.expression}, {}, {}, {}, {}, false, {}}); } if (metadata_snapshot->hasPartitionKey()) @@ -1365,6 +1365,8 @@ static void buildIndexes( else indexes->part_values = MergeTreeDataSelectExecutor::filterPartsByVirtualColumns(data, parts, query_info.query, context); + MergeTreeDataSelectExecutor::buildKeyConditionFromPartOffset(indexes->part_offset_condition, filter_actions_dag, context); + indexes->use_skip_indexes = settings.use_skip_indexes; bool final = query_info.isFinal(); @@ -1549,6 +1551,9 @@ MergeTreeDataSelectAnalysisResultPtr ReadFromMergeTree::selectRangesToReadImpl( } LOG_DEBUG(log, "Key condition: {}", indexes->key_condition.toString()); + if (indexes->part_offset_condition) + LOG_DEBUG(log, "Part offset condition: {}", indexes->part_offset_condition->toString()); + if (indexes->key_condition.alwaysFalse()) return std::make_shared(MergeTreeDataSelectAnalysisResult{.result = std::move(result)}); @@ -1595,6 +1600,7 @@ MergeTreeDataSelectAnalysisResultPtr ReadFromMergeTree::selectRangesToReadImpl( metadata_snapshot, context, indexes->key_condition, + indexes->part_offset_condition, indexes->skip_indexes, reader_settings, log, diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.h b/src/Processors/QueryPlan/ReadFromMergeTree.h index 4e38e06c6af..e4c83fdee48 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.h +++ b/src/Processors/QueryPlan/ReadFromMergeTree.h @@ -151,6 +151,7 @@ public: KeyCondition key_condition; std::optional partition_pruner; std::optional minmax_idx_condition; + std::optional part_offset_condition; UsefulSkipIndexes skip_indexes; bool use_skip_indexes; std::optional> part_values; diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 8485f3f3e1d..5e6c66664ad 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -92,7 +92,7 @@ size_t MergeTreeDataSelectExecutor::getApproximateTotalRowsToRead( for (const auto & part : parts) { - MarkRanges ranges = markRangesFromPKRange(part, metadata_snapshot, key_condition, settings, log); + MarkRanges ranges = markRangesFromPKRange(part, metadata_snapshot, key_condition, {}, settings, log); /** In order to get a lower bound on the number of rows that match the condition on PK, * consider only guaranteed full marks. @@ -770,6 +770,35 @@ MergeTreeDataSelectSamplingData MergeTreeDataSelectExecutor::getSampling( return sampling; } +void MergeTreeDataSelectExecutor::buildKeyConditionFromPartOffset( + std::optional & part_offset_condition, const ActionsDAGPtr & filter_dag, ContextPtr context) +{ + if (!filter_dag) + return; + + auto part_offset_type = std::make_shared(); + auto part_type = std::make_shared(std::make_shared()); + Block sample + = {ColumnWithTypeAndName(part_offset_type->createColumn(), part_offset_type, "_part_offset"), + ColumnWithTypeAndName(part_type->createColumn(), part_type, "_part")}; + + auto dag = VirtualColumnUtils::splitFilterDagForAllowedInputs(filter_dag->getOutputs().at(0), sample); + if (!dag) + return; + + /// The _part filter should only be effective in conjunction with the _part_offset filter. + auto required_columns = dag->getRequiredColumnsNames(); + if (std::find(required_columns.begin(), required_columns.end(), "_part_offset") == required_columns.end()) + return; + + part_offset_condition.emplace(KeyCondition{ + dag, + context, + sample.getNames(), + std::make_shared(std::make_shared(sample.getColumnsWithTypeAndName()), ExpressionActionsSettings{}), + {}}); +} + std::optional> MergeTreeDataSelectExecutor::filterPartsByVirtualColumns( const MergeTreeData & data, const MergeTreeData::DataPartsVector & parts, @@ -909,6 +938,7 @@ RangesInDataParts MergeTreeDataSelectExecutor::filterPartsByPrimaryKeyAndSkipInd StorageMetadataPtr metadata_snapshot, const ContextPtr & context, const KeyCondition & key_condition, + const std::optional & part_offset_condition, const UsefulSkipIndexes & skip_indexes, const MergeTreeReaderSettings & reader_settings, Poco::Logger * log, @@ -983,8 +1013,8 @@ RangesInDataParts MergeTreeDataSelectExecutor::filterPartsByPrimaryKeyAndSkipInd RangesInDataPart ranges(part, alter_conversions_for_part, part_index); size_t total_marks_count = part->index_granularity.getMarksCountWithoutFinal(); - if (metadata_snapshot->hasPrimaryKey()) - ranges.ranges = markRangesFromPKRange(part, metadata_snapshot, key_condition, settings, log); + if (metadata_snapshot->hasPrimaryKey() || part_offset_condition) + ranges.ranges = markRangesFromPKRange(part, metadata_snapshot, key_condition, part_offset_condition, settings, log); else if (total_marks_count) ranges.ranges = MarkRanges{{MarkRange{0, total_marks_count}}}; @@ -1404,6 +1434,7 @@ MarkRanges MergeTreeDataSelectExecutor::markRangesFromPKRange( const MergeTreeData::DataPartPtr & part, const StorageMetadataPtr & metadata_snapshot, const KeyCondition & key_condition, + const std::optional & part_offset_condition, const Settings & settings, Poco::Logger * log) { @@ -1417,7 +1448,7 @@ MarkRanges MergeTreeDataSelectExecutor::markRangesFromPKRange( bool has_final_mark = part->index_granularity.hasFinalMark(); /// If index is not used. - if (key_condition.alwaysUnknownOrTrue()) + if (key_condition.alwaysUnknownOrTrue() && (!part_offset_condition || part_offset_condition->alwaysUnknownOrTrue())) { if (has_final_mark) res.push_back(MarkRange(0, marks_count - 1)); @@ -1467,32 +1498,69 @@ MarkRanges MergeTreeDataSelectExecutor::markRangesFromPKRange( std::vector index_left(used_key_size); std::vector index_right(used_key_size); + /// For _part_offset and _part virtual columns + DataTypes part_offset_types + = {std::make_shared(), std::make_shared(std::make_shared())}; + std::vector part_offset_left(2); + std::vector part_offset_right(2); + auto may_be_true_in_range = [&](MarkRange & range) { - if (range.end == marks_count && !has_final_mark) + bool key_condition_maybe_true = true; + if (!key_condition.alwaysUnknownOrTrue()) { - for (size_t i = 0; i < used_key_size; ++i) + if (range.end == marks_count && !has_final_mark) { - create_field_ref(range.begin, i, index_left[i]); - index_right[i] = POSITIVE_INFINITY; + for (size_t i = 0; i < used_key_size; ++i) + { + create_field_ref(range.begin, i, index_left[i]); + index_right[i] = POSITIVE_INFINITY; + } } - } - else - { - if (has_final_mark && range.end == marks_count) - range.end -= 1; /// Remove final empty mark. It's useful only for primary key condition. + else + { + if (has_final_mark && range.end == marks_count) + range.end -= 1; /// Remove final empty mark. It's useful only for primary key condition. - for (size_t i = 0; i < used_key_size; ++i) + for (size_t i = 0; i < used_key_size; ++i) + { + create_field_ref(range.begin, i, index_left[i]); + create_field_ref(range.end, i, index_right[i]); + } + } + key_condition_maybe_true = key_condition.mayBeTrueInRange(used_key_size, index_left.data(), index_right.data(), key_types); + } + + bool part_offset_condition_maybe_true = true; + + if (part_offset_condition && !part_offset_condition->alwaysUnknownOrTrue()) + { + auto begin = part->index_granularity.getMarkStartingRow(range.begin); + auto end = part->index_granularity.getMarkStartingRow(range.end) - 1; + if (begin > end) { - create_field_ref(range.begin, i, index_left[i]); - create_field_ref(range.end, i, index_right[i]); + /// Empty mark (final mark) + part_offset_condition_maybe_true = false; + } + else + { + part_offset_left[0] = part->index_granularity.getMarkStartingRow(range.begin); + part_offset_right[0] = part->index_granularity.getMarkStartingRow(range.end) - 1; + part_offset_left[1] = part->name; + part_offset_right[1] = part->name; + + part_offset_condition_maybe_true + = part_offset_condition->mayBeTrueInRange(2, part_offset_left.data(), part_offset_right.data(), part_offset_types); } } - return key_condition.mayBeTrueInRange(used_key_size, index_left.data(), index_right.data(), key_types); + return key_condition_maybe_true && part_offset_condition_maybe_true; }; + bool key_condition_exact_range = key_condition.alwaysUnknownOrTrue() || key_condition.matchesExactContinuousRange(); + bool part_offset_condition_exact_range + = !part_offset_condition || part_offset_condition->alwaysUnknownOrTrue() || part_offset_condition->matchesExactContinuousRange(); const String & part_name = part->isProjectionPart() ? fmt::format("{}.{}", part->name, part->getParentPart()->name) : part->name; - if (!key_condition.matchesExactContinuousRange()) + if (!key_condition_exact_range || !part_offset_condition_exact_range) { // Do exclusion search, where we drop ranges that do not match diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h index 01c2da9dd63..43ef4b84c2b 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h @@ -71,6 +71,7 @@ public: const MergeTreeData::DataPartPtr & part, const StorageMetadataPtr & metadata_snapshot, const KeyCondition & key_condition, + const std::optional & part_offset_condition, const Settings & settings, Poco::Logger * log); @@ -161,6 +162,9 @@ public: size_t bytes_granularity, size_t max_marks); + static void buildKeyConditionFromPartOffset( + std::optional & part_offset_condition, const ActionsDAGPtr & filter_dag, ContextPtr context); + /// If possible, filter using expression on virtual columns. /// Example: SELECT count() FROM table WHERE _part = 'part_name' /// If expression found, return a set with allowed part names (std::nullopt otherwise). @@ -199,6 +203,7 @@ public: StorageMetadataPtr metadata_snapshot, const ContextPtr & context, const KeyCondition & key_condition, + const std::optional & part_offset_condition, const UsefulSkipIndexes & skip_indexes, const MergeTreeReaderSettings & reader_settings, Poco::Logger * log, diff --git a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp index 5075e43448a..69cf3cb4266 100644 --- a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp +++ b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp @@ -322,7 +322,7 @@ public: if (!key_condition.alwaysFalse()) mark_ranges = MergeTreeDataSelectExecutor::markRangesFromPKRange( - data_part, metadata_snapshot, key_condition, context->getSettingsRef(), log); + data_part, metadata_snapshot, key_condition, {}, context->getSettingsRef(), log); if (mark_ranges && mark_ranges->empty()) { diff --git a/tests/queries/0_stateless/02950_part_offset_as_primary_key.reference b/tests/queries/0_stateless/02950_part_offset_as_primary_key.reference new file mode 100644 index 00000000000..368f8dd9871 --- /dev/null +++ b/tests/queries/0_stateless/02950_part_offset_as_primary_key.reference @@ -0,0 +1,14 @@ +-4 +-3 +-2 +-1 +0 +-3 +0 +-4 +-2 +-1 +0 +10 +40 +400 diff --git a/tests/queries/0_stateless/02950_part_offset_as_primary_key.sql b/tests/queries/0_stateless/02950_part_offset_as_primary_key.sql new file mode 100644 index 00000000000..4691d2a55fc --- /dev/null +++ b/tests/queries/0_stateless/02950_part_offset_as_primary_key.sql @@ -0,0 +1,40 @@ +drop table if exists a; + +create table a (i int) engine MergeTree order by i settings index_granularity = 2; +insert into a select -number from numbers(5); + +-- nothing to read +select i from a where _part_offset >= 5 order by i settings max_bytes_to_read = 1; + +-- one granules +select i from a where _part_offset = 0 order by i settings max_rows_to_read = 2; +select i from a where _part_offset = 1 order by i settings max_rows_to_read = 2; +select i from a where _part_offset = 2 order by i settings max_rows_to_read = 2; +select i from a where _part_offset = 3 order by i settings max_rows_to_read = 2; +select i from a where _part_offset = 4 order by i settings max_rows_to_read = 1; + +-- other predicates +select i from a where _part_offset in (1, 4) order by i settings max_rows_to_read = 3; +select i from a where _part_offset not in (1, 4) order by i settings max_rows_to_read = 4; + +-- force primary key check still works +select i from a where _part_offset = 4 order by i settings force_primary_key = 1; -- { serverError INDEX_NOT_USED } + +-- combining with other primary keys doesn't work (makes no sense) +select i from a where i = -3 or _part_offset = 4 order by i settings force_primary_key = 1; -- { serverError INDEX_NOT_USED } + +drop table a; + +drop table if exists b; + +create table b (i int) engine MergeTree order by tuple() settings index_granularity = 2; + +-- all_1_1_0 +insert into b select number * 10 from numbers(5); +-- all_2_2_0 +insert into b select number * 100 from numbers(5); + +-- multiple parts with _part predicate +select i from b where (_part = 'all_1_1_0' and _part_offset in (1, 4)) or (_part = 'all_2_2_0' and _part_offset in (0, 4)) order by i settings max_rows_to_read = 6; + +drop table b; From 66660ee4e25528d18eaaba456f1d07539b7d25a3 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Tue, 26 Dec 2023 17:03:15 +0800 Subject: [PATCH 248/253] Add comment --- src/Storages/MergeTree/MergeTreeDataSelectExecutor.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h index 43ef4b84c2b..11c8e172a4f 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h @@ -162,6 +162,7 @@ public: size_t bytes_granularity, size_t max_marks); + /// If possible, construct optional key condition from predicates containing _part_offset column. static void buildKeyConditionFromPartOffset( std::optional & part_offset_condition, const ActionsDAGPtr & filter_dag, ContextPtr context); From cbf9304d1f91b3861bfb270da0b413be51fd375d Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Mon, 25 Dec 2023 14:16:51 +0300 Subject: [PATCH 249/253] MergeTree automatically derive do_not_merge_across_partitions_select_final setting --- .../QueryPlan/ReadFromMergeTree.cpp | 37 ++++++++++++++++++- src/Processors/QueryPlan/ReadFromMergeTree.h | 2 + 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index 875b0d9bdbc..aa3e42a3a64 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -1042,6 +1042,38 @@ static void addMergingFinal( pipe.addTransform(get_merging_processor()); } +bool ReadFromMergeTree::doNotMergePartsAcrossPartitionsFinal() const +{ + const auto & settings = context->getSettingsRef(); + + /// If setting do_not_merge_across_partitions_select_final is set always prefer it + if (settings.do_not_merge_across_partitions_select_final.changed) + return settings.do_not_merge_across_partitions_select_final; + + if (!metadata_for_reading->hasPrimaryKey() || !metadata_for_reading->hasPartitionKey()) + return false; + + /** To avoid merging parts across partitions we want result of partition key expression for + * rows with same primary key to be the same. + * + * If partition key expression is deterministic, and contains only columns that are included + * in primary key, then for same primary key column values, result of partition key expression + * will be the same. + */ + const auto & partition_key_expression = metadata_for_reading->getPartitionKey().expression; + if (partition_key_expression->getActionsDAG().hasNonDeterministic()) + return false; + + const auto & primary_key_columns = metadata_for_reading->getPrimaryKey().column_names; + NameSet primary_key_columns_set(primary_key_columns.begin(), primary_key_columns.end()); + + const auto & partition_key_required_columns = partition_key_expression->getRequiredColumns(); + for (const auto & partition_key_required_column : partition_key_required_columns) + if (!primary_key_columns_set.contains(partition_key_required_column)) + return false; + + return true; +} Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( RangesInDataParts && parts_with_ranges, size_t num_streams, const Names & origin_column_names, const Names & column_names, ActionsDAGPtr & out_projection) @@ -1064,7 +1096,8 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( auto it = parts_with_ranges.begin(); parts_to_merge_ranges.push_back(it); - if (settings.do_not_merge_across_partitions_select_final) + bool do_not_merge_across_partitions_select_final = doNotMergePartsAcrossPartitionsFinal(); + if (do_not_merge_across_partitions_select_final) { while (it != parts_with_ranges.end()) { @@ -1097,7 +1130,7 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( /// If do_not_merge_across_partitions_select_final is true and there is only one part in partition /// with level > 0 then we won't post-process this part, and if num_streams > 1 we /// can use parallel select on such parts. - bool no_merging_final = settings.do_not_merge_across_partitions_select_final && + bool no_merging_final = do_not_merge_across_partitions_select_final && std::distance(parts_to_merge_ranges[range_index], parts_to_merge_ranges[range_index + 1]) == 1 && parts_to_merge_ranges[range_index]->data_part->info.level > 0; Pipes pipes; diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.h b/src/Processors/QueryPlan/ReadFromMergeTree.h index 4e38e06c6af..9919352ef72 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.h +++ b/src/Processors/QueryPlan/ReadFromMergeTree.h @@ -288,6 +288,8 @@ private: ActionsDAGPtr & out_projection, const InputOrderInfoPtr & input_order_info); + bool doNotMergePartsAcrossPartitionsFinal() const; + Pipe spreadMarkRangesAmongStreamsFinal( RangesInDataParts && parts, size_t num_streams, const Names & origin_column_names, const Names & column_names, ActionsDAGPtr & out_projection); From 71921086ae048a1a4d07ada086b13f54f0dd1172 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Tue, 26 Dec 2023 12:40:54 +0300 Subject: [PATCH 250/253] Fixed tests --- tests/queries/0_stateless/02286_parallel_final.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/02286_parallel_final.sh b/tests/queries/0_stateless/02286_parallel_final.sh index de0cca0e966..788b4c0e9b5 100755 --- a/tests/queries/0_stateless/02286_parallel_final.sh +++ b/tests/queries/0_stateless/02286_parallel_final.sh @@ -11,7 +11,7 @@ test_random_values() { create table tbl_8parts_${layers}granules_rnd (key1 UInt32, sign Int8) engine = CollapsingMergeTree(sign) order by (key1) partition by (key1 % 8); insert into tbl_8parts_${layers}granules_rnd select number, 1 from numbers_mt($((layers * 8 * 8192))); optimize table tbl_8parts_${layers}granules_rnd final; - explain pipeline select * from tbl_8parts_${layers}granules_rnd final settings max_threads = 16;" 2>&1 | + explain pipeline select * from tbl_8parts_${layers}granules_rnd final settings max_threads = 16, do_not_merge_across_partitions_select_final = 0;;" 2>&1 | grep -c "CollapsingSortedTransform" } @@ -25,7 +25,7 @@ test_sequential_values() { create table tbl_8parts_${layers}granules_seq (key1 UInt32, sign Int8) engine = CollapsingMergeTree(sign) order by (key1) partition by (key1 / $((layers * 8192)))::UInt64; insert into tbl_8parts_${layers}granules_seq select number, 1 from numbers_mt($((layers * 8 * 8192))); optimize table tbl_8parts_${layers}granules_seq final; - explain pipeline select * from tbl_8parts_${layers}granules_seq final settings max_threads = 8;" 2>&1 | + explain pipeline select * from tbl_8parts_${layers}granules_seq final settings max_threads = 8, do_not_merge_across_partitions_select_final = 0;" 2>&1 | grep -c "CollapsingSortedTransform" } From e87b9751bdaa4a7c220f7a053e8691f48b26f78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Tue, 26 Dec 2023 12:53:00 +0100 Subject: [PATCH 251/253] Cleanup some known short messages --- src/Backups/BackupsWorker.cpp | 1 - src/Functions/geoToS2.cpp | 32 +++-- src/Functions/h3ToString.cpp | 2 +- src/Functions/s2CapContains.cpp | 8 +- src/Functions/s2CellsIntersect.cpp | 8 +- src/Functions/s2GetNeighbors.cpp | 2 +- src/Functions/stringToH3.cpp | 2 +- src/Interpreters/InterpreterWatchQuery.cpp | 2 +- src/Storages/MergeTree/MergeTreeData.cpp | 2 - src/Storages/StorageDistributed.cpp | 4 - ..._log_and_exception_messages_formatting.sql | 122 ++++++++++++++---- 11 files changed, 132 insertions(+), 53 deletions(-) diff --git a/src/Backups/BackupsWorker.cpp b/src/Backups/BackupsWorker.cpp index a87f8520138..8c4bb7e414c 100644 --- a/src/Backups/BackupsWorker.cpp +++ b/src/Backups/BackupsWorker.cpp @@ -610,7 +610,6 @@ void BackupsWorker::doBackup( void BackupsWorker::buildFileInfosForBackupEntries(const BackupPtr & backup, const BackupEntries & backup_entries, const ReadSettings & read_settings, std::shared_ptr backup_coordination) { - LOG_TRACE(log, "{}", Stage::BUILDING_FILE_INFOS); backup_coordination->setStage(Stage::BUILDING_FILE_INFOS, ""); backup_coordination->waitForStage(Stage::BUILDING_FILE_INFOS); backup_coordination->addFileInfos(::DB::buildFileInfosForBackupEntries(backup_entries, backup->getBaseBackup(), read_settings, getThreadPool(ThreadPoolId::BACKUP_MAKE_FILES_LIST))); diff --git a/src/Functions/geoToS2.cpp b/src/Functions/geoToS2.cpp index 8d065b01c34..f27cd26fd9d 100644 --- a/src/Functions/geoToS2.cpp +++ b/src/Functions/geoToS2.cpp @@ -101,19 +101,35 @@ public: const Float64 lon = data_col_lon[row]; const Float64 lat = data_col_lat[row]; - if (isNaN(lon) || isNaN(lat)) - throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Arguments must not be NaN"); + if (isNaN(lon)) + throw Exception( + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal argument for longitude in function {}. It must not be NaN", getName()); + if (!isFinite(lon)) + throw Exception( + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal argument for longitude in function {}. It must not be infinite", + getName()); - if (!(isFinite(lon) && isFinite(lat))) - throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Arguments must not be infinite"); + if (isNaN(lat)) + throw Exception( + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal argument for latitude in function {}. It must not be NaN", getName()); + if (!isFinite(lat)) + throw Exception( + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal argument for latitude in function {}. It must not be infinite", + getName()); - /// S2 acceptes point as (latitude, longitude) + /// S2 accepts point as (latitude, longitude) S2LatLng lat_lng = S2LatLng::FromDegrees(lat, lon); if (!lat_lng.is_valid()) - throw Exception(ErrorCodes::BAD_ARGUMENTS, - "Point is invalid. For valid point the latitude is between -90 and 90 degrees inclusive" - "and the longitude is between -180 and 180 degrees inclusive."); + throw Exception( + ErrorCodes::BAD_ARGUMENTS, + "Point ({}, {}) is invalid in function {}. For valid point the latitude is between -90 and 90 degrees inclusive" + "and the longitude is between -180 and 180 degrees inclusive.", + lon, + lat, + getName()); S2CellId id(lat_lng); diff --git a/src/Functions/h3ToString.cpp b/src/Functions/h3ToString.cpp index 897329ed9ec..f8a10d5252b 100644 --- a/src/Functions/h3ToString.cpp +++ b/src/Functions/h3ToString.cpp @@ -84,7 +84,7 @@ public: const UInt64 hindex = data[row]; if (!isValidCell(hindex)) - throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Invalid H3 index: {}", hindex); + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Invalid H3 index: {} in function {}", hindex, getName()); h3ToString(hindex, pos, H3_INDEX_STRING_LENGTH); diff --git a/src/Functions/s2CapContains.cpp b/src/Functions/s2CapContains.cpp index 9dfbc05a6a0..72e9da69a7d 100644 --- a/src/Functions/s2CapContains.cpp +++ b/src/Functions/s2CapContains.cpp @@ -131,16 +131,16 @@ public: const auto point = S2CellId(data_point[row]); if (isNaN(degrees)) - throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Radius of the cap must not be nan"); + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Radius of the cap must not be nan in function {}", getName()); if (std::isinf(degrees)) - throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Radius of the cap must not be infinite"); + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Radius of the cap must not be infinite in function {}", getName()); if (!center.is_valid()) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "Center is not valid"); + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Center (id {}) is not valid in function {}", data_center[row], getName()); if (!point.is_valid()) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "Point is not valid"); + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Point (id {}) is not valid in function {}", data_point[row], getName()); S1Angle angle = S1Angle::Degrees(degrees); S2Cap cap(center.ToPoint(), angle); diff --git a/src/Functions/s2CellsIntersect.cpp b/src/Functions/s2CellsIntersect.cpp index 1fac5fd6e60..320f3c964a2 100644 --- a/src/Functions/s2CellsIntersect.cpp +++ b/src/Functions/s2CellsIntersect.cpp @@ -100,10 +100,12 @@ public: const UInt64 id_second = data_id_second[row]; auto first_cell = S2CellId(id_first); - auto second_cell = S2CellId(id_second); + if (!first_cell.is_valid()) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "First cell (id {}) is not valid in function {}", id_first, getName()); - if (!first_cell.is_valid() || !second_cell.is_valid()) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cell is not valid"); + auto second_cell = S2CellId(id_second); + if (!second_cell.is_valid()) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Second cell (id {}) is not valid in function {}", id_second, getName()); dst_data.emplace_back(S2CellId(id_first).intersects(S2CellId(id_second))); } diff --git a/src/Functions/s2GetNeighbors.cpp b/src/Functions/s2GetNeighbors.cpp index b200f61315b..a6371b9ff68 100644 --- a/src/Functions/s2GetNeighbors.cpp +++ b/src/Functions/s2GetNeighbors.cpp @@ -94,7 +94,7 @@ public: S2CellId cell_id(id); if (!cell_id.is_valid()) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cell is not valid"); + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cell (id {}) is not valid in function {}", id, getName()); S2CellId neighbors[4]; cell_id.GetEdgeNeighbors(neighbors); diff --git a/src/Functions/stringToH3.cpp b/src/Functions/stringToH3.cpp index d8728b346d0..94418efdfdf 100644 --- a/src/Functions/stringToH3.cpp +++ b/src/Functions/stringToH3.cpp @@ -88,7 +88,7 @@ private: if (res_data[row_num] == 0) { - throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Invalid H3 index: {}", h3index_str); + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Invalid H3 index: {} in function {}", h3index_str, name); } h3index_source.next(); diff --git a/src/Interpreters/InterpreterWatchQuery.cpp b/src/Interpreters/InterpreterWatchQuery.cpp index e1af704a358..8865c47a785 100644 --- a/src/Interpreters/InterpreterWatchQuery.cpp +++ b/src/Interpreters/InterpreterWatchQuery.cpp @@ -61,7 +61,7 @@ QueryPipelineBuilder InterpreterWatchQuery::buildQueryPipeline() storage = DatabaseCatalog::instance().tryGetTable(table_id, getContext()); if (!storage) - throw Exception(ErrorCodes::UNKNOWN_TABLE, "Table {} doesn't exist.", table_id.getNameForLogs()); + throw Exception(ErrorCodes::UNKNOWN_TABLE, "Table {} does not exist.", table_id.getNameForLogs()); auto storage_name = storage->getName(); if (storage_name == "LiveView" diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 86fda17d863..914affcc8f9 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -2803,8 +2803,6 @@ void MergeTreeData::dropAllData() void MergeTreeData::dropIfEmpty() { - LOG_TRACE(log, "dropIfEmpty"); - auto lock = lockParts(); if (!data_parts_by_info.empty()) diff --git a/src/Storages/StorageDistributed.cpp b/src/Storages/StorageDistributed.cpp index 665e057b369..a928a4daf63 100644 --- a/src/Storages/StorageDistributed.cpp +++ b/src/Storages/StorageDistributed.cpp @@ -1300,8 +1300,6 @@ void StorageDistributed::drop() disk->removeRecursive(relative_data_path); } - - LOG_DEBUG(log, "Removed"); } Strings StorageDistributed::getDataPaths() const @@ -1328,8 +1326,6 @@ void StorageDistributed::truncate(const ASTPtr &, const StorageMetadataPtr &, Co it->second.directory_queue->shutdownAndDropAllData(); it = cluster_nodes_data.erase(it); } - - LOG_DEBUG(log, "Removed"); } StoragePolicyPtr StorageDistributed::getStoragePolicy() const diff --git a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql index 31b8d64e285..3a83126ea11 100644 --- a/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql +++ b/tests/queries/0_stateless/00002_log_and_exception_messages_formatting.sql @@ -66,33 +66,101 @@ WHERE -- FIXME some of the following messages are not informative and it has to be fixed -create temporary table known_short_messages (s String) as select * from (select -['', '{} ({})', '({}) Keys: {}', '({}) {}', 'Aggregating', 'Became leader', 'Cleaning queue', -'Creating set.', 'Cyclic aliases', 'Detaching {}', 'Executing {}', 'Fire events: {}', -'Found part {}', 'Loaded queue', 'No sharding key', 'No tables', 'Query: {}', 'Removed', -'Removed part {}', 'Removing parts.', 'Request URI: {}', 'Sending part {}', -'Sent handshake', 'Starting {}', 'Will mimic {}', 'Writing to {}', 'dropIfEmpty', -'loadAll {}', '{} ({}:{})', '{} -> {}', '{} {}', '{}: {}', '{}%', 'Read object: {}', -'New segment: {}', 'Convert overflow', 'Division by zero', 'Files set to {}', -'Bytes set to {}', 'Numeric overflow', 'Invalid mode: {}', -'Write file: {}', 'Unable to parse JSONPath', 'Host is empty in S3 URI.', 'Expected end of line', -'inflate failed: {}{}', 'Center is not valid', 'Column ''{}'' is ambiguous', 'Cannot parse object', 'Invalid date: {}', -'There is no cache by name: {}', 'No part {} in table', '`{}` should be a String', 'There are duplicate id {}', -'Invalid replica name: {}', 'Unexpected value {} in enum', 'Unknown BSON type: {}', 'Point is not valid', -'Invalid qualified name: {}', 'INTO OUTFILE is not allowed', 'Arguments must not be NaN', 'Cell is not valid', -'brotli decode error{}', 'Invalid H3 index: {}', 'Too large node state size', 'No additional keys found.', -'Attempt to read after EOF.', 'Replication was stopped', '{} building file infos', 'Cannot parse uuid {}', -'Query was cancelled', 'Cancelled merging parts', 'Cancelled mutating parts', 'Log pulling is cancelled', -'Transaction was cancelled', 'Could not find table: {}', 'Table {} does not exist', -'Database {} does not exist', 'Dictionary ({}) not found', 'Unknown table function {}', -'Unknown format {}', 'Unknown explain kind ''{}''', 'Unknown setting {}', 'Unknown input format {}', -'Unknown identifier: ''{}''', 'User name is empty', 'Expected function, got: {}', -'Attempt to read after eof', 'String size is too big ({}), maximum: {}', -'Processed: {}%', 'Creating {}: {}', 'Table {}.{} doesn''t exist', 'Invalid cache key hex: {}', -'User has been dropped', 'Illegal type {} of argument of function {}. Should be DateTime or DateTime64', -'Unknown statistic column: {}', -'Bad SSH public key provided', 'Database {} does not exist', 'Substitution {} is not set', 'Invalid cache key hex: {}' -] as arr) array join arr; +create temporary table known_short_messages (s String) as select * from (select [ + '', + '({}) Keys: {}', + '({}) {}', + 'Aggregating', + 'Attempt to read after EOF.', + 'Attempt to read after eof', + 'Bad SSH public key provided', + 'Became leader', + 'Bytes set to {}', + 'Cancelled merging parts', + 'Cancelled mutating parts', + 'Cannot parse date here: {}', + 'Cannot parse object', + 'Cannot parse uuid {}', + 'Cleaning queue', + 'Column \'{}\' is ambiguous', + 'Convert overflow', + 'Could not find table: {}', + 'Creating {}: {}', + 'Cyclic aliases', + 'Database {} does not exist', + 'Detaching {}', + 'Dictionary ({}) not found', + 'Division by zero', + 'Executing {}', + 'Expected end of line', + 'Expected function, got: {}', + 'Files set to {}', + 'Fire events: {}', + 'Found part {}', + 'Host is empty in S3 URI.', + 'INTO OUTFILE is not allowed', + 'Illegal type {} of argument of function {}. Should be DateTime or DateTime64', + 'Illegal UTF-8 sequence, while processing \'{}\'', + 'Invalid cache key hex: {}', + 'Invalid date: {}', + 'Invalid mode: {}', + 'Invalid qualified name: {}', + 'Invalid replica name: {}', + 'Loaded queue', + 'Log pulling is cancelled', + 'New segment: {}', + 'No additional keys found.', + 'No part {} in table', + 'No sharding key', + 'No tables', + 'Numeric overflow', + 'Path to archive is empty', + 'Processed: {}%', + 'Query was cancelled', + 'Query: {}', + 'Read object: {}', + 'Removed part {}', + 'Removing parts.', + 'Replication was stopped', + 'Request URI: {}', + 'Sending part {}', + 'Sent handshake', + 'Starting {}', + 'String size is too big ({}), maximum: {}', + 'Substitution {} is not set', + 'Table {} does not exist', + 'Table {}.{} doesn\'t exist', + 'There are duplicate id {}', + 'There is no cache by name: {}', + 'Too large node state size', + 'Transaction was cancelled', + 'Unable to parse JSONPath', + 'Unexpected value {} in enum', + 'Unknown BSON type: {}', + 'Unknown explain kind \'{}\'', + 'Unknown format {}', + 'Unknown identifier: \'{}\'', + 'Unknown input format {}', + 'Unknown setting {}', + 'Unknown statistic column: {}', + 'Unknown table function {}', + 'User has been dropped', + 'User name is empty', + 'Will mimic {}', + 'Write file: {}', + 'Writing to {}', + '`{}` should be a String', + 'brotli decode error{}', + 'dropIfEmpty', + 'inflate failed: {}{}', + 'loadAll {}', + '{} ({})', + '{} ({}:{})', + '{} -> {}', + '{} {}', + '{}%', + '{}: {}' + ] as arr) array join arr; -- Check that we don't have too many short meaningless message patterns. WITH 1 AS max_messages From 4fbe41b47e21ee9e80467c39f214942d5551f730 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Tue, 26 Dec 2023 16:51:39 +0300 Subject: [PATCH 252/253] Update 02950_part_offset_as_primary_key.sql --- .../queries/0_stateless/02950_part_offset_as_primary_key.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/02950_part_offset_as_primary_key.sql b/tests/queries/0_stateless/02950_part_offset_as_primary_key.sql index 4691d2a55fc..736d54023ce 100644 --- a/tests/queries/0_stateless/02950_part_offset_as_primary_key.sql +++ b/tests/queries/0_stateless/02950_part_offset_as_primary_key.sql @@ -6,7 +6,7 @@ insert into a select -number from numbers(5); -- nothing to read select i from a where _part_offset >= 5 order by i settings max_bytes_to_read = 1; --- one granules +-- one granule select i from a where _part_offset = 0 order by i settings max_rows_to_read = 2; select i from a where _part_offset = 1 order by i settings max_rows_to_read = 2; select i from a where _part_offset = 2 order by i settings max_rows_to_read = 2; @@ -17,7 +17,7 @@ select i from a where _part_offset = 4 order by i settings max_rows_to_read = 1; select i from a where _part_offset in (1, 4) order by i settings max_rows_to_read = 3; select i from a where _part_offset not in (1, 4) order by i settings max_rows_to_read = 4; --- force primary key check still works +-- the force_primary_key check still works select i from a where _part_offset = 4 order by i settings force_primary_key = 1; -- { serverError INDEX_NOT_USED } -- combining with other primary keys doesn't work (makes no sense) From f8d9a850c7f66369ab5726fb03f2dcd8c01a6783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Wed, 27 Dec 2023 12:16:17 +0000 Subject: [PATCH 253/253] Fix perf test README --- tests/performance/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/performance/README.md b/tests/performance/README.md index f554e96203b..289ecaba034 100644 --- a/tests/performance/README.md +++ b/tests/performance/README.md @@ -18,5 +18,5 @@ TODO @akuzm ``` pip3 install clickhouse_driver scipy -../../docker/test/performance-comparison/perf.py --runs 1 insert_parallel.xml +../../tests/performance/scripts/perf.py --runs 1 insert_parallel.xml ```