From 8067f98103e6c366ecd5a976c85cfa69d199b715 Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 1 Feb 2023 18:19:09 +0100 Subject: [PATCH 001/229] Add test which reproduce rename bug --- .../02538_alter_rename_sequence.reference | 0 .../02538_alter_rename_sequence.sql | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/queries/0_stateless/02538_alter_rename_sequence.reference create mode 100644 tests/queries/0_stateless/02538_alter_rename_sequence.sql diff --git a/tests/queries/0_stateless/02538_alter_rename_sequence.reference b/tests/queries/0_stateless/02538_alter_rename_sequence.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02538_alter_rename_sequence.sql b/tests/queries/0_stateless/02538_alter_rename_sequence.sql new file mode 100644 index 00000000000..efdb1d469eb --- /dev/null +++ b/tests/queries/0_stateless/02538_alter_rename_sequence.sql @@ -0,0 +1,27 @@ +DROP TABLE IF EXISTS wrong_metadata; + +CREATE TABLE wrong_metadata( + column1 UInt64, + column2 UInt64, + column3 UInt64 +) +ENGINE ReplicatedMergeTree('/test/tables/wrong_metadata', '1') +ORDER BY tuple(); + +INSERT INTO wrong_metadata VALUES (1, 2, 3); + +SYSTEM STOP REPLICATION QUEUES wrong_metadata; + +ALTER TABLE wrong_metadata RENAME COLUMN column1 TO column1_renamed SETTINGS replication_alter_partitions_sync = 0; + +INSERT INTO wrong_metadata VALUES (4, 5, 6); + +SYSTEM START REPLICATION QUEUES wrong_metadata; + +SYSTEM SYNC REPLICA wrong_metadata; + +ALTER TABLE wrong_metadata RENAME COLUMN column2 to column2_renamed SETTINGS replication_alter_partitions_sync = 2; + +SELECT * FROM wrong_metadata ORDER BY column1_renamed FORMAT JSONEachRow; + +DROP TABLE IF EXISTS wrong_metadata; From 174bdba912565e05a5e6d41ce8d2f44c4848606f Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 16:57:46 +0100 Subject: [PATCH 002/229] Fix bugs in alter rename column --- src/Storages/MergeTree/AlterConversions.h | 2 + src/Storages/MergeTree/MergeTreeData.cpp | 2 + src/Storages/MergeTree/MutateTask.cpp | 60 +++++++++++++++++-- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 11 ++-- ...1_alter_rename_and_other_renames.reference | 6 +- .../01281_alter_rename_and_other_renames.sql | 2 +- .../02538_alter_rename_sequence.reference | 8 +++ .../02538_alter_rename_sequence.sql | 32 ++++++++++ 8 files changed, 110 insertions(+), 13 deletions(-) diff --git a/src/Storages/MergeTree/AlterConversions.h b/src/Storages/MergeTree/AlterConversions.h index 0d58499d424..6f5d5157138 100644 --- a/src/Storages/MergeTree/AlterConversions.h +++ b/src/Storages/MergeTree/AlterConversions.h @@ -17,6 +17,8 @@ struct AlterConversions /// Rename map new_name -> old_name std::unordered_map rename_map; + bool columnHasNewName(const std::string & old_name) const; + std::string getColumnNewName(const std::string & old_name) const; bool isColumnRenamed(const std::string & new_name) const { return rename_map.count(new_name) > 0; } std::string getColumnOldName(const std::string & new_name) const { return rename_map.at(new_name); } }; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index b2e0c14489a..99e84a6c783 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7526,11 +7526,13 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa AlterConversions result{}; for (const auto & command : commands) + { /// Currently we need explicit conversions only for RENAME alter /// all other conversions can be deduced from diff between part columns /// and columns in storage. if (command.type == MutationCommand::Type::RENAME_COLUMN) result.rename_map[command.rename_to] = command.column_name; + } return result; } diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 47df0cfe42e..da87fcaad9a 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -109,13 +109,29 @@ static void splitMutationCommands( } } } + + auto alter_conversions = part->storage.getAlterConversionsForPart(part); /// If it's compact part, then we don't need to actually remove files /// from disk we just don't read dropped columns for (const auto & column : part->getColumns()) { if (!mutated_columns.contains(column.name)) - for_interpreter.emplace_back( - MutationCommand{.type = MutationCommand::Type::READ_COLUMN, .column_name = column.name, .data_type = column.type}); + { + if (alter_conversions.columnHasNewName(column.name)) + { + for_interpreter.push_back( + { + .type = MutationCommand::Type::READ_COLUMN, + .column_name = alter_conversions.getColumnNewName(column.name), + .data_type = column.type + }); + } + else + { + for_interpreter.emplace_back( + MutationCommand{.type = MutationCommand::Type::READ_COLUMN, .column_name = column.name, .data_type = column.type}); + } + } } } else @@ -147,6 +163,22 @@ static void splitMutationCommands( for_file_renames.push_back(command); } } + + auto alter_conversions = part->storage.getAlterConversionsForPart(part); + for (const auto & part_column : part_columns) + { + if (alter_conversions.columnHasNewName(part_column.name)) + { + auto new_column_name = alter_conversions.getColumnNewName(part_column.name); + for_file_renames.push_back({ + .type = MutationCommand::Type::RENAME_COLUMN, + .column_name = part_column.name, + .rename_to = new_column_name, + }); + + part_columns.rename(part_column.name, new_column_name); + } + } } } @@ -276,12 +308,30 @@ getColumnsForNewDataPart( /// Column was renamed and no other column renamed to it's name /// or column is dropped. if (!renamed_columns_to_from.contains(it->name) && (was_renamed || was_removed)) + { it = storage_columns.erase(it); + } else { - /// Take a type from source part column. - /// It may differ from column type in storage. - it->type = source_col->second; + + if (was_removed) + { /// DROP COLUMN xxx, RENAME COLUMN yyy TO xxx + auto renamed_from = renamed_columns_to_from.at(it->name); + auto maybe_name_and_type = source_columns.tryGetByName(renamed_from); + if (!maybe_name_and_type) + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Got incorrect mutation commands, column {} was renamed from {}, but it doesn't exist in source columns {}", + it->name, renamed_from, source_columns.toString()); + + it->type = maybe_name_and_type->type; + } + else + { + /// Take a type from source part column. + /// It may differ from column type in storage. + it->type = source_col->second; + } ++it; } } diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index e47dddb9795..e4f7f67ee02 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1759,12 +1759,15 @@ MutationCommands ReplicatedMergeTreeQueue::getFirstAlterMutationCommandsForPart( if (in_partition == mutations_by_partition.end()) return MutationCommands{}; - Int64 part_version = part->info.getDataVersion(); + Int64 part_mutation_version = part->info.getMutationVersion(); + MutationCommands result; for (auto [mutation_version, mutation_status] : in_partition->second) - if (mutation_version > part_version && mutation_status->entry->alter_version != -1) - return mutation_status->entry->commands; + { + if (mutation_version > part_mutation_version && mutation_status->entry->alter_version != -1) + result.insert(result.end(), mutation_status->entry->commands.begin(), mutation_status->entry->commands.end()); + } - return MutationCommands{}; + return result; } MutationCommands ReplicatedMergeTreeQueue::getMutationCommands( diff --git a/tests/queries/0_stateless/01281_alter_rename_and_other_renames.reference b/tests/queries/0_stateless/01281_alter_rename_and_other_renames.reference index bf3358aea60..532b8ce8712 100644 --- a/tests/queries/0_stateless/01281_alter_rename_and_other_renames.reference +++ b/tests/queries/0_stateless/01281_alter_rename_and_other_renames.reference @@ -1,11 +1,11 @@ -CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS index_granularity = 8192 +CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS min_bytes_for_wide_part = 0, index_granularity = 8192 key value1_string value2 1 2 3 -CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2_old` Int32,\n `value2` Int64 DEFAULT 7\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS index_granularity = 8192 +CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2_old` Int32,\n `value2` Int64 DEFAULT 7\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS min_bytes_for_wide_part = 0, index_granularity = 8192 key value1_string value2_old value2 1 2 3 7 4 5 6 7 -CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2_old` Int64 DEFAULT 7\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS index_granularity = 8192 +CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2_old` Int64 DEFAULT 7\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS min_bytes_for_wide_part = 0, index_granularity = 8192 key value1_string value2_old 1 2 7 4 5 7 diff --git a/tests/queries/0_stateless/01281_alter_rename_and_other_renames.sql b/tests/queries/0_stateless/01281_alter_rename_and_other_renames.sql index f9462f0478e..b0ccd7751ab 100644 --- a/tests/queries/0_stateless/01281_alter_rename_and_other_renames.sql +++ b/tests/queries/0_stateless/01281_alter_rename_and_other_renames.sql @@ -1,6 +1,6 @@ DROP TABLE IF EXISTS rename_table_multiple; -CREATE TABLE rename_table_multiple (key Int32, value1 String, value2 Int32) ENGINE = MergeTree ORDER BY tuple(); +CREATE TABLE rename_table_multiple (key Int32, value1 String, value2 Int32) ENGINE = MergeTree ORDER BY tuple() SETTINGS min_bytes_for_wide_part=0; INSERT INTO rename_table_multiple VALUES (1, 2, 3); diff --git a/tests/queries/0_stateless/02538_alter_rename_sequence.reference b/tests/queries/0_stateless/02538_alter_rename_sequence.reference index e69de29bb2d..73aa1b7e8d8 100644 --- a/tests/queries/0_stateless/02538_alter_rename_sequence.reference +++ b/tests/queries/0_stateless/02538_alter_rename_sequence.reference @@ -0,0 +1,8 @@ +1 2 3 +4 5 6 +{"column1_renamed":"1","column2_renamed":"2","column3":"3"} +{"column1_renamed":"4","column2_renamed":"5","column3":"6"} +1 2 3 +4 5 6 +{"column1_renamed":"1","column2_renamed":"2","column3":"3"} +{"column1_renamed":"4","column2_renamed":"5","column3":"6"} diff --git a/tests/queries/0_stateless/02538_alter_rename_sequence.sql b/tests/queries/0_stateless/02538_alter_rename_sequence.sql index efdb1d469eb..0eb839ebe59 100644 --- a/tests/queries/0_stateless/02538_alter_rename_sequence.sql +++ b/tests/queries/0_stateless/02538_alter_rename_sequence.sql @@ -16,6 +16,8 @@ ALTER TABLE wrong_metadata RENAME COLUMN column1 TO column1_renamed SETTINGS rep INSERT INTO wrong_metadata VALUES (4, 5, 6); +SELECT * FROM wrong_metadata ORDER BY column1; + SYSTEM START REPLICATION QUEUES wrong_metadata; SYSTEM SYNC REPLICA wrong_metadata; @@ -25,3 +27,33 @@ ALTER TABLE wrong_metadata RENAME COLUMN column2 to column2_renamed SETTINGS rep SELECT * FROM wrong_metadata ORDER BY column1_renamed FORMAT JSONEachRow; DROP TABLE IF EXISTS wrong_metadata; + + +CREATE TABLE wrong_metadata_wide( + column1 UInt64, + column2 UInt64, + column3 UInt64 +) +ENGINE ReplicatedMergeTree('/test/tables/wrong_metadata_wide', '1') +ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0; + +INSERT INTO wrong_metadata_wide VALUES (1, 2, 3); + +SYSTEM STOP REPLICATION QUEUES wrong_metadata_wide; + +ALTER TABLE wrong_metadata_wide RENAME COLUMN column1 TO column1_renamed SETTINGS replication_alter_partitions_sync = 0; + +INSERT INTO wrong_metadata_wide VALUES (4, 5, 6); + +SELECT * FROM wrong_metadata_wide ORDER by column1; + +SYSTEM START REPLICATION QUEUES wrong_metadata_wide; + +SYSTEM SYNC REPLICA wrong_metadata_wide; + +ALTER TABLE wrong_metadata_wide RENAME COLUMN column2 to column2_renamed SETTINGS replication_alter_partitions_sync = 2; + +SELECT * FROM wrong_metadata_wide ORDER BY column1_renamed FORMAT JSONEachRow; + +DROP TABLE IF EXISTS wrong_metadata_wide; From dea46e58fbc203c674f2322ce8936614e3acc98d Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 17:03:24 +0100 Subject: [PATCH 003/229] Fixes for ordinary merge tree --- src/Storages/MergeTree/AlterConversions.cpp | 34 +++++++++++++++++++++ src/Storages/StorageMergeTree.cpp | 13 +++++--- 2 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 src/Storages/MergeTree/AlterConversions.cpp diff --git a/src/Storages/MergeTree/AlterConversions.cpp b/src/Storages/MergeTree/AlterConversions.cpp new file mode 100644 index 00000000000..e2eea2e68f6 --- /dev/null +++ b/src/Storages/MergeTree/AlterConversions.cpp @@ -0,0 +1,34 @@ +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +bool AlterConversions::columnHasNewName(const std::string & old_name) const +{ + for (const auto & [new_name, prev_name] : rename_map) + { + if (old_name == prev_name) + return true; + } + + return false; +} + +std::string AlterConversions::getColumnNewName(const std::string & old_name) const +{ + for (const auto & [new_name, prev_name] : rename_map) + { + if (old_name == prev_name) + return new_name; + } + + throw Exception(ErrorCodes::LOGICAL_ERROR, "Column {} was not renamed", old_name); +} + +} diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 4ef34ae91d5..e3d4ca072ca 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -2060,10 +2060,15 @@ MutationCommands StorageMergeTree::getFirstAlterMutationCommandsForPart(const Da { std::lock_guard lock(currently_processing_in_background_mutex); - auto it = current_mutations_by_version.upper_bound(part->info.getDataVersion()); - if (it == current_mutations_by_version.end()) - return {}; - return it->second.commands; + Int64 part_mutation_version = part->info.getMutationVersion(); + + MutationCommands result; + for (const auto & current_mutation_by_version : current_mutations_by_version) + { + if (static_cast(current_mutation_by_version.first) > part_mutation_version) + result.insert(result.end(), current_mutation_by_version.second.commands.begin(), current_mutation_by_version.second.commands.end()); + } + return result; } void StorageMergeTree::startBackgroundMovesIfNeeded() From 8477b811308014674e9f5bfb16c1e3a6a40f0196 Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 17:17:57 +0100 Subject: [PATCH 004/229] Add working test --- .../02543_alter_rename_modify_stuck.reference | 1 + .../02543_alter_rename_modify_stuck.sh | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 tests/queries/0_stateless/02543_alter_rename_modify_stuck.reference create mode 100755 tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh diff --git a/tests/queries/0_stateless/02543_alter_rename_modify_stuck.reference b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.reference new file mode 100644 index 00000000000..156128e3dd2 --- /dev/null +++ b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.reference @@ -0,0 +1 @@ +{"v":"1","v2":"77"} diff --git a/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh new file mode 100755 index 00000000000..0e9b39d6dae --- /dev/null +++ b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + + +$CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS table_to_rename" + +$CLICKHOUSE_CLIENT --query="CREATE TABLE table_to_rename(v UInt64, v1 UInt64)ENGINE = MergeTree ORDER BY tuple() SETTINGS min_bytes_for_wide_part = 0" + +$CLICKHOUSE_CLIENT --query="INSERT INTO table_to_rename VALUES (1, 1)" + + +# we want to following mutations to stuck +# That is why we stop merges and wait in loops until they actually start +$CLICKHOUSE_CLIENT --query="SYSTEM STOP MERGES table_to_rename" + +$CLICKHOUSE_CLIENT --query="ALTER TABLE table_to_rename RENAME COLUMN v1 to v2" & + +counter=0 retries=60 + +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "show create table table_to_rename") + if [[ $result == *"v2"* ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + + +$CLICKHOUSE_CLIENT --query="ALTER TABLE table_to_rename UPDATE v2 = 77 WHERE 1 = 1" & + +counter=0 retries=60 + +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "SELECT count() from system.mutations where database='${CLICKHOUSE_DATABASE}' and table='table_to_rename'") + if [[ $result == "2" ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + + +$CLICKHOUSE_CLIENT --query="SYSTEM START MERGES table_to_rename" + +wait + +$CLICKHOUSE_CLIENT --query="SELECT * FROM table_to_rename FORMAT JSONEachRow" + + + $CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS table_to_rename" From bdc530dead710a4dd108e5effb1c561f57913174 Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 17:30:38 +0100 Subject: [PATCH 005/229] Fix style --- src/Storages/MergeTree/MergeTreeData.cpp | 2 +- src/Storages/MergeTree/MergeTreeData.h | 2 +- src/Storages/MergeTree/MutateTask.cpp | 6 +----- src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp | 6 +++++- src/Storages/MergeTree/ReplicatedMergeTreeQueue.h | 6 +++--- src/Storages/StorageMergeTree.cpp | 2 +- src/Storages/StorageMergeTree.h | 2 +- src/Storages/StorageReplicatedMergeTree.cpp | 4 ++-- src/Storages/StorageReplicatedMergeTree.h | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 99e84a6c783..c8b1020bd82 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7522,7 +7522,7 @@ bool MergeTreeData::canUsePolymorphicParts(const MergeTreeSettings & settings, S AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPartPtr part) const { - MutationCommands commands = getFirstAlterMutationCommandsForPart(part); + MutationCommands commands = getAlterMutationCommandsForPart(part); AlterConversions result{}; for (const auto & command : commands) diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 7dcd0c40553..0dfeb44524e 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -1303,7 +1303,7 @@ protected: /// Used to receive AlterConversions for part and apply them on fly. This /// method has different implementations for replicated and non replicated /// MergeTree because they store mutations in different way. - virtual MutationCommands getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const = 0; + virtual MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const = 0; /// Moves part to specified space, used in ALTER ... MOVE ... queries bool movePartsToSpace(const DataPartsVector & parts, SpacePtr space); diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index da87fcaad9a..44ec321681b 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -170,11 +170,7 @@ static void splitMutationCommands( if (alter_conversions.columnHasNewName(part_column.name)) { auto new_column_name = alter_conversions.getColumnNewName(part_column.name); - for_file_renames.push_back({ - .type = MutationCommand::Type::RENAME_COLUMN, - .column_name = part_column.name, - .rename_to = new_column_name, - }); + for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = part_column.name, .rename_to = new_column_name}); part_columns.rename(part_column.name, new_column_name); } diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index e4f7f67ee02..e8ed0888fa1 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1752,7 +1752,7 @@ ReplicatedMergeTreeMergePredicate ReplicatedMergeTreeQueue::getMergePredicate(zk } -MutationCommands ReplicatedMergeTreeQueue::getFirstAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const +MutationCommands ReplicatedMergeTreeQueue::getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const { std::lock_guard lock(state_mutex); auto in_partition = mutations_by_partition.find(part->info.partition_id); @@ -1761,6 +1761,10 @@ MutationCommands ReplicatedMergeTreeQueue::getFirstAlterMutationCommandsForPart( Int64 part_mutation_version = part->info.getMutationVersion(); MutationCommands result; + /// Here we return mutation commands for part which has bigger mutation version than part mutation version. + /// Please note, we don't use getDataVersion(). It's because these alter commands are used for in-fly conversions + /// of part's metadata. It mean that even if we have mutation with version X and part with data version X+10, but + /// without mutation version part can still have wrong metadata and we have to apply this change on-fly if needed. for (auto [mutation_version, mutation_status] : in_partition->second) { if (mutation_version > part_mutation_version && mutation_status->entry->alter_version != -1) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h index 36f1ee07ad4..2c566668877 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h @@ -393,10 +393,10 @@ public: MutationCommands getMutationCommands(const MergeTreeData::DataPartPtr & part, Int64 desired_mutation_version) const; - /// Return mutation commands for part with smallest mutation version bigger - /// than data part version. Used when we apply alter commands on fly, + /// Return mutation commands for part which could be not applied to + /// it according to part mutation version. Used when we apply alter commands on fly, /// without actual data modification on disk. - MutationCommands getFirstAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const; + MutationCommands getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const; /// Mark finished mutations as done. If the function needs to be called again at some later time /// (because some mutations are probably done but we are not sure yet), returns true. diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index e3d4ca072ca..a9664007614 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -2056,7 +2056,7 @@ void StorageMergeTree::attachRestoredParts(MutableDataPartsVector && parts) } -MutationCommands StorageMergeTree::getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const +MutationCommands StorageMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const { std::lock_guard lock(currently_processing_in_background_mutex); diff --git a/src/Storages/StorageMergeTree.h b/src/Storages/StorageMergeTree.h index 1dff6323e4c..957d7804b96 100644 --- a/src/Storages/StorageMergeTree.h +++ b/src/Storages/StorageMergeTree.h @@ -265,7 +265,7 @@ private: protected: - MutationCommands getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const override; + MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const override; }; } diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 7a4b97f6e49..58adc4be594 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -7892,9 +7892,9 @@ bool StorageReplicatedMergeTree::canUseAdaptiveGranularity() const } -MutationCommands StorageReplicatedMergeTree::getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const +MutationCommands StorageReplicatedMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const { - return queue.getFirstAlterMutationCommandsForPart(part); + return queue.getAlterMutationCommandsForPart(part); } diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index 042e6acf4e2..3dc5ab75891 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -830,7 +830,7 @@ private: void waitMutationToFinishOnReplicas( const Strings & replicas, const String & mutation_id) const; - MutationCommands getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const override; + MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const override; void startBackgroundMovesIfNeeded() override; From fc6c62e2ff62ce754362f9a44731bed0598168a4 Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 17:41:36 +0100 Subject: [PATCH 006/229] Add comment --- src/Storages/MergeTree/AlterConversions.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Storages/MergeTree/AlterConversions.h b/src/Storages/MergeTree/AlterConversions.h index 6f5d5157138..47f964e85fe 100644 --- a/src/Storages/MergeTree/AlterConversions.h +++ b/src/Storages/MergeTree/AlterConversions.h @@ -17,9 +17,13 @@ struct AlterConversions /// Rename map new_name -> old_name std::unordered_map rename_map; + /// Column was renamed (lookup by value in rename_map) bool columnHasNewName(const std::string & old_name) const; + /// Get new name for column (lookup by value in rename_map) std::string getColumnNewName(const std::string & old_name) const; + /// Is this name is new name of column (lookup by key in rename_map) bool isColumnRenamed(const std::string & new_name) const { return rename_map.count(new_name) > 0; } + /// Get column old name before rename (lookup by key in rename_map) std::string getColumnOldName(const std::string & new_name) const { return rename_map.at(new_name); } }; From b8b0f1c3d3a2df7d0d23e4f67d6f6afe3c9dc5ca Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 21:11:19 +0100 Subject: [PATCH 007/229] Make RENAME COLUMN barrier --- .../MergeTree/MergeTreeMarksLoader.cpp | 16 +++++++++- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 25 ++++++++++++++- src/Storages/StorageMergeTree.cpp | 31 +++++++++++++++++-- .../02543_alter_rename_modify_stuck.sh | 2 +- 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeMarksLoader.cpp b/src/Storages/MergeTree/MergeTreeMarksLoader.cpp index 3fc7ff54c35..1d85ac1bd34 100644 --- a/src/Storages/MergeTree/MergeTreeMarksLoader.cpp +++ b/src/Storages/MergeTree/MergeTreeMarksLoader.cpp @@ -102,6 +102,15 @@ MarkCache::MappedPtr MergeTreeMarksLoader::loadMarksImpl() auto res = std::make_shared(marks_count * columns_in_mark); + if (file_size == 0 && marks_count != 0) + { + throw Exception( + ErrorCodes::CORRUPTED_DATA, + "Empty marks file '{}': {}, must be: {}", + std::string(fs::path(data_part_storage->getFullPath()) / mrk_path), + file_size, expected_uncompressed_size); + } + if (!index_granularity_info.mark_type.compressed && expected_uncompressed_size != file_size) throw Exception( ErrorCodes::CORRUPTED_DATA, @@ -138,7 +147,12 @@ MarkCache::MappedPtr MergeTreeMarksLoader::loadMarksImpl() } if (i * mark_size != expected_uncompressed_size) - throw Exception(ErrorCodes::CANNOT_READ_ALL_DATA, "Cannot read all marks from file {}", mrk_path); + { + throw Exception( + ErrorCodes::CANNOT_READ_ALL_DATA, + "Cannot read all marks from file {}, marks expected {} (bytes size {}), marks read {} (bytes size {})", + mrk_path, marks_count, expected_uncompressed_size, i, reader->count()); + } } res->protect(); diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index e8ed0888fa1..2c957132918 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1813,7 +1813,30 @@ MutationCommands ReplicatedMergeTreeQueue::getMutationCommands( MutationCommands commands; for (auto it = begin; it != end; ++it) - commands.insert(commands.end(), it->second->entry->commands.begin(), it->second->entry->commands.end()); + { + bool rename_command = false; + if (it->second->entry->isAlterMutation()) + { + const auto & single_mutation_commands = it->second->entry->commands; + for (const auto & command : single_mutation_commands) + { + if (command.type == MutationCommand::Type::RENAME_COLUMN) + { + rename_command = true; + break; + } + } + } + + if (rename_command) + { + if (commands.empty()) + commands.insert(commands.end(), it->second->entry->commands.begin(), it->second->entry->commands.end()); + break; + } + else + commands.insert(commands.end(), it->second->entry->commands.begin(), it->second->entry->commands.end()); + } return commands; } diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index a9664007614..73585a630cb 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1114,9 +1114,34 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMutate( if (current_ast_elements + commands_size >= max_ast_elements) break; - current_ast_elements += commands_size; - commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); - last_mutation_to_apply = it; + bool rename_command = false; + const auto & single_mutation_commands = it->second.commands; + for (const auto & command : single_mutation_commands) + { + if (command.type == MutationCommand::Type::RENAME_COLUMN) + { + rename_command = true; + break; + } + } + + if (rename_command) + { + if (commands->empty()) + { + current_ast_elements += commands_size; + commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); + last_mutation_to_apply = it; + } + break; + } + else + { + current_ast_elements += commands_size; + commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); + last_mutation_to_apply = it; + } + } assert(commands->empty() == (last_mutation_to_apply == mutations_end_it)); diff --git a/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh index 0e9b39d6dae..adaf1846552 100755 --- a/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh +++ b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh @@ -32,7 +32,7 @@ while [[ $counter -lt $retries ]]; do done -$CLICKHOUSE_CLIENT --query="ALTER TABLE table_to_rename UPDATE v2 = 77 WHERE 1 = 1" & +$CLICKHOUSE_CLIENT --query="ALTER TABLE table_to_rename UPDATE v2 = 77 WHERE 1 = 1 SETTINGS mutations_sync = 2" & counter=0 retries=60 From 15ddcdc4a6606f62b4333333144231300243260c Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 12:34:47 +0100 Subject: [PATCH 008/229] Fix clang tidy --- src/Storages/StorageMergeTree.cpp | 1 - tests/queries/0_stateless/02538_alter_rename_sequence.sql | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 73585a630cb..e36bb31551d 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1129,7 +1129,6 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMutate( { if (commands->empty()) { - current_ast_elements += commands_size; commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); last_mutation_to_apply = it; } diff --git a/tests/queries/0_stateless/02538_alter_rename_sequence.sql b/tests/queries/0_stateless/02538_alter_rename_sequence.sql index 0eb839ebe59..d7df27dc702 100644 --- a/tests/queries/0_stateless/02538_alter_rename_sequence.sql +++ b/tests/queries/0_stateless/02538_alter_rename_sequence.sql @@ -5,7 +5,7 @@ CREATE TABLE wrong_metadata( column2 UInt64, column3 UInt64 ) -ENGINE ReplicatedMergeTree('/test/tables/wrong_metadata', '1') +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata', '1') ORDER BY tuple(); INSERT INTO wrong_metadata VALUES (1, 2, 3); @@ -34,7 +34,7 @@ CREATE TABLE wrong_metadata_wide( column2 UInt64, column3 UInt64 ) -ENGINE ReplicatedMergeTree('/test/tables/wrong_metadata_wide', '1') +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata_wide', '1') ORDER BY tuple() SETTINGS min_bytes_for_wide_part = 0; From 7a0d0f0c5324cddce2131a2af6f6e5535db65f30 Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 19:14:49 +0100 Subject: [PATCH 009/229] Some code which doesn't work --- src/Storages/MergeTree/MergeTreeData.cpp | 42 +++++-- src/Storages/MergeTree/MergeTreeData.h | 2 +- src/Storages/MergeTree/MutateTask.cpp | 116 ++++++++++-------- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 28 +++-- .../MergeTree/ReplicatedMergeTreeQueue.h | 2 +- src/Storages/StorageMergeTree.cpp | 6 +- src/Storages/StorageMergeTree.h | 2 +- src/Storages/StorageReplicatedMergeTree.cpp | 2 +- src/Storages/StorageReplicatedMergeTree.h | 2 +- .../01278_alter_rename_combination.reference | 4 +- .../01278_alter_rename_combination.sql | 2 +- 11 files changed, 128 insertions(+), 80 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index c8b1020bd82..27b9881bcf3 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7522,16 +7522,44 @@ bool MergeTreeData::canUsePolymorphicParts(const MergeTreeSettings & settings, S AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPartPtr part) const { - MutationCommands commands = getAlterMutationCommandsForPart(part); + std::map commands_map = getAlterMutationCommandsForPart(part); + auto part_columns = part->getColumnsDescription(); AlterConversions result{}; - for (const auto & command : commands) + auto & rename_map = result.rename_map; + for (const auto & [version, commands] : commands_map) { - /// Currently we need explicit conversions only for RENAME alter - /// all other conversions can be deduced from diff between part columns - /// and columns in storage. - if (command.type == MutationCommand::Type::RENAME_COLUMN) - result.rename_map[command.rename_to] = command.column_name; + for (const auto & command : commands) + { + /// Currently we need explicit conversions only for RENAME alter + /// all other conversions can be deduced from diff between part columns + /// and columns in storage. + if (command.type == MutationCommand::Type::RENAME_COLUMN) + { + if (!part_columns.has(command.column_name)) + continue; + + part_columns.rename(command.column_name, command.rename_to); + + if (auto it = rename_map.find(command.column_name); it != rename_map.end()) + { + auto rename_source = it->second; + rename_map.erase(it); + + rename_map[command.rename_to] = rename_source; + } + else + rename_map[command.rename_to] = command.column_name; + } + } + } + + for (auto it = rename_map.begin(); it != rename_map.end();) + { + if (it->first == it->second) + it = rename_map.erase(it); + else + ++it; } return result; diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 0dfeb44524e..a60f4721661 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -1303,7 +1303,7 @@ protected: /// Used to receive AlterConversions for part and apply them on fly. This /// method has different implementations for replicated and non replicated /// MergeTree because they store mutations in different way. - virtual MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const = 0; + virtual std::map getAlterMutationCommandsForPart(const DataPartPtr & part) const = 0; /// Moves part to specified space, used in ALTER ... MOVE ... queries bool movePartsToSpace(const DataPartsVector & parts, SpacePtr space); diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 57036d4ba30..85dc9f7f0d6 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -97,40 +97,33 @@ static void splitMutationCommands( else mutated_columns.emplace(command.column_name); } - - if (command.type == MutationCommand::Type::RENAME_COLUMN) - { - for_interpreter.push_back( - { - .type = MutationCommand::Type::READ_COLUMN, - .column_name = command.rename_to, - }); - part_columns.rename(command.column_name, command.rename_to); - } } } auto alter_conversions = part->storage.getAlterConversionsForPart(part); /// If it's compact part, then we don't need to actually remove files /// from disk we just don't read dropped columns - for (const auto & column : part->getColumns()) + + for (const auto & [rename_to, rename_from] : alter_conversions.rename_map) + { + if (part_columns.has(rename_from)) + { + for_interpreter.push_back( + { + .type = MutationCommand::Type::READ_COLUMN, + .column_name = rename_to, + }); + + part_columns.rename(rename_from, rename_to); + } + } + + for (const auto & column : part_columns) { if (!mutated_columns.contains(column.name)) { - if (alter_conversions.columnHasNewName(column.name)) - { - for_interpreter.push_back( - { - .type = MutationCommand::Type::READ_COLUMN, - .column_name = alter_conversions.getColumnNewName(column.name), - .data_type = column.type - }); - } - else - { - for_interpreter.emplace_back( - MutationCommand{.type = MutationCommand::Type::READ_COLUMN, .column_name = column.name, .data_type = column.type}); - } + for_interpreter.emplace_back( + MutationCommand{.type = MutationCommand::Type::READ_COLUMN, .column_name = column.name, .data_type = column.type}); } } } @@ -157,22 +150,18 @@ static void splitMutationCommands( { if (command.type == MutationCommand::Type::READ_COLUMN) for_interpreter.push_back(command); - else if (command.type == MutationCommand::Type::RENAME_COLUMN) - part_columns.rename(command.column_name, command.rename_to); for_file_renames.push_back(command); } } auto alter_conversions = part->storage.getAlterConversionsForPart(part); - for (const auto & part_column : part_columns) + for (const auto & [rename_to, rename_from] : alter_conversions.rename_map) { - if (alter_conversions.columnHasNewName(part_column.name)) + if (part_columns.has(rename_from)) { - auto new_column_name = alter_conversions.getColumnNewName(part_column.name); - for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = part_column.name, .rename_to = new_column_name}); - - part_columns.rename(part_column.name, new_column_name); + for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = rename_from, .rename_to = rename_to}); + part_columns.rename(rename_from, rename_to); } } } @@ -585,6 +574,13 @@ static NameToNameVector collectFilesForRenames( /// Collect counts for shared streams of different columns. As an example, Nested columns have shared stream with array sizes. auto stream_counts = getStreamCounts(source_part, source_part->getColumns().getNames()); NameToNameVector rename_vector; + NameSet collected_names; + + auto add_rename = [&rename_vector, &collected_names] (const std::string & file_rename_from, const std::string & file_rename_to) + { + if (collected_names.emplace(file_rename_from).second) + rename_vector.emplace_back(file_rename_from, file_rename_to); + }; /// Remove old data for (const auto & command : commands_for_removes) @@ -593,19 +589,19 @@ static NameToNameVector collectFilesForRenames( { if (source_part->checksums.has(INDEX_FILE_PREFIX + command.column_name + ".idx2")) { - rename_vector.emplace_back(INDEX_FILE_PREFIX + command.column_name + ".idx2", ""); - rename_vector.emplace_back(INDEX_FILE_PREFIX + command.column_name + mrk_extension, ""); + add_rename(INDEX_FILE_PREFIX + command.column_name + ".idx2", ""); + add_rename(INDEX_FILE_PREFIX + command.column_name + mrk_extension, ""); } else if (source_part->checksums.has(INDEX_FILE_PREFIX + command.column_name + ".idx")) { - rename_vector.emplace_back(INDEX_FILE_PREFIX + command.column_name + ".idx", ""); - rename_vector.emplace_back(INDEX_FILE_PREFIX + command.column_name + mrk_extension, ""); + add_rename(INDEX_FILE_PREFIX + command.column_name + ".idx", ""); + add_rename(INDEX_FILE_PREFIX + command.column_name + mrk_extension, ""); } } else if (command.type == MutationCommand::Type::DROP_PROJECTION) { if (source_part->checksums.has(command.column_name + ".proj")) - rename_vector.emplace_back(command.column_name + ".proj", ""); + add_rename(command.column_name + ".proj", ""); } else if (command.type == MutationCommand::Type::DROP_COLUMN) { @@ -615,8 +611,8 @@ static NameToNameVector collectFilesForRenames( /// Delete files if they are no longer shared with another column. if (--stream_counts[stream_name] == 0) { - rename_vector.emplace_back(stream_name + ".bin", ""); - rename_vector.emplace_back(stream_name + mrk_extension, ""); + add_rename(stream_name + ".bin", ""); + add_rename(stream_name + mrk_extension, ""); } }; @@ -628,6 +624,7 @@ static NameToNameVector collectFilesForRenames( String escaped_name_from = escapeForFileName(command.column_name); String escaped_name_to = escapeForFileName(command.rename_to); + ISerialization::StreamCallback callback = [&](const ISerialization::SubstreamPath & substream_path) { String stream_from = ISerialization::getFileNameForStream(command.column_name, substream_path); @@ -635,8 +632,8 @@ static NameToNameVector collectFilesForRenames( if (stream_from != stream_to) { - rename_vector.emplace_back(stream_from + ".bin", stream_to + ".bin"); - rename_vector.emplace_back(stream_from + mrk_extension, stream_to + mrk_extension); + add_rename(stream_from + ".bin", stream_to + ".bin"); + add_rename(stream_from + mrk_extension, stream_to + mrk_extension); } }; @@ -656,8 +653,8 @@ static NameToNameVector collectFilesForRenames( { if (!new_streams.contains(old_stream) && --stream_counts[old_stream] == 0) { - rename_vector.emplace_back(old_stream + ".bin", ""); - rename_vector.emplace_back(old_stream + mrk_extension, ""); + add_rename(old_stream + ".bin", ""); + add_rename(old_stream + mrk_extension, ""); } } } @@ -1358,13 +1355,27 @@ private: ctx->new_data_part->storeVersionMetadata(); NameSet hardlinked_files; + + /// NOTE: Renames must be done in order + for (const auto & [rename_from, rename_to] : ctx->files_to_rename) + { + if (rename_to.empty()) /// It's DROP COLUMN + { + /// pass + } + else + { + ctx->new_data_part->getDataPartStorage().createHardLinkFrom( + ctx->source_part->getDataPartStorage(), rename_from, rename_to); + hardlinked_files.insert(rename_from); + } + } /// Create hardlinks for unchanged files for (auto it = ctx->source_part->getDataPartStorage().iterate(); it->isValid(); it->next()) { if (ctx->files_to_skip.contains(it->name())) continue; - String destination; String file_name = it->name(); auto rename_it = std::find_if(ctx->files_to_rename.begin(), ctx->files_to_rename.end(), [&file_name](const auto & rename_pair) @@ -1374,20 +1385,17 @@ private: if (rename_it != ctx->files_to_rename.end()) { - if (rename_it->second.empty()) - continue; - destination = rename_it->second; - } - else - { - destination = it->name(); + /// RENAMEs and DROPs already processed + continue; } + String destination = it->name(); + if (it->isFile()) { ctx->new_data_part->getDataPartStorage().createHardLinkFrom( - ctx->source_part->getDataPartStorage(), it->name(), destination); - hardlinked_files.insert(it->name()); + ctx->source_part->getDataPartStorage(), file_name, destination); + hardlinked_files.insert(file_name); } else if (!endsWith(it->name(), ".tmp_proj")) // ignore projection tmp merge dir { diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index 2c957132918..df4dea978a1 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -11,6 +11,7 @@ #include #include +#include namespace DB { @@ -1752,23 +1753,34 @@ ReplicatedMergeTreeMergePredicate ReplicatedMergeTreeQueue::getMergePredicate(zk } -MutationCommands ReplicatedMergeTreeQueue::getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const +std::map ReplicatedMergeTreeQueue::getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const { - std::lock_guard lock(state_mutex); + std::unique_lock lock(state_mutex); auto in_partition = mutations_by_partition.find(part->info.partition_id); if (in_partition == mutations_by_partition.end()) - return MutationCommands{}; + return {}; - Int64 part_mutation_version = part->info.getMutationVersion(); - MutationCommands result; + Int64 part_data_version = part->info.getDataVersion(); + std::map result; /// Here we return mutation commands for part which has bigger mutation version than part mutation version. /// Please note, we don't use getDataVersion(). It's because these alter commands are used for in-fly conversions /// of part's metadata. It mean that even if we have mutation with version X and part with data version X+10, but /// without mutation version part can still have wrong metadata and we have to apply this change on-fly if needed. - for (auto [mutation_version, mutation_status] : in_partition->second) + + for (auto [mutation_version, mutation_status] : in_partition->second | std::views::reverse) { - if (mutation_version > part_mutation_version && mutation_status->entry->alter_version != -1) - result.insert(result.end(), mutation_status->entry->commands.begin(), mutation_status->entry->commands.end()); + if (mutation_status->entry->alter_version != -1) + { + if (mutation_version > part_data_version) + { + result[mutation_version] = mutation_status->entry->commands; + } + else + { + result[mutation_version] = mutation_status->entry->commands; + break; + } + } } return result; diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h index 2c566668877..150fb49b333 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h @@ -396,7 +396,7 @@ public: /// Return mutation commands for part which could be not applied to /// it according to part mutation version. Used when we apply alter commands on fly, /// without actual data modification on disk. - MutationCommands getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const; + std::map getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const; /// Mark finished mutations as done. If the function needs to be called again at some later time /// (because some mutations are probably done but we are not sure yet), returns true. diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index e36bb31551d..b0a45f9cfba 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -2080,17 +2080,17 @@ void StorageMergeTree::attachRestoredParts(MutableDataPartsVector && parts) } -MutationCommands StorageMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const +std::map StorageMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const { std::lock_guard lock(currently_processing_in_background_mutex); Int64 part_mutation_version = part->info.getMutationVersion(); - MutationCommands result; + std::map result; for (const auto & current_mutation_by_version : current_mutations_by_version) { if (static_cast(current_mutation_by_version.first) > part_mutation_version) - result.insert(result.end(), current_mutation_by_version.second.commands.begin(), current_mutation_by_version.second.commands.end()); + result[current_mutation_by_version.first] = current_mutation_by_version.second.commands; } return result; } diff --git a/src/Storages/StorageMergeTree.h b/src/Storages/StorageMergeTree.h index 957d7804b96..9385542dcac 100644 --- a/src/Storages/StorageMergeTree.h +++ b/src/Storages/StorageMergeTree.h @@ -265,7 +265,7 @@ private: protected: - MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const override; + std::map getAlterMutationCommandsForPart(const DataPartPtr & part) const override; }; } diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 58adc4be594..ef2fd46b705 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -7892,7 +7892,7 @@ bool StorageReplicatedMergeTree::canUseAdaptiveGranularity() const } -MutationCommands StorageReplicatedMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const +std::map StorageReplicatedMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const { return queue.getAlterMutationCommandsForPart(part); } diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index 3dc5ab75891..a4ac60052bc 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -830,7 +830,7 @@ private: void waitMutationToFinishOnReplicas( const Strings & replicas, const String & mutation_id) const; - MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const override; + std::map getAlterMutationCommandsForPart(const DataPartPtr & part) const override; void startBackgroundMovesIfNeeded() override; diff --git a/tests/queries/0_stateless/01278_alter_rename_combination.reference b/tests/queries/0_stateless/01278_alter_rename_combination.reference index cc912e9b265..e70c2d2e6f8 100644 --- a/tests/queries/0_stateless/01278_alter_rename_combination.reference +++ b/tests/queries/0_stateless/01278_alter_rename_combination.reference @@ -1,7 +1,7 @@ -CREATE TABLE default.rename_table\n(\n `key` Int32,\n `old_value1` Int32,\n `value1` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS index_granularity = 8192 +CREATE TABLE default.rename_table\n(\n `key` Int32,\n `old_value1` Int32,\n `value1` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS min_bytes_for_wide_part = 0, index_granularity = 8192 key old_value1 value1 1 2 3 -CREATE TABLE default.rename_table\n(\n `k` Int32,\n `v1` Int32,\n `v2` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS index_granularity = 8192 +CREATE TABLE default.rename_table\n(\n `k` Int32,\n `v1` Int32,\n `v2` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS min_bytes_for_wide_part = 0, index_granularity = 8192 k v1 v2 1 2 3 4 5 6 diff --git a/tests/queries/0_stateless/01278_alter_rename_combination.sql b/tests/queries/0_stateless/01278_alter_rename_combination.sql index fa73362622c..51322f5d86f 100644 --- a/tests/queries/0_stateless/01278_alter_rename_combination.sql +++ b/tests/queries/0_stateless/01278_alter_rename_combination.sql @@ -1,6 +1,6 @@ DROP TABLE IF EXISTS rename_table; -CREATE TABLE rename_table (key Int32, value1 Int32, value2 Int32) ENGINE = MergeTree ORDER BY tuple(); +CREATE TABLE rename_table (key Int32, value1 Int32, value2 Int32) ENGINE = MergeTree ORDER BY tuple() SETTINGS min_bytes_for_wide_part=0; INSERT INTO rename_table VALUES (1, 2, 3); From f91c10d09d56de8d23faf326c5c300de1ee20c2a Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 20:49:07 +0100 Subject: [PATCH 010/229] Initial support for version --- src/Storages/MergeTree/DataPartsExchange.cpp | 3 +- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 26 ++++++++++---- src/Storages/MergeTree/IMergeTreeDataPart.h | 7 +++- src/Storages/MergeTree/MergeTask.cpp | 2 +- src/Storages/MergeTree/MergeTreeData.cpp | 3 +- .../MergeTree/MergeTreeDataPartInMemory.cpp | 4 +-- .../MergeTree/MergeTreeDataWriter.cpp | 4 +-- .../MergeTree/MergeTreeWriteAheadLog.cpp | 2 +- .../MergeTree/MergedBlockOutputStream.cpp | 10 +++++- .../MergedColumnOnlyOutputStream.cpp | 2 +- src/Storages/MergeTree/MutateTask.cpp | 2 +- .../ReplicatedMergeTreeAttachThread.cpp | 4 ++- src/Storages/StorageInMemoryMetadata.cpp | 7 ++++ src/Storages/StorageInMemoryMetadata.h | 7 ++++ src/Storages/StorageReplicatedMergeTree.cpp | 35 +++++++++++-------- src/Storages/StorageReplicatedMergeTree.h | 9 +++-- 16 files changed, 89 insertions(+), 38 deletions(-) diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index cbbdb911974..1864ce37ddf 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -692,7 +692,8 @@ MergeTreeData::MutableDataPartPtr Fetcher::downloadPartToMemory( auto block = block_in.read(); throttler->add(block.bytes()); - new_data_part->setColumns(block.getNamesAndTypesList(), {}); + //// TODO Read them from file + new_data_part->setColumns(block.getNamesAndTypesList(), {}, metadata_snapshot->getMetadataVersion()); if (!is_projection) { diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 5de13020a1d..3424ef5bf0a 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -416,10 +416,11 @@ std::pair IMergeTreeDataPart::getMinMaxTime() const } -void IMergeTreeDataPart::setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos) +void IMergeTreeDataPart::setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos, int32_t metadata_version_) { columns = new_columns; serialization_infos = new_infos; + metadata_version = metadata_version_; column_name_to_position.clear(); column_name_to_position.reserve(new_columns.size()); @@ -798,6 +799,9 @@ NameSet IMergeTreeDataPart::getFileNamesWithoutChecksums() const if (getDataPartStorage().exists(TXN_VERSION_METADATA_FILE_NAME)) result.emplace(TXN_VERSION_METADATA_FILE_NAME); + if (getDataPartStorage().exists(METADATA_VERSION_FILE_NAME)) + result.emplace(METADATA_VERSION_FILE_NAME); + return result; } @@ -1288,8 +1292,7 @@ void IMergeTreeDataPart::loadColumns(bool require) metadata_snapshot = metadata_snapshot->projections.get(name).metadata; NamesAndTypesList loaded_columns; - bool exists = metadata_manager->exists("columns.txt"); - if (!exists) + if (!metadata_manager->exists("columns.txt")) { /// We can get list of columns only from columns.txt in compact parts. if (require || part_type == Type::Compact) @@ -1322,14 +1325,25 @@ void IMergeTreeDataPart::loadColumns(bool require) }; SerializationInfoByName infos(loaded_columns, settings); - exists = metadata_manager->exists(SERIALIZATION_FILE_NAME); - if (exists) + if (metadata_manager->exists(SERIALIZATION_FILE_NAME)) { auto in = metadata_manager->read(SERIALIZATION_FILE_NAME); infos.readJSON(*in); } - setColumns(loaded_columns, infos); + int32_t loaded_metadata_version; + if (metadata_manager->exists(METADATA_VERSION_FILE_NAME)) + { + auto in = metadata_manager->read(METADATA_VERSION_FILE_NAME); + readIntText(loaded_metadata_version, *in); + } + else + { + loaded_metadata_version = metadata_snapshot->getMetadataVersion(); + } + + ///TODO read metadata here + setColumns(loaded_columns, infos, loaded_metadata_version); } /// Project part / part with project parts / compact part doesn't support LWD. diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 9d0252bd625..df83d138322 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -139,7 +139,8 @@ public: String getTypeName() const { return getType().toString(); } - void setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos); + void setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos, int32_t metadata_version_); + int32_t getMetadataVersion() const { return metadata_version; } const NamesAndTypesList & getColumns() const { return columns; } const ColumnsDescription & getColumnsDescription() const { return columns_description; } @@ -310,6 +311,8 @@ public: mutable VersionMetadata version; + int32_t metadata_version; + /// For data in RAM ('index') UInt64 getIndexSizeInBytes() const; UInt64 getIndexSizeInAllocatedBytes() const; @@ -383,6 +386,8 @@ public: static inline constexpr auto TXN_VERSION_METADATA_FILE_NAME = "txn_version.txt"; + static inline constexpr auto METADATA_VERSION_FILE_NAME = "metadata_version.txt"; + /// One of part files which is used to check how many references (I'd like /// to say hardlinks, but it will confuse even more) we have for the part /// for zero copy replication. Sadly it's very complex. diff --git a/src/Storages/MergeTree/MergeTask.cpp b/src/Storages/MergeTree/MergeTask.cpp index 5874c257ad0..97bb9099c25 100644 --- a/src/Storages/MergeTree/MergeTask.cpp +++ b/src/Storages/MergeTree/MergeTask.cpp @@ -205,7 +205,7 @@ bool MergeTask::ExecuteAndFinalizeHorizontalPart::prepare() infos.add(part->getSerializationInfos()); } - global_ctx->new_data_part->setColumns(global_ctx->storage_columns, infos); + global_ctx->new_data_part->setColumns(global_ctx->storage_columns, infos, global_ctx->metadata_snapshot->getMetadataVersion()); const auto & local_part_min_ttl = global_ctx->new_data_part->ttl_infos.part_min_ttl; if (local_part_min_ttl && local_part_min_ttl <= global_ctx->time_of_merge) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 27b9881bcf3..b35d58688d5 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7548,6 +7548,7 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa rename_map[command.rename_to] = rename_source; } + else rename_map[command.rename_to] = command.column_name; } @@ -7935,7 +7936,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeData::createEmptyPart( if (settings->assign_part_uuids) new_data_part->uuid = UUIDHelpers::generateV4(); - new_data_part->setColumns(columns, {}); + new_data_part->setColumns(columns, {}, metadata_snapshot->getMetadataVersion()); new_data_part->rows_count = block.rows(); new_data_part->partition = partition; diff --git a/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp index 20049976acf..5b1054d0a0e 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp @@ -73,7 +73,7 @@ MutableDataPartStoragePtr MergeTreeDataPartInMemory::flushToDisk(const String & new_data_part_storage->beginTransaction(); new_data_part->uuid = uuid; - new_data_part->setColumns(columns, {}); + new_data_part->setColumns(columns, {}, metadata_snapshot->getMetadataVersion()); new_data_part->partition.value = partition.value; new_data_part->minmax_idx = minmax_idx; @@ -104,7 +104,7 @@ MutableDataPartStoragePtr MergeTreeDataPartInMemory::flushToDisk(const String & .build(); new_projection_part->is_temp = false; // clean up will be done on parent part - new_projection_part->setColumns(projection->getColumns(), {}); + new_projection_part->setColumns(projection->getColumns(), {}, metadata_snapshot->getMetadataVersion()); auto new_projection_part_storage = new_projection_part->getDataPartStoragePtr(); if (new_projection_part_storage->exists()) diff --git a/src/Storages/MergeTree/MergeTreeDataWriter.cpp b/src/Storages/MergeTree/MergeTreeDataWriter.cpp index 93b0abeca35..ba344dc70aa 100644 --- a/src/Storages/MergeTree/MergeTreeDataWriter.cpp +++ b/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -465,7 +465,7 @@ MergeTreeDataWriter::TemporaryPart MergeTreeDataWriter::writeTempPartImpl( SerializationInfoByName infos(columns, settings); infos.add(block); - new_data_part->setColumns(columns, infos); + new_data_part->setColumns(columns, infos, metadata_snapshot->getMetadataVersion()); new_data_part->rows_count = block.rows(); new_data_part->partition = std::move(partition); new_data_part->minmax_idx = std::move(minmax_idx); @@ -587,7 +587,7 @@ MergeTreeDataWriter::TemporaryPart MergeTreeDataWriter::writeProjectionPartImpl( SerializationInfoByName infos(columns, settings); infos.add(block); - new_data_part->setColumns(columns, infos); + new_data_part->setColumns(columns, infos, metadata_snapshot->getMetadataVersion()); if (new_data_part->isStoredOnDisk()) { diff --git a/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp b/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp index fabf2acdad3..87d9bb0f168 100644 --- a/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp +++ b/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp @@ -229,7 +229,7 @@ MergeTreeData::MutableDataPartsVector MergeTreeWriteAheadLog::restore( part->minmax_idx->update(block, storage.getMinMaxColumnsNames(metadata_snapshot->getPartitionKey())); part->partition.create(metadata_snapshot, block, 0, context); - part->setColumns(block.getNamesAndTypesList(), {}); + part->setColumns(block.getNamesAndTypesList(), {}, metadata_snapshot->getMetadataVersion()); if (metadata_snapshot->hasSortingKey()) metadata_snapshot->getSortingKey().expression->execute(block); diff --git a/src/Storages/MergeTree/MergedBlockOutputStream.cpp b/src/Storages/MergeTree/MergedBlockOutputStream.cpp index ced43ae25b0..53d4a32fc0e 100644 --- a/src/Storages/MergeTree/MergedBlockOutputStream.cpp +++ b/src/Storages/MergeTree/MergedBlockOutputStream.cpp @@ -175,7 +175,7 @@ MergedBlockOutputStream::Finalizer MergedBlockOutputStream::finalizePartAsync( serialization_infos.replaceData(new_serialization_infos); files_to_remove_after_sync = removeEmptyColumnsFromPart(new_part, part_columns, serialization_infos, checksums); - new_part->setColumns(part_columns, serialization_infos); + new_part->setColumns(part_columns, serialization_infos, metadata_snapshot->getMetadataVersion()); } auto finalizer = std::make_unique(*writer, new_part, files_to_remove_after_sync, sync); @@ -289,6 +289,14 @@ MergedBlockOutputStream::WrittenFiles MergedBlockOutputStream::finalizePartOnDis written_files.emplace_back(std::move(out)); } + { + /// Write a file with a description of columns. + auto out = new_part->getDataPartStorage().writeFile(IMergeTreeDataPart::METADATA_VERSION_FILE_NAME, 4096, write_settings); + DB::writeIntText(new_part->getMetadataVersion(), *out); + out->preFinalize(); + written_files.emplace_back(std::move(out)); + } + if (default_codec != nullptr) { auto out = new_part->getDataPartStorage().writeFile(IMergeTreeDataPart::DEFAULT_COMPRESSION_CODEC_FILE_NAME, 4096, write_settings); diff --git a/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp b/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp index 03829f1daf9..c70c5187b8b 100644 --- a/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp +++ b/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp @@ -85,7 +85,7 @@ MergedColumnOnlyOutputStream::fillChecksums( all_checksums.files.erase(removed_file); } - new_part->setColumns(columns, serialization_infos); + new_part->setColumns(columns, serialization_infos, metadata_snapshot->getMetadataVersion()); return checksums; } diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 85dc9f7f0d6..4c1ca86f5cb 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -1700,7 +1700,7 @@ bool MutateTask::prepare() ctx->source_part, ctx->updated_header, ctx->storage_columns, ctx->source_part->getSerializationInfos(), ctx->commands_for_part); - ctx->new_data_part->setColumns(new_columns, new_infos); + ctx->new_data_part->setColumns(new_columns, new_infos, ctx->metadata_snapshot->getMetadataVersion()); ctx->new_data_part->partition.assign(ctx->source_part->partition); /// Don't change granularity type while mutating subset of columns diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp index 557123ddae2..271edb0399f 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp @@ -149,7 +149,9 @@ void ReplicatedMergeTreeAttachThread::runImpl() const bool replica_metadata_version_exists = zookeeper->tryGet(replica_path + "/metadata_version", replica_metadata_version); if (replica_metadata_version_exists) { - storage.metadata_version = parse(replica_metadata_version); + StorageInMemoryMetadata metadata_with_new_version; + metadata_with_new_version.setMetadataVersion(parse(replica_metadata_version)); + storage.setInMemoryMetadata(metadata_with_new_version); } else { diff --git a/src/Storages/StorageInMemoryMetadata.cpp b/src/Storages/StorageInMemoryMetadata.cpp index f6550c6cd5d..5250c6f5330 100644 --- a/src/Storages/StorageInMemoryMetadata.cpp +++ b/src/Storages/StorageInMemoryMetadata.cpp @@ -41,6 +41,7 @@ StorageInMemoryMetadata::StorageInMemoryMetadata(const StorageInMemoryMetadata & , settings_changes(other.settings_changes ? other.settings_changes->clone() : nullptr) , select(other.select) , comment(other.comment) + , metadata_version(other.metadata_version) { } @@ -69,6 +70,7 @@ StorageInMemoryMetadata & StorageInMemoryMetadata::operator=(const StorageInMemo settings_changes.reset(); select = other.select; comment = other.comment; + metadata_version = other.metadata_version; return *this; } @@ -122,6 +124,11 @@ void StorageInMemoryMetadata::setSelectQuery(const SelectQueryDescription & sele select = select_; } +void StorageInMemoryMetadata::setMetadataVersion(int32_t metadata_version_) +{ + metadata_version = metadata_version_; +} + const ColumnsDescription & StorageInMemoryMetadata::getColumns() const { return columns; diff --git a/src/Storages/StorageInMemoryMetadata.h b/src/Storages/StorageInMemoryMetadata.h index eadce581334..2dd6c32e3c6 100644 --- a/src/Storages/StorageInMemoryMetadata.h +++ b/src/Storages/StorageInMemoryMetadata.h @@ -50,6 +50,8 @@ struct StorageInMemoryMetadata String comment; + int32_t metadata_version; + StorageInMemoryMetadata() = default; StorageInMemoryMetadata(const StorageInMemoryMetadata & other); @@ -90,6 +92,8 @@ struct StorageInMemoryMetadata /// Set SELECT query for (Materialized)View void setSelectQuery(const SelectQueryDescription & select_); + void setMetadataVersion(int32_t metadata_version_); + /// Returns combined set of columns const ColumnsDescription & getColumns() const; @@ -218,6 +222,9 @@ struct StorageInMemoryMetadata const SelectQueryDescription & getSelectQuery() const; bool hasSelectQuery() const; + int32_t getMetadataVersion() const { return metadata_version; } + bool hasMetadataVersion() const { return metadata_version != -1; } + /// Check that all the requested names are in the table and have the correct types. void check(const NamesAndTypesList & columns) const; diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index ef2fd46b705..3083fbd2c46 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -452,7 +452,10 @@ StorageReplicatedMergeTree::StorageReplicatedMergeTree( Coordination::Stat metadata_stat; current_zookeeper->get(zookeeper_path + "/metadata", &metadata_stat); - metadata_version = metadata_stat.version; + + StorageInMemoryMetadata storage_metadata; + storage_metadata.setMetadataVersion(metadata_stat.version); + setInMemoryMetadata(storage_metadata); } catch (Coordination::Exception & e) { @@ -772,7 +775,7 @@ bool StorageReplicatedMergeTree::createTableIfNotExists(const StorageMetadataPtr zkutil::CreateMode::Persistent)); ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/columns", metadata_snapshot->getColumns().toString(), zkutil::CreateMode::Persistent)); - ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/metadata_version", std::to_string(metadata_version), + ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/metadata_version", toString(metadata_snapshot->getMetadataVersion()), zkutil::CreateMode::Persistent)); /// The following 3 nodes were added in version 1.1.xxx, so we create them here, not in createNewZooKeeperNodes() @@ -845,7 +848,7 @@ void StorageReplicatedMergeTree::createReplica(const StorageMetadataPtr & metada zkutil::CreateMode::Persistent)); ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/columns", metadata_snapshot->getColumns().toString(), zkutil::CreateMode::Persistent)); - ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/metadata_version", std::to_string(metadata_version), + ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/metadata_version", toString(metadata_snapshot->getMetadataVersion()), zkutil::CreateMode::Persistent)); /// The following 3 nodes were added in version 1.1.xxx, so we create them here, not in createNewZooKeeperNodes() @@ -1140,16 +1143,19 @@ void StorageReplicatedMergeTree::checkTableStructure(const String & zookeeper_pr } void StorageReplicatedMergeTree::setTableStructure(const StorageID & table_id, const ContextPtr & local_context, - ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & metadata_diff) + ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & metadata_diff, int32_t new_metadata_version) { StorageInMemoryMetadata old_metadata = getInMemoryMetadata(); + StorageInMemoryMetadata new_metadata = metadata_diff.getNewMetadata(new_columns, local_context, old_metadata); + new_metadata.setMetadataVersion(new_metadata_version); /// Even if the primary/sorting/partition keys didn't change we must reinitialize it /// because primary/partition key column types might have changed. checkTTLExpressions(new_metadata, old_metadata); setProperties(new_metadata, old_metadata); + DatabaseCatalog::instance().getDatabase(table_id.database_name)->alterTable(local_context, table_id, new_metadata); } @@ -2760,8 +2766,9 @@ void StorageReplicatedMergeTree::cloneMetadataIfNeeded(const String & source_rep return; } + auto metadata_snapshot = getInMemoryMetadataPtr(); Int32 source_metadata_version = parse(source_metadata_version_str); - if (metadata_version == source_metadata_version) + if (metadata_snapshot->getMetadataVersion() == source_metadata_version) return; /// Our metadata it not up to date with source replica metadata. @@ -2779,7 +2786,7 @@ void StorageReplicatedMergeTree::cloneMetadataIfNeeded(const String & source_rep /// if all such entries were cleaned up from the log and source_queue. LOG_WARNING(log, "Metadata version ({}) on replica is not up to date with metadata ({}) on source replica {}", - metadata_version, source_metadata_version, source_replica); + metadata_snapshot->getMetadataVersion(), source_metadata_version, source_replica); String source_metadata; String source_columns; @@ -4926,14 +4933,15 @@ bool StorageReplicatedMergeTree::optimize( bool StorageReplicatedMergeTree::executeMetadataAlter(const StorageReplicatedMergeTree::LogEntry & entry) { - if (entry.alter_version < metadata_version) + auto current_metadata = getInMemoryMetadataPtr(); + if (entry.alter_version < current_metadata->getMetadataVersion()) { /// TODO Can we replace it with LOGICAL_ERROR? /// As for now, it may rarely happen due to reordering of ALTER_METADATA entries in the queue of /// non-initial replica and also may happen after stale replica recovery. LOG_WARNING(log, "Attempt to update metadata of version {} " "to older version {} when processing log entry {}: {}", - metadata_version, entry.alter_version, entry.znode_name, entry.toString()); + current_metadata->getMetadataVersion(), entry.alter_version, entry.znode_name, entry.toString()); return true; } @@ -4981,10 +4989,9 @@ bool StorageReplicatedMergeTree::executeMetadataAlter(const StorageReplicatedMer LOG_INFO(log, "Metadata changed in ZooKeeper. Applying changes locally."); auto metadata_diff = ReplicatedMergeTreeTableMetadata(*this, getInMemoryMetadataPtr()).checkAndFindDiff(metadata_from_entry, getInMemoryMetadataPtr()->getColumns(), getContext()); - setTableStructure(table_id, alter_context, std::move(columns_from_entry), metadata_diff); - metadata_version = entry.alter_version; + setTableStructure(table_id, alter_context, std::move(columns_from_entry), metadata_diff, entry.alter_version); - LOG_INFO(log, "Applied changes to the metadata of the table. Current metadata version: {}", metadata_version); + LOG_INFO(log, "Applied changes to the metadata of the table. Current metadata version: {}", current_metadata->getMetadataVersion()); } { @@ -4996,7 +5003,7 @@ bool StorageReplicatedMergeTree::executeMetadataAlter(const StorageReplicatedMer /// This transaction may not happen, but it's OK, because on the next retry we will eventually create/update this node /// TODO Maybe do in in one transaction for Replicated database? - zookeeper->createOrUpdate(fs::path(replica_path) / "metadata_version", std::to_string(metadata_version), zkutil::CreateMode::Persistent); + zookeeper->createOrUpdate(fs::path(replica_path) / "metadata_version", std::to_string(current_metadata->getMetadataVersion()), zkutil::CreateMode::Persistent); return true; } @@ -5120,7 +5127,7 @@ void StorageReplicatedMergeTree::alter( size_t mutation_path_idx = std::numeric_limits::max(); String new_metadata_str = future_metadata_in_zk.toString(); - ops.emplace_back(zkutil::makeSetRequest(fs::path(zookeeper_path) / "metadata", new_metadata_str, metadata_version)); + ops.emplace_back(zkutil::makeSetRequest(fs::path(zookeeper_path) / "metadata", new_metadata_str, current_metadata->getMetadataVersion())); String new_columns_str = future_metadata.columns.toString(); ops.emplace_back(zkutil::makeSetRequest(fs::path(zookeeper_path) / "columns", new_columns_str, -1)); @@ -5136,7 +5143,7 @@ void StorageReplicatedMergeTree::alter( /// We can be sure, that in case of successful commit in zookeeper our /// version will increments by 1. Because we update with version check. - int new_metadata_version = metadata_version + 1; + int new_metadata_version = current_metadata->getMetadataVersion() + 1; alter_entry->type = LogEntry::ALTER_METADATA; alter_entry->source_replica = replica_name; diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index a4ac60052bc..e0143b9aac6 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -214,8 +214,6 @@ public: /// It's used if not set in engine's arguments while creating a replicated table. static String getDefaultReplicaName(const ContextPtr & context_); - int getMetadataVersion() const { return metadata_version; } - /// Modify a CREATE TABLE query to make a variant which must be written to a backup. void adjustCreateQueryForBackup(ASTPtr & create_query) const override; @@ -425,7 +423,6 @@ private: std::atomic shutdown_called {false}; std::atomic flush_called {false}; - int metadata_version = 0; /// Threads. /// A task that keeps track of the updates in the logs of all replicas and loads them into the queue. @@ -502,8 +499,10 @@ private: /// A part of ALTER: apply metadata changes only (data parts are altered separately). /// Must be called under IStorage::lockForAlter() lock. - void setTableStructure(const StorageID & table_id, const ContextPtr & local_context, - ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & metadata_diff); + void setTableStructure( + const StorageID & table_id, const ContextPtr & local_context, + ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & metadata_diff, + int32_t new_metadata_version); /** Check that the set of parts corresponds to that in ZK (/replicas/me/parts/). * If any parts described in ZK are not locally, throw an exception. From 9416401406b4978677d3d77a41cb80e07213f66c Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 20:52:10 +0100 Subject: [PATCH 011/229] Fix --- src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index df4dea978a1..d6e3f53c757 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1760,7 +1760,7 @@ std::map ReplicatedMergeTreeQueue::getAlterMutationCo if (in_partition == mutations_by_partition.end()) return {}; - Int64 part_data_version = part->info.getDataVersion(); + Int64 part_metadata_version = part->getMetadataVersion(); std::map result; /// Here we return mutation commands for part which has bigger mutation version than part mutation version. /// Please note, we don't use getDataVersion(). It's because these alter commands are used for in-fly conversions @@ -1771,13 +1771,12 @@ std::map ReplicatedMergeTreeQueue::getAlterMutationCo { if (mutation_status->entry->alter_version != -1) { - if (mutation_version > part_data_version) + if (mutation_status->entry->alter_version > part_metadata_version) { result[mutation_version] = mutation_status->entry->commands; } else { - result[mutation_version] = mutation_status->entry->commands; break; } } From 51b6154d68a6fe7da0ab06f955f8fba8415c09a4 Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 20:56:09 +0100 Subject: [PATCH 012/229] Fix stupid bug --- src/Storages/StorageReplicatedMergeTree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 3083fbd2c46..9559c61e1a5 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -453,7 +453,7 @@ StorageReplicatedMergeTree::StorageReplicatedMergeTree( Coordination::Stat metadata_stat; current_zookeeper->get(zookeeper_path + "/metadata", &metadata_stat); - StorageInMemoryMetadata storage_metadata; + StorageInMemoryMetadata storage_metadata(*metadata_snapshot); storage_metadata.setMetadataVersion(metadata_stat.version); setInMemoryMetadata(storage_metadata); } From fd4ae0990ba3bbb00c3353c911d0c8df4ea7abe8 Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 21:07:34 +0100 Subject: [PATCH 013/229] Tiny fix --- src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp | 1 + src/Storages/MergeTree/IMergeTreeDataPart.cpp | 8 ++++++++ src/Storages/MergeTree/IMergeTreeDataPart.h | 2 ++ src/Storages/MergeTree/MergeTreeData.cpp | 6 ------ 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp b/src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp index 175df9b6e28..7a9f927ae0f 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp +++ b/src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp @@ -683,6 +683,7 @@ void DataPartStorageOnDiskBase::clearDirectory( request.emplace_back(fs::path(dir) / "default_compression_codec.txt", true); request.emplace_back(fs::path(dir) / "delete-on-destroy.txt", true); request.emplace_back(fs::path(dir) / "txn_version.txt", true); + request.emplace_back(fs::path(dir) / "metadata_version.txt", true); disk->removeSharedFiles(request, !can_remove_shared_data, names_not_to_remove); disk->removeDirectory(dir); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 3424ef5bf0a..5fb529ddf64 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -661,6 +661,7 @@ void IMergeTreeDataPart::appendFilesOfColumnsChecksumsIndexes(Strings & files, b appendFilesOfPartitionAndMinMaxIndex(files); appendFilesOfTTLInfos(files); appendFilesOfDefaultCompressionCodec(files); + appendFilesOfMetadataVersion(files); } if (!parent_part && include_projection) @@ -980,6 +981,11 @@ void IMergeTreeDataPart::appendFilesOfDefaultCompressionCodec(Strings & files) files.push_back(DEFAULT_COMPRESSION_CODEC_FILE_NAME); } +void IMergeTreeDataPart::appendFilesOfMetadataVersion(Strings & files) +{ + files.push_back(METADATA_VERSION_FILE_NAME); +} + CompressionCodecPtr IMergeTreeDataPart::detectDefaultCompressionCodec() const { /// In memory parts doesn't have any compression @@ -1346,6 +1352,8 @@ void IMergeTreeDataPart::loadColumns(bool require) setColumns(loaded_columns, infos, loaded_metadata_version); } + + /// Project part / part with project parts / compact part doesn't support LWD. bool IMergeTreeDataPart::supportLightweightDeleteMutate() const { diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index df83d138322..d706045c3fe 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -589,6 +589,8 @@ private: static void appendFilesOfDefaultCompressionCodec(Strings & files); + static void appendFilesOfMetadataVersion(Strings & files); + /// Found column without specific compression and return codec /// for this column with default parameters. CompressionCodecPtr detectDefaultCompressionCodec() const; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index b35d58688d5..e569513914e 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7536,11 +7536,6 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa /// and columns in storage. if (command.type == MutationCommand::Type::RENAME_COLUMN) { - if (!part_columns.has(command.column_name)) - continue; - - part_columns.rename(command.column_name, command.rename_to); - if (auto it = rename_map.find(command.column_name); it != rename_map.end()) { auto rename_source = it->second; @@ -7548,7 +7543,6 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa rename_map[command.rename_to] = rename_source; } - else rename_map[command.rename_to] = command.column_name; } From e51e385017178260af81e40e933aae9238f241b5 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 14:16:17 +0100 Subject: [PATCH 014/229] fix style --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 5fb529ddf64..8f5f353b777 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1353,7 +1353,6 @@ void IMergeTreeDataPart::loadColumns(bool require) } - /// Project part / part with project parts / compact part doesn't support LWD. bool IMergeTreeDataPart::supportLightweightDeleteMutate() const { From a02e6f3d5b0f83203b8063ea7ed51d5a24d51b05 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 14:57:21 +0100 Subject: [PATCH 015/229] fix bug ordinary merge tree --- src/Storages/StorageMergeTree.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 37c2ca8b746..a6f26654a80 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -2111,13 +2111,16 @@ std::map StorageMergeTree::getAlterMutationCommandsFo { std::lock_guard lock(currently_processing_in_background_mutex); - Int64 part_mutation_version = part->info.getMutationVersion(); + Int64 part_data_version = part->info.getDataVersion(); std::map result; - for (const auto & current_mutation_by_version : current_mutations_by_version) + if (!current_mutations_by_version.empty()) { - if (static_cast(current_mutation_by_version.first) > part_mutation_version) - result[current_mutation_by_version.first] = current_mutation_by_version.second.commands; + const auto & [latest_mutation_id, latest_commands] = *current_mutations_by_version.rbegin(); + if (part_data_version < static_cast(latest_mutation_id)) + { + result[latest_mutation_id] = latest_commands.commands; + } } return result; } From bd4a3ce2d52d0656fa7e502281023a997c952318 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 15:05:13 +0100 Subject: [PATCH 016/229] Update src/Storages/MergeTree/DataPartsExchange.cpp --- src/Storages/MergeTree/DataPartsExchange.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index 1864ce37ddf..86bf86c916e 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -692,7 +692,6 @@ MergeTreeData::MutableDataPartPtr Fetcher::downloadPartToMemory( auto block = block_in.read(); throttler->add(block.bytes()); - //// TODO Read them from file new_data_part->setColumns(block.getNamesAndTypesList(), {}, metadata_snapshot->getMetadataVersion()); if (!is_projection) From 30a7198411b42f07287ca99b32984ace43de4a40 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 15:06:28 +0100 Subject: [PATCH 017/229] Update src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp --- src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index fb05cc1bddd..6be4cadc03e 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1769,7 +1769,7 @@ std::map ReplicatedMergeTreeQueue::getAlterMutationCo /// of part's metadata. It mean that even if we have mutation with version X and part with data version X+10, but /// without mutation version part can still have wrong metadata and we have to apply this change on-fly if needed. - for (auto [mutation_version, mutation_status] : in_partition->second | std::views::reverse) + for (const auto & [mutation_version, mutation_status] : in_partition->second | std::views::reverse) { if (mutation_status->entry->alter_version != -1) { From 2a0484ca41316a11d6706bdb435262980a996405 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 18:04:42 +0100 Subject: [PATCH 018/229] Add commnents, fix bugs --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 6 ++ src/Storages/MergeTree/IMergeTreeDataPart.h | 10 ++ src/Storages/MergeTree/MergeTreeData.cpp | 18 ++-- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 8 +- .../02555_davengers_rename_chain.reference | 26 ++++++ .../02555_davengers_rename_chain.sql | 91 +++++++++++++++++++ 6 files changed, 145 insertions(+), 14 deletions(-) create mode 100644 tests/queries/0_stateless/02555_davengers_rename_chain.reference create mode 100644 tests/queries/0_stateless/02555_davengers_rename_chain.sql diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 8f5f353b777..cfd298952fa 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -976,6 +976,12 @@ void IMergeTreeDataPart::removeVersionMetadata() getDataPartStorage().removeFileIfExists("txn_version.txt"); } + +void IMergeTreeDataPart::removeMetadataVersion() +{ + getDataPartStorage().removeFileIfExists(METADATA_VERSION_FILE_NAME); +} + void IMergeTreeDataPart::appendFilesOfDefaultCompressionCodec(Strings & files) { files.push_back(DEFAULT_COMPRESSION_CODEC_FILE_NAME); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index d706045c3fe..e31c83a0278 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -139,7 +139,10 @@ public: String getTypeName() const { return getType().toString(); } + /// We could have separate method like setMetadata, but it's much more convenient to set it up with columns void setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos, int32_t metadata_version_); + + /// Version of metadata for part (columns, pk and so on) int32_t getMetadataVersion() const { return metadata_version; } const NamesAndTypesList & getColumns() const { return columns; } @@ -311,6 +314,7 @@ public: mutable VersionMetadata version; + /// Version of part metadata (columns, pk and so on). Managed properly only for replicated merge tree. int32_t metadata_version; /// For data in RAM ('index') @@ -384,8 +388,10 @@ public: /// (number of rows, number of rows with default values, etc). static inline constexpr auto SERIALIZATION_FILE_NAME = "serialization.json"; + /// Version used for transactions. static inline constexpr auto TXN_VERSION_METADATA_FILE_NAME = "txn_version.txt"; + static inline constexpr auto METADATA_VERSION_FILE_NAME = "metadata_version.txt"; /// One of part files which is used to check how many references (I'd like @@ -450,7 +456,11 @@ public: void writeDeleteOnDestroyMarker(); void removeDeleteOnDestroyMarker(); + /// It may look like a stupid joke. but these two methods are absolutely unrelated. + /// This one is about removing file with metadata about part version (for transactions) void removeVersionMetadata(); + /// This one is about removing file with version of part's metadata (columns, pk and so on) + void removeMetadataVersion(); mutable std::atomic removal_state = DataPartRemovalState::NOT_ATTEMPTED; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index be53a126c18..d96a903e6bd 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -4374,6 +4374,11 @@ MergeTreeData::DataPartPtr MergeTreeData::getPartIfExistsUnlocked(const MergeTre static void loadPartAndFixMetadataImpl(MergeTreeData::MutableDataPartPtr part) { + /// Remove metadata version file and take it from table. + /// Currently we cannot attach parts with different schema, so + /// we can assume that it's equal to table's current schema. + part->removeMetadataVersion(); + part->loadColumnsChecksumsIndexes(false, true); part->modification_time = part->getDataPartStorage().getLastModified().epochTime(); part->removeDeleteOnDestroyMarker(); @@ -7565,9 +7570,9 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa { std::map commands_map = getAlterMutationCommandsForPart(part); - auto part_columns = part->getColumnsDescription(); AlterConversions result{}; auto & rename_map = result.rename_map; + /// Squash "log of renames" into single map for (const auto & [version, commands] : commands_map) { for (const auto & command : commands) @@ -7577,15 +7582,7 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa /// and columns in storage. if (command.type == MutationCommand::Type::RENAME_COLUMN) { - if (auto it = rename_map.find(command.column_name); it != rename_map.end()) - { - auto rename_source = it->second; - rename_map.erase(it); - - rename_map[command.rename_to] = rename_source; - } - else - rename_map[command.rename_to] = command.column_name; + rename_map[command.rename_to] = command.column_name; } } } @@ -7596,6 +7593,7 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa it = rename_map.erase(it); else ++it; + } return result; diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index 6be4cadc03e..341007e79e5 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1764,21 +1764,21 @@ std::map ReplicatedMergeTreeQueue::getAlterMutationCo Int64 part_metadata_version = part->getMetadataVersion(); std::map result; - /// Here we return mutation commands for part which has bigger mutation version than part mutation version. + /// Here we return mutation commands for part which has bigger alter version than part metadata version. /// Please note, we don't use getDataVersion(). It's because these alter commands are used for in-fly conversions - /// of part's metadata. It mean that even if we have mutation with version X and part with data version X+10, but - /// without mutation version part can still have wrong metadata and we have to apply this change on-fly if needed. - + /// of part's metadata. for (const auto & [mutation_version, mutation_status] : in_partition->second | std::views::reverse) { if (mutation_status->entry->alter_version != -1) { + /// we take commands with bigger metadata version if (mutation_status->entry->alter_version > part_metadata_version) { result[mutation_version] = mutation_status->entry->commands; } else { + /// entries are ordered, we processing them in reverse order so we can break break; } } diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.reference b/tests/queries/0_stateless/02555_davengers_rename_chain.reference new file mode 100644 index 00000000000..a9fc4b395e2 --- /dev/null +++ b/tests/queries/0_stateless/02555_davengers_rename_chain.reference @@ -0,0 +1,26 @@ +{"a1":"1","b1":"2","c":"3"} +~~~~~~~ +{"a1":"1","b1":"2","c":"3"} +{"a1":"4","b1":"5","c":"6"} +~~~~~~~ +{"a1":"1","b1":"2","c":"3"} +{"a1":"4","b1":"5","c":"6"} +{"a1":"7","b1":"8","c":"9"} +~~~~~~~ +{"b":"1","a":"2","c":"3"} +{"b":"4","a":"5","c":"6"} +{"b":"7","a":"8","c":"9"} +~~~~~~~ +{"a1":"1","b1":"2","c":"3"} +~~~~~~~ +{"a1":"1","b1":"2","c":"3"} +{"a1":"4","b1":"5","c":"6"} +~~~~~~~ +{"a1":"1","b1":"2","c":"3"} +{"a1":"4","b1":"5","c":"6"} +{"a1":"7","b1":"8","c":"9"} +~~~~~~~ +{"b":"1","a":"2","c":"3"} +{"b":"4","a":"5","c":"6"} +{"b":"7","a":"8","c":"9"} +~~~~~~~ diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.sql b/tests/queries/0_stateless/02555_davengers_rename_chain.sql new file mode 100644 index 00000000000..eae345d0472 --- /dev/null +++ b/tests/queries/0_stateless/02555_davengers_rename_chain.sql @@ -0,0 +1,91 @@ +DROP TABLE IF EXISTS wrong_metadata; + +CREATE TABLE wrong_metadata( + a UInt64, + b UInt64, + c UInt64 +) +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata', '1') +ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0; + +INSERT INTO wrong_metadata VALUES (1, 2, 3); + +SYSTEM STOP MERGES wrong_metadata; + +ALTER TABLE wrong_metadata RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0; + +SELECT sleep(1) FORMAT Null; + +SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +INSERT INTO wrong_metadata VALUES (4, 5, 6); + +SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +ALTER TABLE wrong_metadata RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0; + +SELECT sleep(1) FORMAT Null; +SELECT sleep(1) FORMAT Null; + +INSERT INTO wrong_metadata VALUES (7, 8, 9); + +SELECT * FROM wrong_metadata ORDER by a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +SYSTEM START MERGES wrong_metadata; + +SYSTEM SYNC REPLICA wrong_metadata; + +SELECT * FROM wrong_metadata order by a FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +DROP TABLE IF EXISTS wrong_metadata; + +DROP TABLE IF EXISTS wrong_metadata_compact; + +CREATE TABLE wrong_metadata_compact( + a UInt64, + b UInt64, + c UInt64 +) +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata_compact', '1') +ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 10000000; + +INSERT INTO wrong_metadata_compact VALUES (1, 2, 3); + +SYSTEM STOP MERGES wrong_metadata_compact; + +ALTER TABLE wrong_metadata_compact RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0; + +SELECT sleep(1) FORMAT Null; + +SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +INSERT INTO wrong_metadata_compact VALUES (4, 5, 6); + +SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +ALTER TABLE wrong_metadata_compact RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0; + +SELECT sleep(1) FORMAT Null; +SELECT sleep(1) FORMAT Null; + +INSERT INTO wrong_metadata_compact VALUES (7, 8, 9); + +SELECT * FROM wrong_metadata_compact ORDER by a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +SYSTEM START MERGES wrong_metadata_compact; + +SYSTEM SYNC REPLICA wrong_metadata_compact; + +SELECT * FROM wrong_metadata_compact order by a FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +DROP TABLE IF EXISTS wrong_metadata_compact; From aac5da77e88f63a26153e76408fe305549b7c243 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 21:32:40 +0100 Subject: [PATCH 019/229] fix stupid bug --- src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp index 271edb0399f..2082a7c000a 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp @@ -149,7 +149,7 @@ void ReplicatedMergeTreeAttachThread::runImpl() const bool replica_metadata_version_exists = zookeeper->tryGet(replica_path + "/metadata_version", replica_metadata_version); if (replica_metadata_version_exists) { - StorageInMemoryMetadata metadata_with_new_version; + StorageInMemoryMetadata metadata_with_new_version(*metadata_snapshot); metadata_with_new_version.setMetadataVersion(parse(replica_metadata_version)); storage.setInMemoryMetadata(metadata_with_new_version); } From dd3a98e88dc3055ab17e08000e4fd4c2aeb91b2e Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 22:30:31 +0100 Subject: [PATCH 020/229] Fixes --- src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp | 1 + src/Storages/StorageInMemoryMetadata.h | 2 +- src/Storages/StorageReplicatedMergeTree.cpp | 1 + tests/queries/0_stateless/02361_fsync_profile_events.sh | 4 ++-- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp index 2082a7c000a..941d0850f67 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp @@ -150,6 +150,7 @@ void ReplicatedMergeTreeAttachThread::runImpl() if (replica_metadata_version_exists) { StorageInMemoryMetadata metadata_with_new_version(*metadata_snapshot); + metadata_with_new_version.setMetadataVersion(parse(replica_metadata_version)); storage.setInMemoryMetadata(metadata_with_new_version); } diff --git a/src/Storages/StorageInMemoryMetadata.h b/src/Storages/StorageInMemoryMetadata.h index 2dd6c32e3c6..dfb5e9ddb54 100644 --- a/src/Storages/StorageInMemoryMetadata.h +++ b/src/Storages/StorageInMemoryMetadata.h @@ -50,7 +50,7 @@ struct StorageInMemoryMetadata String comment; - int32_t metadata_version; + int32_t metadata_version = 0; StorageInMemoryMetadata() = default; diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 55d857c4c28..8e6e809a54a 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -5018,6 +5018,7 @@ bool StorageReplicatedMergeTree::executeMetadataAlter(const StorageReplicatedMer auto metadata_diff = ReplicatedMergeTreeTableMetadata(*this, getInMemoryMetadataPtr()).checkAndFindDiff(metadata_from_entry, getInMemoryMetadataPtr()->getColumns(), getContext()); setTableStructure(table_id, alter_context, std::move(columns_from_entry), metadata_diff, entry.alter_version); + current_metadata = getInMemoryMetadataPtr(); LOG_INFO(log, "Applied changes to the metadata of the table. Current metadata version: {}", current_metadata->getMetadataVersion()); } diff --git a/tests/queries/0_stateless/02361_fsync_profile_events.sh b/tests/queries/0_stateless/02361_fsync_profile_events.sh index 44a1bd58d36..5b603133f6c 100755 --- a/tests/queries/0_stateless/02361_fsync_profile_events.sh +++ b/tests/queries/0_stateless/02361_fsync_profile_events.sh @@ -44,8 +44,8 @@ for i in {1..100}; do ")" # Non retriable errors - if [[ $FileSync -ne 7 ]]; then - echo "FileSync: $FileSync != 11" >&2 + if [[ $FileSync -ne 8 ]]; then + echo "FileSync: $FileSync != 8" >&2 exit 2 fi # Check that all files was synced From 1fd9baa3f3b2c67196317723031d04687b3db7c9 Mon Sep 17 00:00:00 2001 From: alesapin Date: Tue, 7 Feb 2023 12:49:51 +0100 Subject: [PATCH 021/229] Fix fetch: --- src/Storages/MergeTree/DataPartsExchange.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index 86bf86c916e..a8fbecbf523 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -768,7 +768,8 @@ void Fetcher::downloadBaseOrProjectionPartToDisk( if (file_name != "checksums.txt" && file_name != "columns.txt" && - file_name != IMergeTreeDataPart::DEFAULT_COMPRESSION_CODEC_FILE_NAME) + file_name != IMergeTreeDataPart::DEFAULT_COMPRESSION_CODEC_FILE_NAME && + file_name != IMergeTreeDataPart::METADATA_VERSION_FILE_NAME) checksums.addFile(file_name, file_size, expected_hash); } From 36f41da6e001734574f10be747e8f1762801d48d Mon Sep 17 00:00:00 2001 From: alesapin Date: Tue, 7 Feb 2023 18:53:47 +0100 Subject: [PATCH 022/229] One more bugfix --- src/Storages/MergeTree/AlterConversions.cpp | 21 ++++ src/Storages/MergeTree/AlterConversions.h | 11 ++- src/Storages/MergeTree/MergeTreeData.cpp | 4 +- src/Storages/MergeTree/MutateTask.cpp | 95 ++++++++++++++++--- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 8 +- 5 files changed, 118 insertions(+), 21 deletions(-) diff --git a/src/Storages/MergeTree/AlterConversions.cpp b/src/Storages/MergeTree/AlterConversions.cpp index e2eea2e68f6..7a298b0f6ca 100644 --- a/src/Storages/MergeTree/AlterConversions.cpp +++ b/src/Storages/MergeTree/AlterConversions.cpp @@ -31,4 +31,25 @@ std::string AlterConversions::getColumnNewName(const std::string & old_name) con throw Exception(ErrorCodes::LOGICAL_ERROR, "Column {} was not renamed", old_name); } + +bool AlterConversions::isColumnRenamed(const std::string & new_name) const +{ + for (const auto & [name_to, name_from] : rename_map) + { + if (name_to == new_name) + return true; + } + return false; +} +/// Get column old name before rename (lookup by key in rename_map) +std::string AlterConversions::getColumnOldName(const std::string & new_name) const +{ + for (const auto & [name_to, name_from] : rename_map) + { + if (name_to == new_name) + return name_from; + } + throw Exception(ErrorCodes::LOGICAL_ERROR, "Column {} was not renamed", new_name); +} + } diff --git a/src/Storages/MergeTree/AlterConversions.h b/src/Storages/MergeTree/AlterConversions.h index 47f964e85fe..ada385d6100 100644 --- a/src/Storages/MergeTree/AlterConversions.h +++ b/src/Storages/MergeTree/AlterConversions.h @@ -14,17 +14,22 @@ namespace DB /// part->getColumns() and storage->getColumns(). struct AlterConversions { + struct RenamePair + { + std::string rename_to; + std::string rename_from; + }; /// Rename map new_name -> old_name - std::unordered_map rename_map; + std::vector rename_map; /// Column was renamed (lookup by value in rename_map) bool columnHasNewName(const std::string & old_name) const; /// Get new name for column (lookup by value in rename_map) std::string getColumnNewName(const std::string & old_name) const; /// Is this name is new name of column (lookup by key in rename_map) - bool isColumnRenamed(const std::string & new_name) const { return rename_map.count(new_name) > 0; } + bool isColumnRenamed(const std::string & new_name) const; /// Get column old name before rename (lookup by key in rename_map) - std::string getColumnOldName(const std::string & new_name) const { return rename_map.at(new_name); } + std::string getColumnOldName(const std::string & new_name) const; }; } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index d96a903e6bd..05c75067f3f 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7582,14 +7582,14 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa /// and columns in storage. if (command.type == MutationCommand::Type::RENAME_COLUMN) { - rename_map[command.rename_to] = command.column_name; + rename_map.emplace_back(AlterConversions::RenamePair{command.rename_to, command.column_name}); } } } for (auto it = rename_map.begin(); it != rename_map.end();) { - if (it->first == it->second) + if (it->rename_to == it->rename_from) it = rename_map.erase(it); else ++it; diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 4c1ca86f5cb..16ddcaae89e 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -158,11 +158,7 @@ static void splitMutationCommands( auto alter_conversions = part->storage.getAlterConversionsForPart(part); for (const auto & [rename_to, rename_from] : alter_conversions.rename_map) { - if (part_columns.has(rename_from)) - { - for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = rename_from, .rename_to = rename_to}); - part_columns.rename(rename_from, rename_to); - } + for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = rename_from, .rename_to = rename_to}); } } } @@ -174,8 +170,13 @@ getColumnsForNewDataPart( const Block & updated_header, NamesAndTypesList storage_columns, const SerializationInfoByName & serialization_infos, + const MutationCommands & commands_for_interpreter, const MutationCommands & commands_for_removes) { + MutationCommands all_commands; + all_commands.insert(all_commands.end(), commands_for_interpreter.begin(), commands_for_interpreter.end()); + all_commands.insert(all_commands.end(), commands_for_removes.begin(), commands_for_removes.end()); + NameSet removed_columns; NameToNameMap renamed_columns_to_from; NameToNameMap renamed_columns_from_to; @@ -191,8 +192,37 @@ getColumnsForNewDataPart( storage_columns.emplace_back(column); } - /// All commands are validated in AlterCommand so we don't care about order - for (const auto & command : commands_for_removes) + NameToNameMap squashed_renames; + for (const auto & command : all_commands) + { + std::string result_name = command.rename_to; + + bool squashed = false; + for (const auto & [name_from, name_to] : squashed_renames) + { + if (name_to == command.column_name) + { + squashed = true; + squashed_renames[name_from] = result_name; + break; + } + } + if (!squashed) + squashed_renames[command.column_name] = result_name; + } + + MutationCommands squashed_commands; + for (const auto & command : all_commands) + { + if (squashed_renames.contains(command.column_name)) + { + squashed_commands.push_back(command); + squashed_commands.back().rename_to = squashed_renames[command.column_name]; + } + } + + + for (const auto & command : squashed_commands) { if (command.type == MutationCommand::UPDATE) { @@ -285,10 +315,10 @@ getColumnsForNewDataPart( /// should it's previous version should be dropped or removed if (renamed_columns_to_from.contains(it->name) && !was_renamed && !was_removed) throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Incorrect mutation commands, trying to rename column {} to {}, " - "but part {} already has column {}", - renamed_columns_to_from[it->name], it->name, source_part->name, it->name); + ErrorCodes::LOGICAL_ERROR, + "Incorrect mutation commands, trying to rename column {} to {}, " + "but part {} already has column {}", + renamed_columns_to_from[it->name], it->name, source_part->name, it->name); /// Column was renamed and no other column renamed to it's name /// or column is dropped. @@ -582,8 +612,46 @@ static NameToNameVector collectFilesForRenames( rename_vector.emplace_back(file_rename_from, file_rename_to); }; - /// Remove old data + NameToNameMap squashed_renames; for (const auto & command : commands_for_removes) + { + + std::string result_name; + if (command.type == MutationCommand::Type::DROP_INDEX + || command.type == MutationCommand::Type::DROP_PROJECTION + || command.type == MutationCommand::Type::DROP_COLUMN + || command.type == MutationCommand::Type::READ_COLUMN) + result_name = ""; + + if (command.type == MutationCommand::RENAME_COLUMN) + result_name = command.rename_to; + + bool squashed = false; + for (const auto & [name_from, name_to] : squashed_renames) + { + if (name_to == command.column_name) + { + squashed = true; + squashed_renames[name_from] = result_name; + break; + } + } + if (!squashed) + squashed_renames[command.column_name] = result_name; + } + + MutationCommands squashed_commands; + for (const auto & command : commands_for_removes) + { + if (squashed_renames.contains(command.column_name)) + { + squashed_commands.push_back(command); + squashed_commands.back().rename_to = squashed_renames[command.column_name]; + } + } + + /// Remove old data + for (const auto & command : squashed_commands) { if (command.type == MutationCommand::Type::DROP_INDEX) { @@ -624,7 +692,6 @@ static NameToNameVector collectFilesForRenames( String escaped_name_from = escapeForFileName(command.column_name); String escaped_name_to = escapeForFileName(command.rename_to); - ISerialization::StreamCallback callback = [&](const ISerialization::SubstreamPath & substream_path) { String stream_from = ISerialization::getFileNameForStream(command.column_name, substream_path); @@ -1698,7 +1765,7 @@ bool MutateTask::prepare() auto [new_columns, new_infos] = MutationHelpers::getColumnsForNewDataPart( ctx->source_part, ctx->updated_header, ctx->storage_columns, - ctx->source_part->getSerializationInfos(), ctx->commands_for_part); + ctx->source_part->getSerializationInfos(), ctx->for_interpreter, ctx->for_file_renames); ctx->new_data_part->setColumns(new_columns, new_infos, ctx->metadata_snapshot->getMetadataVersion()); ctx->new_data_part->partition.assign(ctx->source_part->partition); diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index 341007e79e5..f491cdc8cd1 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1769,10 +1769,14 @@ std::map ReplicatedMergeTreeQueue::getAlterMutationCo /// of part's metadata. for (const auto & [mutation_version, mutation_status] : in_partition->second | std::views::reverse) { - if (mutation_status->entry->alter_version != -1) + int32_t alter_version = mutation_status->entry->alter_version; + if (alter_version != -1) { + if (!alter_sequence.canExecuteDataAlter(alter_version, lock)) + continue; + /// we take commands with bigger metadata version - if (mutation_status->entry->alter_version > part_metadata_version) + if (alter_version > part_metadata_version) { result[mutation_version] = mutation_status->entry->commands; } From bbf044f4773547d40401838a7e8112f674ae96cc Mon Sep 17 00:00:00 2001 From: alesapin Date: Tue, 7 Feb 2023 22:30:38 +0100 Subject: [PATCH 023/229] Fxi --- src/Storages/MergeTree/MutateTask.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 16ddcaae89e..5738a48783d 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -114,6 +114,12 @@ static void splitMutationCommands( .column_name = rename_to, }); + for_file_renames.push_back( + { + .type = MutationCommand::Type::RENAME_COLUMN, + .column_name = rename_from, + .rename_to = rename_to + }); part_columns.rename(rename_from, rename_to); } } @@ -615,16 +621,7 @@ static NameToNameVector collectFilesForRenames( NameToNameMap squashed_renames; for (const auto & command : commands_for_removes) { - - std::string result_name; - if (command.type == MutationCommand::Type::DROP_INDEX - || command.type == MutationCommand::Type::DROP_PROJECTION - || command.type == MutationCommand::Type::DROP_COLUMN - || command.type == MutationCommand::Type::READ_COLUMN) - result_name = ""; - - if (command.type == MutationCommand::RENAME_COLUMN) - result_name = command.rename_to; + std::string result_name = command.rename_to; bool squashed = false; for (const auto & [name_from, name_to] : squashed_renames) From a28d10a81013c71d26c8c5d5d87791bc3533617b Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 8 Feb 2023 14:50:30 +0100 Subject: [PATCH 024/229] Fxi --- src/Storages/MergeTree/DataPartsExchange.cpp | 11 +++-- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 6 ++- src/Storages/MergeTree/MutateTask.cpp | 9 ++++- tests/integration/test_merge_tree_s3/test.py | 7 ++-- ...system_cache_on_write_operations.reference | 40 +++++++++---------- 5 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index a8fbecbf523..35c627c3210 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -63,8 +63,9 @@ constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_DEFAULT_COMPRESSION = 4; constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_UUID = 5; constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_ZERO_COPY = 6; constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_PROJECTION = 7; +constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_METADATA_VERSION = 8; // Reserved for ALTER PRIMARY KEY -// constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_PRIMARY_KEY = 8; +// constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_PRIMARY_KEY = 9; std::string getEndpointId(const std::string & node_id) { @@ -120,7 +121,7 @@ void Service::processQuery(const HTMLForm & params, ReadBuffer & /*body*/, Write MergeTreePartInfo::fromPartName(part_name, data.format_version); /// We pretend to work as older server version, to be sure that client will correctly process our version - response.addCookie({"server_protocol_version", toString(std::min(client_protocol_version, REPLICATION_PROTOCOL_VERSION_WITH_PARTS_PROJECTION))}); + response.addCookie({"server_protocol_version", toString(std::min(client_protocol_version, REPLICATION_PROTOCOL_VERSION_WITH_METADATA_VERSION))}); LOG_TRACE(log, "Sending part {}", part_name); @@ -280,6 +281,10 @@ MergeTreeData::DataPart::Checksums Service::sendPartFromDisk( && name == IMergeTreeDataPart::DEFAULT_COMPRESSION_CODEC_FILE_NAME) continue; + if (client_protocol_version < REPLICATION_PROTOCOL_VERSION_WITH_METADATA_VERSION + && name == IMergeTreeDataPart::METADATA_VERSION_FILE_NAME) + continue; + files_to_replicate.insert(name); } @@ -407,7 +412,7 @@ MergeTreeData::MutableDataPartPtr Fetcher::fetchSelectedPart( { {"endpoint", getEndpointId(replica_path)}, {"part", part_name}, - {"client_protocol_version", toString(REPLICATION_PROTOCOL_VERSION_WITH_PARTS_PROJECTION)}, + {"client_protocol_version", toString(REPLICATION_PROTOCOL_VERSION_WITH_METADATA_VERSION)}, {"compress", "false"} }); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index cfd298952fa..94aa2a72949 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1352,9 +1352,13 @@ void IMergeTreeDataPart::loadColumns(bool require) else { loaded_metadata_version = metadata_snapshot->getMetadataVersion(); + + writeMetadata(METADATA_VERSION_FILE_NAME, {}, [loaded_metadata_version](auto & buffer) + { + writeIntText(loaded_metadata_version, buffer); + }); } - ///TODO read metadata here setColumns(loaded_columns, infos, loaded_metadata_version); } diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 5738a48783d..340709bff2c 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -735,6 +735,7 @@ void finalizeMutatedPart( ExecuteTTLType execute_ttl_type, const CompressionCodecPtr & codec, ContextPtr context, + StorageMetadataPtr metadata_snapshot, bool sync) { std::vector> written_files; @@ -783,6 +784,12 @@ void finalizeMutatedPart( written_files.push_back(std::move(out_comp)); } + { + auto out_metadata = new_data_part->getDataPartStorage().writeFile(IMergeTreeDataPart::METADATA_VERSION_FILE_NAME, 4096, context->getWriteSettings()); + DB::writeText(metadata_snapshot->getMetadataVersion(), *out_metadata); + written_files.push_back(std::move(out_metadata)); + } + { /// Write a file with a description of columns. auto out_columns = new_data_part->getDataPartStorage().writeFile("columns.txt", 4096, context->getWriteSettings()); @@ -1555,7 +1562,7 @@ private: } } - MutationHelpers::finalizeMutatedPart(ctx->source_part, ctx->new_data_part, ctx->execute_ttl_type, ctx->compression_codec, ctx->context, ctx->need_sync); + MutationHelpers::finalizeMutatedPart(ctx->source_part, ctx->new_data_part, ctx->execute_ttl_type, ctx->compression_codec, ctx->context, ctx->metadata_snapshot, ctx->need_sync); } diff --git a/tests/integration/test_merge_tree_s3/test.py b/tests/integration/test_merge_tree_s3/test.py index f0f81100320..c8c97f4d40f 100644 --- a/tests/integration/test_merge_tree_s3/test.py +++ b/tests/integration/test_merge_tree_s3/test.py @@ -52,8 +52,10 @@ def cluster(): FILES_OVERHEAD = 1 FILES_OVERHEAD_PER_COLUMN = 2 # Data and mark files -FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + 1 -FILES_OVERHEAD_PER_PART_COMPACT = 10 + 1 +FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC = 1 +FILES_OVERHEAD_METADATA_VERSION = 1 +FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +FILES_OVERHEAD_PER_PART_COMPACT = 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION def create_table(node, table_name, **additional_settings): @@ -232,7 +234,6 @@ def test_insert_same_partition_and_merge(cluster, merge_vertical, node_name): def test_alter_table_columns(cluster, node_name): node = cluster.instances[node_name] create_table(node, "s3_test") - minio = cluster.minio_client node.query( "INSERT INTO s3_test VALUES {}".format(generate_values("2020-01-03", 4096)) diff --git a/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference b/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference index bbca9bbbfee..7fa38f6ff44 100644 --- a/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference +++ b/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference @@ -7,25 +7,25 @@ file_segment_range_begin: 0 file_segment_range_end: 745 size: 746 state: DOWNLOADED -7 -7 +8 +8 0 2 2 -7 +8 Row 1: ────── file_segment_range_begin: 0 file_segment_range_end: 1659 size: 1660 state: DOWNLOADED -7 -7 -7 -7 -21 -31 -38 +8 +8 +8 +8 +24 +27 +27 5010500 18816 Using storage policy: local_cache @@ -37,24 +37,24 @@ file_segment_range_begin: 0 file_segment_range_end: 745 size: 746 state: DOWNLOADED -7 -7 +8 +8 0 2 2 -7 +8 Row 1: ────── file_segment_range_begin: 0 file_segment_range_end: 1659 size: 1660 state: DOWNLOADED -7 -7 -7 -7 -21 -31 -38 +8 +8 +8 +8 +24 +27 +27 5010500 18816 From f81f0b351fc7882aae2ddf4e156c7fec235d9ad2 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 8 Feb 2023 13:58:55 +0000 Subject: [PATCH 025/229] Automatic style fix --- tests/integration/test_merge_tree_s3/test.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_merge_tree_s3/test.py b/tests/integration/test_merge_tree_s3/test.py index c8c97f4d40f..696c016f760 100644 --- a/tests/integration/test_merge_tree_s3/test.py +++ b/tests/integration/test_merge_tree_s3/test.py @@ -54,8 +54,16 @@ FILES_OVERHEAD = 1 FILES_OVERHEAD_PER_COLUMN = 2 # Data and mark files FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC = 1 FILES_OVERHEAD_METADATA_VERSION = 1 -FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION -FILES_OVERHEAD_PER_PART_COMPACT = 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +FILES_OVERHEAD_PER_PART_WIDE = ( + FILES_OVERHEAD_PER_COLUMN * 3 + + 2 + + 6 + + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + + FILES_OVERHEAD_METADATA_VERSION +) +FILES_OVERHEAD_PER_PART_COMPACT = ( + 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +) def create_table(node, table_name, **additional_settings): From 5f33aaf59b12d06d2f1dac3412ce8b58723426b0 Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 8 Feb 2023 16:31:37 +0100 Subject: [PATCH 026/229] Fix three leftovers --- tests/integration/test_merge_tree_hdfs/test.py | 14 ++++++++++++-- .../test_replicated_merge_tree_s3/test.py | 14 ++++++++++++-- .../test.py | 14 ++++++++++++-- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/tests/integration/test_merge_tree_hdfs/test.py b/tests/integration/test_merge_tree_hdfs/test.py index 3950077e619..782237539fa 100644 --- a/tests/integration/test_merge_tree_hdfs/test.py +++ b/tests/integration/test_merge_tree_hdfs/test.py @@ -43,8 +43,18 @@ def create_table(cluster, table_name, additional_settings=None): FILES_OVERHEAD = 1 FILES_OVERHEAD_PER_COLUMN = 2 # Data and mark files -FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + 1 -FILES_OVERHEAD_PER_PART_COMPACT = 10 + 1 +FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC = 1 +FILES_OVERHEAD_METADATA_VERSION = 1 +FILES_OVERHEAD_PER_PART_WIDE = ( + FILES_OVERHEAD_PER_COLUMN * 3 + + 2 + + 6 + + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + + FILES_OVERHEAD_METADATA_VERSION +) +FILES_OVERHEAD_PER_PART_COMPACT = ( + 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +) @pytest.fixture(scope="module") diff --git a/tests/integration/test_replicated_merge_tree_s3/test.py b/tests/integration/test_replicated_merge_tree_s3/test.py index 0d978bb6967..b90e28dfdb2 100644 --- a/tests/integration/test_replicated_merge_tree_s3/test.py +++ b/tests/integration/test_replicated_merge_tree_s3/test.py @@ -44,8 +44,18 @@ def cluster(): FILES_OVERHEAD = 1 FILES_OVERHEAD_PER_COLUMN = 2 # Data and mark files -FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + 1 -FILES_OVERHEAD_PER_PART_COMPACT = 10 + 1 +FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC = 1 +FILES_OVERHEAD_METADATA_VERSION = 1 +FILES_OVERHEAD_PER_PART_WIDE = ( + FILES_OVERHEAD_PER_COLUMN * 3 + + 2 + + 6 + + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + + FILES_OVERHEAD_METADATA_VERSION +) +FILES_OVERHEAD_PER_PART_COMPACT = ( + 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +) def random_string(length): diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index 60a1b9b9746..1b80b80987d 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -45,8 +45,18 @@ def cluster(): FILES_OVERHEAD = 1 FILES_OVERHEAD_PER_COLUMN = 2 # Data and mark files -FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + 1 -FILES_OVERHEAD_PER_PART_COMPACT = 10 + 1 +FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC = 1 +FILES_OVERHEAD_METADATA_VERSION = 1 +FILES_OVERHEAD_PER_PART_WIDE = ( + FILES_OVERHEAD_PER_COLUMN * 3 + + 2 + + 6 + + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + + FILES_OVERHEAD_METADATA_VERSION +) +FILES_OVERHEAD_PER_PART_COMPACT = ( + 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +) def random_string(length): From 242d6d0cb8b928da0d93adc4541d864b33f9fd83 Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 8 Feb 2023 17:25:55 +0100 Subject: [PATCH 027/229] better test --- .../02555_davengers_rename_chain.sh | 143 ++++++++++++++++++ .../02555_davengers_rename_chain.sql | 91 ----------- 2 files changed, 143 insertions(+), 91 deletions(-) create mode 100755 tests/queries/0_stateless/02555_davengers_rename_chain.sh delete mode 100644 tests/queries/0_stateless/02555_davengers_rename_chain.sql diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.sh b/tests/queries/0_stateless/02555_davengers_rename_chain.sh new file mode 100755 index 00000000000..71201537170 --- /dev/null +++ b/tests/queries/0_stateless/02555_davengers_rename_chain.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# Tags: replica +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS wrong_metadata" + +$CLICKHOUSE_CLIENT -n --query="CREATE TABLE wrong_metadata( + a UInt64, + b UInt64, + c UInt64 +) +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata', '1') +ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0" + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata VALUES (1, 2, 3)" + + +$CLICKHOUSE_CLIENT --query="SYSTEM STOP MERGES wrong_metadata" + + +$CLICKHOUSE_CLIENT --query="ALTER TABLE wrong_metadata RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0" + +counter=0 retries=60 +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata") + if [[ $result == *"a1 UInt64"* ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow" + +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata VALUES (4, 5, 6)" + + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + + +$CLICKHOUSE_CLIENT --query="ALTER TABLE wrong_metadata RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0" + +counter=0 retries=60 +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata") + if [[ $result == *"b UInt64"* ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata VALUES (7, 8, 9)" + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata ORDER by a1 FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="SYSTEM START MERGES wrong_metadata" + +$CLICKHOUSE_CLIENT --query="SYSTEM SYNC REPLICA wrong_metadata" + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata order by a FORMAT JSONEachRow" + +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + + +$CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS wrong_metadata" + +$CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS wrong_metadata_compact" + +$CLICKHOUSE_CLIENT -n --query="CREATE TABLE wrong_metadata_compact( + a UInt64, + b UInt64, + c UInt64 +) +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata_compact', '1') +ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 10000000" + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata_compact VALUES (1, 2, 3)" + +$CLICKHOUSE_CLIENT --query="SYSTEM STOP MERGES wrong_metadata_compact" + +$CLICKHOUSE_CLIENT --query="ALTER TABLE wrong_metadata_compact RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0" + +counter=0 retries=60 +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata_compact") + if [[ $result == *"b1 UInt64"* ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata_compact VALUES (4, 5, 6)" + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="ALTER TABLE wrong_metadata_compact RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0" + +counter=0 retries=60 +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata_compact") + if [[ $result == *"b UInt64"* ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata_compact VALUES (7, 8, 9)" + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata_compact ORDER by a1 FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="SYSTEM START MERGES wrong_metadata_compact" + +$CLICKHOUSE_CLIENT --query="SYSTEM SYNC REPLICA wrong_metadata_compact" + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata_compact order by a FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS wrong_metadata_compact" diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.sql b/tests/queries/0_stateless/02555_davengers_rename_chain.sql deleted file mode 100644 index eae345d0472..00000000000 --- a/tests/queries/0_stateless/02555_davengers_rename_chain.sql +++ /dev/null @@ -1,91 +0,0 @@ -DROP TABLE IF EXISTS wrong_metadata; - -CREATE TABLE wrong_metadata( - a UInt64, - b UInt64, - c UInt64 -) -ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata', '1') -ORDER BY tuple() -SETTINGS min_bytes_for_wide_part = 0; - -INSERT INTO wrong_metadata VALUES (1, 2, 3); - -SYSTEM STOP MERGES wrong_metadata; - -ALTER TABLE wrong_metadata RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0; - -SELECT sleep(1) FORMAT Null; - -SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -INSERT INTO wrong_metadata VALUES (4, 5, 6); - -SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -ALTER TABLE wrong_metadata RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0; - -SELECT sleep(1) FORMAT Null; -SELECT sleep(1) FORMAT Null; - -INSERT INTO wrong_metadata VALUES (7, 8, 9); - -SELECT * FROM wrong_metadata ORDER by a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -SYSTEM START MERGES wrong_metadata; - -SYSTEM SYNC REPLICA wrong_metadata; - -SELECT * FROM wrong_metadata order by a FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -DROP TABLE IF EXISTS wrong_metadata; - -DROP TABLE IF EXISTS wrong_metadata_compact; - -CREATE TABLE wrong_metadata_compact( - a UInt64, - b UInt64, - c UInt64 -) -ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata_compact', '1') -ORDER BY tuple() -SETTINGS min_bytes_for_wide_part = 10000000; - -INSERT INTO wrong_metadata_compact VALUES (1, 2, 3); - -SYSTEM STOP MERGES wrong_metadata_compact; - -ALTER TABLE wrong_metadata_compact RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0; - -SELECT sleep(1) FORMAT Null; - -SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -INSERT INTO wrong_metadata_compact VALUES (4, 5, 6); - -SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -ALTER TABLE wrong_metadata_compact RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0; - -SELECT sleep(1) FORMAT Null; -SELECT sleep(1) FORMAT Null; - -INSERT INTO wrong_metadata_compact VALUES (7, 8, 9); - -SELECT * FROM wrong_metadata_compact ORDER by a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -SYSTEM START MERGES wrong_metadata_compact; - -SYSTEM SYNC REPLICA wrong_metadata_compact; - -SELECT * FROM wrong_metadata_compact order by a FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -DROP TABLE IF EXISTS wrong_metadata_compact; From 00686ae03b377482a2ae1ee335d4e61693a22880 Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 8 Feb 2023 22:34:09 +0100 Subject: [PATCH 028/229] Fix tests again --- tests/integration/test_partition/test.py | 2 ++ tests/integration/test_s3_zero_copy_ttl/test.py | 4 ++-- .../02241_filesystem_cache_on_write_operations.reference | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/integration/test_partition/test.py b/tests/integration/test_partition/test.py index ae4393fc6f6..b59cc21f39f 100644 --- a/tests/integration/test_partition/test.py +++ b/tests/integration/test_partition/test.py @@ -105,6 +105,8 @@ def partition_complex_assert_checksums(): "c4ca4238a0b923820dcc509a6f75849b\tshadow/1/data/test/partition_complex/19700102_2_2_0/count.txt\n" "c4ca4238a0b923820dcc509a6f75849b\tshadow/1/data/test/partition_complex/19700201_1_1_0/count.txt\n" "cfcb770c3ecd0990dcceb1bde129e6c6\tshadow/1/data/test/partition_complex/19700102_2_2_0/p.bin\n" + "cfcd208495d565ef66e7dff9f98764da\tshadow/1/data/test/partition_complex/19700102_2_2_0/metadata_version.txt\n" + "cfcd208495d565ef66e7dff9f98764da\tshadow/1/data/test/partition_complex/19700201_1_1_0/metadata_version.txt\n" "e2af3bef1fd129aea73a890ede1e7a30\tshadow/1/data/test/partition_complex/19700201_1_1_0/k.bin\n" "f2312862cc01adf34a93151377be2ddf\tshadow/1/data/test/partition_complex/19700201_1_1_0/minmax_p.idx\n" ) diff --git a/tests/integration/test_s3_zero_copy_ttl/test.py b/tests/integration/test_s3_zero_copy_ttl/test.py index 9a782aacef6..7dcf3734653 100644 --- a/tests/integration/test_s3_zero_copy_ttl/test.py +++ b/tests/integration/test_s3_zero_copy_ttl/test.py @@ -86,9 +86,9 @@ def test_ttl_move_and_s3(started_cluster): print(f"Total objects: {counter}") - if counter == 300: + if counter == 330: break print(f"Attempts remaining: {attempt}") - assert counter == 300 + assert counter == 330 diff --git a/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference b/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference index 7fa38f6ff44..f3fac9b32d3 100644 --- a/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference +++ b/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference @@ -24,8 +24,8 @@ state: DOWNLOADED 8 8 24 -27 -27 +35 +43 5010500 18816 Using storage policy: local_cache @@ -54,7 +54,7 @@ state: DOWNLOADED 8 8 24 -27 -27 +35 +43 5010500 18816 From 0a823854137271ae19c3b498f7b1cfa2923bb220 Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 9 Feb 2023 12:23:18 +0100 Subject: [PATCH 029/229] Blind fix for trash test --- tests/integration/test_merge_tree_s3_failover/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_merge_tree_s3_failover/test.py b/tests/integration/test_merge_tree_s3_failover/test.py index d4c691fdb55..9194a1b68f6 100644 --- a/tests/integration/test_merge_tree_s3_failover/test.py +++ b/tests/integration/test_merge_tree_s3_failover/test.py @@ -89,7 +89,7 @@ def drop_table(cluster): # S3 request will be failed for an appropriate part file write. -FILES_PER_PART_BASE = 5 # partition.dat, default_compression_codec.txt, count.txt, columns.txt, checksums.txt +FILES_PER_PART_BASE = 6 # partition.dat, metadata_version.txt, default_compression_codec.txt, count.txt, columns.txt, checksums.txt FILES_PER_PART_WIDE = ( FILES_PER_PART_BASE + 1 + 1 + 3 * 2 ) # Primary index, MinMax, Mark and data file for column(s) From fd52979991439905a871ff22cefb1fd2775f6ebd Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 9 Feb 2023 14:56:13 +0100 Subject: [PATCH 030/229] Fix snapshot for ordinary merge tree --- src/Storages/StorageMergeTree.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 5aa4a2bc98d..78b29c943dc 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1262,7 +1262,10 @@ bool StorageMergeTree::scheduleDataProcessingJob(BackgroundJobsAssignee & assign } if (mutate_entry) { - auto task = std::make_shared(*this, metadata_snapshot, mutate_entry, shared_lock, common_assignee_trigger); + /// We take new metadata snapshot here. It's because mutation commands can be executed only with metadata snapshot + /// which is equal or more fresh than commands themselves. In extremely rare case it can happen that we will have alter + /// in between we took snapshot above and selected commands. That is why we take new snapshot here. + auto task = std::make_shared(*this, getInMemoryMetadataPtr(), mutate_entry, shared_lock, common_assignee_trigger); assignee.scheduleMergeMutateTask(task); return true; } From 808a939ad2ca2730e186eb48dbd6d130663de3ec Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 10 Feb 2023 14:25:19 +0100 Subject: [PATCH 031/229] Small rehacktoing --- src/Storages/MergeTree/MergeTreeData.cpp | 10 -- src/Storages/MergeTree/MutateTask.cpp | 115 ++++++++---------- .../ReplicatedMergeTreeAttachThread.cpp | 5 +- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 20 +-- src/Storages/MutationCommands.cpp | 16 +++ src/Storages/MutationCommands.h | 5 + src/Storages/StorageInMemoryMetadata.cpp | 7 ++ src/Storages/StorageInMemoryMetadata.h | 9 +- src/Storages/StorageMergeTree.cpp | 15 +-- src/Storages/StorageReplicatedMergeTree.cpp | 5 +- .../02555_davengers_rename_chain.sh | 16 +-- 11 files changed, 106 insertions(+), 117 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index f8f8ab74b31..4461a1470b4 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7574,7 +7574,6 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa AlterConversions result{}; auto & rename_map = result.rename_map; - /// Squash "log of renames" into single map for (const auto & [version, commands] : commands_map) { for (const auto & command : commands) @@ -7589,15 +7588,6 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa } } - for (auto it = rename_map.begin(); it != rename_map.end();) - { - if (it->rename_to == it->rename_from) - it = rename_map.erase(it); - else - ++it; - - } - return result; } diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 340709bff2c..01f7b47a170 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -52,7 +52,7 @@ static bool checkOperationIsNotCanceled(ActionBlocker & merges_blocker, MergeLis * First part should be executed by mutations interpreter. * Other is just simple drop/renames, so they can be executed without interpreter. */ -static void splitMutationCommands( +static void splitAndModifyMutationCommands( MergeTreeData::DataPartPtr part, const MutationCommands & commands, MutationCommands & for_interpreter, @@ -101,29 +101,37 @@ static void splitMutationCommands( } auto alter_conversions = part->storage.getAlterConversionsForPart(part); - /// If it's compact part, then we don't need to actually remove files - /// from disk we just don't read dropped columns + /// We don't add renames from commands, instead we take them from rename_map. + /// It's important because required renames depend not only on part's data version (i.e. mutation version) + /// but also on part's metadata version. Why we have such logic only for renames? Because all other types of alter + /// can be deduced based on difference between part's schema and table schema. for (const auto & [rename_to, rename_from] : alter_conversions.rename_map) { if (part_columns.has(rename_from)) { + /// Actual rename for_interpreter.push_back( { .type = MutationCommand::Type::READ_COLUMN, .column_name = rename_to, }); + /// Not needed for compact parts (not executed), added here only to produce correct + /// set of columns for new part and their serializations for_file_renames.push_back( { .type = MutationCommand::Type::RENAME_COLUMN, .column_name = rename_from, .rename_to = rename_to }); + part_columns.rename(rename_from, rename_to); } } + /// If it's compact part, then we don't need to actually remove files + /// from disk we just don't read dropped columns for (const auto & column : part_columns) { if (!mutated_columns.contains(column.name)) @@ -162,6 +170,11 @@ static void splitMutationCommands( } auto alter_conversions = part->storage.getAlterConversionsForPart(part); + /// We don't add renames from commands, instead we take them from rename_map. + /// It's important because required renames depend not only on part's data version (i.e. mutation version) + /// but also on part's metadata version. Why we have such logic only for renames? Because all other types of alter + /// can be deduced based on difference between part's schema and table schema. + for (const auto & [rename_to, rename_from] : alter_conversions.rename_map) { for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = rename_from, .rename_to = rename_to}); @@ -169,6 +182,41 @@ static void splitMutationCommands( } } +/// It's legal to squash renames because commands with rename are always "barrier" +/// and executed separately from other types of commands. +static MutationCommands squashRenamesInCommands(const MutationCommands & commands) +{ + NameToNameMap squashed_renames; + for (const auto & command : commands) + { + std::string result_name = command.rename_to; + + bool squashed = false; + for (const auto & [name_from, name_to] : squashed_renames) + { + if (name_to == command.column_name) + { + squashed = true; + squashed_renames[name_from] = result_name; + break; + } + } + if (!squashed) + squashed_renames[command.column_name] = result_name; + } + + MutationCommands squashed_commands; + for (const auto & command : commands) + { + if (squashed_renames.contains(command.column_name)) + { + squashed_commands.push_back(command); + squashed_commands.back().rename_to = squashed_renames[command.column_name]; + } + } + return squashed_commands; +} + /// Get the columns list of the resulting part in the same order as storage_columns. static std::pair getColumnsForNewDataPart( @@ -198,35 +246,7 @@ getColumnsForNewDataPart( storage_columns.emplace_back(column); } - NameToNameMap squashed_renames; - for (const auto & command : all_commands) - { - std::string result_name = command.rename_to; - - bool squashed = false; - for (const auto & [name_from, name_to] : squashed_renames) - { - if (name_to == command.column_name) - { - squashed = true; - squashed_renames[name_from] = result_name; - break; - } - } - if (!squashed) - squashed_renames[command.column_name] = result_name; - } - - MutationCommands squashed_commands; - for (const auto & command : all_commands) - { - if (squashed_renames.contains(command.column_name)) - { - squashed_commands.push_back(command); - squashed_commands.back().rename_to = squashed_renames[command.column_name]; - } - } - + MutationCommands squashed_commands = squashRenamesInCommands(all_commands); for (const auto & command : squashed_commands) { @@ -618,34 +638,7 @@ static NameToNameVector collectFilesForRenames( rename_vector.emplace_back(file_rename_from, file_rename_to); }; - NameToNameMap squashed_renames; - for (const auto & command : commands_for_removes) - { - std::string result_name = command.rename_to; - - bool squashed = false; - for (const auto & [name_from, name_to] : squashed_renames) - { - if (name_to == command.column_name) - { - squashed = true; - squashed_renames[name_from] = result_name; - break; - } - } - if (!squashed) - squashed_renames[command.column_name] = result_name; - } - - MutationCommands squashed_commands; - for (const auto & command : commands_for_removes) - { - if (squashed_renames.contains(command.column_name)) - { - squashed_commands.push_back(command); - squashed_commands.back().rename_to = squashed_renames[command.column_name]; - } - } + MutationCommands squashed_commands = squashRenamesInCommands(commands_for_removes); /// Remove old data for (const auto & command : squashed_commands) @@ -1724,7 +1717,7 @@ bool MutateTask::prepare() context_for_reading->setSetting("allow_asynchronous_read_from_io_pool_for_merge_tree", false); context_for_reading->setSetting("max_streams_for_merge_tree_reading", Field(0)); - MutationHelpers::splitMutationCommands(ctx->source_part, ctx->commands_for_part, ctx->for_interpreter, ctx->for_file_renames); + MutationHelpers::splitAndModifyMutationCommands(ctx->source_part, ctx->commands_for_part, ctx->for_interpreter, ctx->for_file_renames); ctx->stage_progress = std::make_unique(1.0); diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp index 941d0850f67..f1bdc9f43af 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp @@ -149,10 +149,7 @@ void ReplicatedMergeTreeAttachThread::runImpl() const bool replica_metadata_version_exists = zookeeper->tryGet(replica_path + "/metadata_version", replica_metadata_version); if (replica_metadata_version_exists) { - StorageInMemoryMetadata metadata_with_new_version(*metadata_snapshot); - - metadata_with_new_version.setMetadataVersion(parse(replica_metadata_version)); - storage.setInMemoryMetadata(metadata_with_new_version); + storage.setInMemoryMetadata(metadata_snapshot->withMetadataVersion(parse(replica_metadata_version))); } else { diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index f491cdc8cd1..e3e558db77b 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1831,28 +1831,16 @@ MutationCommands ReplicatedMergeTreeQueue::getMutationCommands( MutationCommands commands; for (auto it = begin; it != end; ++it) { - bool rename_command = false; - if (it->second->entry->isAlterMutation()) - { - const auto & single_mutation_commands = it->second->entry->commands; - for (const auto & command : single_mutation_commands) - { - if (command.type == MutationCommand::Type::RENAME_COLUMN) - { - rename_command = true; - break; - } - } - } + const auto & commands_from_entry = it->second->entry->commands; - if (rename_command) + if (commands_from_entry.containBarrierCommand()) { if (commands.empty()) - commands.insert(commands.end(), it->second->entry->commands.begin(), it->second->entry->commands.end()); + commands.insert(commands.end(), commands_from_entry.begin(), commands_from_entry.end()); break; } else - commands.insert(commands.end(), it->second->entry->commands.begin(), it->second->entry->commands.end()); + commands.insert(commands.end(), commands_from_entry.begin(), commands_from_entry.end()); } return commands; diff --git a/src/Storages/MutationCommands.cpp b/src/Storages/MutationCommands.cpp index 0c9e9223929..aa77988348d 100644 --- a/src/Storages/MutationCommands.cpp +++ b/src/Storages/MutationCommands.cpp @@ -23,6 +23,12 @@ namespace ErrorCodes extern const int MULTIPLE_ASSIGNMENTS_TO_COLUMN; } + +bool MutationCommand::isBarrierCommand() const +{ + return type == RENAME_COLUMN; +} + std::optional MutationCommand::parse(ASTAlterCommand * command, bool parse_alter_commands) { if (command->type == ASTAlterCommand::DELETE) @@ -212,4 +218,14 @@ bool MutationCommands::hasNonEmptyMutationCommands() const return false; } +bool MutationCommands::containBarrierCommand() const +{ + for (const auto & command : *this) + { + if (command.isBarrierCommand()) + return true; + } + return false; +} + } diff --git a/src/Storages/MutationCommands.h b/src/Storages/MutationCommands.h index aca91c16e85..079b456fa3b 100644 --- a/src/Storages/MutationCommands.h +++ b/src/Storages/MutationCommands.h @@ -67,6 +67,9 @@ struct MutationCommand /// If parse_alter_commands, than consider more Alter commands as mutation commands static std::optional parse(ASTAlterCommand * command, bool parse_alter_commands = false); + + /// This command shouldn't stick with other commands + bool isBarrierCommand() const; }; /// Multiple mutation commands, possible from different ALTER queries @@ -79,6 +82,8 @@ public: void readText(ReadBuffer & in); std::string toString() const; bool hasNonEmptyMutationCommands() const; + + bool containBarrierCommand() const; }; using MutationCommandsConstPtr = std::shared_ptr; diff --git a/src/Storages/StorageInMemoryMetadata.cpp b/src/Storages/StorageInMemoryMetadata.cpp index 5250c6f5330..45abd4bebef 100644 --- a/src/Storages/StorageInMemoryMetadata.cpp +++ b/src/Storages/StorageInMemoryMetadata.cpp @@ -129,6 +129,13 @@ void StorageInMemoryMetadata::setMetadataVersion(int32_t metadata_version_) metadata_version = metadata_version_; } +StorageInMemoryMetadata StorageInMemoryMetadata::withMetadataVersion(int32_t metadata_version_) const +{ + StorageInMemoryMetadata copy(*this); + copy.setMetadataVersion(metadata_version_); + return copy; +} + const ColumnsDescription & StorageInMemoryMetadata::getColumns() const { return columns; diff --git a/src/Storages/StorageInMemoryMetadata.h b/src/Storages/StorageInMemoryMetadata.h index dfb5e9ddb54..25618c5b03f 100644 --- a/src/Storages/StorageInMemoryMetadata.h +++ b/src/Storages/StorageInMemoryMetadata.h @@ -50,6 +50,8 @@ struct StorageInMemoryMetadata String comment; + /// Version of metadata. Managed properly by ReplicatedMergeTree only + /// (zero-initialization is important) int32_t metadata_version = 0; StorageInMemoryMetadata() = default; @@ -60,7 +62,7 @@ struct StorageInMemoryMetadata StorageInMemoryMetadata(StorageInMemoryMetadata && other) = default; StorageInMemoryMetadata & operator=(StorageInMemoryMetadata && other) = default; - /// NOTE: Thread unsafe part. You should modify same StorageInMemoryMetadata + /// NOTE: Thread unsafe part. You should not modify same StorageInMemoryMetadata /// structure from different threads. It should be used as MultiVersion /// object. See example in IStorage. @@ -92,7 +94,10 @@ struct StorageInMemoryMetadata /// Set SELECT query for (Materialized)View void setSelectQuery(const SelectQueryDescription & select_); + /// Set version of metadata. void setMetadataVersion(int32_t metadata_version_); + /// Get copy of current metadata with metadata_version_ + StorageInMemoryMetadata withMetadataVersion(int32_t metadata_version_) const; /// Returns combined set of columns const ColumnsDescription & getColumns() const; @@ -222,8 +227,8 @@ struct StorageInMemoryMetadata const SelectQueryDescription & getSelectQuery() const; bool hasSelectQuery() const; + /// Get version of metadata int32_t getMetadataVersion() const { return metadata_version; } - bool hasMetadataVersion() const { return metadata_version != -1; } /// Check that all the requested names are in the table and have the correct types. void check(const NamesAndTypesList & columns) const; diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 78b29c943dc..3ee56a2e62d 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1141,22 +1141,13 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMutate( if (current_ast_elements + commands_size >= max_ast_elements) break; - bool rename_command = false; const auto & single_mutation_commands = it->second.commands; - for (const auto & command : single_mutation_commands) - { - if (command.type == MutationCommand::Type::RENAME_COLUMN) - { - rename_command = true; - break; - } - } - if (rename_command) + if (single_mutation_commands.containBarrierCommand()) { if (commands->empty()) { - commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); + commands->insert(commands->end(), single_mutation_commands.begin(), single_mutation_commands.end()); last_mutation_to_apply = it; } break; @@ -1164,7 +1155,7 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMutate( else { current_ast_elements += commands_size; - commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); + commands->insert(commands->end(), single_mutation_commands.begin(), single_mutation_commands.end()); last_mutation_to_apply = it; } diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 8e6e809a54a..806e535b711 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -455,10 +455,7 @@ StorageReplicatedMergeTree::StorageReplicatedMergeTree( Coordination::Stat metadata_stat; current_zookeeper->get(zookeeper_path + "/metadata", &metadata_stat); - - StorageInMemoryMetadata storage_metadata(*metadata_snapshot); - storage_metadata.setMetadataVersion(metadata_stat.version); - setInMemoryMetadata(storage_metadata); + setInMemoryMetadata(metadata_snapshot->withMetadataVersion(metadata_stat.version)); } catch (Coordination::Exception & e) { diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.sh b/tests/queries/0_stateless/02555_davengers_rename_chain.sh index 71201537170..f4af5e091ad 100755 --- a/tests/queries/0_stateless/02555_davengers_rename_chain.sh +++ b/tests/queries/0_stateless/02555_davengers_rename_chain.sh @@ -27,8 +27,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata") - if [[ $result == *"a1 UInt64"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata' AND database='${CLICKHOUSE_DATABASE}'") + if [[ $result == *"a TO a1"* ]]; then break; fi sleep 0.1 @@ -53,8 +53,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata") - if [[ $result == *"b UInt64"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata' AND database='${CLICKHOUSE_DATABASE}'") + if [[ $result == *"b1 TO a"* ]]; then break; fi sleep 0.1 @@ -98,8 +98,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata_compact") - if [[ $result == *"b1 UInt64"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata_compact' AND database='${CLICKHOUSE_DATABASE}'") + if [[ $result == *"a TO a1"* ]]; then break; fi sleep 0.1 @@ -120,8 +120,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata_compact") - if [[ $result == *"b UInt64"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata_compact' AND database='${CLICKHOUSE_DATABASE}'") + if [[ $result == *"b1 TO a"* ]]; then break; fi sleep 0.1 From 99dc7fbde8ec125b85176c481396a2ca916854d2 Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 10 Feb 2023 14:27:59 +0100 Subject: [PATCH 032/229] Update src/Storages/MutationCommands.h --- src/Storages/MutationCommands.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Storages/MutationCommands.h b/src/Storages/MutationCommands.h index 079b456fa3b..5ef0cfda1be 100644 --- a/src/Storages/MutationCommands.h +++ b/src/Storages/MutationCommands.h @@ -83,6 +83,9 @@ public: std::string toString() const; bool hasNonEmptyMutationCommands() const; + /// These set of commands contain barrier command and shouldn't + /// stick with other commands. Commands from one set have already been validated + /// to be executed without issues on the creation state. bool containBarrierCommand() const; }; From d1efd024809cea35b2bb099e32208ad88603c37a Mon Sep 17 00:00:00 2001 From: avogar Date: Fri, 10 Feb 2023 16:40:14 +0000 Subject: [PATCH 033/229] Extend setting input_format_null_as_default for more formats --- src/Columns/ColumnLowCardinality.cpp | 28 ++++++++ src/Columns/ColumnLowCardinality.h | 2 + src/Columns/ColumnNullable.cpp | 22 ++++++ src/Columns/ColumnNullable.h | 2 + src/Core/Settings.h | 3 +- src/DataTypes/IDataType.h | 5 ++ src/Formats/FormatFactory.cpp | 1 - src/Formats/FormatSettings.h | 1 - src/Formats/NativeReader.cpp | 31 +++++++-- src/Formats/NativeReader.h | 10 ++- src/Formats/insertNullAsDefaultIfNeeded.cpp | 37 ++++++++++ src/Formats/insertNullAsDefaultIfNeeded.h | 10 +++ .../Formats/Impl/ArrowBlockInputFormat.cpp | 9 +-- .../Formats/Impl/ArrowBlockInputFormat.h | 1 - .../Formats/Impl/ArrowColumnToCHColumn.cpp | 39 ++++------- .../Formats/Impl/ArrowColumnToCHColumn.h | 9 ++- .../Formats/Impl/AvroRowInputFormat.cpp | 69 +++++++++++++++---- .../Formats/Impl/AvroRowInputFormat.h | 7 +- .../Formats/Impl/MsgPackRowInputFormat.cpp | 44 +++++++----- .../Formats/Impl/MsgPackRowInputFormat.h | 10 ++- src/Processors/Formats/Impl/NativeFormat.cpp | 12 +++- .../Formats/Impl/ORCBlockInputFormat.cpp | 8 +-- .../Formats/Impl/ORCBlockInputFormat.h | 1 - .../Formats/Impl/ParquetBlockInputFormat.cpp | 13 ++-- .../Formats/Impl/ParquetBlockInputFormat.h | 1 - ...561_null_as_default_more_formats.reference | 36 ++++++++++ .../02561_null_as_default_more_formats.sh | 21 ++++++ ...2_native_null_on_missing_columns.reference | 4 ++ .../02562_native_null_on_missing_columns.sh | 16 +++++ 29 files changed, 352 insertions(+), 100 deletions(-) create mode 100644 src/Formats/insertNullAsDefaultIfNeeded.cpp create mode 100644 src/Formats/insertNullAsDefaultIfNeeded.h create mode 100644 tests/queries/0_stateless/02561_null_as_default_more_formats.reference create mode 100755 tests/queries/0_stateless/02561_null_as_default_more_formats.sh create mode 100644 tests/queries/0_stateless/02562_native_null_on_missing_columns.reference create mode 100755 tests/queries/0_stateless/02562_native_null_on_missing_columns.sh diff --git a/src/Columns/ColumnLowCardinality.cpp b/src/Columns/ColumnLowCardinality.cpp index ecdaf240e5e..109bf201836 100644 --- a/src/Columns/ColumnLowCardinality.cpp +++ b/src/Columns/ColumnLowCardinality.cpp @@ -830,4 +830,32 @@ void ColumnLowCardinality::Dictionary::compact(ColumnPtr & positions) shared = false; } +ColumnPtr ColumnLowCardinality::cloneWithDefaultOnNull() const +{ + if (!nestedIsNullable()) + return getPtr(); + + auto res = cloneEmpty(); + auto & lc_res = assert_cast(*res); + lc_res.nestedRemoveNullable(); + size_t end = size(); + size_t start = 0; + while (start < end) + { + size_t next_null_index = start; + while (next_null_index < end && !isNullAt(next_null_index)) + ++next_null_index; + + if (next_null_index != start) + lc_res.insertRangeFrom(*this, start, next_null_index - start); + + if (next_null_index < end) + lc_res.insertDefault(); + + start = next_null_index + 1; + } + + return res; +} + } diff --git a/src/Columns/ColumnLowCardinality.h b/src/Columns/ColumnLowCardinality.h index e895bc6b54e..3d42f82a867 100644 --- a/src/Columns/ColumnLowCardinality.h +++ b/src/Columns/ColumnLowCardinality.h @@ -220,6 +220,8 @@ public: void nestedToNullable() { dictionary.getColumnUnique().nestedToNullable(); } void nestedRemoveNullable() { dictionary.getColumnUnique().nestedRemoveNullable(); } + ColumnPtr cloneWithDefaultOnNull() const; + const IColumnUnique & getDictionary() const { return dictionary.getColumnUnique(); } IColumnUnique & getDictionary() { return dictionary.getColumnUnique(); } const ColumnPtr & getDictionaryPtr() const { return dictionary.getColumnUniquePtr(); } diff --git a/src/Columns/ColumnNullable.cpp b/src/Columns/ColumnNullable.cpp index 9398c66bef0..99d377f10eb 100644 --- a/src/Columns/ColumnNullable.cpp +++ b/src/Columns/ColumnNullable.cpp @@ -781,6 +781,28 @@ ColumnPtr ColumnNullable::createWithOffsets(const IColumn::Offsets & offsets, co return ColumnNullable::create(new_values, new_null_map); } +ColumnPtr ColumnNullable::getNestedColumnWithDefaultOnNull() const +{ + auto res = nested_column->cloneEmpty(); + const auto & null_map_data = getNullMapData(); + size_t start = 0; + while (start < nested_column->size()) + { + size_t next_null_index = start; + while (next_null_index < null_map->size() && !null_map_data[next_null_index]) + ++next_null_index; + + if (next_null_index != start) + res->insertRangeFrom(*nested_column, start, next_null_index - start); + + if (next_null_index < null_map->size()) + res->insertDefault(); + + start = next_null_index + 1; + } + return res; +} + ColumnPtr makeNullable(const ColumnPtr & column) { if (isColumnNullable(*column)) diff --git a/src/Columns/ColumnNullable.h b/src/Columns/ColumnNullable.h index 85bf095a9d1..1ec037092b5 100644 --- a/src/Columns/ColumnNullable.h +++ b/src/Columns/ColumnNullable.h @@ -188,6 +188,8 @@ public: NullMap & getNullMapData() { return getNullMapColumn().getData(); } const NullMap & getNullMapData() const { return getNullMapColumn().getData(); } + ColumnPtr getNestedColumnWithDefaultOnNull() const; + /// Apply the null byte map of a specified nullable column onto the /// null byte map of the current column by performing an element-wise OR /// between both byte maps. This method is used to determine the null byte diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 6e085fd27ac..481929d915f 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -751,7 +751,7 @@ class IColumn; M(Bool, input_format_csv_empty_as_default, true, "Treat empty fields in CSV input as default values.", 0) \ M(Bool, input_format_tsv_empty_as_default, false, "Treat empty fields in TSV input as default values.", 0) \ M(Bool, input_format_tsv_enum_as_number, false, "Treat inserted enum values in TSV formats as enum indices.", 0) \ - M(Bool, input_format_null_as_default, true, "For text input formats initialize null fields with default values if data type of this field is not nullable", 0) \ + M(Bool, input_format_null_as_default, true, "For most input formats initialize null fields with default values if data type of this field is not nullable", 0) \ M(Bool, input_format_arrow_import_nested, false, "Allow to insert array of structs into Nested table in Arrow input format.", 0) \ M(Bool, input_format_arrow_case_insensitive_column_matching, false, "Ignore case when matching Arrow columns with CH columns.", 0) \ M(Bool, input_format_orc_import_nested, false, "Allow to insert array of structs into Nested table in ORC input format.", 0) \ @@ -811,6 +811,7 @@ class IColumn; M(Bool, input_format_values_deduce_templates_of_expressions, true, "For Values format: if the field could not be parsed by streaming parser, run SQL parser, deduce template of the SQL expression, try to parse all rows using template and then interpret expression for all rows.", 0) \ M(Bool, input_format_values_accurate_types_of_literals, true, "For Values format: when parsing and interpreting expressions using template, check actual type of literal to avoid possible overflow and precision issues.", 0) \ M(Bool, input_format_avro_allow_missing_fields, false, "For Avro/AvroConfluent format: when field is not found in schema use default value instead of error", 0) \ + /** This setting is obsolete and do nothing, left for compatibility reasons. */ \ M(Bool, input_format_avro_null_as_default, false, "For Avro/AvroConfluent format: insert default in case of null and non Nullable column", 0) \ M(UInt64, format_binary_max_string_size, 1_GiB, "The maximum allowed size for String in RowBinary format. It prevents allocating large amount of memory in case of corrupted data. 0 means there is no limit", 0) \ M(URI, format_avro_schema_registry_url, "", "For AvroConfluent format: Confluent Schema Registry URL.", 0) \ diff --git a/src/DataTypes/IDataType.h b/src/DataTypes/IDataType.h index bafe03dbc3a..f2230b70cab 100644 --- a/src/DataTypes/IDataType.h +++ b/src/DataTypes/IDataType.h @@ -548,6 +548,11 @@ inline bool isAggregateFunction(const DataTypePtr & data_type) return which.isAggregateFunction(); } +inline bool isNullableOrLowCardinalityNullable(const DataTypePtr & data_type) +{ + return data_type->isNullable() || data_type->isLowCardinalityNullable(); +} + template constexpr bool IsDataTypeDecimal = false; template constexpr bool IsDataTypeNumber = false; template constexpr bool IsDataTypeDateOrDateTime = false; diff --git a/src/Formats/FormatFactory.cpp b/src/Formats/FormatFactory.cpp index 3fcecd23f5b..8d8ffebe270 100644 --- a/src/Formats/FormatFactory.cpp +++ b/src/Formats/FormatFactory.cpp @@ -56,7 +56,6 @@ FormatSettings getFormatSettings(ContextPtr context, const Settings & settings) format_settings.avro.schema_registry_url = settings.format_avro_schema_registry_url.toString(); format_settings.avro.string_column_pattern = settings.output_format_avro_string_column_pattern.toString(); format_settings.avro.output_rows_in_file = settings.output_format_avro_rows_in_file; - format_settings.avro.null_as_default = settings.input_format_avro_null_as_default; format_settings.csv.allow_double_quotes = settings.format_csv_allow_double_quotes; format_settings.csv.allow_single_quotes = settings.format_csv_allow_single_quotes; format_settings.csv.crlf_end_of_line = settings.output_format_csv_crlf_end_of_line; diff --git a/src/Formats/FormatSettings.h b/src/Formats/FormatSettings.h index 92e499abb10..2bf8e136c63 100644 --- a/src/Formats/FormatSettings.h +++ b/src/Formats/FormatSettings.h @@ -104,7 +104,6 @@ struct FormatSettings bool allow_missing_fields = false; String string_column_pattern; UInt64 output_rows_in_file = 1; - bool null_as_default = false; } avro; String bool_true_representation = "true"; diff --git a/src/Formats/NativeReader.cpp b/src/Formats/NativeReader.cpp index 58baee5931b..9f8d4ba1930 100644 --- a/src/Formats/NativeReader.cpp +++ b/src/Formats/NativeReader.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -32,8 +33,19 @@ NativeReader::NativeReader(ReadBuffer & istr_, UInt64 server_revision_) { } -NativeReader::NativeReader(ReadBuffer & istr_, const Block & header_, UInt64 server_revision_, bool skip_unknown_columns_) - : istr(istr_), header(header_), server_revision(server_revision_), skip_unknown_columns(skip_unknown_columns_) +NativeReader::NativeReader( + ReadBuffer & istr_, + const Block & header_, + UInt64 server_revision_, + bool skip_unknown_columns_, + bool null_as_default_, + BlockMissingValues * block_missing_values_) + : istr(istr_) + , header(header_) + , server_revision(server_revision_) + , skip_unknown_columns(skip_unknown_columns_) + , null_as_default(null_as_default_) + , block_missing_values(block_missing_values_) { } @@ -187,8 +199,12 @@ Block NativeReader::read() { if (header.has(column.name)) { - /// Support insert from old clients without low cardinality type. auto & header_column = header.getByName(column.name); + + if (null_as_default) + insertNullAsDefaultIfNeeded(column, header_column, header.getPositionByName(column.name), block_missing_values); + + /// Support insert from old clients without low cardinality type. if (!header_column.type->equals(*column.type)) { column.column = recursiveTypeConversion(column.column, column.type, header.safeGetByPosition(i).type); @@ -225,12 +241,19 @@ Block NativeReader::read() /// Allow to skip columns. Fill them with default values. Block tmp_res; - for (auto & col : header) + for (size_t column_i = 0; column_i != header.columns(); ++column_i) { + auto & col = header.getByPosition(column_i); if (res.has(col.name)) + { tmp_res.insert(res.getByName(col.name)); + } else + { tmp_res.insert({col.type->createColumn()->cloneResized(rows), col.type, col.name}); + if (block_missing_values) + block_missing_values->setBits(column_i, rows); + } } tmp_res.info = res.info; diff --git a/src/Formats/NativeReader.h b/src/Formats/NativeReader.h index 3ae53d45faf..64d3e4d6df0 100644 --- a/src/Formats/NativeReader.h +++ b/src/Formats/NativeReader.h @@ -24,7 +24,13 @@ public: /// For cases when data structure (header) is known in advance. /// NOTE We may use header for data validation and/or type conversions. It is not implemented. - NativeReader(ReadBuffer & istr_, const Block & header_, UInt64 server_revision_, bool skip_unknown_columns_ = false); + NativeReader( + ReadBuffer & istr_, + const Block & header_, + UInt64 server_revision_, + bool skip_unknown_columns_ = false, + bool null_as_default_ = false, + BlockMissingValues * block_missing_values_ = nullptr); /// For cases when we have an index. It allows to skip columns. Only columns specified in the index will be read. NativeReader(ReadBuffer & istr_, UInt64 server_revision_, @@ -44,6 +50,8 @@ private: Block header; UInt64 server_revision; bool skip_unknown_columns; + bool null_as_default; + BlockMissingValues * block_missing_values; bool use_index = false; IndexForNativeFormat::Blocks::const_iterator index_block_it; diff --git a/src/Formats/insertNullAsDefaultIfNeeded.cpp b/src/Formats/insertNullAsDefaultIfNeeded.cpp new file mode 100644 index 00000000000..767892718c5 --- /dev/null +++ b/src/Formats/insertNullAsDefaultIfNeeded.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +namespace DB +{ + +void insertNullAsDefaultIfNeeded(ColumnWithTypeAndName & input_column, const ColumnWithTypeAndName & header_column, size_t column_i, BlockMissingValues * block_missing_values) +{ + if (!isNullableOrLowCardinalityNullable(input_column.type) || isNullableOrLowCardinalityNullable(header_column.type)) + return; + + if (block_missing_values) + { + for (size_t i = 0; i < input_column.column->size(); ++i) + { + if (input_column.column->isNullAt(i)) + block_missing_values->setBit(column_i, i); + } + } + + if (input_column.type->isNullable()) + { + input_column.column = assert_cast(input_column.column.get())->getNestedColumnWithDefaultOnNull(); + input_column.type = removeNullable(input_column.type); + } + else + { + input_column.column = assert_cast(input_column.column.get())->cloneWithDefaultOnNull(); + const auto * lc_type = assert_cast(input_column.type.get()); + input_column.type = std::make_shared(removeNullable(lc_type->getDictionaryType())); + } +} + +} diff --git a/src/Formats/insertNullAsDefaultIfNeeded.h b/src/Formats/insertNullAsDefaultIfNeeded.h new file mode 100644 index 00000000000..3e4dcd1e74a --- /dev/null +++ b/src/Formats/insertNullAsDefaultIfNeeded.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace DB +{ + +void insertNullAsDefaultIfNeeded(ColumnWithTypeAndName & input_column, const ColumnWithTypeAndName & header_column, size_t column_i, BlockMissingValues * block_missing_values); + +} diff --git a/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp b/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp index ed963d8a500..cd8facb83eb 100644 --- a/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp @@ -71,13 +71,10 @@ Chunk ArrowBlockInputFormat::generate() ++record_batch_current; - arrow_column_to_ch_column->arrowTableToCHChunk(res, *table_result, (*table_result)->num_rows()); - /// If defaults_for_omitted_fields is true, calculate the default values from default expression for omitted fields. /// Otherwise fill the missing columns with zero values of its type. - if (format_settings.defaults_for_omitted_fields) - for (const auto & column_idx : missing_columns) - block_missing_values.setBits(column_idx, res.getNumRows()); + BlockMissingValues * block_missing_values_ptr = format_settings.defaults_for_omitted_fields ? &block_missing_values : nullptr; + arrow_column_to_ch_column->arrowTableToCHChunk(res, *table_result, (*table_result)->num_rows(), block_missing_values_ptr); return res; } @@ -143,8 +140,8 @@ void ArrowBlockInputFormat::prepareReader() "Arrow", format_settings.arrow.import_nested, format_settings.arrow.allow_missing_columns, + format_settings.null_as_default, format_settings.arrow.case_insensitive_column_matching); - missing_columns = arrow_column_to_ch_column->getMissingColumns(*schema); if (stream) record_batch_total = -1; diff --git a/src/Processors/Formats/Impl/ArrowBlockInputFormat.h b/src/Processors/Formats/Impl/ArrowBlockInputFormat.h index 02648d28048..3db76777891 100644 --- a/src/Processors/Formats/Impl/ArrowBlockInputFormat.h +++ b/src/Processors/Formats/Impl/ArrowBlockInputFormat.h @@ -47,7 +47,6 @@ private: int record_batch_total = 0; int record_batch_current = 0; - std::vector missing_columns; BlockMissingValues block_missing_values; const FormatSettings format_settings; diff --git a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp index 68c40527097..80172ca9c05 100644 --- a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp +++ b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -384,9 +385,10 @@ static ColumnWithTypeAndName readColumnWithIndexesDataImpl(std::shared_ptr(buffer->data()); /// Check that indexes are correct (protection against corrupted files) + /// Note that on null values index can be arbitrary value. for (int64_t i = 0; i != chunk->length(); ++i) { - if (data[i] < 0 || data[i] >= dict_size) + if (!chunk->IsNull(i) && (data[i] < 0 || data[i] >= dict_size)) throw Exception(ErrorCodes::INCORRECT_DATA, "Index {} in Dictionary column is out of bounds, dictionary size is {}", Int64(data[i]), UInt64(dict_size)); @@ -805,16 +807,18 @@ ArrowColumnToCHColumn::ArrowColumnToCHColumn( const std::string & format_name_, bool import_nested_, bool allow_missing_columns_, + bool null_as_default_, bool case_insensitive_matching_) : header(header_) , format_name(format_name_) , import_nested(import_nested_) , allow_missing_columns(allow_missing_columns_) + , null_as_default(null_as_default_) , case_insensitive_matching(case_insensitive_matching_) { } -void ArrowColumnToCHColumn::arrowTableToCHChunk(Chunk & res, std::shared_ptr & table, size_t num_rows) +void ArrowColumnToCHColumn::arrowTableToCHChunk(Chunk & res, std::shared_ptr & table, size_t num_rows, BlockMissingValues * block_missing_values) { NameToColumnPtr name_to_column_ptr; for (auto column_name : table->ColumnNames()) @@ -828,10 +832,10 @@ void ArrowColumnToCHColumn::arrowTableToCHChunk(Chunk & res, std::shared_ptrcloneResized(num_rows); columns_list.push_back(std::move(column.column)); + if (block_missing_values) + block_missing_values->setBits(column_i, num_rows); continue; } } @@ -906,6 +912,9 @@ void ArrowColumnToCHColumn::arrowColumnsToCHChunk(Chunk & res, NameToColumnPtr & arrow_column, header_column.name, format_name, false, dictionary_infos, true, false, skipped, header_column.type); } + if (null_as_default) + insertNullAsDefaultIfNeeded(column, header_column, column_i, block_missing_values); + try { column.column = castColumn(column, header_column.type); @@ -927,28 +936,6 @@ void ArrowColumnToCHColumn::arrowColumnsToCHChunk(Chunk & res, NameToColumnPtr & res.setColumns(columns_list, num_rows); } -std::vector ArrowColumnToCHColumn::getMissingColumns(const arrow::Schema & schema) const -{ - std::vector missing_columns; - auto block_from_arrow = arrowSchemaToCHHeader(schema, format_name, false, &header, case_insensitive_matching); - NestedColumnExtractHelper nested_columns_extractor(block_from_arrow, case_insensitive_matching); - - for (size_t i = 0, columns = header.columns(); i < columns; ++i) - { - const auto & header_column = header.getByPosition(i); - if (!block_from_arrow.has(header_column.name, case_insensitive_matching)) - { - if (!import_nested || !nested_columns_extractor.extractColumn(header_column.name)) - { - if (!allow_missing_columns) - throw Exception{ErrorCodes::THERE_IS_NO_COLUMN, "Column '{}' is not presented in input data.", header_column.name}; - missing_columns.push_back(i); - } - } - } - return missing_columns; -} - } #endif diff --git a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h index dd9f44eb94e..64ff99c70ac 100644 --- a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h +++ b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h @@ -26,14 +26,12 @@ public: const std::string & format_name_, bool import_nested_, bool allow_missing_columns_, + bool null_as_default_, bool case_insensitive_matching_ = false); - void arrowTableToCHChunk(Chunk & res, std::shared_ptr & table, size_t num_rows); + void arrowTableToCHChunk(Chunk & res, std::shared_ptr & table, size_t num_rows, BlockMissingValues * block_missing_values = nullptr); - void arrowColumnsToCHChunk(Chunk & res, NameToColumnPtr & name_to_column_ptr, size_t num_rows); - - /// Get missing columns that exists in header but not in arrow::Schema - std::vector getMissingColumns(const arrow::Schema & schema) const; + void arrowColumnsToCHChunk(Chunk & res, NameToColumnPtr & name_to_column_ptr, size_t num_rows, BlockMissingValues * block_missing_values = nullptr); /// Transform arrow schema to ClickHouse header. If hint_header is provided, /// we will skip columns in schema that are not in hint_header. @@ -58,6 +56,7 @@ private: bool import_nested; /// If false, throw exception if some columns in header not exists in arrow table. bool allow_missing_columns; + bool null_as_default; bool case_insensitive_matching; /// Map {column name : dictionary column}. diff --git a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp index 9a475efa195..e77f4132100 100644 --- a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp @@ -176,8 +176,9 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node { auto & lc_column = assert_cast(column); auto tmp_column = lc_column.getDictionary().getNestedColumn()->cloneEmpty(); - dict_deserialize(*tmp_column, decoder); + auto res = dict_deserialize(*tmp_column, decoder); lc_column.insertFromFullColumn(*tmp_column, 0); + return res; }; } @@ -198,6 +199,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node UUID uuid; parseUUID(reinterpret_cast(tmp.data()), std::reverse_iterator(reinterpret_cast(&uuid) + 16)); assert_cast(column).insertValue(uuid); + return true; }; } if (target.isString() || target.isFixedString()) @@ -206,6 +208,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node { decoder.decodeString(tmp); column.insertData(tmp.c_str(), tmp.length()); + return true; }; } break; @@ -215,6 +218,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [target](IColumn & column, avro::Decoder & decoder) { insertNumber(column, target, decoder.decodeInt()); + return true; }; } break; @@ -224,6 +228,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [target](IColumn & column, avro::Decoder & decoder) { insertNumber(column, target, decoder.decodeLong()); + return true; }; } break; @@ -233,6 +238,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [target](IColumn & column, avro::Decoder & decoder) { insertNumber(column, target, decoder.decodeFloat()); + return true; }; } break; @@ -242,6 +248,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [target](IColumn & column, avro::Decoder & decoder) { insertNumber(column, target, decoder.decodeDouble()); + return true; }; } break; @@ -251,6 +258,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [target](IColumn & column, avro::Decoder & decoder) { insertNumber(column, target, decoder.decodeBool()); + return true; }; } break; @@ -275,6 +283,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node } } offsets.push_back(offsets.back() + total); + return true; }; } break; @@ -301,24 +310,33 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node { col.insertDefault(); } + return true; }; } - - /// If the Union is ['Null', Nested-Type], since the Nested-Type can not be inside - /// Nullable, so we will get Nested-Type, instead of Nullable type. - if (null_as_default || !target.isNullable()) + else if (null_as_default) { auto nested_deserialize = this->createDeserializeFn(root_node->leafAt(non_null_union_index), target_type); return [non_null_union_index, nested_deserialize](IColumn & column, avro::Decoder & decoder) { int union_index = static_cast(decoder.decodeUnionIndex()); if (union_index == non_null_union_index) + { nested_deserialize(column, decoder); - else - column.insertDefault(); + return true; + } + column.insertDefault(); + return false; }; } - + else + { + throw Exception( + ErrorCodes::BAD_ARGUMENTS, + "Cannot insert Avro Union(Null, {}) into non-nullable type {}. To use default value on NULL, enable setting " + "input_format_null_as_default", + avro::toString(root_node->leafAt(non_null_union_index)->type()), + target_type->getName()); + } } break; } @@ -331,6 +349,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [](IColumn &, avro::Decoder & decoder) { decoder.decodeNull(); + return true; }; } else @@ -340,10 +359,26 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node ColumnNullable & col = assert_cast(column); decoder.decodeNull(); col.insertDefault(); + return true; }; } } - break; + else if (null_as_default) + { + return [](IColumn & column, avro::Decoder & decoder) + { + decoder.decodeNull(); + column.insertDefault(); + return false; + }; + } + else + { + throw Exception( + ErrorCodes::BAD_ARGUMENTS, + "Cannot insert Avro Null into non-nullable type {}. To use default value on NULL, enable setting " + "input_format_null_as_default", target_type->getName()); + } case avro::AVRO_ENUM: if (target.isString()) { @@ -358,6 +393,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node size_t enum_index = decoder.decodeEnum(); const auto & enum_symbol = symbols[enum_index]; column.insertData(enum_symbol.c_str(), enum_symbol.length()); + return true; }; } if (target.isEnum()) @@ -372,6 +408,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node { size_t enum_index = decoder.decodeEnum(); column.insert(symbol_mapping[enum_index]); + return true; }; } break; @@ -384,6 +421,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node { decoder.decodeFixed(tmp_fixed.size(), tmp_fixed); column.insertData(reinterpret_cast(tmp_fixed.data()), tmp_fixed.size()); + return true; }; } break; @@ -415,6 +453,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node auto nested_columns = column_tuple.getColumns(); for (const auto & [nested_deserializer, pos] : nested_deserializers) nested_deserializer(*nested_columns[pos], decoder); + return true; }; } break; @@ -449,6 +488,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node } } offsets.push_back(offsets.back() + total); + return true; }; } break; @@ -465,6 +505,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node ColumnNullable & col = assert_cast(column); nested_deserialize(col.getNestedColumn(), decoder); col.getNullMapData().push_back(0); + return true; }; } @@ -593,7 +634,6 @@ void AvroDeserializer::Action::deserializeNested(MutableColumns & columns, avro: ColumnArray & column_array = assert_cast(*columns[index]); arrays_offsets.push_back(&column_array.getOffsets()); nested_columns.push_back(&column_array.getData()); - ext.read_columns[index] = true; } size_t total = 0; @@ -603,7 +643,7 @@ void AvroDeserializer::Action::deserializeNested(MutableColumns & columns, avro: for (size_t i = 0; i < n; ++i) { for (size_t j = 0; j != nested_deserializers.size(); ++j) - nested_deserializers[j](*nested_columns[j], decoder); + ext.read_columns[nested_column_indexes[j]] = nested_deserializers[j](*nested_columns[j], decoder); } } @@ -742,7 +782,8 @@ void AvroDeserializer::deserializeRow(MutableColumns & columns, avro::Decoder & row_action.execute(columns, decoder, ext); for (size_t i = 0; i < ext.read_columns.size(); ++i) { - if (!ext.read_columns[i]) + /// Insert default in missing columns. + if (!column_found[i]) { columns[i]->insertDefault(); } @@ -759,7 +800,7 @@ void AvroRowInputFormat::readPrefix() { file_reader_ptr = std::make_unique(std::make_unique(*in)); deserializer_ptr = std::make_unique( - output.getHeader(), file_reader_ptr->dataSchema(), format_settings.avro.allow_missing_fields, format_settings.avro.null_as_default); + output.getHeader(), file_reader_ptr->dataSchema(), format_settings.avro.allow_missing_fields, format_settings.null_as_default); file_reader_ptr->init(); } @@ -950,7 +991,7 @@ const AvroDeserializer & AvroConfluentRowInputFormat::getOrCreateDeserializer(Sc { auto schema = schema_registry->getSchema(schema_id); AvroDeserializer deserializer( - output.getHeader(), schema, format_settings.avro.allow_missing_fields, format_settings.avro.null_as_default); + output.getHeader(), schema, format_settings.avro.allow_missing_fields, format_settings.null_as_default); it = deserializer_cache.emplace(schema_id, deserializer).first; } return it->second; diff --git a/src/Processors/Formats/Impl/AvroRowInputFormat.h b/src/Processors/Formats/Impl/AvroRowInputFormat.h index 96370b8c4c7..dcd51398032 100644 --- a/src/Processors/Formats/Impl/AvroRowInputFormat.h +++ b/src/Processors/Formats/Impl/AvroRowInputFormat.h @@ -35,8 +35,8 @@ public: void deserializeRow(MutableColumns & columns, avro::Decoder & decoder, RowReadExtension & ext) const; private: - using DeserializeFn = std::function; - using DeserializeNestedFn = std::function; + using DeserializeFn = std::function; + using DeserializeNestedFn = std::function; using SkipFn = std::function; DeserializeFn createDeserializeFn(avro::NodePtr root_node, DataTypePtr target_type); @@ -86,8 +86,7 @@ private: case Noop: break; case Deserialize: - deserialize_fn(*columns[target_column_idx], decoder); - ext.read_columns[target_column_idx] = true; + ext.read_columns[target_column_idx] = deserialize_fn(*columns[target_column_idx], decoder); break; case Skip: skip_fn(decoder); diff --git a/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp b/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp index f337eedbb05..488f4ff9a73 100644 --- a/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp @@ -45,11 +45,11 @@ namespace ErrorCodes extern const int UNEXPECTED_END_OF_FILE; } -MsgPackRowInputFormat::MsgPackRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_) - : MsgPackRowInputFormat(header_, std::make_unique(in_), params_) {} +MsgPackRowInputFormat::MsgPackRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_, const FormatSettings & settings) + : MsgPackRowInputFormat(header_, std::make_unique(in_), params_, settings) {} -MsgPackRowInputFormat::MsgPackRowInputFormat(const Block & header_, std::unique_ptr buf_, Params params_) - : IRowInputFormat(header_, *buf_, std::move(params_)), buf(std::move(buf_)), parser(visitor), data_types(header_.getDataTypes()) {} +MsgPackRowInputFormat::MsgPackRowInputFormat(const Block & header_, std::unique_ptr buf_, Params params_, const FormatSettings & settings) + : IRowInputFormat(header_, *buf_, std::move(params_)), buf(std::move(buf_)), visitor(settings.null_as_default), parser(visitor), data_types(header_.getDataTypes()) {} void MsgPackRowInputFormat::resetParser() { @@ -58,13 +58,13 @@ void MsgPackRowInputFormat::resetParser() visitor.reset(); } -void MsgPackVisitor::set_info(IColumn & column, DataTypePtr type) // NOLINT +void MsgPackVisitor::set_info(IColumn & column, DataTypePtr type, UInt8 & read) // NOLINT { while (!info_stack.empty()) { info_stack.pop(); } - info_stack.push(Info{column, type}); + info_stack.push(Info{column, type, &read}); } void MsgPackVisitor::reset() @@ -228,11 +228,11 @@ static void insertFloat64(IColumn & column, DataTypePtr type, Float64 value) // assert_cast(column).insertValue(value); } -static void insertNull(IColumn & column, DataTypePtr type) +static void insertNull(IColumn & column, DataTypePtr type, UInt8 * read, bool null_as_default) { auto insert_func = [&](IColumn & column_, DataTypePtr type_) { - insertNull(column_, type_); + insertNull(column_, type_, read, null_as_default); }; /// LowCardinality(Nullable(...)) @@ -240,7 +240,16 @@ static void insertNull(IColumn & column, DataTypePtr type) return; if (!type->isNullable()) - throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Cannot insert MessagePack null into non-nullable column with type {}.", type->getName()); + { + if (!null_as_default) + throw Exception( + ErrorCodes::ILLEGAL_COLUMN, "Cannot insert MessagePack null into non-nullable column with type {}.", type->getName()); + column.insertDefault(); + /// In case of default on null column can have defined DEFAULT expression that should be used. + if (read) + *read = false; + return; + } assert_cast(column).insertDefault(); } @@ -316,7 +325,7 @@ bool MsgPackVisitor::start_array(size_t size) // NOLINT ColumnArray::Offsets & offsets = column_array.getOffsets(); IColumn & nested_column = column_array.getData(); offsets.push_back(offsets.back() + size); - info_stack.push(Info{nested_column, nested_type}); + info_stack.push(Info{nested_column, nested_type, nullptr}); return true; } @@ -340,7 +349,7 @@ bool MsgPackVisitor::start_map_key() // NOLINT { auto key_column = assert_cast(info_stack.top().column).getNestedData().getColumns()[0]; auto key_type = assert_cast(*info_stack.top().type).getKeyType(); - info_stack.push(Info{*key_column, key_type}); + info_stack.push(Info{*key_column, key_type, nullptr}); return true; } @@ -354,7 +363,7 @@ bool MsgPackVisitor::start_map_value() // NOLINT { auto value_column = assert_cast(info_stack.top().column).getNestedData().getColumns()[1]; auto value_type = assert_cast(*info_stack.top().type).getValueType(); - info_stack.push(Info{*value_column, value_type}); + info_stack.push(Info{*value_column, value_type, nullptr}); return true; } @@ -366,7 +375,7 @@ bool MsgPackVisitor::end_map_value() // NOLINT bool MsgPackVisitor::visit_nil() { - insertNull(info_stack.top().column, info_stack.top().type); + insertNull(info_stack.top().column, info_stack.top().type, info_stack.top().read, null_as_default); return true; } @@ -407,13 +416,14 @@ bool MsgPackRowInputFormat::readObject() return true; } -bool MsgPackRowInputFormat::readRow(MutableColumns & columns, RowReadExtension &) +bool MsgPackRowInputFormat::readRow(MutableColumns & columns, RowReadExtension & ext) { size_t column_index = 0; bool has_more_data = true; + ext.read_columns.resize(columns.size(), true); for (; column_index != columns.size(); ++column_index) { - visitor.set_info(*columns[column_index], data_types[column_index]); + visitor.set_info(*columns[column_index], data_types[column_index], ext.read_columns[column_index]); has_more_data = readObject(); if (!has_more_data) break; @@ -547,9 +557,9 @@ void registerInputFormatMsgPack(FormatFactory & factory) ReadBuffer & buf, const Block & sample, const RowInputFormatParams & params, - const FormatSettings &) + const FormatSettings & settings) { - return std::make_shared(sample, buf, params); + return std::make_shared(sample, buf, params, settings); }); factory.registerFileExtension("messagepack", "MsgPack"); } diff --git a/src/Processors/Formats/Impl/MsgPackRowInputFormat.h b/src/Processors/Formats/Impl/MsgPackRowInputFormat.h index 64bb8b569e0..5eaa3719d0c 100644 --- a/src/Processors/Formats/Impl/MsgPackRowInputFormat.h +++ b/src/Processors/Formats/Impl/MsgPackRowInputFormat.h @@ -19,10 +19,13 @@ class ReadBuffer; class MsgPackVisitor : public msgpack::null_visitor { public: + MsgPackVisitor(bool null_as_default_) : null_as_default(null_as_default_) {} + struct Info { IColumn & column; DataTypePtr type; + UInt8 * read; }; /// These functions are called when parser meets corresponding object in parsed data @@ -47,25 +50,26 @@ public: [[noreturn]] void parse_error(size_t parsed_offset, size_t error_offset); /// Update info_stack - void set_info(IColumn & column, DataTypePtr type); + void set_info(IColumn & column, DataTypePtr type, UInt8 & read); void reset(); private: /// Stack is needed to process arrays and maps std::stack info_stack; + bool null_as_default; }; class MsgPackRowInputFormat : public IRowInputFormat { public: - MsgPackRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_); + MsgPackRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_, const FormatSettings & settings); String getName() const override { return "MagPackRowInputFormat"; } void resetParser() override; void setReadBuffer(ReadBuffer & in_) override; private: - MsgPackRowInputFormat(const Block & header_, std::unique_ptr buf_, Params params_); + MsgPackRowInputFormat(const Block & header_, std::unique_ptr buf_, Params params_, const FormatSettings & settings); bool readRow(MutableColumns & columns, RowReadExtension & ext) override; diff --git a/src/Processors/Formats/Impl/NativeFormat.cpp b/src/Processors/Formats/Impl/NativeFormat.cpp index 959b86ec051..3c1a2bd5965 100644 --- a/src/Processors/Formats/Impl/NativeFormat.cpp +++ b/src/Processors/Formats/Impl/NativeFormat.cpp @@ -17,7 +17,13 @@ class NativeInputFormat final : public IInputFormat public: NativeInputFormat(ReadBuffer & buf, const Block & header_, const FormatSettings & settings) : IInputFormat(header_, buf) - , reader(std::make_unique(buf, header_, 0, settings.skip_unknown_fields)) + , reader(std::make_unique( + buf, + header_, + 0, + settings.skip_unknown_fields, + settings.null_as_default, + settings.defaults_for_omitted_fields ? &block_missing_values : nullptr)) , header(header_) {} String getName() const override { return "Native"; } @@ -30,6 +36,7 @@ public: Chunk generate() override { + block_missing_values.clear(); auto block = reader->read(); if (!block) return {}; @@ -47,9 +54,12 @@ public: IInputFormat::setReadBuffer(in_); } + const BlockMissingValues & getMissingValues() const override { return block_missing_values; } + private: std::unique_ptr reader; Block header; + BlockMissingValues block_missing_values; }; class NativeOutputFormat final : public IOutputFormat diff --git a/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp b/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp index 2e45d817506..03f056e22b3 100644 --- a/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp @@ -67,12 +67,10 @@ Chunk ORCBlockInputFormat::generate() ++stripe_current; Chunk res; - arrow_column_to_ch_column->arrowTableToCHChunk(res, table, num_rows); /// If defaults_for_omitted_fields is true, calculate the default values from default expression for omitted fields. /// Otherwise fill the missing columns with zero values of its type. - if (format_settings.defaults_for_omitted_fields) - for (const auto & column_idx : missing_columns) - block_missing_values.setBits(column_idx, res.getNumRows()); + BlockMissingValues * block_missing_values_ptr = format_settings.defaults_for_omitted_fields ? &block_missing_values : nullptr; + arrow_column_to_ch_column->arrowTableToCHChunk(res, table, num_rows, block_missing_values_ptr); return res; } @@ -128,8 +126,8 @@ void ORCBlockInputFormat::prepareReader() "ORC", format_settings.orc.import_nested, format_settings.orc.allow_missing_columns, + format_settings.null_as_default, format_settings.orc.case_insensitive_column_matching); - missing_columns = arrow_column_to_ch_column->getMissingColumns(*schema); ArrowFieldIndexUtil field_util( format_settings.orc.case_insensitive_column_matching, diff --git a/src/Processors/Formats/Impl/ORCBlockInputFormat.h b/src/Processors/Formats/Impl/ORCBlockInputFormat.h index bc2abe41cc1..3d8bc781278 100644 --- a/src/Processors/Formats/Impl/ORCBlockInputFormat.h +++ b/src/Processors/Formats/Impl/ORCBlockInputFormat.h @@ -49,7 +49,6 @@ private: // indices of columns to read from ORC file std::vector include_indices; - std::vector missing_columns; BlockMissingValues block_missing_values; const FormatSettings format_settings; diff --git a/src/Processors/Formats/Impl/ParquetBlockInputFormat.cpp b/src/Processors/Formats/Impl/ParquetBlockInputFormat.cpp index d2ec3c02eed..fca097d8ea7 100644 --- a/src/Processors/Formats/Impl/ParquetBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/ParquetBlockInputFormat.cpp @@ -71,7 +71,10 @@ Chunk ParquetBlockInputFormat::generate() if (*batch) { auto tmp_table = arrow::Table::FromRecordBatches({*batch}); - arrow_column_to_ch_column->arrowTableToCHChunk(res, *tmp_table, (*tmp_table)->num_rows()); + /// If defaults_for_omitted_fields is true, calculate the default values from default expression for omitted fields. + /// Otherwise fill the missing columns with zero values of its type. + BlockMissingValues * block_missing_values_ptr = format_settings.defaults_for_omitted_fields ? &block_missing_values : nullptr; + arrow_column_to_ch_column->arrowTableToCHChunk(res, *tmp_table, (*tmp_table)->num_rows(), block_missing_values_ptr); } else { @@ -80,12 +83,6 @@ Chunk ParquetBlockInputFormat::generate() return {}; } - /// If defaults_for_omitted_fields is true, calculate the default values from default expression for omitted fields. - /// Otherwise fill the missing columns with zero values of its type. - if (format_settings.defaults_for_omitted_fields) - for (const auto & column_idx : missing_columns) - block_missing_values.setBits(column_idx, res.getNumRows()); - return res; } @@ -133,8 +130,8 @@ void ParquetBlockInputFormat::prepareReader() "Parquet", format_settings.parquet.import_nested, format_settings.parquet.allow_missing_columns, + format_settings.null_as_default, format_settings.parquet.case_insensitive_column_matching); - missing_columns = arrow_column_to_ch_column->getMissingColumns(*schema); ArrowFieldIndexUtil field_util( format_settings.parquet.case_insensitive_column_matching, diff --git a/src/Processors/Formats/Impl/ParquetBlockInputFormat.h b/src/Processors/Formats/Impl/ParquetBlockInputFormat.h index 37878a94dd9..afc46939c79 100644 --- a/src/Processors/Formats/Impl/ParquetBlockInputFormat.h +++ b/src/Processors/Formats/Impl/ParquetBlockInputFormat.h @@ -42,7 +42,6 @@ private: // indices of columns to read from Parquet file std::vector column_indices; std::unique_ptr arrow_column_to_ch_column; - std::vector missing_columns; BlockMissingValues block_missing_values; const FormatSettings format_settings; const std::unordered_set & skip_row_groups; diff --git a/tests/queries/0_stateless/02561_null_as_default_more_formats.reference b/tests/queries/0_stateless/02561_null_as_default_more_formats.reference new file mode 100644 index 00000000000..f5d4f41efe8 --- /dev/null +++ b/tests/queries/0_stateless/02561_null_as_default_more_formats.reference @@ -0,0 +1,36 @@ +Parquet +1 +0 0 0 +0 0 +0 0 0 +42 0 42 +Arrow +1 +0 0 0 +0 0 +0 0 0 +42 0 42 +ORC +1 +0 0 0 +0 0 +0 0 0 +42 0 42 +Avro +1 +0 0 0 +0 0 +0 0 0 +42 0 42 +MsgPack +1 +0 0 0 +0 0 +0 0 0 +42 0 42 +Native +1 +0 0 0 +0 0 +0 0 0 +42 0 42 diff --git a/tests/queries/0_stateless/02561_null_as_default_more_formats.sh b/tests/queries/0_stateless/02561_null_as_default_more_formats.sh new file mode 100755 index 00000000000..eacd8e964a6 --- /dev/null +++ b/tests/queries/0_stateless/02561_null_as_default_more_formats.sh @@ -0,0 +1,21 @@ +#!/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_CLIENT -q "drop table if exists test" +$CLICKHOUSE_CLIENT -q "create table test (x UInt64 default 42, y UInt64, z LowCardinality(String) default '42') engine=Memory"; +for format in Parquet Arrow ORC Avro MsgPack Native +do + echo $format + $CLICKHOUSE_CLIENT -q "select number % 2 ? NULL : number as x, x as y, CAST(number % 2 ? NULL : toString(number), 'LowCardinality(Nullable(String))') as z from numbers(2) format $format" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_null_as_default=0 format $format" 2>&1 | grep "Exception" -c + $CLICKHOUSE_CLIENT -q "select number % 2 ? NULL : number as x, x as y, CAST(number % 2 ? NULL : toString(number), 'LowCardinality(Nullable(String))') as z from numbers(2) format $format settings output_format_arrow_low_cardinality_as_dictionary=1" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_null_as_default=1, input_format_defaults_for_omitted_fields=0 format $format" + $CLICKHOUSE_CLIENT -q "select * from test" + $CLICKHOUSE_CLIENT -q "truncate table test" + $CLICKHOUSE_CLIENT -q "select number % 2 ? NULL : number as x, x as y, CAST(number % 2 ? NULL : toString(number), 'LowCardinality(Nullable(String))') as z from numbers(2) format $format settings output_format_arrow_low_cardinality_as_dictionary=1" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_null_as_default=1, input_format_defaults_for_omitted_fields=1 format $format" + $CLICKHOUSE_CLIENT -q "select * from test" + $CLICKHOUSE_CLIENT -q "truncate table test" +done + diff --git a/tests/queries/0_stateless/02562_native_null_on_missing_columns.reference b/tests/queries/0_stateless/02562_native_null_on_missing_columns.reference new file mode 100644 index 00000000000..e072efc3352 --- /dev/null +++ b/tests/queries/0_stateless/02562_native_null_on_missing_columns.reference @@ -0,0 +1,4 @@ +0 0 +1 0 +0 42 +1 42 diff --git a/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh b/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh new file mode 100755 index 00000000000..c3d174d77e8 --- /dev/null +++ b/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT -q "drop table if exists test" +$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=Memory" + +$CLICKHOUSE_CLIENT -q "select number as x from numbers(2) format Native" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_defaults_for_omitted_fields=0 format Native" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "truncate table test" + +$CLICKHOUSE_CLIENT -q "select number as x from numbers(2) format Native" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_defaults_for_omitted_fields=1 format Native" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "truncate table test" From 04cf144edc9abba1f9e61ff4f4a949140f8f51e7 Mon Sep 17 00:00:00 2001 From: avogar Date: Fri, 10 Feb 2023 17:20:51 +0000 Subject: [PATCH 034/229] Fix TSKV, update docs --- .../operations/settings/settings-formats.md | 11 +++++--- .../Formats/Impl/TSKVRowInputFormat.cpp | 5 +++- .../02562_native_null_on_missing_columns.sh | 16 ------------ ...tskv_default_for_omitted_fields.reference} | 4 +-- ..._native_tskv_default_for_omitted_fields.sh | 25 +++++++++++++++++++ 5 files changed, 38 insertions(+), 23 deletions(-) delete mode 100755 tests/queries/0_stateless/02562_native_null_on_missing_columns.sh rename tests/queries/0_stateless/{02562_native_null_on_missing_columns.reference => 02562_native_tskv_default_for_omitted_fields.reference} (50%) create mode 100755 tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.sh diff --git a/docs/en/operations/settings/settings-formats.md b/docs/en/operations/settings/settings-formats.md index fd727704710..dda97696de5 100644 --- a/docs/en/operations/settings/settings-formats.md +++ b/docs/en/operations/settings/settings-formats.md @@ -15,11 +15,12 @@ When writing data, ClickHouse throws an exception if input data contain columns Supported formats: -- [JSONEachRow](../../interfaces/formats.md/#jsoneachrow) +- [JSONEachRow](../../interfaces/formats.md/#jsoneachrow) (and other JSON formats) +- [BSONEachRow](../../interfaces/formats.md/#bsoneachrow) (and other JSON formats) - [TSKV](../../interfaces/formats.md/#tskv) - All formats with suffixes WithNames/WithNamesAndTypes -- [JSONColumns](../../interfaces/formats.md/#jsoncolumns) - [MySQLDump](../../interfaces/formats.md/#mysqldump) +- [Native](../../interfaces/formats.md/#native) Possible values: @@ -78,7 +79,7 @@ Default value: 1. ## input_format_defaults_for_omitted_fields {#input_format_defaults_for_omitted_fields} -When performing `INSERT` queries, replace omitted input column values with default values of the respective columns. This option only applies to [JSONEachRow](../../interfaces/formats.md/#jsoneachrow), [CSV](../../interfaces/formats.md/#csv), [TabSeparated](../../interfaces/formats.md/#tabseparated) formats and formats with `WithNames`/`WithNamesAndTypes` suffixes. +When performing `INSERT` queries, replace omitted input column values with default values of the respective columns. This option applies to [JSONEachRow](../../interfaces/formats.md/#jsoneachrow) (and other JSON formats), [CSV](../../interfaces/formats.md/#csv), [TabSeparated](../../interfaces/formats.md/#tabseparated), [TSKV](../../interfaces/formats.md/#tskv), [Parquet](../../interfaces/formats.md/#parquet), [Arrow](../../interfaces/formats.md/#arrow), [Avro](../../interfaces/formats.md/#avro), [ORC](../../interfaces/formats.md/#orc), [Native](../../interfaces/formats.md/#native) formats and formats with `WithNames`/`WithNamesAndTypes` suffixes. :::note When this option is enabled, extended table metadata are sent from server to client. It consumes additional computing resources on the server and can reduce performance. @@ -96,7 +97,9 @@ Default value: 1. Enables or disables the initialization of [NULL](../../sql-reference/syntax.md/#null-literal) fields with [default values](../../sql-reference/statements/create/table.md/#create-default-values), if data type of these fields is not [nullable](../../sql-reference/data-types/nullable.md/#data_type-nullable). If column type is not nullable and this setting is disabled, then inserting `NULL` causes an exception. If column type is nullable, then `NULL` values are inserted as is, regardless of this setting. -This setting is applicable to [INSERT ... VALUES](../../sql-reference/statements/insert-into.md) queries for text input formats. +This setting is applicable for most input formats. + +For complex default expressions `input_format_defaults_for_omitted_fields` must be enabled too. Possible values: diff --git a/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp b/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp index bf6d0ab88d2..23a8589bd0a 100644 --- a/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp @@ -193,7 +193,10 @@ bool TSKVRowInputFormat::readRow(MutableColumns & columns, RowReadExtension & ex header.getByPosition(i).type->insertDefaultInto(*columns[i]); /// return info about defaults set - ext.read_columns = read_columns; + if (format_settings.defaults_for_omitted_fields) + ext.read_columns = read_columns; + else + ext.read_columns.assign(num_columns, true); return true; } diff --git a/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh b/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh deleted file mode 100755 index c3d174d77e8..00000000000 --- a/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -# shellcheck source=../shell_config.sh -. "$CUR_DIR"/../shell_config.sh - -$CLICKHOUSE_CLIENT -q "drop table if exists test" -$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=Memory" - -$CLICKHOUSE_CLIENT -q "select number as x from numbers(2) format Native" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_defaults_for_omitted_fields=0 format Native" -$CLICKHOUSE_CLIENT -q "select * from test" -$CLICKHOUSE_CLIENT -q "truncate table test" - -$CLICKHOUSE_CLIENT -q "select number as x from numbers(2) format Native" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_defaults_for_omitted_fields=1 format Native" -$CLICKHOUSE_CLIENT -q "select * from test" -$CLICKHOUSE_CLIENT -q "truncate table test" diff --git a/tests/queries/0_stateless/02562_native_null_on_missing_columns.reference b/tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.reference similarity index 50% rename from tests/queries/0_stateless/02562_native_null_on_missing_columns.reference rename to tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.reference index e072efc3352..17197fa3563 100644 --- a/tests/queries/0_stateless/02562_native_null_on_missing_columns.reference +++ b/tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.reference @@ -1,4 +1,4 @@ -0 0 1 0 -0 42 +1 42 +1 0 1 42 diff --git a/tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.sh b/tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.sh new file mode 100755 index 00000000000..a08c948705d --- /dev/null +++ b/tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT -q "drop table if exists test" +$CLICKHOUSE_CLIENT -q "insert into function file(02562_data.native) select 1::UInt64 as x settings engine_file_truncate_on_insert=1" +$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=File(Native, '02562_data.native') settings input_format_defaults_for_omitted_fields=0" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "drop table test" + +$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=File(Native, '02562_data.native') settings input_format_defaults_for_omitted_fields=1" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "drop table test" + +$CLICKHOUSE_CLIENT -q "insert into function file(02562_data.tskv) select 1::UInt64 as x settings engine_file_truncate_on_insert=1" +$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=File(TSKV, '02562_data.tskv') settings input_format_defaults_for_omitted_fields=0" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "drop table test" + +$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=File(TSKV, '02562_data.tskv') settings input_format_defaults_for_omitted_fields=1" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "drop table test" + From c68ef743b0a073bce75ddd649f1b85cc7411cf95 Mon Sep 17 00:00:00 2001 From: alesapin Date: Sat, 11 Feb 2023 12:55:05 +0100 Subject: [PATCH 035/229] Fix flaky test --- tests/queries/0_stateless/02555_davengers_rename_chain.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.sh b/tests/queries/0_stateless/02555_davengers_rename_chain.sh index f4af5e091ad..b23f8085fd7 100755 --- a/tests/queries/0_stateless/02555_davengers_rename_chain.sh +++ b/tests/queries/0_stateless/02555_davengers_rename_chain.sh @@ -27,8 +27,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata' AND database='${CLICKHOUSE_DATABASE}'") - if [[ $result == *"a TO a1"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SHOW CREATE TABLE wrong_metadata") + if [[ $result == *"\`a1\` UInt64"* ]]; then break; fi sleep 0.1 @@ -98,8 +98,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata_compact' AND database='${CLICKHOUSE_DATABASE}'") - if [[ $result == *"a TO a1"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SHOW CREATE TABLE wrong_metadata_compact") + if [[ $result == *"\`a1\` UInt64"* ]]; then break; fi sleep 0.1 From d5f413304dfe2b9807c6bbef530f803c14e86bdf Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 13 Feb 2023 13:28:09 +0100 Subject: [PATCH 036/229] Store null map size into a variable --- src/Columns/ColumnNullable.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Columns/ColumnNullable.cpp b/src/Columns/ColumnNullable.cpp index 99d377f10eb..139323f9770 100644 --- a/src/Columns/ColumnNullable.cpp +++ b/src/Columns/ColumnNullable.cpp @@ -786,16 +786,17 @@ ColumnPtr ColumnNullable::getNestedColumnWithDefaultOnNull() const auto res = nested_column->cloneEmpty(); const auto & null_map_data = getNullMapData(); size_t start = 0; + size_t end = null_map->size(); while (start < nested_column->size()) { size_t next_null_index = start; - while (next_null_index < null_map->size() && !null_map_data[next_null_index]) + while (next_null_index < end && !null_map_data[next_null_index]) ++next_null_index; if (next_null_index != start) res->insertRangeFrom(*nested_column, start, next_null_index - start); - if (next_null_index < null_map->size()) + if (next_null_index < end) res->insertDefault(); start = next_null_index + 1; From d67e7e47f5025744e4fdc291952303e646634684 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 13 Feb 2023 13:28:46 +0100 Subject: [PATCH 037/229] Update src/Core/Settings.h --- src/Core/Settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 481929d915f..7fec350f208 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -751,7 +751,7 @@ class IColumn; M(Bool, input_format_csv_empty_as_default, true, "Treat empty fields in CSV input as default values.", 0) \ M(Bool, input_format_tsv_empty_as_default, false, "Treat empty fields in TSV input as default values.", 0) \ M(Bool, input_format_tsv_enum_as_number, false, "Treat inserted enum values in TSV formats as enum indices.", 0) \ - M(Bool, input_format_null_as_default, true, "For most input formats initialize null fields with default values if data type of this field is not nullable", 0) \ + M(Bool, input_format_null_as_default, true, "Initialize null fields with default values if the data type of this field is not nullable and it is supported by the input format", 0) \ M(Bool, input_format_arrow_import_nested, false, "Allow to insert array of structs into Nested table in Arrow input format.", 0) \ M(Bool, input_format_arrow_case_insensitive_column_matching, false, "Ignore case when matching Arrow columns with CH columns.", 0) \ M(Bool, input_format_orc_import_nested, false, "Allow to insert array of structs into Nested table in ORC input format.", 0) \ From e9e6d735ef12a3276b03c1aa4ca2fc9c087c1fea Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 13 Feb 2023 16:44:54 +0000 Subject: [PATCH 038/229] add query_id header in all queries --- src/Interpreters/executeQuery.cpp | 14 +++++++++++--- src/Interpreters/executeQuery.h | 10 +++++++++- src/Server/HTTPHandler.cpp | 18 +++++++++++++----- src/Server/MySQLHandler.cpp | 4 ++-- .../02564_query_id_header.reference | 5 +++++ .../0_stateless/02564_query_id_header.sh | 13 +++++++++++++ 6 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 tests/queries/0_stateless/02564_query_id_header.reference create mode 100755 tests/queries/0_stateless/02564_query_id_header.sh diff --git a/src/Interpreters/executeQuery.cpp b/src/Interpreters/executeQuery.cpp index 06d92116adc..6fd3528ec65 100644 --- a/src/Interpreters/executeQuery.cpp +++ b/src/Interpreters/executeQuery.cpp @@ -1249,6 +1249,12 @@ void executeQuery( std::tie(ast, streams) = executeQueryImpl(begin, end, context, false, QueryProcessingStage::Complete, &istr); auto & pipeline = streams.pipeline; + QueryResultDetails result_details + { + .query_id = context->getClientInfo().current_query_id, + .timezone = DateLUT::instance().getTimeZone(), + }; + std::unique_ptr compressed_buffer; try { @@ -1307,9 +1313,8 @@ void executeQuery( out->onProgress(progress); }); - if (set_result_details) - set_result_details( - context->getClientInfo().current_query_id, out->getContentType(), format_name, DateLUT::instance().getTimeZone()); + result_details.content_type = out->getContentType(); + result_details.format = format_name; pipeline.complete(std::move(out)); } @@ -1318,6 +1323,9 @@ void executeQuery( pipeline.setProgressCallback(context->getProgressCallback()); } + if (set_result_details) + set_result_details(result_details); + if (pipeline.initialized()) { CompletedPipelineExecutor executor(pipeline); diff --git a/src/Interpreters/executeQuery.h b/src/Interpreters/executeQuery.h index 9c561d8b88c..93152cc1de6 100644 --- a/src/Interpreters/executeQuery.h +++ b/src/Interpreters/executeQuery.h @@ -11,7 +11,15 @@ namespace DB class ReadBuffer; class WriteBuffer; -using SetResultDetailsFunc = std::function; +struct QueryResultDetails +{ + String query_id; + std::optional content_type; + std::optional format; + std::optional timezone; +}; + +using SetResultDetailsFunc = std::function; /// Parse and execute a query. void executeQuery( diff --git a/src/Server/HTTPHandler.cpp b/src/Server/HTTPHandler.cpp index 29bfa8065ba..1e249b77079 100644 --- a/src/Server/HTTPHandler.cpp +++ b/src/Server/HTTPHandler.cpp @@ -819,12 +819,20 @@ void HTTPHandler::processQuery( customizeContext(request, context); executeQuery(*in, *used_output.out_maybe_delayed_and_compressed, /* allow_into_outfile = */ false, context, - [&response, this] (const String & current_query_id, const String & content_type, const String & format, const String & timezone) + [&response, this] (const QueryResultDetails & details) { - response.setContentType(content_type_override.value_or(content_type)); - response.add("X-ClickHouse-Query-Id", current_query_id); - response.add("X-ClickHouse-Format", format); - response.add("X-ClickHouse-Timezone", timezone); + response.add("X-ClickHouse-Query-Id", details.query_id); + + if (content_type_override) + response.setContentType(*content_type_override); + else if (details.content_type) + response.setContentType(*details.content_type); + + if (details.format) + response.add("X-ClickHouse-Format", *details.format); + + if (details.timezone) + response.add("X-ClickHouse-Timezone", *details.timezone); } ); diff --git a/src/Server/MySQLHandler.cpp b/src/Server/MySQLHandler.cpp index 3715d658730..e3467d664e7 100644 --- a/src/Server/MySQLHandler.cpp +++ b/src/Server/MySQLHandler.cpp @@ -352,9 +352,9 @@ void MySQLHandler::comQuery(ReadBuffer & payload) format_settings.mysql_wire.max_packet_size = max_packet_size; format_settings.mysql_wire.sequence_id = &sequence_id; - auto set_result_details = [&with_output](const String &, const String &, const String &format, const String &) + auto set_result_details = [&with_output](const QueryResultDetails & details) { - if (format != "MySQLWire") + if (details.format && *details.format != "MySQLWire") throw Exception(ErrorCodes::UNSUPPORTED_METHOD, "MySQL protocol does not support custom output formats"); with_output = true; }; diff --git a/tests/queries/0_stateless/02564_query_id_header.reference b/tests/queries/0_stateless/02564_query_id_header.reference new file mode 100644 index 00000000000..655f9fa9eaa --- /dev/null +++ b/tests/queries/0_stateless/02564_query_id_header.reference @@ -0,0 +1,5 @@ +X-ClickHouse-Query-Id +X-ClickHouse-Query-Id +X-ClickHouse-Query-Id +X-ClickHouse-Query-Id +X-ClickHouse-Query-Id diff --git a/tests/queries/0_stateless/02564_query_id_header.sh b/tests/queries/0_stateless/02564_query_id_header.sh new file mode 100755 index 00000000000..440182cd243 --- /dev/null +++ b/tests/queries/0_stateless/02564_query_id_header.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS t_query_id_header" + +${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "CREATE TABLE t_query_id_header (a UInt64) ENGINE = Memory" 2>&1 | grep -o "X-ClickHouse-Query-Id" +${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "INSERT INTO t_query_id_header VALUES (1)" 2>&1 | grep -o "X-ClickHouse-Query-Id" +${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "EXISTS TABLE t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" +${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "SELECT * FROM t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" +${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "DROP TABLE t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" From a4d9688775d8d39b8b462b4711d649776d4dcfaa Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 13 Feb 2023 19:35:07 +0100 Subject: [PATCH 039/229] fix 'Directory not empty after drop' with zero copy replication --- src/Storages/MergeTree/MergeTreeData.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index c3c4cd3082d..3c260484d95 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1852,11 +1852,11 @@ void MergeTreeData::stopOutdatedDataPartsLoadingTask() /// (Only files on the first level of nesting are considered). static bool isOldPartDirectory(const DiskPtr & disk, const String & directory_path, time_t threshold) { - if (!disk->isDirectory(directory_path) || disk->getLastModified(directory_path).epochTime() >= threshold) + if (!disk->isDirectory(directory_path) || disk->getLastModified(directory_path).epochTime() > threshold) return false; for (auto it = disk->iterateDirectory(directory_path); it->isValid(); it->next()) - if (disk->getLastModified(it->path()).epochTime() >= threshold) + if (disk->getLastModified(it->path()).epochTime() > threshold) return false; return true; From e712fbecb2ee7aeb2dd1bd1913218d3ba21d6252 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 14 Feb 2023 00:45:28 +0100 Subject: [PATCH 040/229] fix race between drop and create --- src/Interpreters/DatabaseCatalog.cpp | 1 + src/Interpreters/DatabaseCatalog.h | 4 +- src/Storages/StorageReplicatedMergeTree.cpp | 41 ++++++++++---- src/Storages/StorageReplicatedMergeTree.h | 2 +- tests/integration/helpers/cluster.py | 4 +- .../configs/config.d/storage_conf.xml | 2 + .../test.py | 56 +++++++++++++++++++ 7 files changed, 96 insertions(+), 14 deletions(-) diff --git a/src/Interpreters/DatabaseCatalog.cpp b/src/Interpreters/DatabaseCatalog.cpp index ad5d9d4d325..c3ac7122362 100644 --- a/src/Interpreters/DatabaseCatalog.cpp +++ b/src/Interpreters/DatabaseCatalog.cpp @@ -147,6 +147,7 @@ void DatabaseCatalog::initializeAndLoadTemporaryDatabase() unused_dir_hide_timeout_sec = getContext()->getConfigRef().getInt64("database_catalog_unused_dir_hide_timeout_sec", unused_dir_hide_timeout_sec); unused_dir_rm_timeout_sec = getContext()->getConfigRef().getInt64("database_catalog_unused_dir_rm_timeout_sec", unused_dir_rm_timeout_sec); unused_dir_cleanup_period_sec = getContext()->getConfigRef().getInt64("database_catalog_unused_dir_cleanup_period_sec", unused_dir_cleanup_period_sec); + drop_error_cooldown_sec = getContext()->getConfigRef().getInt64("database_catalog_drop_error_cooldown_sec", drop_error_cooldown_sec); auto db_for_temporary_and_external_tables = std::make_shared(TEMPORARY_DATABASE, getContext()); attachDatabase(TEMPORARY_DATABASE, db_for_temporary_and_external_tables); diff --git a/src/Interpreters/DatabaseCatalog.h b/src/Interpreters/DatabaseCatalog.h index ba3625626da..c37029d8b6b 100644 --- a/src/Interpreters/DatabaseCatalog.h +++ b/src/Interpreters/DatabaseCatalog.h @@ -278,7 +278,6 @@ private: bool maybeRemoveDirectory(const String & disk_name, const DiskPtr & disk, const String & unused_dir); static constexpr size_t reschedule_time_ms = 100; - static constexpr time_t drop_error_cooldown_sec = 5; mutable std::mutex databases_mutex; @@ -325,6 +324,9 @@ private: time_t unused_dir_rm_timeout_sec = default_unused_dir_rm_timeout_sec; static constexpr time_t default_unused_dir_cleanup_period_sec = 24 * 60 * 60; /// 1 day time_t unused_dir_cleanup_period_sec = default_unused_dir_cleanup_period_sec; + + static constexpr time_t default_drop_error_cooldown_sec = 5; + time_t drop_error_cooldown_sec = default_drop_error_cooldown_sec; }; /// This class is useful when creating a table or database. diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index d3590657a5c..c92d4efab0d 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -903,17 +903,16 @@ void StorageReplicatedMergeTree::drop() /// in this case, has_metadata_in_zookeeper = false, and we also permit to drop the table. bool maybe_has_metadata_in_zookeeper = !has_metadata_in_zookeeper.has_value() || *has_metadata_in_zookeeper; + zkutil::ZooKeeperPtr zookeeper; if (maybe_has_metadata_in_zookeeper) { /// Table can be shut down, restarting thread is not active /// and calling StorageReplicatedMergeTree::getZooKeeper()/getAuxiliaryZooKeeper() won't suffice. - zkutil::ZooKeeperPtr zookeeper = getZooKeeperIfTableShutDown(); + zookeeper = getZooKeeperIfTableShutDown(); /// If probably there is metadata in ZooKeeper, we don't allow to drop the table. if (!zookeeper) throw Exception(ErrorCodes::TABLE_IS_READ_ONLY, "Can't drop readonly replicated table (need to drop data in ZooKeeper as well)"); - - dropReplica(zookeeper, zookeeper_path, replica_name, log, getSettings()); } /// Wait for loading of all outdated parts because @@ -927,10 +926,13 @@ void StorageReplicatedMergeTree::drop() } dropAllData(); + + if (maybe_has_metadata_in_zookeeper) + dropReplica(zookeeper, zookeeper_path, replica_name, log, getSettings(), &has_metadata_in_zookeeper); } void StorageReplicatedMergeTree::dropReplica(zkutil::ZooKeeperPtr zookeeper, const String & zookeeper_path, const String & replica, - Poco::Logger * logger, MergeTreeSettingsPtr table_settings) + Poco::Logger * logger, MergeTreeSettingsPtr table_settings, std::optional * has_metadata_out) { if (zookeeper->expired()) throw Exception(ErrorCodes::TABLE_WAS_NOT_DROPPED, "Table was not dropped because ZooKeeper session has expired."); @@ -988,12 +990,16 @@ void StorageReplicatedMergeTree::dropReplica(zkutil::ZooKeeperPtr zookeeper, con Coordination::errorMessage(code), remote_replica_path); /// And finally remove everything else recursively - zookeeper->tryRemoveRecursive(remote_replica_path); - } + /// It may left some garbage if replica_path subtree is concurrently modified + zookeeper->tryRemoveChildrenRecursive(remote_replica_path); - /// It may left some garbage if replica_path subtree are concurrently modified - if (zookeeper->exists(remote_replica_path)) - LOG_ERROR(logger, "Replica was not completely removed from ZooKeeper, {} still exists and may contain some garbage.", remote_replica_path); + /// Update has_metadata_in_zookeeper to avoid retries. Otherwise we can accidentally remove metadata of a new table on retries + if (has_metadata_out) + *has_metadata_out = false; + + if (zookeeper->tryRemove(remote_replica_path) != Coordination::Error::ZOK) + LOG_ERROR(logger, "Replica was not completely removed from ZooKeeper, {} still exists and may contain some garbage.", remote_replica_path); + } /// Check that `zookeeper_path` exists: it could have been deleted by another replica after execution of previous line. Strings replicas; @@ -8152,6 +8158,12 @@ StorageReplicatedMergeTree::unlockSharedData(const IMergeTreeDataPart & part, co auto shared_id = getTableSharedID(); if (shared_id == toString(UUIDHelpers::Nil)) { + if (zookeeper->exists(zookeeper_path)) + { + LOG_WARNING(log, "Not removing shared data for part {} because replica does not have metadata in ZooKeeper, " + "but table path exist and other replicas may exist. It may leave some garbage on S3", part.name); + return std::make_pair(false, NameSet{}); + } LOG_TRACE(log, "Part {} blobs can be removed, because table {} completely dropped", part.name, getStorageID().getNameForLogs()); return std::make_pair(true, NameSet{}); } @@ -8177,9 +8189,18 @@ StorageReplicatedMergeTree::unlockSharedData(const IMergeTreeDataPart & part, co return std::make_pair(true, NameSet{}); } - /// If table was completely dropped (no meta in zookeeper) we can safely remove parts if (has_metadata_in_zookeeper.has_value() && !has_metadata_in_zookeeper) + { + if (zookeeper->exists(zookeeper_path)) + { + LOG_WARNING(log, "Not removing shared data for part {} because replica does not have metadata in ZooKeeper, " + "but table path exist and other replicas may exist. It may leave some garbage on S3", part.name); + return std::make_pair(false, NameSet{}); + } + + /// If table was completely dropped (no meta in zookeeper) we can safely remove parts return std::make_pair(true, NameSet{}); + } /// We remove parts during table shutdown. If exception happen, restarting thread will be already turned /// off and nobody will reconnect our zookeeper connection. In this case we use zookeeper connection from diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index 0d3856ce672..75e5629b627 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -228,7 +228,7 @@ public: /** Remove a specific replica from zookeeper. */ static void dropReplica(zkutil::ZooKeeperPtr zookeeper, const String & zookeeper_path, const String & replica, - Poco::Logger * logger, MergeTreeSettingsPtr table_settings = nullptr); + Poco::Logger * logger, MergeTreeSettingsPtr table_settings = nullptr, std::optional * has_metadata_out = nullptr); /// Removes table from ZooKeeper after the last replica was dropped static bool removeTableNodesFromZooKeeper(zkutil::ZooKeeperPtr zookeeper, const String & zookeeper_path, diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 0073c25e5d5..0c6b60ee166 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -3263,7 +3263,7 @@ class ClickHouseInstance: sleep_time=0.5, check_callback=lambda x: True, ): - logging.debug(f"Executing query {sql} on {self.name}") + #logging.debug(f"Executing query {sql} on {self.name}") result = None for i in range(retry_count): try: @@ -3282,7 +3282,7 @@ class ClickHouseInstance: return result time.sleep(sleep_time) except Exception as ex: - logging.debug("Retry {} got exception {}".format(i + 1, ex)) + #logging.debug("Retry {} got exception {}".format(i + 1, ex)) time.sleep(sleep_time) if result is not None: diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml index bd59694f65a..15239041478 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml @@ -53,4 +53,6 @@ 0 + 3 + 0 diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index 60a1b9b9746..434957de53f 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -1,9 +1,11 @@ import logging import random import string +import time import pytest from helpers.cluster import ClickHouseCluster +from helpers.network import PartitionManager logging.getLogger().setLevel(logging.INFO) logging.getLogger().addHandler(logging.StreamHandler()) @@ -127,3 +129,57 @@ def test_insert_select_replicated(cluster, min_rows_for_wide_part, files_per_par assert len( list(minio.list_objects(cluster.minio_bucket, "data/", recursive=True)) ) == (3 * FILES_OVERHEAD) + (files_per_part * 3) + + +def test_drop_table(cluster): + node = list(cluster.instances.values())[0] + node2 = list(cluster.instances.values())[1] + node.query( + "create table test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') order by n partition by n % 99 settings storage_policy='s3'" + ) + node2.query( + "create table test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '2') order by n partition by n % 99 settings storage_policy='s3'" + ) + node.query("insert into test_drop_table select * from numbers(1000)") + node2.query("system sync replica test_drop_table") + + with PartitionManager() as pm: + pm._add_rule( + { + "probability": 0.01, + "destination": node.ip_address, + "source_port": 2181, + "action": "REJECT --reject-with tcp-reset", + } + ) + pm._add_rule( + { + "probability": 0.01, + "source": node.ip_address, + "destination_port": 2181, + "action": "REJECT --reject-with tcp-reset", + } + ) + node.query("drop table test_drop_table") + for i in range(0, 100): + node.query_and_get_answer_with_error( + "create table if not exists test_drop_table (n int) " + "engine=ReplicatedMergeTree('/test/drop_table', '1') " + "order by n partition by n % 99 settings storage_policy='s3'" + ) + time.sleep(0.2) + + replicas = node.query_with_retry( + "select name from system.zookeeper where path='/test/drop_table/replicas'" + ) + if "1" in replicas and "test_drop_table" not in node.query("show tables"): + node2.query("system drop replica '1' from table test_drop_table") + node.query( + "create table test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " + "order by n partition by n % 99 settings storage_policy='s3'" + ) + node.query("system sync replica test_drop_table", settings={"receive_timeout": 60}) + node2.query("drop table test_drop_table") + assert "1000\t499500\n" == node.query( + "select count(n), sum(n) from test_drop_table" + ) From dce8dca4e5f6d0391fd52e287aea42a09645bfa7 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 13 Feb 2023 23:53:15 +0000 Subject: [PATCH 041/229] Automatic style fix --- tests/integration/helpers/cluster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 0c6b60ee166..f6445f07482 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -3263,7 +3263,7 @@ class ClickHouseInstance: sleep_time=0.5, check_callback=lambda x: True, ): - #logging.debug(f"Executing query {sql} on {self.name}") + # logging.debug(f"Executing query {sql} on {self.name}") result = None for i in range(retry_count): try: @@ -3282,7 +3282,7 @@ class ClickHouseInstance: return result time.sleep(sleep_time) except Exception as ex: - #logging.debug("Retry {} got exception {}".format(i + 1, ex)) + # logging.debug("Retry {} got exception {}".format(i + 1, ex)) time.sleep(sleep_time) if result is not None: From bbf94a2664be9b5ba3bdc5f10ccde150cae08f34 Mon Sep 17 00:00:00 2001 From: lzydmxy <13126752315@163.com> Date: Tue, 14 Feb 2023 17:29:44 +0800 Subject: [PATCH 042/229] Apply `ALTER TABLE table_name ON CLUSTER cluster MOVE PARTITION|PART partition_expr TO DISK|VOLUME 'disk_name'` to all replicas. Because `ALTER TABLE t MOVE` is not replicated. --- src/Interpreters/DDLWorker.cpp | 7 +++---- src/Parsers/ASTAlterQuery.cpp | 18 ++++++++++++++++++ src/Parsers/ASTAlterQuery.h | 2 ++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Interpreters/DDLWorker.cpp b/src/Interpreters/DDLWorker.cpp index 0f91212e6a9..54d1067de31 100644 --- a/src/Interpreters/DDLWorker.cpp +++ b/src/Interpreters/DDLWorker.cpp @@ -691,10 +691,9 @@ bool DDLWorker::taskShouldBeExecutedOnLeader(const ASTPtr & ast_ddl, const Stora if (auto * alter = ast_ddl->as()) { // Setting alters should be executed on all replicas - if (alter->isSettingsAlter()) - return false; - - if (alter->isFreezeAlter()) + if (alter->isSettingsAlter() || + alter->isFreezeAlter() || + alter->isMovePartitionToDiskOrVolumeAlter()) return false; } diff --git a/src/Parsers/ASTAlterQuery.cpp b/src/Parsers/ASTAlterQuery.cpp index 5d347446d37..426b63a9d28 100644 --- a/src/Parsers/ASTAlterQuery.cpp +++ b/src/Parsers/ASTAlterQuery.cpp @@ -533,6 +533,24 @@ bool ASTAlterQuery::isDropPartitionAlter() const return isOneCommandTypeOnly(ASTAlterCommand::DROP_PARTITION) || isOneCommandTypeOnly(ASTAlterCommand::DROP_DETACHED_PARTITION); } +bool ASTAlterQuery::isMovePartitionToDiskOrVolumeAlter() const +{ + if (command_list) + { + if (command_list->children.empty()) + return false; + for (const auto & child : command_list->children) + { + const auto & command = child->as(); + if (command.type != ASTAlterCommand::MOVE_PARTITION || + (command.move_destination_type != DataDestinationType::DISK && command.move_destination_type != DataDestinationType::VOLUME)) + return false; + } + return true; + } + return false; +} + /** Get the text that identifies this element. */ String ASTAlterQuery::getID(char delim) const diff --git a/src/Parsers/ASTAlterQuery.h b/src/Parsers/ASTAlterQuery.h index 4a8c9c14ea9..2a48f5bbd9e 100644 --- a/src/Parsers/ASTAlterQuery.h +++ b/src/Parsers/ASTAlterQuery.h @@ -239,6 +239,8 @@ public: bool isDropPartitionAlter() const; + bool isMovePartitionToDiskOrVolumeAlter() const; + String getID(char) const override; ASTPtr clone() const override; From d4518281bbefe830c2d964f4dc35133c536d5850 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 14 Feb 2023 17:03:04 +0000 Subject: [PATCH 043/229] test more headers --- .../02564_query_id_header.reference | 27 +++++++++++++++---- .../0_stateless/02564_query_id_header.sh | 27 +++++++++++++++---- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/tests/queries/0_stateless/02564_query_id_header.reference b/tests/queries/0_stateless/02564_query_id_header.reference index 655f9fa9eaa..413e8929f36 100644 --- a/tests/queries/0_stateless/02564_query_id_header.reference +++ b/tests/queries/0_stateless/02564_query_id_header.reference @@ -1,5 +1,22 @@ -X-ClickHouse-Query-Id -X-ClickHouse-Query-Id -X-ClickHouse-Query-Id -X-ClickHouse-Query-Id -X-ClickHouse-Query-Id +CREATE TABLE t_query_id_header (a UInt64) ENGINE = Memory +< Content-Type: text/plain; charset=UTF-8 +< X-ClickHouse-Query-Id: query_id +< X-ClickHouse-Timezone: timezone +INSERT INTO t_query_id_header VALUES (1) +< Content-Type: text/plain; charset=UTF-8 +< X-ClickHouse-Query-Id: query_id +< X-ClickHouse-Timezone: timezone +EXISTS TABLE t_query_id_header +< Content-Type: text/tab-separated-values; charset=UTF-8 +< X-ClickHouse-Format: TabSeparated +< X-ClickHouse-Query-Id: query_id +< X-ClickHouse-Timezone: timezone +SELECT * FROM t_query_id_header +< Content-Type: text/tab-separated-values; charset=UTF-8 +< X-ClickHouse-Format: TabSeparated +< X-ClickHouse-Query-Id: query_id +< X-ClickHouse-Timezone: timezone +DROP TABLE t_query_id_header +< Content-Type: text/plain; charset=UTF-8 +< X-ClickHouse-Query-Id: query_id +< X-ClickHouse-Timezone: timezone diff --git a/tests/queries/0_stateless/02564_query_id_header.sh b/tests/queries/0_stateless/02564_query_id_header.sh index 440182cd243..67ddbcfcc46 100755 --- a/tests/queries/0_stateless/02564_query_id_header.sh +++ b/tests/queries/0_stateless/02564_query_id_header.sh @@ -4,10 +4,27 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh +CLICKHOUSE_TIMEZONE_ESCAPED=$($CLICKHOUSE_CLIENT --query="SELECT timezone()" | sed 's/[]\/$*.^+:()[]/\\&/g') + +function run_and_check_headers() +{ + query=$1 + query_id="${CLICKHOUSE_DATABASE}_${RANDOM}" + + echo "$query" + + ${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}&query_id=$query_id" -d "$1" 2>&1 \ + | grep -e "< X-ClickHouse-Query-Id" -e "< X-ClickHouse-Timezone" -e "< X-ClickHouse-Format" -e "< Content-Type" \ + | sed "s/$CLICKHOUSE_TIMEZONE_ESCAPED/timezone/" \ + | sed "s/$query_id/query_id/" \ + | sed "s/\r$//" \ + | sort +} + ${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS t_query_id_header" -${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "CREATE TABLE t_query_id_header (a UInt64) ENGINE = Memory" 2>&1 | grep -o "X-ClickHouse-Query-Id" -${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "INSERT INTO t_query_id_header VALUES (1)" 2>&1 | grep -o "X-ClickHouse-Query-Id" -${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "EXISTS TABLE t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" -${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "SELECT * FROM t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" -${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "DROP TABLE t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" +run_and_check_headers "CREATE TABLE t_query_id_header (a UInt64) ENGINE = Memory" +run_and_check_headers "INSERT INTO t_query_id_header VALUES (1)" +run_and_check_headers "EXISTS TABLE t_query_id_header" +run_and_check_headers "SELECT * FROM t_query_id_header" +run_and_check_headers "DROP TABLE t_query_id_header" From ff5023049b0b8ea72c1ee911e6cd4c46a01e0242 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 15 Feb 2023 02:17:02 +0300 Subject: [PATCH 044/229] Update test.py --- .../test_replicated_merge_tree_s3_zero_copy/test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index 434957de53f..158d559d4b9 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -174,10 +174,10 @@ def test_drop_table(cluster): ) if "1" in replicas and "test_drop_table" not in node.query("show tables"): node2.query("system drop replica '1' from table test_drop_table") - node.query( - "create table test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " - "order by n partition by n % 99 settings storage_policy='s3'" - ) + node.query( + "create table if not exists test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " + "order by n partition by n % 99 settings storage_policy='s3'" + ) node.query("system sync replica test_drop_table", settings={"receive_timeout": 60}) node2.query("drop table test_drop_table") assert "1000\t499500\n" == node.query( From 9fd2226c4c5395baf8f9fdea0eced26959ba7a62 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 15 Feb 2023 15:13:04 +0100 Subject: [PATCH 045/229] Update NativeReader.h --- src/Formats/NativeReader.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Formats/NativeReader.h b/src/Formats/NativeReader.h index 64d3e4d6df0..2d8b16e06eb 100644 --- a/src/Formats/NativeReader.h +++ b/src/Formats/NativeReader.h @@ -49,9 +49,9 @@ private: ReadBuffer & istr; Block header; UInt64 server_revision; - bool skip_unknown_columns; - bool null_as_default; - BlockMissingValues * block_missing_values; + bool skip_unknown_columns = false; + bool null_as_default = false; + BlockMissingValues * block_missing_values = nullptr; bool use_index = false; IndexForNativeFormat::Blocks::const_iterator index_block_it; From 885f14311c2ba31bb40e714a47e1f088bb204429 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 15 Feb 2023 16:37:44 +0100 Subject: [PATCH 046/229] fix --- src/Storages/StorageReplicatedMergeTree.cpp | 4 ++++ .../configs/config.d/storage_conf.xml | 7 +++++++ .../test_replicated_merge_tree_s3_zero_copy/test.py | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index c92d4efab0d..4a8f38da349 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -928,7 +928,11 @@ void StorageReplicatedMergeTree::drop() dropAllData(); if (maybe_has_metadata_in_zookeeper) + { + /// Session could expire, get it again + zookeeper = getZooKeeperIfTableShutDown(); dropReplica(zookeeper, zookeeper_path, replica_name, log, getSettings(), &has_metadata_in_zookeeper); + } } void StorageReplicatedMergeTree::dropReplica(zkutil::ZooKeeperPtr zookeeper, const String & zookeeper_path, const String & replica, diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml index 15239041478..801e2072f17 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml @@ -55,4 +55,11 @@ 3 0 + + + system + zookeeper_log
+ 5000 + 5000 +
diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index 158d559d4b9..f955b0cb754 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -178,7 +178,7 @@ def test_drop_table(cluster): "create table if not exists test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " "order by n partition by n % 99 settings storage_policy='s3'" ) - node.query("system sync replica test_drop_table", settings={"receive_timeout": 60}) + node.query_with_retry("system sync replica test_drop_table", settings={"receive_timeout": 10}, retry_count=5) node2.query("drop table test_drop_table") assert "1000\t499500\n" == node.query( "select count(n), sum(n) from test_drop_table" From 69463f852e6b770d69357ca7ac57f2f5b6ec3a66 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 15 Feb 2023 15:44:19 +0000 Subject: [PATCH 047/229] Automatic style fix --- .../test_replicated_merge_tree_s3_zero_copy/test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index f955b0cb754..fb93dc8aa7c 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -178,7 +178,11 @@ def test_drop_table(cluster): "create table if not exists test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " "order by n partition by n % 99 settings storage_policy='s3'" ) - node.query_with_retry("system sync replica test_drop_table", settings={"receive_timeout": 10}, retry_count=5) + node.query_with_retry( + "system sync replica test_drop_table", + settings={"receive_timeout": 10}, + retry_count=5, + ) node2.query("drop table test_drop_table") assert "1000\t499500\n" == node.query( "select count(n), sum(n) from test_drop_table" From ac62356477c87a84142609afa2e315a1ce81f4cd Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 16 Feb 2023 13:05:13 +0100 Subject: [PATCH 048/229] fix --- .../configs/config.d/storage_conf.xml | 7 ------- .../test.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml index 801e2072f17..15239041478 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml @@ -55,11 +55,4 @@ 3 0 - - - system - zookeeper_log
- 5000 - 5000 -
diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index fb93dc8aa7c..f0bc12e3125 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -160,7 +160,11 @@ def test_drop_table(cluster): "action": "REJECT --reject-with tcp-reset", } ) + + # Will drop in background with retries node.query("drop table test_drop_table") + + # It should not be possible to create a replica with the same path until the previous one is completely dropped for i in range(0, 100): node.query_and_get_answer_with_error( "create table if not exists test_drop_table (n int) " @@ -169,11 +173,21 @@ def test_drop_table(cluster): ) time.sleep(0.2) + # Wait for drop to actually finish + node.wait_for_log_line( + "Removing metadata /var/lib/clickhouse/metadata_dropped/default.test_drop_table", + timeout=60, + look_behind_lines=1000000, + ) + + # It could leave some leftovers, remove them replicas = node.query_with_retry( "select name from system.zookeeper where path='/test/drop_table/replicas'" ) if "1" in replicas and "test_drop_table" not in node.query("show tables"): node2.query("system drop replica '1' from table test_drop_table") + + # Just in case table was not created due to connection errors node.query( "create table if not exists test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " "order by n partition by n % 99 settings storage_policy='s3'" From 3a635e428a48b93d4b99f52a263f8ad6a58d71e0 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Thu, 16 Feb 2023 11:03:41 -0800 Subject: [PATCH 049/229] Fix xxhash endian issue for s390x --- src/Functions/FunctionsHashing.h | 106 ++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/src/Functions/FunctionsHashing.h b/src/Functions/FunctionsHashing.h index 69c3a299eea..6bf1a2db3ac 100644 --- a/src/Functions/FunctionsHashing.h +++ b/src/Functions/FunctionsHashing.h @@ -55,7 +55,7 @@ #include #include #include - +#include namespace DB { @@ -1025,17 +1025,58 @@ private: if constexpr (Impl::use_int_hash_for_pods) { - if constexpr (std::is_same_v) - h = IntHash64Impl::apply(bit_cast(vec_from[i])); + if constexpr (std::endian::native == std::endian::little) + { + if constexpr (std::is_same_v) + h = IntHash64Impl::apply(bit_cast(vec_from[i])); + else + h = IntHash32Impl::apply(bit_cast(vec_from[i])); + } else - h = IntHash32Impl::apply(bit_cast(vec_from[i])); + { + if constexpr (std::is_same_v) + { + UInt64 v = bit_cast(vec_from[i]); + v = __builtin_bswap64(v); + h = IntHash64Impl::apply(v); + } + else + { + UInt32 v = bit_cast(vec_from[i]); + v = __builtin_bswap32(v); + h = IntHash32Impl::apply(v); + } + } } else { - if (std::is_same_v) - h = JavaHashImpl::apply(vec_from[i]); + if constexpr (std::endian::native == std::endian::little) + { + if (std::is_same_v) + h = JavaHashImpl::apply(vec_from[i]); + else + h = apply(key, reinterpret_cast(&vec_from[i]), sizeof(vec_from[i])); + } else - h = apply(key, reinterpret_cast(&vec_from[i]), sizeof(vec_from[i])); + { + if (std::is_same_v) + h = JavaHashImpl::apply(vec_from[i]); + else + { + if constexpr (std::is_same_v) + { + UInt64 v = bit_cast(vec_from[i]); + v = __builtin_bswap64(v); + h = apply(key, reinterpret_cast(&v), sizeof(vec_from[i])); + } + else + { + UInt32 v = bit_cast(vec_from[i]); + v = __builtin_bswap32(v); + h = apply(key, reinterpret_cast(&v), sizeof(vec_from[i])); + } + } + } } if constexpr (first) @@ -1048,11 +1089,28 @@ private: { auto value = col_from_const->template getValue(); ToType hash; - if constexpr (std::is_same_v) - hash = IntHash64Impl::apply(bit_cast(value)); + if constexpr (std::endian::native == std::endian::little) + { + if constexpr (std::is_same_v) + hash = IntHash64Impl::apply(bit_cast(value)); + else + hash = IntHash32Impl::apply(bit_cast(value)); + } else - hash = IntHash32Impl::apply(bit_cast(value)); - + { + if constexpr (std::is_same_v) + { + UInt64 v = bit_cast(value); + v = __builtin_bswap64(v); + hash = IntHash64Impl::apply(v); + } + else + { + UInt32 v = bit_cast(value); + v = __builtin_bswap32(v); + hash = IntHash32Impl::apply(bit_cast(v)); + } + } size_t size = vec_to.size(); if constexpr (first) { @@ -1080,8 +1138,17 @@ private: size_t size = vec_from.size(); for (size_t i = 0; i < size; ++i) { - ToType h = apply(key, reinterpret_cast(&vec_from[i]), sizeof(vec_from[i])); - + ToType h; + if constexpr (std::endian::native == std::endian::little) + { + h = apply(key, reinterpret_cast(&vec_from[i]), sizeof(vec_from[i])); + } + else + { + char tmp_buffer[sizeof(vec_from[i])]; + reverseMemcpy(tmp_buffer, &vec_from[i], sizeof(vec_from[i])); + h = apply(key, reinterpret_cast(tmp_buffer), sizeof(vec_from[i])); + } if constexpr (first) vec_to[i] = h; else @@ -1092,8 +1159,17 @@ private: { auto value = col_from_const->template getValue(); - ToType h = apply(key, reinterpret_cast(&value), sizeof(value)); - + ToType h; + if constexpr (std::endian::native == std::endian::little) + { + h = apply(key, reinterpret_cast(&value), sizeof(value)); + } + else + { + char tmp_buffer[sizeof(value)]; + reverseMemcpy(tmp_buffer, &value, sizeof(value)); + h = apply(key, reinterpret_cast(tmp_buffer), sizeof(value)); + } size_t size = vec_to.size(); if constexpr (first) { From 1a352b9021895c63946544301bff1011b7ba8ba0 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 17 Feb 2023 10:21:52 +0100 Subject: [PATCH 050/229] Fix flakiness of test_backup_restore_on_cluster/test_disallow_concurrency The problem is that if you will run first test_concurrent_restores_on_different_node and after test_concurrent_restores_on_same_node, like in [1], the first one will leave the RESTORE and the second will fail. [1]: https://s3.amazonaws.com/clickhouse-test-reports/45282/5af2967f65c3293b277872a002cb5d570200c008/integration_tests__asan__[3/6].html Signed-off-by: Azat Khuzhin --- .../test_disallow_concurrency.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py b/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py index 43e7682ec1d..d689e44e4d4 100644 --- a/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py +++ b/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py @@ -229,3 +229,9 @@ def test_concurrent_restores_on_different_node(): assert "Concurrent restores not supported" in nodes[1].query_and_get_error( f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name}" ) + + assert_eq_with_retry( + nodes[0], + f"SELECT status FROM system.backups WHERE status == 'RESTORED'", + "RESTORED", + ) From fb35a6852ea55a693250449dcc5a07fb30ac4c2e Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 18 Feb 2023 11:07:04 +0100 Subject: [PATCH 051/229] More flakiness fiexes for test_backup_restore_on_cluster/test_disallow_concurrency Signed-off-by: Azat Khuzhin --- .../test_disallow_concurrency.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py b/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py index d689e44e4d4..ad16725e266 100644 --- a/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py +++ b/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py @@ -186,10 +186,14 @@ def test_concurrent_restores_on_same_node(): ) nodes[0].query(f"DROP TABLE tbl ON CLUSTER 'cluster' NO DELAY") - nodes[0].query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name} ASYNC") + restore_id = ( + nodes[0] + .query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name} ASYNC") + .split("\t")[0] + ) assert_eq_with_retry( nodes[0], - f"SELECT status FROM system.backups WHERE status == 'RESTORING'", + f"SELECT status FROM system.backups WHERE status == 'RESTORING' AND id == '{restore_id}'", "RESTORING", ) assert "Concurrent restores not supported" in nodes[0].query_and_get_error( @@ -220,7 +224,11 @@ def test_concurrent_restores_on_different_node(): ) nodes[0].query(f"DROP TABLE tbl ON CLUSTER 'cluster' NO DELAY") - nodes[0].query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name} ASYNC") + restore_id = ( + nodes[0] + .query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name} ASYNC") + .split("\t")[0] + ) assert_eq_with_retry( nodes[0], f"SELECT status FROM system.backups WHERE status == 'RESTORING'", @@ -232,6 +240,6 @@ def test_concurrent_restores_on_different_node(): assert_eq_with_retry( nodes[0], - f"SELECT status FROM system.backups WHERE status == 'RESTORED'", + f"SELECT status FROM system.backups WHERE status == 'RESTORED' AND id == '{restore_id}'", "RESTORED", ) From ccf87a6afd1fa93ea6ad8fe454ee27edd75c73ef Mon Sep 17 00:00:00 2001 From: lzydmxy <13126752315@163.com> Date: Mon, 20 Feb 2023 16:52:55 +0800 Subject: [PATCH 052/229] add integration test for move partition to disk on cluster --- .../configs/config.d/cluster.xml | 17 ++++ .../config.d/storage_configuration.xml | 28 ++++++ .../test.py | 94 +++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/cluster.xml create mode 100644 tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/storage_configuration.xml create mode 100644 tests/integration/test_move_partition_to_disk_on_cluster/test.py diff --git a/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/cluster.xml b/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/cluster.xml new file mode 100644 index 00000000000..2316050b629 --- /dev/null +++ b/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/cluster.xml @@ -0,0 +1,17 @@ + + + + + true + + node1 + 9000 + + + node2 + 9000 + + + + + \ No newline at end of file diff --git a/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/storage_configuration.xml b/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/storage_configuration.xml new file mode 100644 index 00000000000..3289186c175 --- /dev/null +++ b/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/storage_configuration.xml @@ -0,0 +1,28 @@ + + + + + + /jbod1/ + + + /external/ + + + + + + +
+ jbod1 +
+ + external + +
+
+
+ +
+ +
diff --git a/tests/integration/test_move_partition_to_disk_on_cluster/test.py b/tests/integration/test_move_partition_to_disk_on_cluster/test.py new file mode 100644 index 00000000000..fe8606bd549 --- /dev/null +++ b/tests/integration/test_move_partition_to_disk_on_cluster/test.py @@ -0,0 +1,94 @@ +import pytest +from helpers.client import QueryRuntimeException +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__) + +node1 = cluster.add_instance( + "node1", + main_configs=[ + "configs/config.d/storage_configuration.xml", + "configs/config.d/cluster.xml", + ], + with_zookeeper=True, + stay_alive=True, + tmpfs=["/jbod1:size=10M", "/external:size=10M"], + macros={"shard": 0, "replica": 1}, +) + +node2 = cluster.add_instance( + "node2", + main_configs=[ + "configs/config.d/storage_configuration.xml", + "configs/config.d/cluster.xml", + ], + with_zookeeper=True, + stay_alive=True, + tmpfs=["/jbod1:size=10M", "/external:size=10M"], + macros={"shard": 0, "replica": 2}, +) + + +@pytest.fixture(scope="module") +def start_cluster(): + try: + cluster.start() + yield cluster + + finally: + cluster.shutdown() + + +def test_move_partition_to_disk_on_cluster(start_cluster): + for node in [node1, node2]: + node.query( + sql="CREATE TABLE test_local_table" + "(x UInt64) " + "ENGINE=ReplicatedMergeTree('/clickhouse/tables/test_local_table', '{replica}') " + "ORDER BY tuple()" + "SETTINGS storage_policy = 'jbod_with_external';", + ) + + node1.query("INSERT INTO test_local_table VALUES (0)") + node1.query("SYSTEM SYNC REPLICA test_local_table", timeout=30) + + try: + node1.query( + sql="ALTER TABLE test_local_table ON CLUSTER 'test_cluster' MOVE PARTITION tuple() TO DISK 'jbod1';", + ) + except QueryRuntimeException: + pass + + for node in [node1, node2]: + assert ( + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','jbod1')" + ) + + node1.query( + sql="ALTER TABLE test_local_table ON CLUSTER 'test_cluster' MOVE PARTITION tuple() TO DISK 'external';", + ) + + for node in [node1, node2]: + assert ( + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','external')" + ) + + node1.query( + sql="ALTER TABLE test_local_table ON CLUSTER 'test_cluster' MOVE PARTITION tuple() TO VOLUME 'main';", + ) + + for node in [node1, node2]: + assert ( + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','jbod1')" + ) + + From 9d16205c8a532f5b2600fe9049c99372535a6dc9 Mon Sep 17 00:00:00 2001 From: kssenii Date: Mon, 20 Feb 2023 15:29:09 +0100 Subject: [PATCH 053/229] Load named collections on first access --- programs/local/LocalServer.cpp | 5 -- programs/server/Server.cpp | 4 -- .../NamedCollections/NamedCollectionUtils.cpp | 51 ++++++++++++++++++- .../NamedCollections/NamedCollectionUtils.h | 2 + src/Storages/NamedCollectionsHelpers.cpp | 2 + .../System/StorageSystemNamedCollections.cpp | 2 + 6 files changed, 55 insertions(+), 11 deletions(-) diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index 133d629bbb1..23ba65fed44 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -131,8 +130,6 @@ void LocalServer::initialize(Poco::Util::Application & self) config().getUInt("max_io_thread_pool_size", 100), config().getUInt("max_io_thread_pool_free_size", 0), config().getUInt("io_thread_pool_queue_size", 10000)); - - NamedCollectionUtils::loadFromConfig(config()); } @@ -224,8 +221,6 @@ void LocalServer::tryInitPath() global_context->setUserFilesPath(""); // user's files are everywhere - NamedCollectionUtils::loadFromSQL(global_context); - /// top_level_domains_lists const std::string & top_level_domains_path = config().getString("top_level_domains_path", path + "top_level_domains/"); if (!top_level_domains_path.empty()) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index b97b48d9c68..10710d61b84 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -770,8 +770,6 @@ try config().getUInt("max_io_thread_pool_free_size", 0), config().getUInt("io_thread_pool_queue_size", 10000)); - NamedCollectionUtils::loadFromConfig(config()); - /// Initialize global local cache for remote filesystem. if (config().has("local_cache_for_remote_fs")) { @@ -1177,8 +1175,6 @@ try SensitiveDataMasker::setInstance(std::make_unique(config(), "query_masking_rules")); } - NamedCollectionUtils::loadFromSQL(global_context); - auto main_config_reloader = std::make_unique( config_path, include_from_path, diff --git a/src/Common/NamedCollections/NamedCollectionUtils.cpp b/src/Common/NamedCollections/NamedCollectionUtils.cpp index 8beaa38210e..6ec09fb8a77 100644 --- a/src/Common/NamedCollections/NamedCollectionUtils.cpp +++ b/src/Common/NamedCollections/NamedCollectionUtils.cpp @@ -32,6 +32,9 @@ namespace ErrorCodes namespace NamedCollectionUtils { +static std::atomic is_loaded_from_config = false; +static std::atomic is_loaded_from_sql = false; + class LoadFromConfig { private: @@ -329,10 +332,21 @@ std::unique_lock lockNamedCollectionsTransaction() return std::unique_lock(transaction_lock); } +void loadFromConfigUnlocked(const Poco::Util::AbstractConfiguration & config, std::unique_lock &) +{ + auto named_collections = LoadFromConfig(config).getAll(); + LOG_TRACE( + &Poco::Logger::get("NamedCollectionsUtils"), + "Loaded {} collections from config", named_collections.size()); + + NamedCollectionFactory::instance().add(std::move(named_collections)); + is_loaded_from_config = true; +} + void loadFromConfig(const Poco::Util::AbstractConfiguration & config) { auto lock = lockNamedCollectionsTransaction(); - NamedCollectionFactory::instance().add(LoadFromConfig(config).getAll()); + loadFromConfigUnlocked(config, lock); } void reloadFromConfig(const Poco::Util::AbstractConfiguration & config) @@ -342,17 +356,47 @@ void reloadFromConfig(const Poco::Util::AbstractConfiguration & config) auto & instance = NamedCollectionFactory::instance(); instance.removeById(SourceId::CONFIG); instance.add(collections); + is_loaded_from_config = true; +} + +void loadFromSQLUnlocked(ContextPtr context, std::unique_lock &) +{ + auto named_collections = LoadFromSQL(context).getAll(); + LOG_TRACE( + &Poco::Logger::get("NamedCollectionsUtils"), + "Loaded {} collections from SQL", named_collections.size()); + + NamedCollectionFactory::instance().add(std::move(named_collections)); + is_loaded_from_sql = true; } void loadFromSQL(ContextPtr context) { auto lock = lockNamedCollectionsTransaction(); - NamedCollectionFactory::instance().add(LoadFromSQL(context).getAll()); + loadFromSQLUnlocked(context, lock); +} + +void loadIfNotUnlocked(std::unique_lock & lock) +{ + auto global_context = Context::getGlobalContextInstance(); + if (!is_loaded_from_config) + loadFromConfigUnlocked(global_context->getConfigRef(), lock); + if (!is_loaded_from_sql) + loadFromSQLUnlocked(global_context, lock); +} + +void loadIfNot() +{ + if (is_loaded_from_sql && is_loaded_from_config) + return; + auto lock = lockNamedCollectionsTransaction(); + return loadIfNotUnlocked(lock); } void removeFromSQL(const std::string & collection_name, ContextPtr context) { auto lock = lockNamedCollectionsTransaction(); + loadIfNotUnlocked(lock); LoadFromSQL(context).remove(collection_name); NamedCollectionFactory::instance().remove(collection_name); } @@ -360,6 +404,7 @@ void removeFromSQL(const std::string & collection_name, ContextPtr context) void removeIfExistsFromSQL(const std::string & collection_name, ContextPtr context) { auto lock = lockNamedCollectionsTransaction(); + loadIfNotUnlocked(lock); LoadFromSQL(context).removeIfExists(collection_name); NamedCollectionFactory::instance().removeIfExists(collection_name); } @@ -367,12 +412,14 @@ void removeIfExistsFromSQL(const std::string & collection_name, ContextPtr conte void createFromSQL(const ASTCreateNamedCollectionQuery & query, ContextPtr context) { auto lock = lockNamedCollectionsTransaction(); + loadIfNotUnlocked(lock); NamedCollectionFactory::instance().add(query.collection_name, LoadFromSQL(context).create(query)); } void updateFromSQL(const ASTAlterNamedCollectionQuery & query, ContextPtr context) { auto lock = lockNamedCollectionsTransaction(); + loadIfNotUnlocked(lock); LoadFromSQL(context).update(query); auto collection = NamedCollectionFactory::instance().getMutable(query.collection_name); diff --git a/src/Common/NamedCollections/NamedCollectionUtils.h b/src/Common/NamedCollections/NamedCollectionUtils.h index 8befc9cac3c..c929abb5d74 100644 --- a/src/Common/NamedCollections/NamedCollectionUtils.h +++ b/src/Common/NamedCollections/NamedCollectionUtils.h @@ -35,6 +35,8 @@ void createFromSQL(const ASTCreateNamedCollectionQuery & query, ContextPtr conte /// Update definition of already existing collection from AST and update result in `context->getPath() / named_collections /`. void updateFromSQL(const ASTAlterNamedCollectionQuery & query, ContextPtr context); +void loadIfNot(); + } } diff --git a/src/Storages/NamedCollectionsHelpers.cpp b/src/Storages/NamedCollectionsHelpers.cpp index cefed555781..6c783beaecb 100644 --- a/src/Storages/NamedCollectionsHelpers.cpp +++ b/src/Storages/NamedCollectionsHelpers.cpp @@ -58,6 +58,8 @@ NamedCollectionPtr tryGetNamedCollectionWithOverrides(ASTs asts) if (asts.empty()) return nullptr; + NamedCollectionUtils::loadIfNot(); + auto collection = tryGetNamedCollectionFromASTs(asts); if (!collection) return nullptr; diff --git a/src/Storages/System/StorageSystemNamedCollections.cpp b/src/Storages/System/StorageSystemNamedCollections.cpp index bc1e3a45e6b..aa095f48179 100644 --- a/src/Storages/System/StorageSystemNamedCollections.cpp +++ b/src/Storages/System/StorageSystemNamedCollections.cpp @@ -31,6 +31,8 @@ void StorageSystemNamedCollections::fillData(MutableColumns & res_columns, Conte { context->checkAccess(AccessType::SHOW_NAMED_COLLECTIONS); + NamedCollectionUtils::loadIfNot(); + auto collections = NamedCollectionFactory::instance().getAll(); for (const auto & [name, collection] : collections) { From 2273acaa30548da72c31f39bb5f8adde06ce4a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 20 Feb 2023 16:47:25 +0100 Subject: [PATCH 054/229] Fix incorrect alias recursion in QueryNormalizer --- src/Interpreters/QueryNormalizer.cpp | 4 +++ ...2667_order_by_aggregation_result.reference | 4 +++ .../02667_order_by_aggregation_result.sql | 36 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 tests/queries/0_stateless/02667_order_by_aggregation_result.reference create mode 100644 tests/queries/0_stateless/02667_order_by_aggregation_result.sql diff --git a/src/Interpreters/QueryNormalizer.cpp b/src/Interpreters/QueryNormalizer.cpp index 4db61501d3d..df454aecac1 100644 --- a/src/Interpreters/QueryNormalizer.cpp +++ b/src/Interpreters/QueryNormalizer.cpp @@ -118,6 +118,8 @@ void QueryNormalizer::visit(ASTIdentifier & node, ASTPtr & ast, Data & data) alias_node->checkSize(data.settings.max_expanded_ast_elements); ast = alias_node->clone(); ast->setAlias(node_alias); + if (data.finished_asts.contains(alias_node)) + data.finished_asts[ast] = ast; } } else @@ -127,6 +129,8 @@ void QueryNormalizer::visit(ASTIdentifier & node, ASTPtr & ast, Data & data) auto alias_name = ast->getAliasOrColumnName(); ast = alias_node->clone(); ast->setAlias(alias_name); + if (data.finished_asts.contains(alias_node)) + data.finished_asts[ast] = ast; } } } diff --git a/tests/queries/0_stateless/02667_order_by_aggregation_result.reference b/tests/queries/0_stateless/02667_order_by_aggregation_result.reference new file mode 100644 index 00000000000..642fc2ed645 --- /dev/null +++ b/tests/queries/0_stateless/02667_order_by_aggregation_result.reference @@ -0,0 +1,4 @@ +0 0 +0 1 █████████████████████████████████████████████████▉ +1 2 0.5 +1 0.1 1.1 diff --git a/tests/queries/0_stateless/02667_order_by_aggregation_result.sql b/tests/queries/0_stateless/02667_order_by_aggregation_result.sql new file mode 100644 index 00000000000..277430888d7 --- /dev/null +++ b/tests/queries/0_stateless/02667_order_by_aggregation_result.sql @@ -0,0 +1,36 @@ +-- Github issues: +-- - https://github.com/ClickHouse/ClickHouse/issues/46268 +-- - https://github.com/ClickHouse/ClickHouse/issues/46273 + +-- Queries that the original PR (https://github.com/ClickHouse/ClickHouse/pull/42827) tried to fix +SELECT (number = 1) AND (number = 2) AS value, sum(value) OVER () FROM numbers(1) WHERE 1; +SELECT time, round(exp_smooth, 10), bar(exp_smooth, -9223372036854775807, 1048575, 50) AS bar FROM (SELECT 2 OR (number = 0) OR (number >= 1) AS value, number AS time, exponentialTimeDecayedSum(2147483646)(value, time) OVER (RANGE BETWEEN CURRENT ROW AND CURRENT ROW) AS exp_smooth FROM numbers(1) WHERE 10) WHERE 25; + +CREATE TABLE ttttttt +( + `timestamp` DateTime, + `col1` Float64, + `col2` Float64, + `col3` Float64 +) +ENGINE = MergeTree() +ORDER BY tuple(); + +INSERT INTO ttttttt VALUES ('2023-02-20 00:00:00', 1, 2, 3); + +-- Query that https://github.com/ClickHouse/ClickHouse/pull/42827 broke +SELECT + argMax(col1, timestamp) AS col1, + argMax(col2, timestamp) AS col2, + col1 / col2 AS final_col +FROM ttttttt +GROUP BY + col3 +ORDER BY final_col DESC; + +SELECT + argMax(col1, timestamp) AS col1, + col1 / 10 AS final_col, + final_col + 1 AS final_col2 +FROM ttttttt +GROUP BY col3; From 87a4fb025275d1837868952f7f47e0305023b230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 20 Feb 2023 19:08:54 +0100 Subject: [PATCH 055/229] Update aliases when clone happens --- src/Interpreters/QueryNormalizer.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Interpreters/QueryNormalizer.cpp b/src/Interpreters/QueryNormalizer.cpp index df454aecac1..56b81b3d224 100644 --- a/src/Interpreters/QueryNormalizer.cpp +++ b/src/Interpreters/QueryNormalizer.cpp @@ -118,8 +118,15 @@ void QueryNormalizer::visit(ASTIdentifier & node, ASTPtr & ast, Data & data) alias_node->checkSize(data.settings.max_expanded_ast_elements); ast = alias_node->clone(); ast->setAlias(node_alias); + + /// If the cloned AST was finished, this one should also be considered finished if (data.finished_asts.contains(alias_node)) data.finished_asts[ast] = ast; + + /// If we had an alias for node_alias, point it instead to the new node so we don't have to revisit it + /// on subsequent calls + if (auto existing_alias = data.aliases.find(node_alias); existing_alias != data.aliases.end()) + existing_alias->second = ast; } } else @@ -129,8 +136,15 @@ void QueryNormalizer::visit(ASTIdentifier & node, ASTPtr & ast, Data & data) auto alias_name = ast->getAliasOrColumnName(); ast = alias_node->clone(); ast->setAlias(alias_name); + + /// If the cloned AST was finished, this one should also be considered finished if (data.finished_asts.contains(alias_node)) data.finished_asts[ast] = ast; + + /// If we had an alias for node_alias, point it instead to the new node so we don't have to revisit it + /// on subsequent calls + if (auto existing_alias = data.aliases.find(node_alias); existing_alias != data.aliases.end()) + existing_alias->second = ast; } } } From 3c4c527bce41b7418de94f5c730478c1ed715938 Mon Sep 17 00:00:00 2001 From: Dmitry Novik Date: Mon, 20 Feb 2023 18:40:57 +0000 Subject: [PATCH 056/229] Fix MemoryTracker counters for async inserts --- src/Common/CurrentThread.cpp | 22 +++++++++++ src/Common/CurrentThread.h | 6 +++ src/Interpreters/AsynchronousInsertQueue.cpp | 6 ++- src/Interpreters/AsynchronousInsertQueue.h | 39 +++++++++++++++++++- 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/Common/CurrentThread.cpp b/src/Common/CurrentThread.cpp index e54b2c8abe4..95b316671a4 100644 --- a/src/Common/CurrentThread.cpp +++ b/src/Common/CurrentThread.cpp @@ -110,4 +110,26 @@ ThreadGroupStatusPtr CurrentThread::getGroup() return current_thread->getThreadGroup(); } +MemoryTracker * CurrentThread::getUserMemoryTracker() +{ + if (unlikely(!current_thread)) + return nullptr; + + if (auto group = current_thread->getThreadGroup()) + return group->memory_tracker.getParent(); + + return nullptr; +} + +void CurrentThread::flushUntrackedMemory() +{ + if (unlikely(!current_thread)) + return; + if (current_thread->untracked_memory == 0) + return; + + current_thread->memory_tracker.adjustWithUntrackedMemory(current_thread->untracked_memory); + current_thread->untracked_memory = 0; +} + } diff --git a/src/Common/CurrentThread.h b/src/Common/CurrentThread.h index cbe60365798..382ae5f6e77 100644 --- a/src/Common/CurrentThread.h +++ b/src/Common/CurrentThread.h @@ -40,6 +40,12 @@ public: /// Group to which belongs current thread static ThreadGroupStatusPtr getGroup(); + /// MemoryTracker for user that owns current thread if any + static MemoryTracker * getUserMemoryTracker(); + + /// Adjust counters in MemoryTracker hierarchy if untracked_memory is not 0. + static void flushUntrackedMemory(); + /// A logs queue used by TCPHandler to pass logs to a client static void attachInternalTextLogsQueue(const std::shared_ptr & logs_queue, LogsLevel client_logs_level); diff --git a/src/Interpreters/AsynchronousInsertQueue.cpp b/src/Interpreters/AsynchronousInsertQueue.cpp index fa3e9915e8f..bfd9988fd10 100644 --- a/src/Interpreters/AsynchronousInsertQueue.cpp +++ b/src/Interpreters/AsynchronousInsertQueue.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -102,9 +103,10 @@ bool AsynchronousInsertQueue::InsertQuery::operator==(const InsertQuery & other) return query_str == other.query_str && settings == other.settings; } -AsynchronousInsertQueue::InsertData::Entry::Entry(String && bytes_, String && query_id_) +AsynchronousInsertQueue::InsertData::Entry::Entry(String && bytes_, String && query_id_, MemoryTracker * user_memory_tracker_) : bytes(std::move(bytes_)) , query_id(std::move(query_id_)) + , user_memory_tracker(user_memory_tracker_) , create_time(std::chrono::system_clock::now()) { } @@ -209,7 +211,7 @@ std::future AsynchronousInsertQueue::push(ASTPtr query, ContextPtr query_c if (auto quota = query_context->getQuota()) quota->used(QuotaType::WRITTEN_BYTES, bytes.size()); - auto entry = std::make_shared(std::move(bytes), query_context->getCurrentQueryId()); + auto entry = std::make_shared(std::move(bytes), query_context->getCurrentQueryId(), CurrentThread::getUserMemoryTracker()); InsertQuery key{query, settings}; InsertDataPtr data_to_process; diff --git a/src/Interpreters/AsynchronousInsertQueue.h b/src/Interpreters/AsynchronousInsertQueue.h index ee1265673a6..b8c7d9d285b 100644 --- a/src/Interpreters/AsynchronousInsertQueue.h +++ b/src/Interpreters/AsynchronousInsertQueue.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -41,6 +42,31 @@ private: UInt128 calculateHash() const; }; + struct UserMemoryTrackerSwitcher + { + explicit UserMemoryTrackerSwitcher(MemoryTracker * new_tracker) + { + auto * thread_tracker = CurrentThread::getMemoryTracker(); + prev_untracked_memory = current_thread->untracked_memory; + prev_memory_tracker_parent = thread_tracker->getParent(); + + current_thread->untracked_memory = 0; + thread_tracker->setParent(new_tracker); + } + + ~UserMemoryTrackerSwitcher() + { + CurrentThread::flushUntrackedMemory(); + auto * thread_tracker = CurrentThread::getMemoryTracker(); + + current_thread->untracked_memory = prev_untracked_memory; + thread_tracker->setParent(prev_memory_tracker_parent); + } + + MemoryTracker * prev_memory_tracker_parent; + Int64 prev_untracked_memory; + }; + struct InsertData { struct Entry @@ -48,9 +74,10 @@ private: public: const String bytes; const String query_id; + MemoryTracker * const user_memory_tracker; const std::chrono::time_point create_time; - Entry(String && bytes_, String && query_id_); + Entry(String && bytes_, String && query_id_, MemoryTracker * user_memory_tracker_); void finish(std::exception_ptr exception_ = nullptr); std::future getFuture() { return promise.get_future(); } @@ -61,6 +88,16 @@ private: std::atomic_bool finished = false; }; + ~InsertData() + { + auto it = entries.begin(); + while (it != entries.end()) + { + UserMemoryTrackerSwitcher switcher((*it)->user_memory_tracker); + it = entries.erase(it); + } + } + using EntryPtr = std::shared_ptr; std::list entries; From 186a29a2aad4e05be56c4462e55ee3eb5a0ec360 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 20 Feb 2023 19:46:39 +0100 Subject: [PATCH 057/229] Resurrect processors_profile_log docs. --- docs/en/operations/settings/settings.md | 9 +++ .../system-tables/processors_profile_log.md | 74 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 docs/en/operations/system-tables/processors_profile_log.md diff --git a/docs/en/operations/settings/settings.md b/docs/en/operations/settings/settings.md index 1d1d8b32d1d..9b16188ad6d 100644 --- a/docs/en/operations/settings/settings.md +++ b/docs/en/operations/settings/settings.md @@ -851,6 +851,15 @@ Result: └─────────────┴───────────┘ ``` +## log_processors_profiles {#settings-log_processors_profiles} + +Write time that processor spent during execution/waiting for data to `system.processors_profile_log` table. + +See also: + +- [`system.processors_profile_log`](../../operations/system-tables/processors_profile_log.md#system-processors_profile_log) +- [`EXPLAIN PIPELINE`](../../sql-reference/statements/explain.md#explain-pipeline) + ## max_insert_block_size {#settings-max_insert_block_size} The size of blocks (in a count of rows) to form for insertion into a table. diff --git a/docs/en/operations/system-tables/processors_profile_log.md b/docs/en/operations/system-tables/processors_profile_log.md new file mode 100644 index 00000000000..269385deab6 --- /dev/null +++ b/docs/en/operations/system-tables/processors_profile_log.md @@ -0,0 +1,74 @@ +# system.processors_profile_log {#system-processors_profile_log} + +This table contains profiling on processors level (that you can find in [`EXPLAIN PIPELINE`](../../sql-reference/statements/explain.md#explain-pipeline)). + +Columns: + +- `event_date` ([Date](../../sql-reference/data-types/date.md)) — The date when the event happened. +- `event_time` ([DateTime64](../../sql-reference/data-types/datetime64.md)) — The date and time when the event happened. +- `id` ([UInt64](../../sql-reference/data-types/int-uint.md)) — ID of processor +- `parent_ids` ([Array(UInt64)](../../sql-reference/data-types/array.md)) — Parent processors IDs +- `query_id` ([String](../../sql-reference/data-types/string.md)) — ID of the query +- `name` ([LowCardinality(String)](../../sql-reference/data-types/lowcardinality.md)) — Name of the processor. +- `elapsed_us` ([UInt64](../../sql-reference/data-types/int-uint.md)) — Number of microseconds this processor was executed. +- `input_wait_elapsed_us` ([UInt64](../../sql-reference/data-types/int-uint.md)) — Number of microseconds this processor was waiting for data (from other processor). +- `output_wait_elapsed_us` ([UInt64](../../sql-reference/data-types/int-uint.md)) — Number of microseconds this processor was waiting because output port was full. +- `plan_step` ([UInt64](../../sql-reference/data-types/int-uint.md)) — ID of the query plan step which created this processor. The value is zero if the processor was not added from any step. +- `plan_group` ([UInt64](../../sql-reference/data-types/int-uint.md)) — Group of the processor if it was created by query plan step. A group is a logical partitioning of processors added from the same query plan step. Group is used only for beautifying the result of EXPLAIN PIPELINE result. +- `input_rows` ([UInt64](../../sql-reference/data-types/int-uint.md)) — The number of rows consumed by processor. +- `input_bytes` ([UInt64](../../sql-reference/data-types/int-uint.md)) — The number of bytes consumed by processor. +- `output_rows` ([UInt64](../../sql-reference/data-types/int-uint.md)) — The number of rows generated by processor. +- `output_bytes` ([UInt64](../../sql-reference/data-types/int-uint.md)) — The number of bytes generated by processor. +**Example** + +Query: + +``` sql +EXPLAIN PIPELINE +SELECT sleep(1) +┌─explain─────────────────────────┐ +│ (Expression) │ +│ ExpressionTransform │ +│ (SettingQuotaAndLimits) │ +│ (ReadFromStorage) │ +│ SourceFromSingleChunk 0 → 1 │ +└─────────────────────────────────┘ +SELECT sleep(1) +SETTINGS log_processors_profiles = 1 +Query id: feb5ed16-1c24-4227-aa54-78c02b3b27d4 +┌─sleep(1)─┐ +│ 0 │ +└──────────┘ +1 rows in set. Elapsed: 1.018 sec. +SELECT + name, + elapsed_us, + input_wait_elapsed_us, + output_wait_elapsed_us +FROM system.processors_profile_log +WHERE query_id = 'feb5ed16-1c24-4227-aa54-78c02b3b27d4' +ORDER BY name ASC +``` + +Result: + +``` text +┌─name────────────────────┬─elapsed_us─┬─input_wait_elapsed_us─┬─output_wait_elapsed_us─┐ +│ ExpressionTransform │ 1000497 │ 2823 │ 197 │ +│ LazyOutputFormat │ 36 │ 1002188 │ 0 │ +│ LimitsCheckingTransform │ 10 │ 1002994 │ 106 │ +│ NullSource │ 5 │ 1002074 │ 0 │ +│ NullSource │ 1 │ 1002084 │ 0 │ +│ SourceFromSingleChunk │ 45 │ 4736 │ 1000819 │ +└─────────────────────────┴────────────┴───────────────────────┴────────────────────────┘ +``` + +Here you can see: + +- `ExpressionTransform` was executing `sleep(1)` function, so it `work` will takes 1e6, and so `elapsed_us` > 1e6. +- `SourceFromSingleChunk` need to wait, because `ExpressionTransform` does not accept any data during execution of `sleep(1)`, so it will be in `PortFull` state for 1e6 us, and so `output_wait_elapsed_us` > 1e6. +- `LimitsCheckingTransform`/`NullSource`/`LazyOutputFormat` need to wait until `ExpressionTransform` will execute `sleep(1)` to process the result, so `input_wait_elapsed_us` > 1e6. + +**See Also** + +- [`EXPLAIN PIPELINE`](../../sql-reference/statements/explain.md#explain-pipeline) \ No newline at end of file From 7f487f03e75a769722c6c1115f8120e1176c4033 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 20 Feb 2023 19:24:15 +0000 Subject: [PATCH 058/229] Automatic style fix --- tests/ci/workflow_approve_rerun_lambda/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/workflow_approve_rerun_lambda/app.py b/tests/ci/workflow_approve_rerun_lambda/app.py index b563a9786c4..fb14dfd2258 100644 --- a/tests/ci/workflow_approve_rerun_lambda/app.py +++ b/tests/ci/workflow_approve_rerun_lambda/app.py @@ -123,7 +123,7 @@ TRUSTED_CONTRIBUTORS = { "BoloniniD", # Seasoned contributor, HSE "tonickkozlov", # Cloudflare "tylerhannan", # ClickHouse Employee - "myrrc", # Mike Kot, DoubleCloud + "myrrc", # Mike Kot, DoubleCloud ] } From a568704d63a5402de9af2852a06f5419afc3da38 Mon Sep 17 00:00:00 2001 From: avogar Date: Mon, 20 Feb 2023 20:43:28 +0000 Subject: [PATCH 059/229] Fix avro --- src/Processors/Formats/Impl/AvroRowInputFormat.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp index bedf3cc932e..c3ea1b5e23b 100644 --- a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp @@ -778,15 +778,14 @@ AvroDeserializer::AvroDeserializer(const Block & header, avro::ValidSchema schem void AvroDeserializer::deserializeRow(MutableColumns & columns, avro::Decoder & decoder, RowReadExtension & ext) const { + size_t row = columns[0]->size() + 1; ext.read_columns.assign(columns.size(), false); row_action.execute(columns, decoder, ext); for (size_t i = 0; i < ext.read_columns.size(); ++i) { /// Insert default in missing columns. - if (!column_found[i]) - { + if (columns[i]->size() != row) columns[i]->insertDefault(); - } } } From 16a9ac9118cfd8eb8ed579fd12becb2eff8858f0 Mon Sep 17 00:00:00 2001 From: Dmitry Novik Date: Tue, 21 Feb 2023 00:51:39 +0000 Subject: [PATCH 060/229] Try to add test --- .../test_async_insert_memory/__init__.py | 0 .../test_async_insert_memory/test.py | 35 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/integration/test_async_insert_memory/__init__.py create mode 100644 tests/integration/test_async_insert_memory/test.py diff --git a/tests/integration/test_async_insert_memory/__init__.py b/tests/integration/test_async_insert_memory/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_async_insert_memory/test.py b/tests/integration/test_async_insert_memory/test.py new file mode 100644 index 00000000000..cf7b59c8d4a --- /dev/null +++ b/tests/integration/test_async_insert_memory/test.py @@ -0,0 +1,35 @@ +import pytest + +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__) + +node = cluster.add_instance("node") + + +@pytest.fixture(scope="module", autouse=True) +def start_cluster(): + try: + cluster.start() + yield cluster + finally: + cluster.shutdown() + + +def test_memory_usage(): + node.query( + "CREATE TABLE async_table(data Array(UInt64)) ENGINE=MergeTree() ORDER BY data" + ) + + response = node.get_query_request( + "SELECT groupArray(number + sleepEachRow(0.0001)) FROM numbers(1000000) SETTINGS max_memory_usage_for_user={}".format(30 * (2 ** 23)) + ) + + INSERT_QUERY = "INSERT INTO async_table SETTINGS async_insert=1, wait_for_async_insert=1 VALUES ({})" + for i in range(10): + node.query(INSERT_QUERY.format([i in range(i * 5000000, (i + 1) * 5000000)])) + + _, err = response.get_answer_and_error() + assert err == "", "Query failed" + + node.query("DROP TABLE async_table") From 573ce5040315ebd8dd82ef90796498280052dc57 Mon Sep 17 00:00:00 2001 From: Dmitry Novik Date: Tue, 21 Feb 2023 01:43:01 +0000 Subject: [PATCH 061/229] Fix test --- .../integration/test_async_insert_memory/test.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_async_insert_memory/test.py b/tests/integration/test_async_insert_memory/test.py index cf7b59c8d4a..93e3e771c71 100644 --- a/tests/integration/test_async_insert_memory/test.py +++ b/tests/integration/test_async_insert_memory/test.py @@ -21,15 +21,20 @@ def test_memory_usage(): "CREATE TABLE async_table(data Array(UInt64)) ENGINE=MergeTree() ORDER BY data" ) - response = node.get_query_request( - "SELECT groupArray(number + sleepEachRow(0.0001)) FROM numbers(1000000) SETTINGS max_memory_usage_for_user={}".format(30 * (2 ** 23)) + node.get_query_request( + "SELECT count() FROM system.numbers" ) INSERT_QUERY = "INSERT INTO async_table SETTINGS async_insert=1, wait_for_async_insert=1 VALUES ({})" - for i in range(10): - node.query(INSERT_QUERY.format([i in range(i * 5000000, (i + 1) * 5000000)])) + for iter in range(10): + values = list(range(iter * 5000000, (iter + 1) * 5000000)) + node.query(INSERT_QUERY.format(values)) + + response = node.get_query_request( + "SELECT groupArray(number) FROM numbers(1000000) SETTINGS max_memory_usage_for_user={}".format(30 * (2 ** 23)) + ) _, err = response.get_answer_and_error() - assert err == "", "Query failed" + assert err == "", "Query failed with error {}".format(err) node.query("DROP TABLE async_table") From 93aabf8c66006d892583d4a3f5478897b166ce4c Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 21 Feb 2023 02:06:16 +0000 Subject: [PATCH 062/229] Automatic style fix --- tests/integration/test_async_insert_memory/test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_async_insert_memory/test.py b/tests/integration/test_async_insert_memory/test.py index 93e3e771c71..279542f087c 100644 --- a/tests/integration/test_async_insert_memory/test.py +++ b/tests/integration/test_async_insert_memory/test.py @@ -21,9 +21,7 @@ def test_memory_usage(): "CREATE TABLE async_table(data Array(UInt64)) ENGINE=MergeTree() ORDER BY data" ) - node.get_query_request( - "SELECT count() FROM system.numbers" - ) + node.get_query_request("SELECT count() FROM system.numbers") INSERT_QUERY = "INSERT INTO async_table SETTINGS async_insert=1, wait_for_async_insert=1 VALUES ({})" for iter in range(10): @@ -31,7 +29,9 @@ def test_memory_usage(): node.query(INSERT_QUERY.format(values)) response = node.get_query_request( - "SELECT groupArray(number) FROM numbers(1000000) SETTINGS max_memory_usage_for_user={}".format(30 * (2 ** 23)) + "SELECT groupArray(number) FROM numbers(1000000) SETTINGS max_memory_usage_for_user={}".format( + 30 * (2**23) + ) ) _, err = response.get_answer_and_error() From 367cf9e77cefccbf40b9489ac59c3e64d6449a5b Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Mon, 20 Feb 2023 16:37:59 +0100 Subject: [PATCH 063/229] Fix 01179_insert_values_semicolon test Back in #19925 a check for reading data after semicolon had been added, but after #40474 it does not work, the test does not show the problem because of timeout does not work without stdin before (a more generic fix for timeouts in expect tests I will submit later). To make this test works, the only type that I can found that will work right now is DateTime64, other types does use peeking, or even if they do, they will fail while parsing the query as SQL expression. Signed-off-by: Azat Khuzhin --- .../0_stateless/01179_insert_values_semicolon.expect | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/queries/0_stateless/01179_insert_values_semicolon.expect b/tests/queries/0_stateless/01179_insert_values_semicolon.expect index 9d35941ae40..c0b67de5302 100755 --- a/tests/queries/0_stateless/01179_insert_values_semicolon.expect +++ b/tests/queries/0_stateless/01179_insert_values_semicolon.expect @@ -21,7 +21,7 @@ expect ":) " send -- "DROP TABLE IF EXISTS test_01179\r" expect "Ok." -send -- "CREATE TABLE test_01179 (date DateTime) ENGINE=Memory()\r" +send -- "CREATE TABLE test_01179 (date DateTime64(3)) ENGINE=Memory()\r" expect "Ok." send -- "INSERT INTO test_01179 values ('2020-01-01')\r" @@ -30,11 +30,11 @@ expect "Ok." send -- "INSERT INTO test_01179 values ('2020-01-01'); \r" expect "Ok." -send -- "INSERT INTO test_01179 values ('2020-01-01'); (1) \r" +send -- "INSERT INTO test_01179 values ('2020-01-01 0'); (1) \r" expect "Cannot read data after semicolon" send -- "SELECT date, count() FROM test_01179 GROUP BY date FORMAT TSV\r" -expect "2020-01-01 00:00:00\t3" +expect "2020-01-01 00:00:00.000\t2" send -- "DROP TABLE test_01179\r" expect "Ok." From 5cc183ac39836aca060590eb96a572e46b653058 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Mon, 20 Feb 2023 19:27:09 +0100 Subject: [PATCH 064/229] Fix timeouts in 01179_insert_values_semicolon Signed-off-by: Azat Khuzhin --- .../queries/0_stateless/01179_insert_values_semicolon.expect | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/01179_insert_values_semicolon.expect b/tests/queries/0_stateless/01179_insert_values_semicolon.expect index c0b67de5302..16c62443856 100755 --- a/tests/queries/0_stateless/01179_insert_values_semicolon.expect +++ b/tests/queries/0_stateless/01179_insert_values_semicolon.expect @@ -10,9 +10,9 @@ set timeout 60 match_max 100000 expect_after { # Do not ignore eof from expect - eof { exp_continue } + -i $any_spawn_id eof { exp_continue } # A default timeout action is to do nothing, change it to fail - timeout { exit 1 } + -i $any_spawn_id timeout { exit 1 } } spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_CLIENT_BINARY \$CLICKHOUSE_CLIENT_OPT --disable_suggestion" From 2dfc0d008b5f120a468f91ef66d9fb8b93743d9a Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Tue, 21 Feb 2023 11:48:28 +0000 Subject: [PATCH 065/229] Fix: remove redundant sorting optimization + incorrect sorting step removal in case of parent step has more than 1 children --- .../QueryPlan/Optimizations/removeRedundantSorting.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp b/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp index 20d964dcb4f..c7b945b755c 100644 --- a/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp +++ b/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp @@ -188,7 +188,15 @@ private: return false; /// remove sorting - parent_node->children.front() = sorting_node->children.front(); + // parent_node->children.front() = sorting_node->children.front(); + for (auto & child : parent_node->children) + { + if (child == sorting_node) + { + child = sorting_node->children.front(); + break; + } + } /// sorting removed, so need to update sorting traits for upstream steps const DataStream * input_stream = &parent_node->children.front()->step->getOutputStream(); From 6a1621fcad2f7810dcba9765cd4de7382d2e9ff0 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Tue, 21 Feb 2023 14:15:01 +0100 Subject: [PATCH 066/229] Fix flaky test 01710_normal_projections --- tests/queries/0_stateless/01710_normal_projections.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01710_normal_projections.sh b/tests/queries/0_stateless/01710_normal_projections.sh index 70e38b3722a..8ee3f41ea28 100755 --- a/tests/queries/0_stateless/01710_normal_projections.sh +++ b/tests/queries/0_stateless/01710_normal_projections.sh @@ -4,7 +4,7 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh -$CLICKHOUSE_CLIENT -q "CREATE TABLE test_sort_proj (x UInt32, y UInt32, PROJECTION p (SELECT x, y ORDER BY y)) ENGINE = MergeTree ORDER BY x" +$CLICKHOUSE_CLIENT -q "CREATE TABLE test_sort_proj (x UInt32, y UInt32, PROJECTION p (SELECT x, y ORDER BY y)) ENGINE = MergeTree ORDER BY x SETTINGS index_granularity=8192" $CLICKHOUSE_CLIENT -q "insert into test_sort_proj select number, toUInt32(-number - 1) from numbers(100)" echo "select where x < 10" From c83583e46b71b2dc4c5e5d17b033b5a3b3ddfb84 Mon Sep 17 00:00:00 2001 From: Salvatore Mesoraca Date: Tue, 21 Feb 2023 14:04:51 +0100 Subject: [PATCH 067/229] ActionsDAG: do not change result of and() during optimization Previously `and` results were inconsistent. Some optimizations can try to simplify and completely remove a single-argument `and`, replacing it with a cast to UInt8. This works well for all numeric types except floats. A float passed to `and` is normally interpreted as `bool` and values between 0 and 1 are treated as 1. The same value cast to an integer type will be truncated to 0. --- src/Interpreters/ActionsDAG.cpp | 20 +++- .../02667_and_consistency.reference | 23 ++++ .../0_stateless/02667_and_consistency.sql | 106 ++++++++++++++++++ 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/02667_and_consistency.reference create mode 100644 tests/queries/0_stateless/02667_and_consistency.sql diff --git a/src/Interpreters/ActionsDAG.cpp b/src/Interpreters/ActionsDAG.cpp index 96e7daa306f..623ead29921 100644 --- a/src/Interpreters/ActionsDAG.cpp +++ b/src/Interpreters/ActionsDAG.cpp @@ -1943,6 +1943,10 @@ ActionsDAGPtr ActionsDAG::cloneActionsForFilterPushDown( } auto conjunction = getConjunctionNodes(predicate, allowed_nodes); + if (conjunction.allowed.size() == 1 && conjunction.rejected.size() == 1 + && WhichDataType{conjunction.rejected.front()->result_type}.isFloat()) + return nullptr; + auto actions = cloneActionsForConjunction(conjunction.allowed, all_inputs); if (!actions) return nullptr; @@ -2008,10 +2012,12 @@ ActionsDAGPtr ActionsDAG::cloneActionsForFilterPushDown( node.children.swap(new_children); *predicate = std::move(node); } - else + else if (!WhichDataType{new_children.front()->result_type}.isFloat()) { /// If type is different, cast column. /// This case is possible, cause AND can use any numeric type as argument. + /// But casting floats to UInt8 or Bool produces different results. + /// so we can't apply this optimization to them. Node node; node.type = ActionType::COLUMN; node.result_name = predicate->result_type->getName(); @@ -2033,8 +2039,20 @@ ActionsDAGPtr ActionsDAG::cloneActionsForFilterPushDown( else { /// Predicate is function AND, which still have more then one argument. + /// Or there is only one argument that is a float and we can't just + /// remove the AND. /// Just update children and rebuild it. predicate->children.swap(new_children); + if (WhichDataType{predicate->children.front()->result_type}.isFloat()) + { + Node node; + node.type = ActionType::COLUMN; + node.result_name = "1"; + node.column = DataTypeUInt8().createColumnConst(0, 1u); + node.result_type = std::make_shared(); + const auto * const_col = &nodes.emplace_back(std::move(node)); + predicate->children.emplace_back(const_col); + } auto arguments = prepareFunctionArguments(predicate->children); FunctionOverloadResolverPtr func_builder_and = std::make_unique(std::make_shared()); diff --git a/tests/queries/0_stateless/02667_and_consistency.reference b/tests/queries/0_stateless/02667_and_consistency.reference new file mode 100644 index 00000000000..bcb2b5aecfb --- /dev/null +++ b/tests/queries/0_stateless/02667_and_consistency.reference @@ -0,0 +1,23 @@ +true +===== +true +===== +true +===== +true +===== +===== +1 +===== +===== +allow_experimental_analyzer +true +#45440 +2086579505 0 1 0 0 +-542998757 -542998757 1 0 0 += +2086579505 0 1 0 0 +-542998757 -542998757 1 0 0 += +2086579505 0 1 0 0 +-542998757 -542998757 1 0 0 diff --git a/tests/queries/0_stateless/02667_and_consistency.sql b/tests/queries/0_stateless/02667_and_consistency.sql new file mode 100644 index 00000000000..f02185a1a52 --- /dev/null +++ b/tests/queries/0_stateless/02667_and_consistency.sql @@ -0,0 +1,106 @@ +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING 1 AND sin(sum(number)) +SETTINGS enable_optimize_predicate_expression = 0; + +SELECT '====='; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING 1 AND sin(1) +SETTINGS enable_optimize_predicate_expression = 0; + +SELECT '====='; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING x AND sin(sum(number)) +SETTINGS enable_optimize_predicate_expression = 1; + +SELECT '====='; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING 1 AND sin(sum(number)) +SETTINGS enable_optimize_predicate_expression = 0; + +SELECT '====='; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING 1 AND sin(sum(number)) +SETTINGS enable_optimize_predicate_expression = 1; -- { serverError 59 } + +SELECT '====='; + +SELECT 1 and sin(1); + +SELECT '====='; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING x AND sin(1) +SETTINGS enable_optimize_predicate_expression = 0; -- { serverError 59 } + +SELECT '====='; +SELECT 'allow_experimental_analyzer'; + +SET allow_experimental_analyzer = 1; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING 1 AND sin(sum(number)) +SETTINGS enable_optimize_predicate_expression = 1; + +select '#45440'; + +DROP TABLE IF EXISTS t2; +CREATE TABLE t2(c0 Int32) ENGINE = MergeTree ORDER BY c0; +INSERT INTO t2 VALUES (928386547), (1541944097), (2086579505), (1990427322), (-542998757), (390253678), (554855248), (203290629), (1504693323); + +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; +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; +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; + +DROP TABLE IF EXISTS t2; From 3df7a10ac7e13c8d7836583e9e0896a797183939 Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:25:11 +0100 Subject: [PATCH 068/229] Update postgres_utility.py --- tests/integration/helpers/postgres_utility.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/helpers/postgres_utility.py b/tests/integration/helpers/postgres_utility.py index 978b9a98fb4..838c22c8a7c 100644 --- a/tests/integration/helpers/postgres_utility.py +++ b/tests/integration/helpers/postgres_utility.py @@ -320,11 +320,11 @@ def check_tables_are_synchronized( ) result = instance.query(result_query) - for _ in range(30): + for _ in range(50): if result == expected: break else: - time.sleep(0.5) + time.sleep(1) result = instance.query(result_query) assert result == expected From 1d4352d82af2cc354890018d1e5d715d2968f7a9 Mon Sep 17 00:00:00 2001 From: alesapin Date: Tue, 21 Feb 2023 17:01:19 +0100 Subject: [PATCH 069/229] Fix integration test: terminate old version without wait --- .../test_backup_with_other_granularity/test.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_backup_with_other_granularity/test.py b/tests/integration/test_backup_with_other_granularity/test.py index d30c45c3691..f456fae23a8 100644 --- a/tests/integration/test_backup_with_other_granularity/test.py +++ b/tests/integration/test_backup_with_other_granularity/test.py @@ -54,7 +54,8 @@ def test_backup_from_old_version(started_cluster): node1.query("ALTER TABLE source_table FREEZE PARTITION tuple();") - node1.restart_with_latest_version(fix_metadata=True) + # We don't want to wait old outdated version to finish properly, just terminate it + node1.restart_with_latest_version(fix_metadata=True, signal=9) node1.query( "CREATE TABLE dest_table (A Int64, B String, Y String) ENGINE = ReplicatedMergeTree('/test/dest_table1', '1') ORDER BY tuple()" @@ -107,7 +108,8 @@ def test_backup_from_old_version_setting(started_cluster): node2.query("ALTER TABLE source_table FREEZE PARTITION tuple();") - node2.restart_with_latest_version(fix_metadata=True) + # We don't want to wait old outdated version to finish properly, just terminate it + node2.restart_with_latest_version(fix_metadata=True, signal=9) node2.query( "CREATE TABLE dest_table (A Int64, B String, Y String) ENGINE = ReplicatedMergeTree('/test/dest_table2', '1') ORDER BY tuple() SETTINGS enable_mixed_granularity_parts = 1" @@ -163,7 +165,8 @@ def test_backup_from_old_version_config(started_cluster): "1", ) - node3.restart_with_latest_version(callback_onstop=callback, fix_metadata=True) + # We don't want to wait old outdated version to finish properly, just terminate it + node3.restart_with_latest_version(callback_onstop=callback, fix_metadata=True, signal=9) node3.query( "CREATE TABLE dest_table (A Int64, B String, Y String) ENGINE = ReplicatedMergeTree('/test/dest_table3', '1') ORDER BY tuple() SETTINGS enable_mixed_granularity_parts = 1" From af677c7dcd6663cc89fa5756c2bff9c59bbb0d8d Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 21 Feb 2023 16:08:13 +0000 Subject: [PATCH 070/229] Automatic style fix --- tests/integration/test_backup_with_other_granularity/test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_backup_with_other_granularity/test.py b/tests/integration/test_backup_with_other_granularity/test.py index f456fae23a8..2a82fc71951 100644 --- a/tests/integration/test_backup_with_other_granularity/test.py +++ b/tests/integration/test_backup_with_other_granularity/test.py @@ -166,7 +166,9 @@ def test_backup_from_old_version_config(started_cluster): ) # We don't want to wait old outdated version to finish properly, just terminate it - node3.restart_with_latest_version(callback_onstop=callback, fix_metadata=True, signal=9) + node3.restart_with_latest_version( + callback_onstop=callback, fix_metadata=True, signal=9 + ) node3.query( "CREATE TABLE dest_table (A Int64, B String, Y String) ENGINE = ReplicatedMergeTree('/test/dest_table3', '1') ORDER BY tuple() SETTINGS enable_mixed_granularity_parts = 1" From 45b1b66fd8d420db449eedb87b0271dcd53e0b08 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Tue, 21 Feb 2023 16:58:45 +0000 Subject: [PATCH 071/229] Remove unnecessary comment --- .../QueryPlan/Optimizations/removeRedundantSorting.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp b/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp index c7b945b755c..41e30dee83e 100644 --- a/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp +++ b/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp @@ -188,7 +188,6 @@ private: return false; /// remove sorting - // parent_node->children.front() = sorting_node->children.front(); for (auto & child : parent_node->children) { if (child == sorting_node) From 9a7c71b78e24e0f3b38a2a3d49a1f446b4a74e7e Mon Sep 17 00:00:00 2001 From: kssenii Date: Tue, 21 Feb 2023 18:07:57 +0100 Subject: [PATCH 072/229] Allow to hide only values from system.named_collections --- src/Access/Common/AccessType.h | 1 + src/Access/UsersConfigAccessStorage.cpp | 6 ++++++ .../System/StorageSystemNamedCollections.cpp | 7 ++++++- .../configs/users.d/users.xml | 1 + .../integration/test_named_collections/test.py | 18 +++++++++++++++++- 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Access/Common/AccessType.h b/src/Access/Common/AccessType.h index 497327c1bad..f57cc2886e3 100644 --- a/src/Access/Common/AccessType.h +++ b/src/Access/Common/AccessType.h @@ -135,6 +135,7 @@ enum class AccessType M(SHOW_SETTINGS_PROFILES, "SHOW PROFILES, SHOW CREATE SETTINGS PROFILE, SHOW CREATE PROFILE", GLOBAL, SHOW_ACCESS) \ M(SHOW_ACCESS, "", GROUP, ACCESS_MANAGEMENT) \ M(SHOW_NAMED_COLLECTIONS, "SHOW NAMED COLLECTIONS", GLOBAL, ACCESS_MANAGEMENT) \ + M(SHOW_NAMED_COLLECTIONS_SECRETS, "SHOW NAMED COLLECTIONS SECRETS", GLOBAL, ACCESS_MANAGEMENT) \ M(ACCESS_MANAGEMENT, "", GROUP, ALL) \ \ M(SYSTEM_SHUTDOWN, "SYSTEM KILL, SHUTDOWN", GLOBAL, SYSTEM) \ diff --git a/src/Access/UsersConfigAccessStorage.cpp b/src/Access/UsersConfigAccessStorage.cpp index 58edff039ca..b893554cb8a 100644 --- a/src/Access/UsersConfigAccessStorage.cpp +++ b/src/Access/UsersConfigAccessStorage.cpp @@ -239,6 +239,12 @@ namespace user->access.revoke(AccessType::SHOW_NAMED_COLLECTIONS); } + bool show_named_collections_secrets = config.getBool(user_config + ".show_named_collections_secrets", false); + if (!show_named_collections_secrets) + { + user->access.revoke(AccessType::SHOW_NAMED_COLLECTIONS_SECRETS); + } + String default_database = config.getString(user_config + ".default_database", ""); user->default_database = default_database; diff --git a/src/Storages/System/StorageSystemNamedCollections.cpp b/src/Storages/System/StorageSystemNamedCollections.cpp index bc1e3a45e6b..621799c37f2 100644 --- a/src/Storages/System/StorageSystemNamedCollections.cpp +++ b/src/Storages/System/StorageSystemNamedCollections.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace DB @@ -30,6 +31,7 @@ StorageSystemNamedCollections::StorageSystemNamedCollections(const StorageID & t void StorageSystemNamedCollections::fillData(MutableColumns & res_columns, ContextPtr context, const SelectQueryInfo &) const { context->checkAccess(AccessType::SHOW_NAMED_COLLECTIONS); + const auto & access = context->getAccess(); auto collections = NamedCollectionFactory::instance().getAll(); for (const auto & [name, collection] : collections) @@ -47,7 +49,10 @@ void StorageSystemNamedCollections::fillData(MutableColumns & res_columns, Conte for (const auto & key : collection->getKeys()) { key_column.insertData(key.data(), key.size()); - value_column.insert(collection->get(key)); + if (access->isGranted(AccessType::SHOW_NAMED_COLLECTIONS_SECRETS)) + value_column.insert(collection->get(key)); + else + value_column.insert("[HIDDEN]"); size++; } diff --git a/tests/integration/test_named_collections/configs/users.d/users.xml b/tests/integration/test_named_collections/configs/users.d/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_named_collections/configs/users.d/users.xml +++ b/tests/integration/test_named_collections/configs/users.d/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_named_collections/test.py b/tests/integration/test_named_collections/test.py index 3b102f1aa70..612b894461b 100644 --- a/tests/integration/test_named_collections/test.py +++ b/tests/integration/test_named_collections/test.py @@ -102,7 +102,23 @@ def test_access(cluster): ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] ) node.restart_clickhouse() - assert int(node.query("select count() from system.named_collections")) > 0 + assert node.query("select collection['key1'] from system.named_collections").strip() == "value1" + replace_in_users_config( + node, "show_named_collections_secrets>1", "show_named_collections_secrets>0" + ) + assert "show_named_collections_secrets>0" in node.exec_in_container( + ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] + ) + node.restart_clickhouse() + assert node.query("select collection['key1'] from system.named_collections").strip() == "[HIDDEN]" + replace_in_users_config( + node, "show_named_collections_secrets>0", "show_named_collections_secrets>1" + ) + assert "show_named_collections_secrets>1" in node.exec_in_container( + ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] + ) + node.restart_clickhouse() + assert node.query("select collection['key1'] from system.named_collections").strip() == "value1" def test_config_reload(cluster): From 07158e625302e596cd7b5280e71712bd79fd285b Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 13 Feb 2023 17:41:39 +0000 Subject: [PATCH 073/229] Add CrossToInnerJoinPass --- src/Analyzer/JoinNode.cpp | 18 ++ src/Analyzer/JoinNode.h | 2 + src/Analyzer/Passes/CrossToInnerJoinPass.cpp | 253 +++++++++++++++++++ src/Analyzer/Passes/CrossToInnerJoinPass.h | 24 ++ src/Analyzer/QueryTreePassManager.cpp | 2 + 5 files changed, 299 insertions(+) create mode 100644 src/Analyzer/Passes/CrossToInnerJoinPass.cpp create mode 100644 src/Analyzer/Passes/CrossToInnerJoinPass.h diff --git a/src/Analyzer/JoinNode.cpp b/src/Analyzer/JoinNode.cpp index 28a0c4ad7e0..86cf7e90c3f 100644 --- a/src/Analyzer/JoinNode.cpp +++ b/src/Analyzer/JoinNode.cpp @@ -15,6 +15,11 @@ namespace DB { +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + JoinNode::JoinNode(QueryTreeNodePtr left_table_expression_, QueryTreeNodePtr right_table_expression_, QueryTreeNodePtr join_expression_, @@ -113,4 +118,17 @@ ASTPtr JoinNode::toASTImpl() const return tables_in_select_query_ast; } +void JoinNode::crossToInner(const QueryTreeNodePtr & join_expression_) +{ + if (kind != JoinKind::Cross && kind != JoinKind::Comma) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot rewrite join {} to inner join, expected cross", toString(kind)); + + if (children[join_expression_child_index]) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Join expression is not empty: '{}'", children[join_expression_child_index]->formatConvertedASTForErrorMessage()); + + kind = JoinKind::Inner; + strictness = JoinStrictness::All; + children[join_expression_child_index] = std::move(join_expression_); +} + } diff --git a/src/Analyzer/JoinNode.h b/src/Analyzer/JoinNode.h index 15ba11a0122..d1cd64e67b7 100644 --- a/src/Analyzer/JoinNode.h +++ b/src/Analyzer/JoinNode.h @@ -126,6 +126,8 @@ public: return QueryTreeNodeType::JOIN; } + void crossToInner(const QueryTreeNodePtr & join_expression_); + void dumpTreeImpl(WriteBuffer & buffer, FormatState & format_state, size_t indent) const override; protected: diff --git a/src/Analyzer/Passes/CrossToInnerJoinPass.cpp b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp new file mode 100644 index 00000000000..dc03e340b15 --- /dev/null +++ b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp @@ -0,0 +1,253 @@ +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int INCORRECT_QUERY; +} + +namespace +{ + +using EquiCondition = std::tuple; + +void exctractJoinConditions(const QueryTreeNodePtr & node, QueryTreeNodes & equi_conditions, QueryTreeNodes & other) +{ + if (auto * func = node->as()) + { + const auto & args = func->getArguments().getNodes(); + + if (args.size() == 2 && func->getFunctionName() == "equals") + { + equi_conditions.push_back(node); + return; + } + + if (func->getFunctionName() == "and") + { + for (auto & arg : args) + exctractJoinConditions(arg, equi_conditions, other); + return; + } + } + + other.push_back(node); +} + +const QueryTreeNodePtr & getEquiArgument(const QueryTreeNodePtr & cond, size_t index) +{ + const auto * func = cond->as(); + chassert(func && func->getFunctionName() == "equals" && func->getArguments().getNodes().size() == 2); + return func->getArguments().getNodes()[index]; +} + + +/// Check that node has only one source and return it. +/// {_, false} - multiple sources +/// {nullptr, true} - no sources +/// {source, true} - single source +std::pair getExpressionSource(const QueryTreeNodePtr & node) +{ + if (const auto * column = node->as()) + { + auto source = column->getColumnSourceOrNull(); + if (!source) + return {nullptr, false}; + return {source.get(), true}; + } + + if (const auto * func = node->as()) + { + const IQueryTreeNode * source = nullptr; + const auto & args = func->getArguments().getNodes(); + for (auto & arg : args) + { + auto [arg_source, is_ok] = getExpressionSource(arg); + if (!is_ok) + return {nullptr, false}; + + if (!source) + source = arg_source; + else if (arg_source && !source->isEqual(*arg_source)) + return {nullptr, false}; + } + return {source, true}; + + } + + if (node->as()) + return {nullptr, true}; + + return {nullptr, false}; +} + +bool findInTableExpression(const IQueryTreeNode * source, const QueryTreeNodePtr & table_expression) +{ + if (!source) + return true; + + if (source->isEqual(*table_expression)) + return true; + + if (const auto * join_node = table_expression->as()) + { + return findInTableExpression(source, join_node->getLeftTableExpression()) + || findInTableExpression(source, join_node->getRightTableExpression()); + } + + if (const auto * query_node = table_expression->as()) + { + return findInTableExpression(source, query_node->getJoinTree()); + } + + return false; +} + +class CrossToInnerJoinVisitor : public InDepthQueryTreeVisitorWithContext +{ +public: + using Base = InDepthQueryTreeVisitorWithContext; + using Base::Base; + + /// Returns false if can't rewrite cross to inner join + bool tryRewrite(JoinNode * join_node) + { + if (!isCrossOrComma(join_node->getKind())) + return true; + + if (where_stack.empty()) + return false; + + auto & where_condition = *where_stack.back(); + if (!where_condition) + return false; + + const auto & left_table = join_node->getLeftTableExpression(); + const auto & right_table = join_node->getRightTableExpression(); + + QueryTreeNodes equi_conditions; + QueryTreeNodes other_conditions; + exctractJoinConditions(where_condition, equi_conditions, other_conditions); + bool can_join_on_anything = false; + for (auto & cond : equi_conditions) + { + auto left_src = getExpressionSource(getEquiArgument(cond, 0)); + auto right_src = getExpressionSource(getEquiArgument(cond, 1)); + if (left_src.second && right_src.second && left_src.first && right_src.first) + { + bool can_join_on = (findInTableExpression(left_src.first, left_table) && findInTableExpression(right_src.first, right_table)) + || (findInTableExpression(left_src.first, right_table) && findInTableExpression(right_src.first, left_table)); + + if (can_join_on) + { + can_join_on_anything = true; + continue; + } + } + + /// Can't join on this condition, move it to other conditions + other_conditions.push_back(cond); + cond = nullptr; + } + + if (!can_join_on_anything) + return false; + + equi_conditions.erase(std::remove(equi_conditions.begin(), equi_conditions.end(), nullptr), equi_conditions.end()); + join_node->crossToInner(makeConjunction(equi_conditions)); + where_condition = makeConjunction(other_conditions); + return true; + } + + void visitImpl(QueryTreeNodePtr & node) + { + if (!isEnabled()) + return; + + if (auto * query_node = node->as()) + { + /// We are entering the subtree and can use WHERE condition from this subtree + if (auto & where_node = query_node->getWhere()) + where_stack.push_back(&where_node); + } + + if (auto * join_node = node->as()) + { + bool is_rewritten = tryRewrite(join_node); + if (!is_rewritten && forceRewrite(join_node->getKind())) + { + throw Exception(ErrorCodes::INCORRECT_QUERY, + "Failed to rewrite '{}' to INNER JOIN: " + "no equi-join conditions found in WHERE clause. " + "You may set setting `cross_to_inner_join_rewrite` to `1` to allow slow CROSS JOIN for this case", + join_node->formatASTForErrorMessage()); + } + } + + if (!where_stack.empty() && where_stack.back()->get() == node.get()) + { + /// We are visiting the WHERE clause. + /// It means that we have visited current subtree and will go out of WHERE scope. + where_stack.pop_back(); + } + } + +private: + bool isEnabled() const + { + return getSettings().cross_to_inner_join_rewrite; + } + + bool forceRewrite(JoinKind kind) const + { + if (kind == JoinKind::Cross) + return false; + /// Comma join can be forced to rewrite + return getSettings().cross_to_inner_join_rewrite >= 2; + } + + QueryTreeNodePtr makeConjunction(const QueryTreeNodes & nodes) + { + if (nodes.empty()) + return nullptr; + + if (nodes.size() == 1) + return nodes.front(); + + auto function_node = std::make_shared("and"); + for (auto & node : nodes) + function_node->getArguments().getNodes().push_back(node); + + const auto & function = FunctionFactory::instance().get("and", getContext()); + function_node->resolveAsFunction(function->build(function_node->getArgumentColumns())); + return function_node; + } + + std::deque where_stack; +}; + +} + +void CrossToInnerJoinPass::run(QueryTreeNodePtr query_tree_node, ContextPtr context) +{ + CrossToInnerJoinVisitor visitor(std::move(context)); + visitor.visit(query_tree_node); +} + +} diff --git a/src/Analyzer/Passes/CrossToInnerJoinPass.h b/src/Analyzer/Passes/CrossToInnerJoinPass.h new file mode 100644 index 00000000000..8f50f72e3d1 --- /dev/null +++ b/src/Analyzer/Passes/CrossToInnerJoinPass.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace DB +{ + + +/** Replace CROSS JOIN with INNER JOIN. + */ +class CrossToInnerJoinPass final : public IQueryTreePass +{ +public: + String getName() override { return "CrossToInnerJoin"; } + + String getDescription() override + { + return "Replace CROSS JOIN with INNER JOIN"; + } + + void run(QueryTreeNodePtr query_tree_node, ContextPtr context) override; +}; + +} diff --git a/src/Analyzer/QueryTreePassManager.cpp b/src/Analyzer/QueryTreePassManager.cpp index 218e47d973f..9ba18e27f73 100644 --- a/src/Analyzer/QueryTreePassManager.cpp +++ b/src/Analyzer/QueryTreePassManager.cpp @@ -39,6 +39,7 @@ #include #include #include +#include namespace DB @@ -268,6 +269,7 @@ void addQueryTreePasses(QueryTreePassManager & manager) manager.addPass(std::make_unique()); + manager.addPass(std::make_unique()); } } From 6296109be0901691bcd970bc7038fd6d0116f589 Mon Sep 17 00:00:00 2001 From: vdimir Date: Tue, 14 Feb 2023 13:12:10 +0000 Subject: [PATCH 074/229] Tests for CrossToInnerJoinPass --- .../00849_multiple_comma_join_2.reference | 232 +++--------------- .../00849_multiple_comma_join_2.sql | 108 ++++++-- .../02364_setting_cross_to_inner_rewrite.sql | 2 - .../02564_analyzer_cross_to_inner.reference | 10 + .../02564_analyzer_cross_to_inner.sql | 62 +++++ 5 files changed, 194 insertions(+), 220 deletions(-) create mode 100644 tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference create mode 100644 tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql diff --git a/tests/queries/0_stateless/00849_multiple_comma_join_2.reference b/tests/queries/0_stateless/00849_multiple_comma_join_2.reference index 2652a82ab54..16f228a5569 100644 --- a/tests/queries/0_stateless/00849_multiple_comma_join_2.reference +++ b/tests/queries/0_stateless/00849_multiple_comma_join_2.reference @@ -1,205 +1,33 @@ -SELECT a -FROM t1 -CROSS JOIN t2 -SELECT a -FROM t1 -ALL INNER JOIN t2 ON a = t2.a -WHERE a = t2.a -SELECT a -FROM t1 -ALL INNER JOIN t2 ON b = t2.b -WHERE b = t2.b -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - ALL INNER JOIN t2 ON `--t1.a` = `--t2.a` -) AS `--.s` -ALL INNER JOIN t3 ON `--t1.a` = a -WHERE (`--t1.a` = `--t2.a`) AND (`--t1.a` = a) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - b AS `--t1.b`, - a AS `--t1.a`, - t2.b AS `--t2.b` - FROM t1 - ALL INNER JOIN t2 ON `--t1.b` = `--t2.b` -) AS `--.s` -ALL INNER JOIN t3 ON `--t1.b` = b -WHERE (`--t1.b` = `--t2.b`) AND (`--t1.b` = b) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.a`, - `--t2.a`, - a AS `--t3.a` - FROM - ( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - ALL INNER JOIN t2 ON `--t1.a` = `--t2.a` - ) AS `--.s` - ALL INNER JOIN t3 ON `--t1.a` = `--t3.a` -) AS `--.s` -ALL INNER JOIN t4 ON `--t1.a` = a -WHERE (`--t1.a` = `--t2.a`) AND (`--t1.a` = `--t3.a`) AND (`--t1.a` = a) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.b`, - `--t1.a`, - `--t2.b`, - b AS `--t3.b` - FROM - ( - SELECT - b AS `--t1.b`, - a AS `--t1.a`, - t2.b AS `--t2.b` - FROM t1 - ALL INNER JOIN t2 ON `--t1.b` = `--t2.b` - ) AS `--.s` - ALL INNER JOIN t3 ON `--t1.b` = `--t3.b` -) AS `--.s` -ALL INNER JOIN t4 ON `--t1.b` = b -WHERE (`--t1.b` = `--t2.b`) AND (`--t1.b` = `--t3.b`) AND (`--t1.b` = b) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.a`, - `--t2.a`, - a AS `--t3.a` - FROM - ( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - ALL INNER JOIN t2 ON `--t2.a` = `--t1.a` - ) AS `--.s` - ALL INNER JOIN t3 ON `--t2.a` = `--t3.a` -) AS `--.s` -ALL INNER JOIN t4 ON `--t2.a` = a -WHERE (`--t2.a` = `--t1.a`) AND (`--t2.a` = `--t3.a`) AND (`--t2.a` = a) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.a`, - `--t2.a`, - a AS `--t3.a` - FROM - ( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - CROSS JOIN t2 - ) AS `--.s` - ALL INNER JOIN t3 ON (`--t3.a` = `--t1.a`) AND (`--t3.a` = `--t2.a`) -) AS `--.s` -ALL INNER JOIN t4 ON `--t3.a` = a -WHERE (`--t3.a` = `--t1.a`) AND (`--t3.a` = `--t2.a`) AND (`--t3.a` = a) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.a`, - `--t2.a`, - a AS `--t3.a` - FROM - ( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - CROSS JOIN t2 - ) AS `--.s` - CROSS JOIN t3 -) AS `--.s` -ALL INNER JOIN t4 ON (a = `--t1.a`) AND (a = `--t2.a`) AND (a = `--t3.a`) -WHERE (a = `--t1.a`) AND (a = `--t2.a`) AND (a = `--t3.a`) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.a`, - `--t2.a`, - a AS `--t3.a` - FROM - ( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - ALL INNER JOIN t2 ON `--t1.a` = `--t2.a` - ) AS `--.s` - ALL INNER JOIN t3 ON `--t2.a` = `--t3.a` -) AS `--.s` -ALL INNER JOIN t4 ON `--t3.a` = a -WHERE (`--t1.a` = `--t2.a`) AND (`--t2.a` = `--t3.a`) AND (`--t3.a` = a) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT `--t1.a` - FROM - ( - SELECT a AS `--t1.a` - FROM t1 - CROSS JOIN t2 - ) AS `--.s` - CROSS JOIN t3 -) AS `--.s` -CROSS JOIN t4 -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT `--t1.a` - FROM - ( - SELECT a AS `--t1.a` - FROM t1 - CROSS JOIN t2 - ) AS `--.s` - CROSS JOIN t3 -) AS `--.s` -CROSS JOIN t4 -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT a AS `--t1.a` - FROM t1 - CROSS JOIN t2 -) AS `--.s` -CROSS JOIN t3 -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT a AS `--t1.a` - FROM t1 - ALL INNER JOIN t2 USING (a) -) AS `--.s` -CROSS JOIN t3 -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - ALL INNER JOIN t2 ON `--t1.a` = `--t2.a` -) AS `--.s` -CROSS JOIN t3 +0 1 +0 1 +0 2 +0 2 +0 3 +0 3 +0 3 +1 2 +2 1 +0 3 +3 0 +3 0 +2 0 +1 1 +1 1 +0 1 +0 1 +0 2 +0 2 +0 3 +0 3 +0 3 +1 2 +2 1 +0 3 +3 0 +3 0 +2 0 +1 1 +1 1 SELECT * FROM t1, t2 1 1 1 1 1 1 1 \N diff --git a/tests/queries/0_stateless/00849_multiple_comma_join_2.sql b/tests/queries/0_stateless/00849_multiple_comma_join_2.sql index eb803450ff7..db8b27c4d4d 100644 --- a/tests/queries/0_stateless/00849_multiple_comma_join_2.sql +++ b/tests/queries/0_stateless/00849_multiple_comma_join_2.sql @@ -12,31 +12,107 @@ CREATE TABLE t2 (a UInt32, b Nullable(Int32)) ENGINE = Memory; CREATE TABLE t3 (a UInt32, b Nullable(Int32)) ENGINE = Memory; CREATE TABLE t4 (a UInt32, b Nullable(Int32)) ENGINE = Memory; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 WHERE t1.a = t2.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 WHERE t1.b = t2.b; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3 WHERE t1.a = t2.a AND t1.a = t3.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3 WHERE t1.b = t2.b AND t1.b = t3.b; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t1.a = t3.a AND t1.a = t4.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.b = t2.b AND t1.b = t3.b AND t1.b = t4.b; +SET allow_experimental_analyzer = 0; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t2.a = t1.a AND t2.a = t3.a AND t2.a = t4.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t3.a = t1.a AND t3.a = t2.a AND t3.a = t4.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t4.a = t1.a AND t4.a = t2.a AND t4.a = t3.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t2.a = t3.a AND t3.a = t4.a; +--- EXPLAIN SYNTAX (old AST based optimization) +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 WHERE t1.a = t2.a); -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4; -EXPLAIN SYNTAX SELECT t1.a FROM t1 CROSS JOIN t2 CROSS JOIN t3 CROSS JOIN t4; +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 WHERE t1.b = t2.b); -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 CROSS JOIN t3; -EXPLAIN SYNTAX SELECT t1.a FROM t1 JOIN t2 USING a CROSS JOIN t3; -EXPLAIN SYNTAX SELECT t1.a FROM t1 JOIN t2 ON t1.a = t2.a CROSS JOIN t3; +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3 WHERE t1.a = t2.a AND t1.a = t3.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3 WHERE t1.b = t2.b AND t1.b = t3.b); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t1.a = t3.a AND t1.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.b = t2.b AND t1.b = t3.b AND t1.b = t4.b); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t2.a = t1.a AND t2.a = t3.a AND t2.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t3.a = t1.a AND t3.a = t2.a AND t3.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t4.a = t1.a AND t4.a = t2.a AND t4.a = t3.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t2.a = t3.a AND t3.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1 CROSS JOIN t2 CROSS JOIN t3 CROSS JOIN t4); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 CROSS JOIN t3); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1 JOIN t2 USING a CROSS JOIN t3); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1 JOIN t2 ON t1.a = t2.a CROSS JOIN t3); + +--- EXPLAIN QUERY TREE +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2 WHERE t1.a = t2.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2 WHERE t1.b = t2.b); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3 WHERE t1.a = t2.a AND t1.a = t3.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3 WHERE t1.b = t2.b AND t1.b = t3.b); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t1.a = t3.a AND t1.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.b = t2.b AND t1.b = t3.b AND t1.b = t4.b); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t2.a = t1.a AND t2.a = t3.a AND t2.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t3.a = t1.a AND t3.a = t2.a AND t3.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t4.a = t1.a AND t4.a = t2.a AND t4.a = t3.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t2.a = t3.a AND t3.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1 CROSS JOIN t2 CROSS JOIN t3 CROSS JOIN t4); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2 CROSS JOIN t3); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1 JOIN t2 USING a CROSS JOIN t3); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1 JOIN t2 ON t1.a = t2.a CROSS JOIN t3); INSERT INTO t1 values (1,1), (2,2), (3,3), (4,4); INSERT INTO t2 values (1,1), (1, Null); INSERT INTO t3 values (1,1), (1, Null); INSERT INTO t4 values (1,1), (1, Null); +SET allow_experimental_analyzer = 1; + SELECT 'SELECT * FROM t1, t2'; SELECT * FROM t1, t2 ORDER BY t1.a, t2.b; diff --git a/tests/queries/0_stateless/02364_setting_cross_to_inner_rewrite.sql b/tests/queries/0_stateless/02364_setting_cross_to_inner_rewrite.sql index cdbac93937e..86a8414e799 100644 --- a/tests/queries/0_stateless/02364_setting_cross_to_inner_rewrite.sql +++ b/tests/queries/0_stateless/02364_setting_cross_to_inner_rewrite.sql @@ -1,5 +1,3 @@ - - DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; diff --git a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference new file mode 100644 index 00000000000..63d28b57a01 --- /dev/null +++ b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference @@ -0,0 +1,10 @@ +5 6 5 6 5 +3 4 3 4 5 +3 4 3 4 7 +3 4 3 4 9 +5 6 5 6 5 +5 6 5 6 7 +5 6 5 6 9 +2 0 +0 2 +1 1 diff --git a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql new file mode 100644 index 00000000000..42446252ea7 --- /dev/null +++ b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql @@ -0,0 +1,62 @@ +SET allow_experimental_analyzer = 1; + +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP TABLE IF EXISTS t3; + +CREATE TABLE t1 (a UInt64, b UInt64) ENGINE = Memory; +INSERT INTO t1 VALUES (1, 2), (3, 4), (5, 6); + +CREATE TABLE t2 (a UInt64, b UInt64) ENGINE = Memory; +INSERT INTO t2 VALUES (3, 4), (5, 6), (7, 8); + +CREATE TABLE t3 (a UInt64, b UInt64) ENGINE = Memory; +INSERT INTO t3 VALUES (5, 6), (7, 8), (9, 10); + +SET cross_to_inner_join_rewrite = 1; + +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 +; + +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) +ORDER BY t1.a, t2.a, t3.x +; + +-- rewrite two joins +SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( + EXPLAIN QUERY TREE + SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 + WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 +) WHERE explain like '% JOIN % kind: %' +SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain +; + +-- setting is disabled +SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( + EXPLAIN QUERY TREE + SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 + WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 + SETTINGS cross_to_inner_join_rewrite = 0 +) WHERE explain like '% JOIN % kind: %' +SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain +; + +-- only one join can be rewritten +SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( + EXPLAIN QUERY TREE + SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 + WHERE t1.a = if(t2.b > 0, t2.a, 0) +) WHERE explain like '% JOIN % kind: %' +SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain +; + +-- throw in force mode +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) +SETTINGS cross_to_inner_join_rewrite = 2; -- { serverError INCORRECT_QUERY } + +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP TABLE IF EXISTS t3; From ba04a3cc1f10df1dad9e40890d0de55d97830390 Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 20 Feb 2023 11:49:57 +0000 Subject: [PATCH 075/229] Fixes for CrossToInnerPass --- src/Analyzer/JoinNode.cpp | 7 ++-- src/Analyzer/JoinNode.h | 5 +++ src/Analyzer/Passes/CrossToInnerJoinPass.cpp | 37 ++++++++++---------- src/Analyzer/Passes/CrossToInnerJoinPass.h | 4 +++ 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/Analyzer/JoinNode.cpp b/src/Analyzer/JoinNode.cpp index 86cf7e90c3f..fe4dd2c5016 100644 --- a/src/Analyzer/JoinNode.cpp +++ b/src/Analyzer/JoinNode.cpp @@ -121,14 +121,15 @@ ASTPtr JoinNode::toASTImpl() const void JoinNode::crossToInner(const QueryTreeNodePtr & join_expression_) { if (kind != JoinKind::Cross && kind != JoinKind::Comma) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot rewrite join {} to inner join, expected cross", toString(kind)); + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot rewrite {} to INNER JOIN, expected CROSS", toString(kind)); if (children[join_expression_child_index]) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Join expression is not empty: '{}'", children[join_expression_child_index]->formatConvertedASTForErrorMessage()); + throw Exception(ErrorCodes::LOGICAL_ERROR, "Join expression is expected to be empty for CROSS JOIN, got '{}'", + children[join_expression_child_index]->formatConvertedASTForErrorMessage()); kind = JoinKind::Inner; strictness = JoinStrictness::All; - children[join_expression_child_index] = std::move(join_expression_); + children[join_expression_child_index] = join_expression_; } } diff --git a/src/Analyzer/JoinNode.h b/src/Analyzer/JoinNode.h index d1cd64e67b7..0d856985794 100644 --- a/src/Analyzer/JoinNode.h +++ b/src/Analyzer/JoinNode.h @@ -126,6 +126,11 @@ public: return QueryTreeNodeType::JOIN; } + /* + * Convert CROSS to INNER JOIN - changes JOIN kind and sets a new join expression + * (that was moved from WHERE clause). + * Expects the current kind to be CROSS (and join expression to be null because of that). + */ void crossToInner(const QueryTreeNodePtr & join_expression_); void dumpTreeImpl(WriteBuffer & buffer, FormatState & format_state, size_t indent) const override; diff --git a/src/Analyzer/Passes/CrossToInnerJoinPass.cpp b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp index dc03e340b15..400c760cd20 100644 --- a/src/Analyzer/Passes/CrossToInnerJoinPass.cpp +++ b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp @@ -25,29 +25,30 @@ namespace ErrorCodes namespace { -using EquiCondition = std::tuple; - void exctractJoinConditions(const QueryTreeNodePtr & node, QueryTreeNodes & equi_conditions, QueryTreeNodes & other) { - if (auto * func = node->as()) + auto * func = node->as(); + if (!func) { - const auto & args = func->getArguments().getNodes(); - - if (args.size() == 2 && func->getFunctionName() == "equals") - { - equi_conditions.push_back(node); - return; - } - - if (func->getFunctionName() == "and") - { - for (auto & arg : args) - exctractJoinConditions(arg, equi_conditions, other); - return; - } + other.push_back(node); + return; } - other.push_back(node); + const auto & args = func->getArguments().getNodes(); + + if (args.size() == 2 && func->getFunctionName() == "equals") + { + equi_conditions.push_back(node); + } + else if (func->getFunctionName() == "and") + { + for (auto & arg : args) + exctractJoinConditions(arg, equi_conditions, other); + } + else + { + other.push_back(node); + } } const QueryTreeNodePtr & getEquiArgument(const QueryTreeNodePtr & cond, size_t index) diff --git a/src/Analyzer/Passes/CrossToInnerJoinPass.h b/src/Analyzer/Passes/CrossToInnerJoinPass.h index 8f50f72e3d1..127d26dc41d 100644 --- a/src/Analyzer/Passes/CrossToInnerJoinPass.h +++ b/src/Analyzer/Passes/CrossToInnerJoinPass.h @@ -7,6 +7,10 @@ namespace DB /** Replace CROSS JOIN with INNER JOIN. + * Example: + * SELECT * FROM t1 CROSS JOIN t2 WHERE t1.a = t2.a AND t1.b > 10 AND t2.b = t2.c + * We can move equality condition to ON section of INNER JOIN: + * SELECT * FROM t1 INNER JOIN t2 ON t1.a = t2.a WHERE t1.b > 10 AND t2.b = t2.c */ class CrossToInnerJoinPass final : public IQueryTreePass { From 7cf7d31b4b4b24c98d0f5a16ee940ca7b90483d4 Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 20 Feb 2023 11:50:28 +0000 Subject: [PATCH 076/229] Update 02564_analyzer_cross_to_inner --- .../02564_analyzer_cross_to_inner.reference | 201 +++++++++++++++++- .../02564_analyzer_cross_to_inner.sql | 40 ++-- 2 files changed, 212 insertions(+), 29 deletions(-) diff --git a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference index 63d28b57a01..e4d7ff55b86 100644 --- a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference +++ b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference @@ -5,6 +5,201 @@ 5 6 5 6 5 5 6 5 6 7 5 6 5 6 9 -2 0 -0 2 -1 1 +-- { echoOn } + +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1; +QUERY id: 0 + PROJECTION COLUMNS + t1.a UInt64 + t1.b UInt64 + t2.a UInt64 + t2.b UInt64 + x UInt64 + PROJECTION + LIST id: 1, nodes: 5 + COLUMN id: 2, column_name: a, result_type: UInt64, source_id: 3 + COLUMN id: 4, column_name: b, result_type: UInt64, source_id: 3 + COLUMN id: 5, column_name: a, result_type: UInt64, source_id: 6 + COLUMN id: 7, column_name: b, result_type: UInt64, source_id: 6 + COLUMN id: 8, column_name: x, result_type: UInt64, source_id: 9 + JOIN TREE + JOIN id: 10, strictness: ALL, kind: INNER + LEFT TABLE EXPRESSION + JOIN id: 11, strictness: ALL, kind: INNER + LEFT TABLE EXPRESSION + TABLE id: 3, table_name: default.t1 + RIGHT TABLE EXPRESSION + TABLE id: 6, table_name: default.t2 + JOIN EXPRESSION + FUNCTION id: 12, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 13, nodes: 2 + COLUMN id: 14, column_name: a, result_type: UInt64, source_id: 3 + FUNCTION id: 15, function_name: if, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 16, nodes: 3 + FUNCTION id: 17, function_name: greater, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 18, nodes: 2 + COLUMN id: 19, column_name: b, result_type: UInt64, source_id: 6 + CONSTANT id: 20, constant_value: UInt64_0, constant_value_type: UInt8 + COLUMN id: 21, column_name: a, result_type: UInt64, source_id: 6 + CONSTANT id: 22, constant_value: UInt64_0, constant_value_type: UInt8 + RIGHT TABLE EXPRESSION + QUERY id: 9, alias: t3, is_subquery: 1 + PROJECTION COLUMNS + x UInt64 + PROJECTION + LIST id: 23, nodes: 1 + COLUMN id: 24, column_name: a, result_type: UInt64, source_id: 25 + JOIN TREE + TABLE id: 25, table_name: default.t3 + WHERE + FUNCTION id: 26, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 27, nodes: 2 + FUNCTION id: 28, function_name: plus, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 29, nodes: 2 + COLUMN id: 24, column_name: a, result_type: UInt64, source_id: 25 + CONSTANT id: 30, constant_value: UInt64_1, constant_value_type: UInt8 + COLUMN id: 31, column_name: b, result_type: UInt64, source_id: 25 + JOIN EXPRESSION + FUNCTION id: 32, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 33, nodes: 2 + COLUMN id: 21, column_name: a, result_type: UInt64, source_id: 6 + COLUMN id: 34, column_name: x, result_type: UInt64, source_id: 9 + WHERE + CONSTANT id: 35, constant_value: UInt64_1, constant_value_type: UInt8 +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 +SETTINGS cross_to_inner_join_rewrite = 0; +QUERY id: 0 + PROJECTION COLUMNS + t1.a UInt64 + t1.b UInt64 + t2.a UInt64 + t2.b UInt64 + x UInt64 + PROJECTION + LIST id: 1, nodes: 5 + COLUMN id: 2, column_name: a, result_type: UInt64, source_id: 3 + COLUMN id: 4, column_name: b, result_type: UInt64, source_id: 3 + COLUMN id: 5, column_name: a, result_type: UInt64, source_id: 6 + COLUMN id: 7, column_name: b, result_type: UInt64, source_id: 6 + COLUMN id: 8, column_name: x, result_type: UInt64, source_id: 9 + JOIN TREE + JOIN id: 10, kind: COMMA + LEFT TABLE EXPRESSION + JOIN id: 11, kind: COMMA + LEFT TABLE EXPRESSION + TABLE id: 3, table_name: default.t1 + RIGHT TABLE EXPRESSION + TABLE id: 6, table_name: default.t2 + RIGHT TABLE EXPRESSION + QUERY id: 9, alias: t3, is_subquery: 1 + PROJECTION COLUMNS + x UInt64 + PROJECTION + LIST id: 12, nodes: 1 + COLUMN id: 13, column_name: a, result_type: UInt64, source_id: 14 + JOIN TREE + TABLE id: 14, table_name: default.t3 + WHERE + FUNCTION id: 15, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 16, nodes: 2 + FUNCTION id: 17, function_name: plus, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 18, nodes: 2 + COLUMN id: 13, column_name: a, result_type: UInt64, source_id: 14 + CONSTANT id: 19, constant_value: UInt64_1, constant_value_type: UInt8 + COLUMN id: 20, column_name: b, result_type: UInt64, source_id: 14 + WHERE + FUNCTION id: 21, function_name: and, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 22, nodes: 3 + FUNCTION id: 23, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 24, nodes: 2 + COLUMN id: 25, column_name: a, result_type: UInt64, source_id: 3 + FUNCTION id: 26, function_name: if, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 27, nodes: 3 + FUNCTION id: 28, function_name: greater, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 29, nodes: 2 + COLUMN id: 30, column_name: b, result_type: UInt64, source_id: 6 + CONSTANT id: 31, constant_value: UInt64_0, constant_value_type: UInt8 + COLUMN id: 32, column_name: a, result_type: UInt64, source_id: 6 + CONSTANT id: 33, constant_value: UInt64_0, constant_value_type: UInt8 + FUNCTION id: 34, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 35, nodes: 2 + COLUMN id: 32, column_name: a, result_type: UInt64, source_id: 6 + COLUMN id: 36, column_name: x, result_type: UInt64, source_id: 9 + CONSTANT id: 37, constant_value: UInt64_1, constant_value_type: UInt8 + SETTINGS cross_to_inner_join_rewrite=0 +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0); +QUERY id: 0 + PROJECTION COLUMNS + t1.a UInt64 + t1.b UInt64 + t2.a UInt64 + t2.b UInt64 + x UInt64 + PROJECTION + LIST id: 1, nodes: 5 + COLUMN id: 2, column_name: a, result_type: UInt64, source_id: 3 + COLUMN id: 4, column_name: b, result_type: UInt64, source_id: 3 + COLUMN id: 5, column_name: a, result_type: UInt64, source_id: 6 + COLUMN id: 7, column_name: b, result_type: UInt64, source_id: 6 + COLUMN id: 8, column_name: x, result_type: UInt64, source_id: 9 + JOIN TREE + JOIN id: 10, kind: COMMA + LEFT TABLE EXPRESSION + JOIN id: 11, strictness: ALL, kind: INNER + LEFT TABLE EXPRESSION + TABLE id: 3, table_name: default.t1 + RIGHT TABLE EXPRESSION + TABLE id: 6, table_name: default.t2 + JOIN EXPRESSION + FUNCTION id: 12, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 13, nodes: 2 + COLUMN id: 14, column_name: a, result_type: UInt64, source_id: 3 + FUNCTION id: 15, function_name: if, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 16, nodes: 3 + FUNCTION id: 17, function_name: greater, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 18, nodes: 2 + COLUMN id: 19, column_name: b, result_type: UInt64, source_id: 6 + CONSTANT id: 20, constant_value: UInt64_0, constant_value_type: UInt8 + COLUMN id: 21, column_name: a, result_type: UInt64, source_id: 6 + CONSTANT id: 22, constant_value: UInt64_0, constant_value_type: UInt8 + RIGHT TABLE EXPRESSION + QUERY id: 9, alias: t3, is_subquery: 1 + PROJECTION COLUMNS + x UInt64 + PROJECTION + LIST id: 23, nodes: 1 + COLUMN id: 24, column_name: a, result_type: UInt64, source_id: 25 + JOIN TREE + TABLE id: 25, table_name: default.t3 + WHERE + FUNCTION id: 26, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 27, nodes: 2 + FUNCTION id: 28, function_name: plus, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 29, nodes: 2 + COLUMN id: 24, column_name: a, result_type: UInt64, source_id: 25 + CONSTANT id: 30, constant_value: UInt64_1, constant_value_type: UInt8 + COLUMN id: 31, column_name: b, result_type: UInt64, source_id: 25 diff --git a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql index 42446252ea7..a83cd238982 100644 --- a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql +++ b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql @@ -24,35 +24,23 @@ WHERE t1.a = if(t2.b > 0, t2.a, 0) ORDER BY t1.a, t2.a, t3.x ; --- rewrite two joins -SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( - EXPLAIN QUERY TREE - SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 - WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 -) WHERE explain like '% JOIN % kind: %' -SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain -; +-- { echoOn } --- setting is disabled -SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( - EXPLAIN QUERY TREE - SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 - WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 - SETTINGS cross_to_inner_join_rewrite = 0 -) WHERE explain like '% JOIN % kind: %' -SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain -; +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1; --- only one join can be rewritten -SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( - EXPLAIN QUERY TREE - SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 - WHERE t1.a = if(t2.b > 0, t2.a, 0) -) WHERE explain like '% JOIN % kind: %' -SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain -; +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 +SETTINGS cross_to_inner_join_rewrite = 0; + +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0); + +-- { echoOff } --- throw in force mode SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 WHERE t1.a = if(t2.b > 0, t2.a, 0) SETTINGS cross_to_inner_join_rewrite = 2; -- { serverError INCORRECT_QUERY } From 08b0e3c6309f520cdf1dcc97fd205ba5d4ffbd19 Mon Sep 17 00:00:00 2001 From: kssenii Date: Tue, 21 Feb 2023 18:27:37 +0100 Subject: [PATCH 077/229] Fix style check --- tests/integration/test_named_collections/test.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_named_collections/test.py b/tests/integration/test_named_collections/test.py index 612b894461b..ba62880e9de 100644 --- a/tests/integration/test_named_collections/test.py +++ b/tests/integration/test_named_collections/test.py @@ -102,7 +102,10 @@ def test_access(cluster): ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] ) node.restart_clickhouse() - assert node.query("select collection['key1'] from system.named_collections").strip() == "value1" + assert ( + node.query("select collection['key1'] from system.named_collections").strip() + == "value1" + ) replace_in_users_config( node, "show_named_collections_secrets>1", "show_named_collections_secrets>0" ) @@ -110,7 +113,10 @@ def test_access(cluster): ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] ) node.restart_clickhouse() - assert node.query("select collection['key1'] from system.named_collections").strip() == "[HIDDEN]" + assert ( + node.query("select collection['key1'] from system.named_collections").strip() + == "[HIDDEN]" + ) replace_in_users_config( node, "show_named_collections_secrets>0", "show_named_collections_secrets>1" ) @@ -118,7 +124,10 @@ def test_access(cluster): ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] ) node.restart_clickhouse() - assert node.query("select collection['key1'] from system.named_collections").strip() == "value1" + assert ( + node.query("select collection['key1'] from system.named_collections").strip() + == "value1" + ) def test_config_reload(cluster): From bf9f1663bb73bd969fcb35242b060c7e55a61024 Mon Sep 17 00:00:00 2001 From: avogar Date: Tue, 21 Feb 2023 18:15:16 +0000 Subject: [PATCH 078/229] Fix totals and extremes with constants in clickhouse-local --- src/Client/ClientBase.cpp | 4 ++-- .../02556_local_with_totals_and_extremes.reference | 6 ++++++ .../0_stateless/02556_local_with_totals_and_extremes.sh | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 tests/queries/0_stateless/02556_local_with_totals_and_extremes.reference create mode 100755 tests/queries/0_stateless/02556_local_with_totals_and_extremes.sh diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index bc8c43af8c6..96aff9aa304 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -481,14 +481,14 @@ void ClientBase::onLogData(Block & block) void ClientBase::onTotals(Block & block, ASTPtr parsed_query) { initOutputFormat(block, parsed_query); - output_format->setTotals(block); + output_format->setTotals(materializeBlock(block)); } void ClientBase::onExtremes(Block & block, ASTPtr parsed_query) { initOutputFormat(block, parsed_query); - output_format->setExtremes(block); + output_format->setExtremes(materializeBlock(block)); } diff --git a/tests/queries/0_stateless/02556_local_with_totals_and_extremes.reference b/tests/queries/0_stateless/02556_local_with_totals_and_extremes.reference new file mode 100644 index 00000000000..0b9836e530b --- /dev/null +++ b/tests/queries/0_stateless/02556_local_with_totals_and_extremes.reference @@ -0,0 +1,6 @@ +1,1 + +1,1 + +1,1 +1,1 diff --git a/tests/queries/0_stateless/02556_local_with_totals_and_extremes.sh b/tests/queries/0_stateless/02556_local_with_totals_and_extremes.sh new file mode 100755 index 00000000000..ef31b3855cd --- /dev/null +++ b/tests/queries/0_stateless/02556_local_with_totals_and_extremes.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_LOCAL -q "SELECT 1, sum(1) with totals format CSV settings extremes=1" + From 1f0ab8d427b7819baf13176234ae94acbd9addd7 Mon Sep 17 00:00:00 2001 From: kssenii Date: Tue, 21 Feb 2023 19:24:01 +0100 Subject: [PATCH 079/229] Hide disk setting arguemtns --- src/Core/Field.h | 6 +- src/Parsers/ASTSetQuery.cpp | 6 +- src/Parsers/FieldFromAST.cpp | 55 +++++++++++++++++++ src/Parsers/FieldFromAST.h | 4 +- ...54_create_table_with_custom_disk.reference | 2 +- 5 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/Core/Field.h b/src/Core/Field.h index 95ce43ccd44..2e772a64afc 100644 --- a/src/Core/Field.h +++ b/src/Core/Field.h @@ -108,7 +108,8 @@ struct CustomType { virtual ~CustomTypeImpl() = default; virtual const char * getTypeName() const = 0; - virtual String toString() const = 0; + virtual String toString(bool show_secrets) const = 0; + virtual bool isSecret() const = 0; virtual bool operator < (const CustomTypeImpl &) const = 0; virtual bool operator <= (const CustomTypeImpl &) const = 0; @@ -120,8 +121,9 @@ struct CustomType CustomType() = default; explicit CustomType(std::shared_ptr impl_) : impl(impl_) {} + bool isSecret() const { return impl->isSecret(); } const char * getTypeName() const { return impl->getTypeName(); } - String toString() const { return impl->toString(); } + String toString(bool show_secrets = true) const { return impl->toString(show_secrets); } const CustomTypeImpl & getImpl() { return *impl; } bool operator < (const CustomType & rhs) const { return *impl < *rhs.impl; } diff --git a/src/Parsers/ASTSetQuery.cpp b/src/Parsers/ASTSetQuery.cpp index 26420f4988c..0b8d76dbb89 100644 --- a/src/Parsers/ASTSetQuery.cpp +++ b/src/Parsers/ASTSetQuery.cpp @@ -34,7 +34,11 @@ void ASTSetQuery::formatImpl(const FormatSettings & format, FormatState &, Forma first = false; formatSettingName(change.name, format.ostr); - format.ostr << " = " << applyVisitor(FieldVisitorToString(), change.value); + CustomType custom; + if (!format.show_secrets && change.value.tryGet(custom) && custom.isSecret()) + format.ostr << " = " << custom.toString(false); + else + format.ostr << " = " << applyVisitor(FieldVisitorToString(), change.value); } for (const auto & setting_name : default_settings) diff --git a/src/Parsers/FieldFromAST.cpp b/src/Parsers/FieldFromAST.cpp index 7b7302696ed..5889699c081 100644 --- a/src/Parsers/FieldFromAST.cpp +++ b/src/Parsers/FieldFromAST.cpp @@ -1,10 +1,18 @@ #include +#include +#include +#include +#include +#include +#include + namespace DB { namespace ErrorCodes { extern const int LOGICAL_ERROR; + extern const int BAD_ARGUMENTS; } Field createFieldFromAST(ASTPtr ast) @@ -17,4 +25,51 @@ Field createFieldFromAST(ASTPtr ast) throw Exception(ErrorCodes::LOGICAL_ERROR, "Method {} not implemented for {}", method, getTypeName()); } +bool FieldFromASTImpl::isSecret() const +{ + return isDiskFunction(ast); +} + +String FieldFromASTImpl::toString(bool show_secrets) const +{ + if (!show_secrets && isDiskFunction(ast)) + { + auto hidden = ast->clone(); + auto & disk_function = assert_cast(*hidden); + auto * disk_function_args_expr = assert_cast(disk_function.arguments.get()); + auto & disk_function_args = disk_function_args_expr->children; + + auto is_secret_arg = [](const std::string & arg_name) + { + return arg_name != "type"; + }; + + for (auto & arg : disk_function_args) + { + auto * setting_function = arg->as(); + if (!setting_function || setting_function->name != "equals") + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Bad format: expected equals function"); + + auto * function_args_expr = assert_cast(setting_function->arguments.get()); + if (!function_args_expr) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Bad format: expected arguments"); + + auto & function_args = function_args_expr->children; + if (function_args.empty()) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Bad format: expected non zero number of arguments"); + + auto * key_identifier = function_args[0]->as(); + if (!key_identifier) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Bad format: expected Identifier"); + + const std::string & key = key_identifier->name(); + if (is_secret_arg(key)) + function_args[1] = std::make_shared("[HIDDEN]"); + } + return serializeAST(*hidden); + } + + return serializeAST(*ast); +} + } diff --git a/src/Parsers/FieldFromAST.h b/src/Parsers/FieldFromAST.h index 132f7e3e705..a69c086a170 100644 --- a/src/Parsers/FieldFromAST.h +++ b/src/Parsers/FieldFromAST.h @@ -1,7 +1,6 @@ #pragma once #include #include -#include namespace DB { @@ -13,7 +12,8 @@ struct FieldFromASTImpl : public CustomType::CustomTypeImpl explicit FieldFromASTImpl(ASTPtr ast_) : ast(ast_) {} const char * getTypeName() const override { return name; } - String toString() const override { return serializeAST(*ast); } + String toString(bool show_secrets) const override; + bool isSecret() const override; [[noreturn]] void throwNotImplemented(std::string_view method) const; diff --git a/tests/queries/0_stateless/02454_create_table_with_custom_disk.reference b/tests/queries/0_stateless/02454_create_table_with_custom_disk.reference index 378722b5166..1d8610c59c9 100644 --- a/tests/queries/0_stateless/02454_create_table_with_custom_disk.reference +++ b/tests/queries/0_stateless/02454_create_table_with_custom_disk.reference @@ -6,6 +6,6 @@ ENGINE = MergeTree ORDER BY tuple() SETTINGS disk = disk(type = local, path = \'/var/lib/clickhouse/disks/local/\') 100 -CREATE TABLE default.test\n(\n `a` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS disk = disk(type = local, path = \'/var/lib/clickhouse/disks/local/\'), index_granularity = 8192 +CREATE TABLE default.test\n(\n `a` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS disk = disk(type = local, path = \'[HIDDEN]\'), index_granularity = 8192 a Int32 200 From cfef911f0d1be82e9a36edce0234e78438fd33c0 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 22 Feb 2023 00:32:55 +0300 Subject: [PATCH 080/229] Update 01710_normal_projections.sh --- tests/queries/0_stateless/01710_normal_projections.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01710_normal_projections.sh b/tests/queries/0_stateless/01710_normal_projections.sh index 8ee3f41ea28..3f2114b9a2b 100755 --- a/tests/queries/0_stateless/01710_normal_projections.sh +++ b/tests/queries/0_stateless/01710_normal_projections.sh @@ -4,7 +4,7 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh -$CLICKHOUSE_CLIENT -q "CREATE TABLE test_sort_proj (x UInt32, y UInt32, PROJECTION p (SELECT x, y ORDER BY y)) ENGINE = MergeTree ORDER BY x SETTINGS index_granularity=8192" +$CLICKHOUSE_CLIENT -q "CREATE TABLE test_sort_proj (x UInt32, y UInt32, PROJECTION p (SELECT x, y ORDER BY y)) ENGINE = MergeTree ORDER BY x SETTINGS index_granularity=8192, index_granularity_bytes='10Mi'" $CLICKHOUSE_CLIENT -q "insert into test_sort_proj select number, toUInt32(-number - 1) from numbers(100)" echo "select where x < 10" From c009c2f4cbf7333407734f92780d16c8a0aed908 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 22 Feb 2023 00:50:21 +0300 Subject: [PATCH 081/229] Update test_ttl_move_memory_usage.py --- .../test_s3_zero_copy_ttl/test_ttl_move_memory_usage.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration/test_s3_zero_copy_ttl/test_ttl_move_memory_usage.py b/tests/integration/test_s3_zero_copy_ttl/test_ttl_move_memory_usage.py index 29177b6a67b..5fbe426074f 100644 --- a/tests/integration/test_s3_zero_copy_ttl/test_ttl_move_memory_usage.py +++ b/tests/integration/test_s3_zero_copy_ttl/test_ttl_move_memory_usage.py @@ -2,6 +2,12 @@ import time import pytest + +# FIXME This test is too flaky +# https://github.com/ClickHouse/ClickHouse/issues/45887 + +pytestmark = pytest.mark.skip + from helpers.cluster import ClickHouseCluster From 8232966b9e923f12e711ac4bb036949c61eb87db Mon Sep 17 00:00:00 2001 From: Dmitry Novik Date: Tue, 21 Feb 2023 22:02:23 +0000 Subject: [PATCH 082/229] Add a comment --- src/Interpreters/AsynchronousInsertQueue.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Interpreters/AsynchronousInsertQueue.h b/src/Interpreters/AsynchronousInsertQueue.h index b8c7d9d285b..4cda5078801 100644 --- a/src/Interpreters/AsynchronousInsertQueue.h +++ b/src/Interpreters/AsynchronousInsertQueue.h @@ -91,6 +91,9 @@ private: ~InsertData() { auto it = entries.begin(); + // Entries must be destroyed in context of user who runs async insert. + // Each entry in the list may correspond to a different user, + // so we need to switch current thread's MemoryTracker parent on each iteration. while (it != entries.end()) { UserMemoryTrackerSwitcher switcher((*it)->user_memory_tracker); From ad1e5f391888cc5b1ac02b89fbdb19ecc201859d Mon Sep 17 00:00:00 2001 From: Dmitry Novik Date: Tue, 21 Feb 2023 22:06:17 +0000 Subject: [PATCH 083/229] Review fixes --- src/Common/CurrentThread.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Common/CurrentThread.cpp b/src/Common/CurrentThread.cpp index 95b316671a4..f5cc9658f7a 100644 --- a/src/Common/CurrentThread.cpp +++ b/src/Common/CurrentThread.cpp @@ -115,10 +115,11 @@ MemoryTracker * CurrentThread::getUserMemoryTracker() if (unlikely(!current_thread)) return nullptr; - if (auto group = current_thread->getThreadGroup()) - return group->memory_tracker.getParent(); + auto * tracker = current_thread->memory_tracker.getParent(); + while (tracker && tracker->level != VariableContext::User) + tracker = tracker->getParent(); - return nullptr; + return tracker; } void CurrentThread::flushUntrackedMemory() From 0bf0fe488eb4b6637e863c95c2ed8f07c4509ec8 Mon Sep 17 00:00:00 2001 From: AVMusorin Date: Sun, 19 Feb 2023 21:09:40 +0100 Subject: [PATCH 084/229] added last_exception_time column into distribution_queue table --- src/Storages/Distributed/DirectoryMonitor.cpp | 1 + src/Storages/Distributed/DirectoryMonitor.h | 1 + src/Storages/System/StorageSystemDistributionQueue.cpp | 2 ++ .../0_stateless/01555_system_distribution_queue_mask.reference | 2 +- .../0_stateless/01555_system_distribution_queue_mask.sql | 2 +- .../0_stateless/02117_show_create_table_system.reference | 3 ++- 6 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Storages/Distributed/DirectoryMonitor.cpp b/src/Storages/Distributed/DirectoryMonitor.cpp index 7aa7aac2ef3..cb6659e59ce 100644 --- a/src/Storages/Distributed/DirectoryMonitor.cpp +++ b/src/Storages/Distributed/DirectoryMonitor.cpp @@ -465,6 +465,7 @@ void StorageDistributedDirectoryMonitor::run() tryLogCurrentException(getLoggerName().data()); status.last_exception = std::current_exception(); + status.last_exception_time = std::chrono::system_clock::now(); } } else diff --git a/src/Storages/Distributed/DirectoryMonitor.h b/src/Storages/Distributed/DirectoryMonitor.h index 7015fca0311..030d6acf6e2 100644 --- a/src/Storages/Distributed/DirectoryMonitor.h +++ b/src/Storages/Distributed/DirectoryMonitor.h @@ -58,6 +58,7 @@ public: struct InternalStatus { std::exception_ptr last_exception; + std::chrono::system_clock::time_point last_exception_time; size_t error_count = 0; diff --git a/src/Storages/System/StorageSystemDistributionQueue.cpp b/src/Storages/System/StorageSystemDistributionQueue.cpp index 5297c4eb93c..34cff7df65d 100644 --- a/src/Storages/System/StorageSystemDistributionQueue.cpp +++ b/src/Storages/System/StorageSystemDistributionQueue.cpp @@ -101,6 +101,7 @@ NamesAndTypesList StorageSystemDistributionQueue::getNamesAndTypes() { "broken_data_files", std::make_shared() }, { "broken_data_compressed_bytes", std::make_shared() }, { "last_exception", std::make_shared() }, + { "last_exception_time", std::make_shared() }, }; } @@ -190,6 +191,7 @@ void StorageSystemDistributionQueue::fillData(MutableColumns & res_columns, Cont res_columns[col_num++]->insert(getExceptionMessage(status.last_exception, false)); else res_columns[col_num++]->insertDefault(); + res_columns[col_num++]->insert(static_cast(std::chrono::system_clock::to_time_t(status.last_exception_time))); } } } diff --git a/tests/queries/0_stateless/01555_system_distribution_queue_mask.reference b/tests/queries/0_stateless/01555_system_distribution_queue_mask.reference index bd0eac10816..745160a517e 100644 --- a/tests/queries/0_stateless/01555_system_distribution_queue_mask.reference +++ b/tests/queries/0_stateless/01555_system_distribution_queue_mask.reference @@ -1,4 +1,4 @@ masked -3,"default:*@127%2E0%2E0%2E1:9000,default:*@127%2E0%2E0%2E2:9000" +3,"default:*@127%2E0%2E0%2E1:9000,default:*@127%2E0%2E0%2E2:9000","AUTHENTICATION_FAILED",1 no masking 1,"default@localhost:9000" diff --git a/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql b/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql index bdcde1adbad..285e93a4f90 100644 --- a/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql +++ b/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql @@ -18,7 +18,7 @@ create table dist_01555 (key Int) Engine=Distributed(test_cluster_with_incorrect insert into dist_01555 values (1)(2); -- since test_cluster_with_incorrect_pw contains incorrect password ignore error system flush distributed dist_01555; -- { serverError 516; } -select length(splitByChar('*', data_path)), replaceRegexpOne(data_path, '^.*/([^/]*)/' , '\\1') from system.distribution_queue where database = currentDatabase() and table = 'dist_01555' format CSV; +select length(splitByChar('*', data_path)), replaceRegexpOne(data_path, '^.*/([^/]*)/' , '\\1'), extract(last_exception, 'AUTHENTICATION_FAILED'), dateDiff('s', last_exception_time, now()) < 5 from system.distribution_queue where database = currentDatabase() and table = 'dist_01555' format CSV; drop table dist_01555; 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 aabe05ea5e2..1840c5aa5a3 100644 --- a/tests/queries/0_stateless/02117_show_create_table_system.reference +++ b/tests/queries/0_stateless/02117_show_create_table_system.reference @@ -229,7 +229,8 @@ CREATE TABLE system.distribution_queue `data_compressed_bytes` UInt64, `broken_data_files` UInt64, `broken_data_compressed_bytes` UInt64, - `last_exception` String + `last_exception` String, + `last_exception_time` DateTime ) ENGINE = SystemDistributionQueue COMMENT 'SYSTEM TABLE is built on the fly.' From ef33d11e3fbc9c8eebf6ed1b2dfd40eac9a32a75 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Tue, 21 Feb 2023 18:40:11 -0800 Subject: [PATCH 085/229] Refactor code according to code review --- src/Functions/FunctionsHashing.h | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/Functions/FunctionsHashing.h b/src/Functions/FunctionsHashing.h index 6bf1a2db3ac..59d573df3d1 100644 --- a/src/Functions/FunctionsHashing.h +++ b/src/Functions/FunctionsHashing.h @@ -1025,27 +1025,19 @@ private: if constexpr (Impl::use_int_hash_for_pods) { - if constexpr (std::endian::native == std::endian::little) + if constexpr (std::is_same_v) { - if constexpr (std::is_same_v) - h = IntHash64Impl::apply(bit_cast(vec_from[i])); - else - h = IntHash32Impl::apply(bit_cast(vec_from[i])); + UInt64 v = bit_cast(vec_from[i]); + if constexpr (std::endian::native == std::endian::big) + v = __builtin_bswap64(v); + h = IntHash64Impl::apply(v); } else { - if constexpr (std::is_same_v) - { - UInt64 v = bit_cast(vec_from[i]); - v = __builtin_bswap64(v); - h = IntHash64Impl::apply(v); - } - else - { - UInt32 v = bit_cast(vec_from[i]); + UInt32 v = bit_cast(vec_from[i]); + if constexpr (std::endian::native == std::endian::big) v = __builtin_bswap32(v); - h = IntHash32Impl::apply(v); - } + h = IntHash32Impl::apply(v); } } else From 2ca47a6eb60ba886f689122ab6e8b22b2d2bbb84 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 22 Feb 2023 10:41:57 +0100 Subject: [PATCH 086/229] BackgroundSchedulePool should not have any query context BackgroundSchedulePool is used for some peridic jobs, not from the query context, i.e. flush of Buffer table. And for such jobs there cannot be any query context, and more importantly it will not work correctly since that query_context will eventually expires. And this is the reason of this failures [1]. [1]: https://s3.amazonaws.com/clickhouse-test-reports/46668/015991bc5e20c787851050c2eaa13f0fef3aac00/stateless_tests_flaky_check__asan_.html Signed-off-by: Azat Khuzhin --- src/Core/BackgroundSchedulePool.cpp | 28 ------------------- src/Core/BackgroundSchedulePool.h | 5 ---- src/Interpreters/ConcurrentHashJoin.h | 1 - .../MergeTree/MergeTreePrefetchedReadPool.h | 1 - 4 files changed, 35 deletions(-) diff --git a/src/Core/BackgroundSchedulePool.cpp b/src/Core/BackgroundSchedulePool.cpp index 165d8902e85..993cfb6ef04 100644 --- a/src/Core/BackgroundSchedulePool.cpp +++ b/src/Core/BackgroundSchedulePool.cpp @@ -252,36 +252,10 @@ void BackgroundSchedulePool::cancelDelayedTask(const TaskInfoPtr & task, std::lo } -scope_guard BackgroundSchedulePool::attachToThreadGroup() -{ - scope_guard guard = [&]() - { - if (thread_group) - CurrentThread::detachQueryIfNotDetached(); - }; - - std::lock_guard lock(delayed_tasks_mutex); - - if (thread_group) - { - /// Put all threads to one thread pool - CurrentThread::attachTo(thread_group); - } - else - { - CurrentThread::initializeQuery(); - thread_group = CurrentThread::getGroup(); - } - return guard; -} - - void BackgroundSchedulePool::threadFunction() { setThreadName(thread_name.c_str()); - auto detach_thread_guard = attachToThreadGroup(); - while (!shutdown) { TaskInfoPtr task; @@ -311,8 +285,6 @@ void BackgroundSchedulePool::delayExecutionThreadFunction() { setThreadName((thread_name + "/D").c_str()); - auto detach_thread_guard = attachToThreadGroup(); - while (!shutdown) { TaskInfoPtr task; diff --git a/src/Core/BackgroundSchedulePool.h b/src/Core/BackgroundSchedulePool.h index ba1be312f27..0fb70b1f715 100644 --- a/src/Core/BackgroundSchedulePool.h +++ b/src/Core/BackgroundSchedulePool.h @@ -90,13 +90,8 @@ private: /// Tasks ordered by scheduled time. DelayedTasks delayed_tasks; - /// Thread group used for profiling purposes - ThreadGroupStatusPtr thread_group; - CurrentMetrics::Metric tasks_metric; std::string thread_name; - - [[nodiscard]] scope_guard attachToThreadGroup(); }; diff --git a/src/Interpreters/ConcurrentHashJoin.h b/src/Interpreters/ConcurrentHashJoin.h index a00c3ed1326..5e53f9845aa 100644 --- a/src/Interpreters/ConcurrentHashJoin.h +++ b/src/Interpreters/ConcurrentHashJoin.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include diff --git a/src/Storages/MergeTree/MergeTreePrefetchedReadPool.h b/src/Storages/MergeTree/MergeTreePrefetchedReadPool.h index bad158cd7a7..98cfe28c563 100644 --- a/src/Storages/MergeTree/MergeTreePrefetchedReadPool.h +++ b/src/Storages/MergeTree/MergeTreePrefetchedReadPool.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #include From d4bb84e68b6b91e9168df1555ecd08ecf53fb547 Mon Sep 17 00:00:00 2001 From: vdimir Date: Wed, 22 Feb 2023 09:56:10 +0000 Subject: [PATCH 087/229] make clang-tidy happy about CrossToInnerJoinPass --- src/Analyzer/Passes/CrossToInnerJoinPass.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Analyzer/Passes/CrossToInnerJoinPass.cpp b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp index 400c760cd20..0c4fa0ed82f 100644 --- a/src/Analyzer/Passes/CrossToInnerJoinPass.cpp +++ b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp @@ -42,7 +42,7 @@ void exctractJoinConditions(const QueryTreeNodePtr & node, QueryTreeNodes & equi } else if (func->getFunctionName() == "and") { - for (auto & arg : args) + for (const auto & arg : args) exctractJoinConditions(arg, equi_conditions, other); } else @@ -77,7 +77,7 @@ std::pair getExpressionSource(const QueryTreeNodeP { const IQueryTreeNode * source = nullptr; const auto & args = func->getArguments().getNodes(); - for (auto & arg : args) + for (const auto & arg : args) { auto [arg_source, is_ok] = getExpressionSource(arg); if (!is_ok) @@ -232,7 +232,7 @@ private: return nodes.front(); auto function_node = std::make_shared("and"); - for (auto & node : nodes) + for (const auto & node : nodes) function_node->getArguments().getNodes().push_back(node); const auto & function = FunctionFactory::instance().get("and", getContext()); From 16d61832fbb032d42ebbd869488ae2ea32fec9a0 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 22 Feb 2023 10:03:08 +0000 Subject: [PATCH 088/229] Bump minimum required Clang from 12 to 15 Needed due to https://github.com/ClickHouse/ClickHouse/pull/46247#discussion_r1109855435 --- cmake/tools.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/tools.cmake b/cmake/tools.cmake index 84376d13d9b..4d4d741cc3a 100644 --- a/cmake/tools.cmake +++ b/cmake/tools.cmake @@ -15,7 +15,7 @@ execute_process(COMMAND ${CMAKE_CXX_COMPILER} --version OUTPUT_VARIABLE COMPILER message (STATUS "Using compiler:\n${COMPILER_SELF_IDENTIFICATION}") # Require minimum compiler versions -set (CLANG_MINIMUM_VERSION 12) +set (CLANG_MINIMUM_VERSION 15) set (XCODE_MINIMUM_VERSION 12.0) set (APPLE_CLANG_MINIMUM_VERSION 12.0.0) set (GCC_MINIMUM_VERSION 11) From a4919ce3a20f93404512561e7e8655ff412df346 Mon Sep 17 00:00:00 2001 From: vdimir Date: Wed, 22 Feb 2023 10:13:39 +0000 Subject: [PATCH 089/229] Add doc for temporary_data_in_cache --- .../settings.md | 90 ++++++++++++++++--- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/docs/en/operations/server-configuration-parameters/settings.md b/docs/en/operations/server-configuration-parameters/settings.md index da42b31b78a..75ae6f3d2bc 100644 --- a/docs/en/operations/server-configuration-parameters/settings.md +++ b/docs/en/operations/server-configuration-parameters/settings.md @@ -1539,33 +1539,103 @@ Example 9005 ``` + ## tmp_path {#tmp-path} -Path to temporary data for processing large queries. +Path on the local filesystem to store temporary data for processing large queries. :::note -The trailing slash is mandatory. +- Only one option can be used to configure temporary data storage: `tmp_path` ,`tmp_policy`, `temporary_data_in_cache`. +- The trailing slash is mandatory. ::: **Example** -``` xml +```xml /var/lib/clickhouse/tmp/ ``` ## tmp_policy {#tmp-policy} -Policy from [storage_configuration](../../engines/table-engines/mergetree-family/mergetree.md#table_engine-mergetree-multiple-volumes) to store temporary files. - -If not set, [tmp_path](#tmp-path) is used, otherwise it is ignored. +Alternatively, a policy from [storage_configuration](../../engines/table-engines/mergetree-family/mergetree.md#table_engine-mergetree-multiple-volumes) can be used to store temporary files. :::note -- `move_factor` is ignored. -- `keep_free_space_bytes` is ignored. -- `max_data_part_size_bytes` is ignored. -- Policy should have exactly one volume with local disks. +- Only one option can be used to configure temporary data storage: `tmp_path` ,`tmp_policy`, `temporary_data_in_cache`. +- `move_factor`, `keep_free_space_bytes`,`max_data_part_size_bytes` and are ignored. +- Policy should have exactly *one volume* with *local* disks. ::: +**Example** + +```xml + + + + /disk1/ + + + /disk2/ + + + + + + +
+ disk1 + disk2 +
+
+
+
+
+ + tmp_two_disks +
+ +``` + +When `/disk1` is full, temporary data will be stored on `/disk2`. + +## temporary_data_in_cache {#temporary-data-in-cache} + +With this option, temporary data will be stored in the cache for the particular disk. +In this section, you should specify the disk name with the type `cache`. +In that case, the cache and temporary data will share the same space, and the disk cache can be evicted to create temporary data. + +:::note +- Only one option can be used to configure temporary data storage: `tmp_path` ,`tmp_policy`, `temporary_data_in_cache`. +::: + +**Example** + +```xml + + + + + local + /local_disk/ + + + + cache + local_disk + /tiny_local_cache/ + 10M + 1M + 1 + 0 + + + + + tiny_local_cache + +``` + +Cache for `local_disk` and temporary data will be stored in `/tiny_local_cache` on the filesystem, managed by `tiny_local_cache`. + ## max_temporary_data_on_disk_size {#max_temporary_data_on_disk_size} Limit the amount of disk space consumed by temporary files in `tmp_path` for the server. From 678e4250cd40c8849a0c01e1de70545455cf0c06 Mon Sep 17 00:00:00 2001 From: flynn Date: Wed, 22 Feb 2023 18:54:19 +0800 Subject: [PATCH 090/229] Fix incorrect predicate push down with grouping sets (#46151) --- .../PredicateExpressionsOptimizer.cpp | 3 +- src/Processors/QueryPlan/AggregatingStep.h | 2 + .../Optimizations/filterPushDown.cpp | 61 ++++++++++ ...rouping_sets_predicate_push_down.reference | 62 ++++++++++ ..._fix_grouping_sets_predicate_push_down.sql | 109 ++++++++++++++++++ 5 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/02554_fix_grouping_sets_predicate_push_down.reference create mode 100644 tests/queries/0_stateless/02554_fix_grouping_sets_predicate_push_down.sql diff --git a/src/Interpreters/PredicateExpressionsOptimizer.cpp b/src/Interpreters/PredicateExpressionsOptimizer.cpp index d9ea29fe1d8..6606e64f689 100644 --- a/src/Interpreters/PredicateExpressionsOptimizer.cpp +++ b/src/Interpreters/PredicateExpressionsOptimizer.cpp @@ -35,7 +35,8 @@ bool PredicateExpressionsOptimizer::optimize(ASTSelectQuery & select_query) if (!enable_optimize_predicate_expression) return false; - if (select_query.having() && (!select_query.group_by_with_cube && !select_query.group_by_with_rollup && !select_query.group_by_with_totals)) + const bool has_incompatible_constructs = select_query.group_by_with_cube || select_query.group_by_with_rollup || select_query.group_by_with_totals || select_query.group_by_with_grouping_sets; + if (select_query.having() && !has_incompatible_constructs) tryMovePredicatesFromHavingToWhere(select_query); if (!select_query.tables() || select_query.tables()->children.empty()) diff --git a/src/Processors/QueryPlan/AggregatingStep.h b/src/Processors/QueryPlan/AggregatingStep.h index d395e94c58b..5f5557fb204 100644 --- a/src/Processors/QueryPlan/AggregatingStep.h +++ b/src/Processors/QueryPlan/AggregatingStep.h @@ -56,6 +56,8 @@ public: const Aggregator::Params & getParams() const { return params; } + const auto & getGroupingSetsParamsList() const { return grouping_sets_params; } + bool inOrder() const { return !sort_description_for_merging.empty(); } bool explicitSortingRequired() const { return explicit_sorting_required_for_aggregation_in_order; } bool isGroupingSets() const { return !grouping_sets_params.empty(); } diff --git a/src/Processors/QueryPlan/Optimizations/filterPushDown.cpp b/src/Processors/QueryPlan/Optimizations/filterPushDown.cpp index 46fe3055e32..d466c52725f 100644 --- a/src/Processors/QueryPlan/Optimizations/filterPushDown.cpp +++ b/src/Processors/QueryPlan/Optimizations/filterPushDown.cpp @@ -53,6 +53,53 @@ static void checkChildrenSize(QueryPlan::Node * node, size_t child_num) child_num, child->getInputStreams().size(), node->children.size()); } +static bool identifiersIsAmongAllGroupingSets(const GroupingSetsParamsList & grouping_sets_params, const NameSet & identifiers_in_predicate) +{ + for (const auto & grouping_set : grouping_sets_params) + { + for (const auto & identifier : identifiers_in_predicate) + { + if (std::find(grouping_set.used_keys.begin(), grouping_set.used_keys.end(), identifier) == grouping_set.used_keys.end()) + return false; + } + } + return true; +} + +static NameSet findIdentifiersOfNode(const ActionsDAG::Node * node) +{ + NameSet res; + + /// We treat all INPUT as identifier + if (node->type == ActionsDAG::ActionType::INPUT) + { + res.emplace(node->result_name); + return res; + } + + std::queue queue; + queue.push(node); + + while (!queue.empty()) + { + const auto * top = queue.front(); + for (const auto * child : top->children) + { + if (child->type == ActionsDAG::ActionType::INPUT) + { + res.emplace(child->result_name); + } + else + { + /// Only push non INPUT child into the queue + queue.push(child); + } + } + queue.pop(); + } + return res; +} + static ActionsDAGPtr splitFilter(QueryPlan::Node * parent_node, const Names & allowed_inputs, size_t child_idx = 0) { QueryPlan::Node * child_node = parent_node->children.front(); @@ -176,6 +223,20 @@ size_t tryPushDownFilter(QueryPlan::Node * parent_node, QueryPlan::Nodes & nodes if (auto * aggregating = typeid_cast(child.get())) { + /// If aggregating is GROUPING SETS, and not all the identifiers exist in all + /// of the grouping sets, we could not push the filter down. + if (aggregating->isGroupingSets()) + { + + const auto & actions = filter->getExpression(); + const auto & filter_node = actions->findInOutputs(filter->getFilterColumnName()); + + auto identifiers_in_predicate = findIdentifiersOfNode(&filter_node); + + if (!identifiersIsAmongAllGroupingSets(aggregating->getGroupingSetsParamsList(), identifiers_in_predicate)) + return 0; + } + const auto & params = aggregating->getParams(); const auto & keys = params.keys; diff --git a/tests/queries/0_stateless/02554_fix_grouping_sets_predicate_push_down.reference b/tests/queries/0_stateless/02554_fix_grouping_sets_predicate_push_down.reference new file mode 100644 index 00000000000..440f668c614 --- /dev/null +++ b/tests/queries/0_stateless/02554_fix_grouping_sets_predicate_push_down.reference @@ -0,0 +1,62 @@ +---Explain Syntax--- +SELECT + day_, + type_1 +FROM +( + SELECT + day_, + if(type_1 = \'\', \'all\', type_1) AS type_1 + FROM + ( + SELECT + day_, + type_1 + FROM test_grouping_sets_predicate + PREWHERE day_ = \'2023-01-05\' + GROUP BY + GROUPING SETS ( + (day_, type_1), + (day_)) + HAVING if(type_1 = \'\', \'all\', type_1) = \'all\' + ) AS t + WHERE type_1 = \'all\' +) +WHERE type_1 = \'all\' + +---Explain Pipeline--- +(Expression) +ExpressionTransform × 2 + (Filter) + FilterTransform × 2 + (Filter) + FilterTransform × 2 + (Filter) + FilterTransform × 2 + (Aggregating) + ExpressionTransform × 2 + AggregatingTransform × 2 + Copy 1 → 2 + (Expression) + ExpressionTransform + (ReadFromMergeTree) + MergeTreeInOrder 0 → 1 + +---Result--- +2023-01-05 all + +---Explain Pipeline--- +(Expression) +ExpressionTransform × 2 + (Aggregating) + ExpressionTransform × 2 + AggregatingTransform × 2 + Copy 1 → 2 + (Filter) + FilterTransform + (Filter) + FilterTransform + (Filter) + FilterTransform + (ReadFromMergeTree) + MergeTreeInOrder 0 → 1 diff --git a/tests/queries/0_stateless/02554_fix_grouping_sets_predicate_push_down.sql b/tests/queries/0_stateless/02554_fix_grouping_sets_predicate_push_down.sql new file mode 100644 index 00000000000..9a970674890 --- /dev/null +++ b/tests/queries/0_stateless/02554_fix_grouping_sets_predicate_push_down.sql @@ -0,0 +1,109 @@ +DROP TABLE IF EXISTS test_grouping_sets_predicate; + +CREATE TABLE test_grouping_sets_predicate +( + day_ Date, + type_1 String +) +ENGINE=MergeTree +ORDER BY day_; + +INSERT INTO test_grouping_sets_predicate SELECT + toDate('2023-01-05') AS day_, + 'hello, world' +FROM numbers (10); + +SELECT '---Explain Syntax---'; +EXPLAIN SYNTAX +SELECT * +FROM +( + SELECT + day_, + if(type_1 = '', 'all', type_1) AS type_1 + FROM + ( + SELECT + day_, + type_1 + FROM test_grouping_sets_predicate + WHERE day_ = '2023-01-05' + GROUP BY + GROUPING SETS ( + (day_, type_1), + (day_)) + ) AS t +) +WHERE type_1 = 'all'; + +SELECT ''; +SELECT '---Explain Pipeline---'; +EXPLAIN PIPELINE +SELECT * +FROM +( + SELECT + day_, + if(type_1 = '', 'all', type_1) AS type_1 + FROM + ( + SELECT + day_, + type_1 + FROM test_grouping_sets_predicate + WHERE day_ = '2023-01-05' + GROUP BY + GROUPING SETS ( + (day_, type_1), + (day_)) + ) AS t +) +WHERE type_1 = 'all'; + +SELECT ''; +SELECT '---Result---'; +SELECT * +FROM +( + SELECT + day_, + if(type_1 = '', 'all', type_1) AS type_1 + FROM + ( + SELECT + day_, + type_1 + FROM test_grouping_sets_predicate + WHERE day_ = '2023-01-05' + GROUP BY + GROUPING SETS ( + (day_, type_1), + (day_)) + ) AS t +) +WHERE type_1 = 'all'; + +SELECT ''; +SELECT '---Explain Pipeline---'; +EXPLAIN PIPELINE +SELECT * +FROM +( + SELECT + day_, + if(type_1 = '', 'all', type_1) AS type_1 + FROM + ( + SELECT + day_, + type_1 + FROM test_grouping_sets_predicate + GROUP BY + GROUPING SETS ( + (day_, type_1), + (day_)) + ) AS t +) +WHERE day_ = '2023-01-05'; + +DROP TABLE test_grouping_sets_predicate; From 21fcc3b69c6355945617e1a5d77aa57de4694e91 Mon Sep 17 00:00:00 2001 From: kssenii Date: Wed, 22 Feb 2023 12:01:18 +0100 Subject: [PATCH 091/229] Add iceberg doc --- .../table-engines/integrations/deltalake.md | 23 +++++++- .../table-engines/integrations/hudi.md | 23 +++++++- .../table-engines/integrations/iceberg.md | 52 +++++++++++++++++ .../sql-reference/table-functions/iceberg.md | 58 +++++++++++++++++++ 4 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 docs/en/engines/table-engines/integrations/iceberg.md create mode 100644 docs/en/sql-reference/table-functions/iceberg.md diff --git a/docs/en/engines/table-engines/integrations/deltalake.md b/docs/en/engines/table-engines/integrations/deltalake.md index 83526ac944d..64ef7ec4dfc 100644 --- a/docs/en/engines/table-engines/integrations/deltalake.md +++ b/docs/en/engines/table-engines/integrations/deltalake.md @@ -19,7 +19,9 @@ CREATE TABLE deltalake **Engine parameters** - `url` — Bucket url with path to the existing Delta Lake table. -- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. For more information see [Using S3 for Data Storage](../mergetree-family/mergetree.md#table_engine-mergetree-s3). +- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. + +Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) **Example** @@ -27,7 +29,24 @@ CREATE TABLE deltalake CREATE TABLE deltalake ENGINE=DeltaLake('http://mars-doc-test.s3.amazonaws.com/clickhouse-bucket-3/test_table/', 'ABC123', 'Abc+123') ``` +Using named collections: + +``` xml + + + + http://mars-doc-test.s3.amazonaws.com/clickhouse-bucket-3/ + ABC123 + Abc+123 + + + +``` + +```sql +CREATE TABLE iceberg_table ENGINE=DeltaLake(deltalake_conf, filename = 'test_table') +``` + ## See also - [deltaLake table function](../../../sql-reference/table-functions/deltalake.md) - diff --git a/docs/en/engines/table-engines/integrations/hudi.md b/docs/en/engines/table-engines/integrations/hudi.md index 4e335e6c075..eb916b17cf9 100644 --- a/docs/en/engines/table-engines/integrations/hudi.md +++ b/docs/en/engines/table-engines/integrations/hudi.md @@ -19,7 +19,9 @@ CREATE TABLE hudi_table **Engine parameters** - `url` — Bucket url with the path to an existing Hudi table. -- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. For more information see [Using S3 for Data Storage](../mergetree-family/mergetree.md#table_engine-mergetree-s3). +- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. + +Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) **Example** @@ -27,7 +29,24 @@ CREATE TABLE hudi_table CREATE TABLE hudi_table ENGINE=Hudi('http://mars-doc-test.s3.amazonaws.com/clickhouse-bucket-3/test_table/', 'ABC123', 'Abc+123') ``` +Using named collections: + +``` xml + + + + http://mars-doc-test.s3.amazonaws.com/clickhouse-bucket-3/ + ABC123 + Abc+123 + + + +``` + +```sql +CREATE TABLE iceberg_table ENGINE=Hudi(hudi_conf, filename = 'test_table') +``` + ## See also - [hudi table function](/docs/en/sql-reference/table-functions/hudi.md) - diff --git a/docs/en/engines/table-engines/integrations/iceberg.md b/docs/en/engines/table-engines/integrations/iceberg.md new file mode 100644 index 00000000000..33ec5f877bf --- /dev/null +++ b/docs/en/engines/table-engines/integrations/iceberg.md @@ -0,0 +1,52 @@ +--- +slug: /en/engines/table-engines/integrations/iceberg +sidebar_label: Iceberg +--- + +# Iceberg Table Engine + +This engine provides a read-only integration with existing Apache [Iceberg](https://iceberg.apache.org/) tables in Amazon S3. + +## Create Table + +Note that the Iceberg table must already exist in S3, this command does not take DDL parameters to create a new table. + +``` sql +CREATE TABLE iceberg_table + ENGINE = Iceberg(url, [aws_access_key_id, aws_secret_access_key,]) +``` + +**Engine parameters** + +- `url` — url with the path to an existing Iceberg table. +- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. + +Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) + +**Example** + +```sql +CREATE TABLE iceberg_table ENGINE=Iceberg('http://test.s3.amazonaws.com/clickhouse-bucket/test_table', 'test', 'test') +``` + +Using named collections: + +``` xml + + + + http://test.s3.amazonaws.com/clickhouse-bucket/ + test + test + + + +``` + +```sql +CREATE TABLE iceberg_table ENGINE=Iceberg(iceberg_conf, filename = 'test_table') +``` + +## See also + +- [iceberg table function](/docs/en/sql-reference/table-functions/iceberg.md) diff --git a/docs/en/sql-reference/table-functions/iceberg.md b/docs/en/sql-reference/table-functions/iceberg.md new file mode 100644 index 00000000000..036c1379847 --- /dev/null +++ b/docs/en/sql-reference/table-functions/iceberg.md @@ -0,0 +1,58 @@ +--- +slug: /en/sql-reference/table-functions/iceberg +sidebar_label: Iceberg +--- + +# iceberg Table Function + +Provides a read-only table-like interface to Apache [Iceberg](https://iceberg.apache.org/) tables in Amazon S3. + +## Syntax + +``` sql +iceberg(url [,aws_access_key_id, aws_secret_access_key] [,format] [,structure]) +``` + +## Arguments + +- `url` — Bucket url with the path to an existing Iceberg table in S3. +- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. These parameters are optional. If credentials are not specified, they are used from the ClickHouse configuration. For more information see [Using S3 for Data Storage](/docs/en/engines/table-engines/mergetree-family/mergetree.md/#table_engine-mergetree-s3). +- `format` — The [format](/docs/en/interfaces/formats.md/#formats) of the file. By default `Parquet` is used. +- `structure` — Structure of the table. Format `'column1_name column1_type, column2_name column2_type, ...'`. + +Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) + +**Returned value** + +A table with the specified structure for reading data in the specified Iceberg table in S3. + +**Example** + +```sql +SELECT * FROM iceberg('http://test.s3.amazonaws.com/clickhouse-bucket/test_table', 'test', 'test') +``` + +Using named collections: + +```xml + + + + http://test.s3.amazonaws.com/clickhouse-bucket/ + test + test + auto + auto + + + +``` + +```sql +SELECT * FROM iceberg(iceberg_conf, filename = 'test_table') +DESCRIBE iceberg(iceberg_conf, filename = 'test_table') +``` + +**See Also** + +- [Iceberg engine](/docs/en/engines/table-engines/integrations/iceberg.md) From ef15d6489565a8486815cfd43500ebf6fa0729ed Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Wed, 22 Feb 2023 12:11:23 +0100 Subject: [PATCH 092/229] Update docs/en/engines/table-engines/integrations/deltalake.md Co-authored-by: flynn --- docs/en/engines/table-engines/integrations/deltalake.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/engines/table-engines/integrations/deltalake.md b/docs/en/engines/table-engines/integrations/deltalake.md index 64ef7ec4dfc..a2816c7ff57 100644 --- a/docs/en/engines/table-engines/integrations/deltalake.md +++ b/docs/en/engines/table-engines/integrations/deltalake.md @@ -44,7 +44,7 @@ Using named collections: ``` ```sql -CREATE TABLE iceberg_table ENGINE=DeltaLake(deltalake_conf, filename = 'test_table') +CREATE TABLE deltalake ENGINE=DeltaLake(deltalake_conf, filename = 'test_table') ``` ## See also From c242fe3e5e80c2bcc3259c47b8e896be3aa13952 Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Wed, 22 Feb 2023 12:11:42 +0100 Subject: [PATCH 093/229] Update docs/en/engines/table-engines/integrations/hudi.md Co-authored-by: flynn --- docs/en/engines/table-engines/integrations/hudi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/engines/table-engines/integrations/hudi.md b/docs/en/engines/table-engines/integrations/hudi.md index eb916b17cf9..6ff998d86d9 100644 --- a/docs/en/engines/table-engines/integrations/hudi.md +++ b/docs/en/engines/table-engines/integrations/hudi.md @@ -44,7 +44,7 @@ Using named collections: ``` ```sql -CREATE TABLE iceberg_table ENGINE=Hudi(hudi_conf, filename = 'test_table') +CREATE TABLE hudi_table ENGINE=Hudi(hudi_conf, filename = 'test_table') ``` ## See also From 98c10ff6e5751404c48106293befb212b126e7d1 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 22 Feb 2023 12:16:09 +0100 Subject: [PATCH 094/229] Update docs/en/operations/system-tables/processors_profile_log.md Co-authored-by: Nikita Taranov --- docs/en/operations/system-tables/processors_profile_log.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/operations/system-tables/processors_profile_log.md b/docs/en/operations/system-tables/processors_profile_log.md index 269385deab6..cc917138741 100644 --- a/docs/en/operations/system-tables/processors_profile_log.md +++ b/docs/en/operations/system-tables/processors_profile_log.md @@ -33,6 +33,7 @@ SELECT sleep(1) │ (ReadFromStorage) │ │ SourceFromSingleChunk 0 → 1 │ └─────────────────────────────────┘ + SELECT sleep(1) SETTINGS log_processors_profiles = 1 Query id: feb5ed16-1c24-4227-aa54-78c02b3b27d4 From ab94d6dc1831de374f1530c9c5e78bcc06227133 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 22 Feb 2023 12:16:19 +0100 Subject: [PATCH 095/229] Update docs/en/operations/system-tables/processors_profile_log.md Co-authored-by: Nikita Taranov --- docs/en/operations/system-tables/processors_profile_log.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/operations/system-tables/processors_profile_log.md b/docs/en/operations/system-tables/processors_profile_log.md index cc917138741..a2e7a9ebabd 100644 --- a/docs/en/operations/system-tables/processors_profile_log.md +++ b/docs/en/operations/system-tables/processors_profile_log.md @@ -41,6 +41,7 @@ Query id: feb5ed16-1c24-4227-aa54-78c02b3b27d4 │ 0 │ └──────────┘ 1 rows in set. Elapsed: 1.018 sec. + SELECT name, elapsed_us, From ceff5f41d14653a3c95208bcef963a18902db64d Mon Sep 17 00:00:00 2001 From: kssenii Date: Wed, 22 Feb 2023 12:23:14 +0100 Subject: [PATCH 096/229] Fix tests --- tests/config/users.d/access_management.xml | 1 + .../configs/users.d/users.xml | 1 + .../test_create_query_constraints/configs/users.xml | 1 + .../test_global_overcommit_tracker/configs/users.xml | 1 + .../test_grant_and_revoke/configs/users.d/users.xml | 1 + .../test_overcommit_tracker/configs/users.d/users.xml | 1 + .../configs/users.d/users.xml | 1 + tests/queries/0_stateless/01271_show_privileges.reference | 1 + .../0_stateless/02117_show_create_table_system.reference | 6 +++--- 9 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/config/users.d/access_management.xml b/tests/config/users.d/access_management.xml index 8f4d82805be..f7963cdb7f2 100644 --- a/tests/config/users.d/access_management.xml +++ b/tests/config/users.d/access_management.xml @@ -3,6 +3,7 @@ 1 1 + 1 diff --git a/tests/integration/test_access_control_on_cluster/configs/users.d/users.xml b/tests/integration/test_access_control_on_cluster/configs/users.d/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_access_control_on_cluster/configs/users.d/users.xml +++ b/tests/integration/test_access_control_on_cluster/configs/users.d/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_create_query_constraints/configs/users.xml b/tests/integration/test_create_query_constraints/configs/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_create_query_constraints/configs/users.xml +++ b/tests/integration/test_create_query_constraints/configs/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_global_overcommit_tracker/configs/users.xml b/tests/integration/test_global_overcommit_tracker/configs/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_global_overcommit_tracker/configs/users.xml +++ b/tests/integration/test_global_overcommit_tracker/configs/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_grant_and_revoke/configs/users.d/users.xml b/tests/integration/test_grant_and_revoke/configs/users.d/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_grant_and_revoke/configs/users.d/users.xml +++ b/tests/integration/test_grant_and_revoke/configs/users.d/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_overcommit_tracker/configs/users.d/users.xml b/tests/integration/test_overcommit_tracker/configs/users.d/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_overcommit_tracker/configs/users.d/users.xml +++ b/tests/integration/test_overcommit_tracker/configs/users.d/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_settings_constraints_distributed/configs/users.d/users.xml b/tests/integration/test_settings_constraints_distributed/configs/users.d/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_settings_constraints_distributed/configs/users.d/users.xml +++ b/tests/integration/test_settings_constraints_distributed/configs/users.d/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/queries/0_stateless/01271_show_privileges.reference b/tests/queries/0_stateless/01271_show_privileges.reference index 58b1cab6e20..c061eb95a65 100644 --- a/tests/queries/0_stateless/01271_show_privileges.reference +++ b/tests/queries/0_stateless/01271_show_privileges.reference @@ -90,6 +90,7 @@ SHOW QUOTAS ['SHOW CREATE QUOTA'] GLOBAL SHOW ACCESS SHOW SETTINGS PROFILES ['SHOW PROFILES','SHOW CREATE SETTINGS PROFILE','SHOW CREATE PROFILE'] GLOBAL SHOW ACCESS SHOW ACCESS [] \N ACCESS MANAGEMENT SHOW NAMED COLLECTIONS ['SHOW NAMED COLLECTIONS'] GLOBAL ACCESS MANAGEMENT +SHOW NAMED COLLECTIONS SECRETS ['SHOW NAMED COLLECTIONS SECRETS'] GLOBAL ACCESS MANAGEMENT ACCESS MANAGEMENT [] \N ALL SYSTEM SHUTDOWN ['SYSTEM KILL','SHUTDOWN'] GLOBAL SYSTEM SYSTEM DROP DNS CACHE ['SYSTEM DROP DNS','DROP DNS CACHE','DROP DNS'] GLOBAL SYSTEM DROP CACHE 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 aabe05ea5e2..fe93418aa6d 100644 --- a/tests/queries/0_stateless/02117_show_create_table_system.reference +++ b/tests/queries/0_stateless/02117_show_create_table_system.reference @@ -288,7 +288,7 @@ CREATE TABLE system.grants ( `user_name` Nullable(String), `role_name` Nullable(String), - `access_type` Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'ACCESS MANAGEMENT' = 92, 'SYSTEM SHUTDOWN' = 93, 'SYSTEM DROP DNS CACHE' = 94, 'SYSTEM DROP MARK CACHE' = 95, 'SYSTEM DROP UNCOMPRESSED CACHE' = 96, 'SYSTEM DROP MMAP CACHE' = 97, 'SYSTEM DROP QUERY CACHE' = 98, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 99, 'SYSTEM DROP FILESYSTEM CACHE' = 100, 'SYSTEM DROP SCHEMA CACHE' = 101, 'SYSTEM DROP S3 CLIENT CACHE' = 102, 'SYSTEM DROP CACHE' = 103, 'SYSTEM RELOAD CONFIG' = 104, 'SYSTEM RELOAD USERS' = 105, 'SYSTEM RELOAD SYMBOLS' = 106, 'SYSTEM RELOAD DICTIONARY' = 107, 'SYSTEM RELOAD MODEL' = 108, 'SYSTEM RELOAD FUNCTION' = 109, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 110, 'SYSTEM RELOAD' = 111, 'SYSTEM RESTART DISK' = 112, 'SYSTEM MERGES' = 113, 'SYSTEM TTL MERGES' = 114, 'SYSTEM FETCHES' = 115, 'SYSTEM MOVES' = 116, 'SYSTEM DISTRIBUTED SENDS' = 117, 'SYSTEM REPLICATED SENDS' = 118, 'SYSTEM SENDS' = 119, 'SYSTEM REPLICATION QUEUES' = 120, 'SYSTEM DROP REPLICA' = 121, 'SYSTEM SYNC REPLICA' = 122, 'SYSTEM RESTART REPLICA' = 123, 'SYSTEM RESTORE REPLICA' = 124, 'SYSTEM WAIT LOADING PARTS' = 125, 'SYSTEM SYNC DATABASE REPLICA' = 126, 'SYSTEM SYNC TRANSACTION LOG' = 127, 'SYSTEM SYNC FILE CACHE' = 128, 'SYSTEM FLUSH DISTRIBUTED' = 129, 'SYSTEM FLUSH LOGS' = 130, 'SYSTEM FLUSH' = 131, 'SYSTEM THREAD FUZZER' = 132, 'SYSTEM UNFREEZE' = 133, 'SYSTEM' = 134, 'dictGet' = 135, 'addressToLine' = 136, 'addressToLineWithInlines' = 137, 'addressToSymbol' = 138, 'demangle' = 139, 'INTROSPECTION' = 140, 'FILE' = 141, 'URL' = 142, 'REMOTE' = 143, 'MONGO' = 144, 'MEILISEARCH' = 145, 'MYSQL' = 146, 'POSTGRES' = 147, 'SQLITE' = 148, 'ODBC' = 149, 'JDBC' = 150, 'HDFS' = 151, 'S3' = 152, 'HIVE' = 153, 'SOURCES' = 154, 'CLUSTER' = 155, 'ALL' = 156, 'NONE' = 157), + `access_type` Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'SHOW NAMED COLLECTIONS SECRETS' = 92, 'ACCESS MANAGEMENT' = 93, 'SYSTEM SHUTDOWN' = 94, 'SYSTEM DROP DNS CACHE' = 95, 'SYSTEM DROP MARK CACHE' = 96, 'SYSTEM DROP UNCOMPRESSED CACHE' = 97, 'SYSTEM DROP MMAP CACHE' = 98, 'SYSTEM DROP QUERY CACHE' = 99, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 100, 'SYSTEM DROP FILESYSTEM CACHE' = 101, 'SYSTEM DROP SCHEMA CACHE' = 102, 'SYSTEM DROP S3 CLIENT CACHE' = 103, 'SYSTEM DROP CACHE' = 104, 'SYSTEM RELOAD CONFIG' = 105, 'SYSTEM RELOAD USERS' = 106, 'SYSTEM RELOAD SYMBOLS' = 107, 'SYSTEM RELOAD DICTIONARY' = 108, 'SYSTEM RELOAD MODEL' = 109, 'SYSTEM RELOAD FUNCTION' = 110, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 111, 'SYSTEM RELOAD' = 112, 'SYSTEM RESTART DISK' = 113, 'SYSTEM MERGES' = 114, 'SYSTEM TTL MERGES' = 115, 'SYSTEM FETCHES' = 116, 'SYSTEM MOVES' = 117, 'SYSTEM DISTRIBUTED SENDS' = 118, 'SYSTEM REPLICATED SENDS' = 119, 'SYSTEM SENDS' = 120, 'SYSTEM REPLICATION QUEUES' = 121, 'SYSTEM DROP REPLICA' = 122, 'SYSTEM SYNC REPLICA' = 123, 'SYSTEM RESTART REPLICA' = 124, 'SYSTEM RESTORE REPLICA' = 125, 'SYSTEM WAIT LOADING PARTS' = 126, 'SYSTEM SYNC DATABASE REPLICA' = 127, 'SYSTEM SYNC TRANSACTION LOG' = 128, 'SYSTEM SYNC FILE CACHE' = 129, 'SYSTEM FLUSH DISTRIBUTED' = 130, 'SYSTEM FLUSH LOGS' = 131, 'SYSTEM FLUSH' = 132, 'SYSTEM THREAD FUZZER' = 133, 'SYSTEM UNFREEZE' = 134, 'SYSTEM' = 135, 'dictGet' = 136, 'addressToLine' = 137, 'addressToLineWithInlines' = 138, 'addressToSymbol' = 139, 'demangle' = 140, 'INTROSPECTION' = 141, 'FILE' = 142, 'URL' = 143, 'REMOTE' = 144, 'MONGO' = 145, 'MEILISEARCH' = 146, 'MYSQL' = 147, 'POSTGRES' = 148, 'SQLITE' = 149, 'ODBC' = 150, 'JDBC' = 151, 'HDFS' = 152, 'S3' = 153, 'HIVE' = 154, 'SOURCES' = 155, 'CLUSTER' = 156, 'ALL' = 157, 'NONE' = 158), `database` Nullable(String), `table` Nullable(String), `column` Nullable(String), @@ -569,10 +569,10 @@ ENGINE = SystemPartsColumns COMMENT 'SYSTEM TABLE is built on the fly.' CREATE TABLE system.privileges ( - `privilege` Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'ACCESS MANAGEMENT' = 92, 'SYSTEM SHUTDOWN' = 93, 'SYSTEM DROP DNS CACHE' = 94, 'SYSTEM DROP MARK CACHE' = 95, 'SYSTEM DROP UNCOMPRESSED CACHE' = 96, 'SYSTEM DROP MMAP CACHE' = 97, 'SYSTEM DROP QUERY CACHE' = 98, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 99, 'SYSTEM DROP FILESYSTEM CACHE' = 100, 'SYSTEM DROP SCHEMA CACHE' = 101, 'SYSTEM DROP S3 CLIENT CACHE' = 102, 'SYSTEM DROP CACHE' = 103, 'SYSTEM RELOAD CONFIG' = 104, 'SYSTEM RELOAD USERS' = 105, 'SYSTEM RELOAD SYMBOLS' = 106, 'SYSTEM RELOAD DICTIONARY' = 107, 'SYSTEM RELOAD MODEL' = 108, 'SYSTEM RELOAD FUNCTION' = 109, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 110, 'SYSTEM RELOAD' = 111, 'SYSTEM RESTART DISK' = 112, 'SYSTEM MERGES' = 113, 'SYSTEM TTL MERGES' = 114, 'SYSTEM FETCHES' = 115, 'SYSTEM MOVES' = 116, 'SYSTEM DISTRIBUTED SENDS' = 117, 'SYSTEM REPLICATED SENDS' = 118, 'SYSTEM SENDS' = 119, 'SYSTEM REPLICATION QUEUES' = 120, 'SYSTEM DROP REPLICA' = 121, 'SYSTEM SYNC REPLICA' = 122, 'SYSTEM RESTART REPLICA' = 123, 'SYSTEM RESTORE REPLICA' = 124, 'SYSTEM WAIT LOADING PARTS' = 125, 'SYSTEM SYNC DATABASE REPLICA' = 126, 'SYSTEM SYNC TRANSACTION LOG' = 127, 'SYSTEM SYNC FILE CACHE' = 128, 'SYSTEM FLUSH DISTRIBUTED' = 129, 'SYSTEM FLUSH LOGS' = 130, 'SYSTEM FLUSH' = 131, 'SYSTEM THREAD FUZZER' = 132, 'SYSTEM UNFREEZE' = 133, 'SYSTEM' = 134, 'dictGet' = 135, 'addressToLine' = 136, 'addressToLineWithInlines' = 137, 'addressToSymbol' = 138, 'demangle' = 139, 'INTROSPECTION' = 140, 'FILE' = 141, 'URL' = 142, 'REMOTE' = 143, 'MONGO' = 144, 'MEILISEARCH' = 145, 'MYSQL' = 146, 'POSTGRES' = 147, 'SQLITE' = 148, 'ODBC' = 149, 'JDBC' = 150, 'HDFS' = 151, 'S3' = 152, 'HIVE' = 153, 'SOURCES' = 154, 'CLUSTER' = 155, 'ALL' = 156, 'NONE' = 157), + `privilege` Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'SHOW NAMED COLLECTIONS SECRETS' = 92, 'ACCESS MANAGEMENT' = 93, 'SYSTEM SHUTDOWN' = 94, 'SYSTEM DROP DNS CACHE' = 95, 'SYSTEM DROP MARK CACHE' = 96, 'SYSTEM DROP UNCOMPRESSED CACHE' = 97, 'SYSTEM DROP MMAP CACHE' = 98, 'SYSTEM DROP QUERY CACHE' = 99, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 100, 'SYSTEM DROP FILESYSTEM CACHE' = 101, 'SYSTEM DROP SCHEMA CACHE' = 102, 'SYSTEM DROP S3 CLIENT CACHE' = 103, 'SYSTEM DROP CACHE' = 104, 'SYSTEM RELOAD CONFIG' = 105, 'SYSTEM RELOAD USERS' = 106, 'SYSTEM RELOAD SYMBOLS' = 107, 'SYSTEM RELOAD DICTIONARY' = 108, 'SYSTEM RELOAD MODEL' = 109, 'SYSTEM RELOAD FUNCTION' = 110, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 111, 'SYSTEM RELOAD' = 112, 'SYSTEM RESTART DISK' = 113, 'SYSTEM MERGES' = 114, 'SYSTEM TTL MERGES' = 115, 'SYSTEM FETCHES' = 116, 'SYSTEM MOVES' = 117, 'SYSTEM DISTRIBUTED SENDS' = 118, 'SYSTEM REPLICATED SENDS' = 119, 'SYSTEM SENDS' = 120, 'SYSTEM REPLICATION QUEUES' = 121, 'SYSTEM DROP REPLICA' = 122, 'SYSTEM SYNC REPLICA' = 123, 'SYSTEM RESTART REPLICA' = 124, 'SYSTEM RESTORE REPLICA' = 125, 'SYSTEM WAIT LOADING PARTS' = 126, 'SYSTEM SYNC DATABASE REPLICA' = 127, 'SYSTEM SYNC TRANSACTION LOG' = 128, 'SYSTEM SYNC FILE CACHE' = 129, 'SYSTEM FLUSH DISTRIBUTED' = 130, 'SYSTEM FLUSH LOGS' = 131, 'SYSTEM FLUSH' = 132, 'SYSTEM THREAD FUZZER' = 133, 'SYSTEM UNFREEZE' = 134, 'SYSTEM' = 135, 'dictGet' = 136, 'addressToLine' = 137, 'addressToLineWithInlines' = 138, 'addressToSymbol' = 139, 'demangle' = 140, 'INTROSPECTION' = 141, 'FILE' = 142, 'URL' = 143, 'REMOTE' = 144, 'MONGO' = 145, 'MEILISEARCH' = 146, 'MYSQL' = 147, 'POSTGRES' = 148, 'SQLITE' = 149, 'ODBC' = 150, 'JDBC' = 151, 'HDFS' = 152, 'S3' = 153, 'HIVE' = 154, 'SOURCES' = 155, 'CLUSTER' = 156, 'ALL' = 157, 'NONE' = 158), `aliases` Array(String), `level` Nullable(Enum8('GLOBAL' = 0, 'DATABASE' = 1, 'TABLE' = 2, 'DICTIONARY' = 3, 'VIEW' = 4, 'COLUMN' = 5)), - `parent_group` Nullable(Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'ACCESS MANAGEMENT' = 92, 'SYSTEM SHUTDOWN' = 93, 'SYSTEM DROP DNS CACHE' = 94, 'SYSTEM DROP MARK CACHE' = 95, 'SYSTEM DROP UNCOMPRESSED CACHE' = 96, 'SYSTEM DROP MMAP CACHE' = 97, 'SYSTEM DROP QUERY CACHE' = 98, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 99, 'SYSTEM DROP FILESYSTEM CACHE' = 100, 'SYSTEM DROP SCHEMA CACHE' = 101, 'SYSTEM DROP S3 CLIENT CACHE' = 102, 'SYSTEM DROP CACHE' = 103, 'SYSTEM RELOAD CONFIG' = 104, 'SYSTEM RELOAD USERS' = 105, 'SYSTEM RELOAD SYMBOLS' = 106, 'SYSTEM RELOAD DICTIONARY' = 107, 'SYSTEM RELOAD MODEL' = 108, 'SYSTEM RELOAD FUNCTION' = 109, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 110, 'SYSTEM RELOAD' = 111, 'SYSTEM RESTART DISK' = 112, 'SYSTEM MERGES' = 113, 'SYSTEM TTL MERGES' = 114, 'SYSTEM FETCHES' = 115, 'SYSTEM MOVES' = 116, 'SYSTEM DISTRIBUTED SENDS' = 117, 'SYSTEM REPLICATED SENDS' = 118, 'SYSTEM SENDS' = 119, 'SYSTEM REPLICATION QUEUES' = 120, 'SYSTEM DROP REPLICA' = 121, 'SYSTEM SYNC REPLICA' = 122, 'SYSTEM RESTART REPLICA' = 123, 'SYSTEM RESTORE REPLICA' = 124, 'SYSTEM WAIT LOADING PARTS' = 125, 'SYSTEM SYNC DATABASE REPLICA' = 126, 'SYSTEM SYNC TRANSACTION LOG' = 127, 'SYSTEM SYNC FILE CACHE' = 128, 'SYSTEM FLUSH DISTRIBUTED' = 129, 'SYSTEM FLUSH LOGS' = 130, 'SYSTEM FLUSH' = 131, 'SYSTEM THREAD FUZZER' = 132, 'SYSTEM UNFREEZE' = 133, 'SYSTEM' = 134, 'dictGet' = 135, 'addressToLine' = 136, 'addressToLineWithInlines' = 137, 'addressToSymbol' = 138, 'demangle' = 139, 'INTROSPECTION' = 140, 'FILE' = 141, 'URL' = 142, 'REMOTE' = 143, 'MONGO' = 144, 'MEILISEARCH' = 145, 'MYSQL' = 146, 'POSTGRES' = 147, 'SQLITE' = 148, 'ODBC' = 149, 'JDBC' = 150, 'HDFS' = 151, 'S3' = 152, 'HIVE' = 153, 'SOURCES' = 154, 'CLUSTER' = 155, 'ALL' = 156, 'NONE' = 157)) + `parent_group` Nullable(Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'SHOW NAMED COLLECTIONS SECRETS' = 92, 'ACCESS MANAGEMENT' = 93, 'SYSTEM SHUTDOWN' = 94, 'SYSTEM DROP DNS CACHE' = 95, 'SYSTEM DROP MARK CACHE' = 96, 'SYSTEM DROP UNCOMPRESSED CACHE' = 97, 'SYSTEM DROP MMAP CACHE' = 98, 'SYSTEM DROP QUERY CACHE' = 99, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 100, 'SYSTEM DROP FILESYSTEM CACHE' = 101, 'SYSTEM DROP SCHEMA CACHE' = 102, 'SYSTEM DROP S3 CLIENT CACHE' = 103, 'SYSTEM DROP CACHE' = 104, 'SYSTEM RELOAD CONFIG' = 105, 'SYSTEM RELOAD USERS' = 106, 'SYSTEM RELOAD SYMBOLS' = 107, 'SYSTEM RELOAD DICTIONARY' = 108, 'SYSTEM RELOAD MODEL' = 109, 'SYSTEM RELOAD FUNCTION' = 110, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 111, 'SYSTEM RELOAD' = 112, 'SYSTEM RESTART DISK' = 113, 'SYSTEM MERGES' = 114, 'SYSTEM TTL MERGES' = 115, 'SYSTEM FETCHES' = 116, 'SYSTEM MOVES' = 117, 'SYSTEM DISTRIBUTED SENDS' = 118, 'SYSTEM REPLICATED SENDS' = 119, 'SYSTEM SENDS' = 120, 'SYSTEM REPLICATION QUEUES' = 121, 'SYSTEM DROP REPLICA' = 122, 'SYSTEM SYNC REPLICA' = 123, 'SYSTEM RESTART REPLICA' = 124, 'SYSTEM RESTORE REPLICA' = 125, 'SYSTEM WAIT LOADING PARTS' = 126, 'SYSTEM SYNC DATABASE REPLICA' = 127, 'SYSTEM SYNC TRANSACTION LOG' = 128, 'SYSTEM SYNC FILE CACHE' = 129, 'SYSTEM FLUSH DISTRIBUTED' = 130, 'SYSTEM FLUSH LOGS' = 131, 'SYSTEM FLUSH' = 132, 'SYSTEM THREAD FUZZER' = 133, 'SYSTEM UNFREEZE' = 134, 'SYSTEM' = 135, 'dictGet' = 136, 'addressToLine' = 137, 'addressToLineWithInlines' = 138, 'addressToSymbol' = 139, 'demangle' = 140, 'INTROSPECTION' = 141, 'FILE' = 142, 'URL' = 143, 'REMOTE' = 144, 'MONGO' = 145, 'MEILISEARCH' = 146, 'MYSQL' = 147, 'POSTGRES' = 148, 'SQLITE' = 149, 'ODBC' = 150, 'JDBC' = 151, 'HDFS' = 152, 'S3' = 153, 'HIVE' = 154, 'SOURCES' = 155, 'CLUSTER' = 156, 'ALL' = 157, 'NONE' = 158)) ) ENGINE = SystemPrivileges COMMENT 'SYSTEM TABLE is built on the fly.' From ec8b6c5590ff64b6ab1045a385b61ab04736861b Mon Sep 17 00:00:00 2001 From: lzydmxy <13126752315@163.com> Date: Wed, 22 Feb 2023 19:57:56 +0800 Subject: [PATCH 097/229] add __init__.py for integration test test_move_partition_to_disk_on_cluster --- .../test_move_partition_to_disk_on_cluster/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/integration/test_move_partition_to_disk_on_cluster/__init__.py diff --git a/tests/integration/test_move_partition_to_disk_on_cluster/__init__.py b/tests/integration/test_move_partition_to_disk_on_cluster/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From fba2ec30a2a3d117e95f591fbfc635887e27ed6a Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 22 Feb 2023 13:53:43 +0100 Subject: [PATCH 098/229] fix style check --- .../test.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/tests/integration/test_move_partition_to_disk_on_cluster/test.py b/tests/integration/test_move_partition_to_disk_on_cluster/test.py index fe8606bd549..90753fc8ce3 100644 --- a/tests/integration/test_move_partition_to_disk_on_cluster/test.py +++ b/tests/integration/test_move_partition_to_disk_on_cluster/test.py @@ -43,10 +43,10 @@ def test_move_partition_to_disk_on_cluster(start_cluster): for node in [node1, node2]: node.query( sql="CREATE TABLE test_local_table" - "(x UInt64) " - "ENGINE=ReplicatedMergeTree('/clickhouse/tables/test_local_table', '{replica}') " - "ORDER BY tuple()" - "SETTINGS storage_policy = 'jbod_with_external';", + "(x UInt64) " + "ENGINE=ReplicatedMergeTree('/clickhouse/tables/test_local_table', '{replica}') " + "ORDER BY tuple()" + "SETTINGS storage_policy = 'jbod_with_external';", ) node1.query("INSERT INTO test_local_table VALUES (0)") @@ -61,10 +61,10 @@ def test_move_partition_to_disk_on_cluster(start_cluster): for node in [node1, node2]: assert ( - node.query( - "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" - ) - == "('all','jbod1')" + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','jbod1')" ) node1.query( @@ -73,10 +73,10 @@ def test_move_partition_to_disk_on_cluster(start_cluster): for node in [node1, node2]: assert ( - node.query( - "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" - ) - == "('all','external')" + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','external')" ) node1.query( @@ -85,10 +85,8 @@ def test_move_partition_to_disk_on_cluster(start_cluster): for node in [node1, node2]: assert ( - node.query( - "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" - ) - == "('all','jbod1')" + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','jbod1')" ) - - From e8094c9707d46ef950232e475f671627b809a46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Wed, 22 Feb 2023 14:20:48 +0100 Subject: [PATCH 099/229] Add test for #46724 --- ...2667_order_by_aggregation_result.reference | 3 +++ .../02667_order_by_aggregation_result.sql | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/tests/queries/0_stateless/02667_order_by_aggregation_result.reference b/tests/queries/0_stateless/02667_order_by_aggregation_result.reference index 642fc2ed645..a89e39d87b3 100644 --- a/tests/queries/0_stateless/02667_order_by_aggregation_result.reference +++ b/tests/queries/0_stateless/02667_order_by_aggregation_result.reference @@ -2,3 +2,6 @@ 0 1 █████████████████████████████████████████████████▉ 1 2 0.5 1 0.1 1.1 +00000000-0000-0000-0000-000000000000 b 1 +417ddc5d-e556-4d27-95dd-a34d84e46a50 c 1 +notEmpty a 1 diff --git a/tests/queries/0_stateless/02667_order_by_aggregation_result.sql b/tests/queries/0_stateless/02667_order_by_aggregation_result.sql index 277430888d7..3fef0374d83 100644 --- a/tests/queries/0_stateless/02667_order_by_aggregation_result.sql +++ b/tests/queries/0_stateless/02667_order_by_aggregation_result.sql @@ -34,3 +34,27 @@ SELECT final_col + 1 AS final_col2 FROM ttttttt GROUP BY col3; + +-- https://github.com/ClickHouse/ClickHouse/issues/46724 + +CREATE TABLE table1 +( + id String, + device UUID +) +ENGINE = MergeTree() ORDER BY tuple(); + +INSERT INTO table1 VALUES ('notEmpty', '417ddc5d-e556-4d27-95dd-a34d84e46a50'); +INSERT INTO table1 VALUES ('', '417ddc5d-e556-4d27-95dd-a34d84e46a50'); +INSERT INTO table1 VALUES ('', '00000000-0000-0000-0000-000000000000'); + +SELECT + if(empty(id), toString(device), id) AS device, + multiIf( + notEmpty(id),'a', + device == '00000000-0000-0000-0000-000000000000', 'b', + 'c' ) AS device_id_type, + count() +FROM table1 +GROUP BY device, device_id_type +ORDER BY device; From bac464f89b3fdfe612e721a2fc976146e17dd696 Mon Sep 17 00:00:00 2001 From: kssenii Date: Wed, 22 Feb 2023 13:57:30 +0100 Subject: [PATCH 100/229] Fix --- docs/en/engines/table-engines/integrations/deltalake.md | 2 +- docs/en/engines/table-engines/integrations/hudi.md | 2 +- docs/en/engines/table-engines/integrations/iceberg.md | 2 +- docs/en/sql-reference/table-functions/iceberg.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/engines/table-engines/integrations/deltalake.md b/docs/en/engines/table-engines/integrations/deltalake.md index a2816c7ff57..99183ac7308 100644 --- a/docs/en/engines/table-engines/integrations/deltalake.md +++ b/docs/en/engines/table-engines/integrations/deltalake.md @@ -21,7 +21,7 @@ CREATE TABLE deltalake - `url` — Bucket url with path to the existing Delta Lake table. - `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. -Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) +Engine parameters can be specified using [Named Collections](../../../operations/named-collections.md) **Example** diff --git a/docs/en/engines/table-engines/integrations/hudi.md b/docs/en/engines/table-engines/integrations/hudi.md index 6ff998d86d9..a14134ecdfa 100644 --- a/docs/en/engines/table-engines/integrations/hudi.md +++ b/docs/en/engines/table-engines/integrations/hudi.md @@ -21,7 +21,7 @@ CREATE TABLE hudi_table - `url` — Bucket url with the path to an existing Hudi table. - `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. -Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) +Engine parameters can be specified using [Named Collections](../../../operations/named-collections.md) **Example** diff --git a/docs/en/engines/table-engines/integrations/iceberg.md b/docs/en/engines/table-engines/integrations/iceberg.md index 33ec5f877bf..4322fc6b773 100644 --- a/docs/en/engines/table-engines/integrations/iceberg.md +++ b/docs/en/engines/table-engines/integrations/iceberg.md @@ -21,7 +21,7 @@ CREATE TABLE iceberg_table - `url` — url with the path to an existing Iceberg table. - `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. -Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) +Engine parameters can be specified using [Named Collections](../../../operations/named-collections.md) **Example** diff --git a/docs/en/sql-reference/table-functions/iceberg.md b/docs/en/sql-reference/table-functions/iceberg.md index 036c1379847..fda4d274005 100644 --- a/docs/en/sql-reference/table-functions/iceberg.md +++ b/docs/en/sql-reference/table-functions/iceberg.md @@ -20,7 +20,7 @@ iceberg(url [,aws_access_key_id, aws_secret_access_key] [,format] [,structure]) - `format` — The [format](/docs/en/interfaces/formats.md/#formats) of the file. By default `Parquet` is used. - `structure` — Structure of the table. Format `'column1_name column1_type, column2_name column2_type, ...'`. -Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) +Engine parameters can be specified using [Named Collections](../../operations/named-collections.md) **Returned value** From c4761d6cc688733124081b87a5fefffd1d7541ce Mon Sep 17 00:00:00 2001 From: kssenii Date: Wed, 22 Feb 2023 12:14:59 +0100 Subject: [PATCH 101/229] Fix checks --- src/Disks/getOrCreateDiskFromAST.cpp | 10 +--------- src/Disks/getOrCreateDiskFromAST.h | 5 ----- src/Parsers/FieldFromAST.cpp | 1 + src/Parsers/isDiskFunction.cpp | 16 ++++++++++++++++ src/Parsers/isDiskFunction.h | 9 +++++++++ src/Storages/MergeTree/MergeTreeSettings.cpp | 1 + .../integration/test_disk_configuration/test.py | 2 +- 7 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 src/Parsers/isDiskFunction.cpp create mode 100644 src/Parsers/isDiskFunction.h diff --git a/src/Disks/getOrCreateDiskFromAST.cpp b/src/Disks/getOrCreateDiskFromAST.cpp index fc9cd7edbee..997bd2c853f 100644 --- a/src/Disks/getOrCreateDiskFromAST.cpp +++ b/src/Disks/getOrCreateDiskFromAST.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace DB @@ -17,15 +18,6 @@ namespace ErrorCodes extern const int BAD_ARGUMENTS; } -bool isDiskFunction(ASTPtr ast) -{ - if (!ast) - return false; - - const auto * function = ast->as(); - return function && function->name == "disk" && function->arguments->as(); -} - std::string getOrCreateDiskFromDiskAST(const ASTFunction & function, ContextPtr context) { /// We need a unique name for a created custom disk, but it needs to be the same diff --git a/src/Disks/getOrCreateDiskFromAST.h b/src/Disks/getOrCreateDiskFromAST.h index c1d4bda1a49..7c64707b0bd 100644 --- a/src/Disks/getOrCreateDiskFromAST.h +++ b/src/Disks/getOrCreateDiskFromAST.h @@ -15,9 +15,4 @@ class ASTFunction; */ std::string getOrCreateDiskFromDiskAST(const ASTFunction & function, ContextPtr context); -/* - * Is given ast has form of a disk() function. - */ -bool isDiskFunction(ASTPtr ast); - } diff --git a/src/Parsers/FieldFromAST.cpp b/src/Parsers/FieldFromAST.cpp index 5889699c081..c46a9a08e68 100644 --- a/src/Parsers/FieldFromAST.cpp +++ b/src/Parsers/FieldFromAST.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include diff --git a/src/Parsers/isDiskFunction.cpp b/src/Parsers/isDiskFunction.cpp new file mode 100644 index 00000000000..e60229cb3f7 --- /dev/null +++ b/src/Parsers/isDiskFunction.cpp @@ -0,0 +1,16 @@ +#include +#include + +namespace DB +{ + +bool isDiskFunction(ASTPtr ast) +{ + if (!ast) + return false; + + const auto * function = ast->as(); + return function && function->name == "disk" && function->arguments->as(); +} + +} diff --git a/src/Parsers/isDiskFunction.h b/src/Parsers/isDiskFunction.h new file mode 100644 index 00000000000..97b3c58fa17 --- /dev/null +++ b/src/Parsers/isDiskFunction.h @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace DB +{ + +bool isDiskFunction(ASTPtr ast); + +} diff --git a/src/Storages/MergeTree/MergeTreeSettings.cpp b/src/Storages/MergeTree/MergeTreeSettings.cpp index e5af0c772ba..e951b8f54cf 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.cpp +++ b/src/Storages/MergeTree/MergeTreeSettings.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/integration/test_disk_configuration/test.py b/tests/integration/test_disk_configuration/test.py index 60d75e4dac1..34f8bea219f 100644 --- a/tests/integration/test_disk_configuration/test.py +++ b/tests/integration/test_disk_configuration/test.py @@ -262,7 +262,7 @@ def test_merge_tree_custom_disk_setting(start_cluster): ) expected = """ - SETTINGS disk = disk(type = s3, endpoint = \\'http://minio1:9001/root/data2/\\', access_key_id = \\'minio\\', secret_access_key = \\'minio123\\'), index_granularity = 8192 + SETTINGS disk = disk(type = s3, endpoint = \\'[HIDDEN]\\', access_key_id = \\'[HIDDEN]\\', secret_access_key = \\'[HIDDEN]\\'), index_granularity = 8192 """ assert expected.strip() in node1.query(f"SHOW CREATE TABLE {TABLE_NAME}_4").strip() From e433ecc18f896bb61193ea059ed217502d0f2101 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 22 Feb 2023 14:37:55 +0100 Subject: [PATCH 102/229] Better exception message during Tuple JSON deserialization --- src/DataTypes/Serializations/SerializationTuple.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/DataTypes/Serializations/SerializationTuple.cpp b/src/DataTypes/Serializations/SerializationTuple.cpp index ce15e099222..2b703a15a9b 100644 --- a/src/DataTypes/Serializations/SerializationTuple.cpp +++ b/src/DataTypes/Serializations/SerializationTuple.cpp @@ -231,7 +231,15 @@ void SerializationTuple::deserializeTextJSON(IColumn & column, ReadBuffer & istr seen_elements[element_pos] = 1; auto & element_column = extractElementColumn(column, element_pos); - elems[element_pos]->deserializeTextJSON(element_column, istr, settings); + try + { + elems[element_pos]->deserializeTextJSON(element_column, istr, settings); + } + catch (Exception & e) + { + e.addMessage("(while reading the value of nested key " + name + ")"); + throw; + } skipWhitespaceIfAny(istr); ++processed; From 4fd4e77737b4f1aa29898849baf289c7e05b8710 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 22 Feb 2023 13:48:29 +0000 Subject: [PATCH 103/229] Poco: POCO_HAVE_INT64 is always defined --- .../Foundation/include/Poco/BinaryReader.h | 4 +-- .../Foundation/include/Poco/BinaryWriter.h | 4 +-- base/poco/Foundation/include/Poco/ByteOrder.h | 29 ------------------- .../Foundation/include/Poco/NumberFormatter.h | 6 ---- .../Foundation/include/Poco/NumberParser.h | 2 -- .../Foundation/include/Poco/StreamCopier.h | 6 ---- base/poco/Foundation/include/Poco/Token.h | 2 -- base/poco/Foundation/include/Poco/Types.h | 1 - base/poco/Foundation/src/BinaryReader.cpp | 4 +-- base/poco/Foundation/src/BinaryWriter.cpp | 4 +-- base/poco/Foundation/src/NumberFormatter.cpp | 2 -- base/poco/Foundation/src/NumberParser.cpp | 2 -- base/poco/Foundation/src/StreamCopier.cpp | 6 ---- base/poco/Foundation/src/Token.cpp | 2 -- base/poco/JSON/include/Poco/JSON/Handler.h | 2 -- .../JSON/include/Poco/JSON/ParseHandler.h | 4 --- .../JSON/include/Poco/JSON/PrintHandler.h | 2 -- base/poco/JSON/src/PrintHandler.cpp | 2 -- .../include/Poco/Net/HTTPFixedLengthStream.h | 4 --- base/poco/Net/include/Poco/Net/HTTPMessage.h | 2 -- base/poco/Net/src/HTTPClientSession.cpp | 8 ----- base/poco/Net/src/HTTPMessage.cpp | 2 -- base/poco/Net/src/HTTPServerRequestImpl.cpp | 4 --- base/poco/Net/src/HTTPServerResponseImpl.cpp | 8 ----- .../include/Poco/Util/AbstractConfiguration.h | 6 ---- .../Util/include/Poco/Util/WinRegistryKey.h | 2 -- base/poco/Util/src/AbstractConfiguration.cpp | 4 --- 27 files changed, 4 insertions(+), 120 deletions(-) diff --git a/base/poco/Foundation/include/Poco/BinaryReader.h b/base/poco/Foundation/include/Poco/BinaryReader.h index 280724a8a47..4042b507a2f 100644 --- a/base/poco/Foundation/include/Poco/BinaryReader.h +++ b/base/poco/Foundation/include/Poco/BinaryReader.h @@ -76,7 +76,7 @@ public: BinaryReader & operator>>(float & value); BinaryReader & operator>>(double & value); -#if defined(POCO_HAVE_INT64) && !defined(POCO_LONG_IS_64_BIT) +#if !defined(POCO_LONG_IS_64_BIT) BinaryReader & operator>>(Int64 & value); BinaryReader & operator>>(UInt64 & value); #endif @@ -106,12 +106,10 @@ public: /// See BinaryWriter::write7BitEncoded() for a description /// of the compression algorithm. -#if defined(POCO_HAVE_INT64) void read7BitEncoded(UInt64 & value); /// Reads a 64-bit unsigned integer in compressed format. /// See BinaryWriter::write7BitEncoded() for a description /// of the compression algorithm. -#endif void readRaw(std::streamsize length, std::string & value); /// Reads length bytes of raw data into value. diff --git a/base/poco/Foundation/include/Poco/BinaryWriter.h b/base/poco/Foundation/include/Poco/BinaryWriter.h index 30a353a8ff7..aa280d4ccab 100644 --- a/base/poco/Foundation/include/Poco/BinaryWriter.h +++ b/base/poco/Foundation/include/Poco/BinaryWriter.h @@ -81,7 +81,7 @@ public: BinaryWriter & operator<<(float value); BinaryWriter & operator<<(double value); -#if defined(POCO_HAVE_INT64) && !defined(POCO_LONG_IS_64_BIT) +#if !defined(POCO_LONG_IS_64_BIT) BinaryWriter & operator<<(Int64 value); BinaryWriter & operator<<(UInt64 value); #endif @@ -114,7 +114,6 @@ public: /// written out. value is then shifted by seven bits and the next byte is written. /// This process is repeated until the entire integer has been written. -#if defined(POCO_HAVE_INT64) void write7BitEncoded(UInt64 value); /// Writes a 64-bit unsigned integer in a compressed format. /// The value written out seven bits at a time, starting @@ -125,7 +124,6 @@ public: /// If value will not fit in seven bits, the high bit is set on the first byte and /// written out. value is then shifted by seven bits and the next byte is written. /// This process is repeated until the entire integer has been written. -#endif void writeRaw(const std::string & rawData); /// Writes the string as-is to the stream. diff --git a/base/poco/Foundation/include/Poco/ByteOrder.h b/base/poco/Foundation/include/Poco/ByteOrder.h index 4f2644ddf4e..09f673c2718 100644 --- a/base/poco/Foundation/include/Poco/ByteOrder.h +++ b/base/poco/Foundation/include/Poco/ByteOrder.h @@ -34,64 +34,50 @@ public: static UInt16 flipBytes(UInt16 value); static Int32 flipBytes(Int32 value); static UInt32 flipBytes(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 flipBytes(Int64 value); static UInt64 flipBytes(UInt64 value); -#endif static Int16 toBigEndian(Int16 value); static UInt16 toBigEndian(UInt16 value); static Int32 toBigEndian(Int32 value); static UInt32 toBigEndian(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 toBigEndian(Int64 value); static UInt64 toBigEndian(UInt64 value); -#endif static Int16 fromBigEndian(Int16 value); static UInt16 fromBigEndian(UInt16 value); static Int32 fromBigEndian(Int32 value); static UInt32 fromBigEndian(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 fromBigEndian(Int64 value); static UInt64 fromBigEndian(UInt64 value); -#endif static Int16 toLittleEndian(Int16 value); static UInt16 toLittleEndian(UInt16 value); static Int32 toLittleEndian(Int32 value); static UInt32 toLittleEndian(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 toLittleEndian(Int64 value); static UInt64 toLittleEndian(UInt64 value); -#endif static Int16 fromLittleEndian(Int16 value); static UInt16 fromLittleEndian(UInt16 value); static Int32 fromLittleEndian(Int32 value); static UInt32 fromLittleEndian(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 fromLittleEndian(Int64 value); static UInt64 fromLittleEndian(UInt64 value); -#endif static Int16 toNetwork(Int16 value); static UInt16 toNetwork(UInt16 value); static Int32 toNetwork(Int32 value); static UInt32 toNetwork(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 toNetwork(Int64 value); static UInt64 toNetwork(UInt64 value); -#endif static Int16 fromNetwork(Int16 value); static UInt16 fromNetwork(UInt16 value); static Int32 fromNetwork(Int32 value); static UInt32 fromNetwork(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 fromNetwork(Int64 value); static UInt64 fromNetwork(UInt64 value); -#endif }; @@ -143,7 +129,6 @@ inline Int32 ByteOrder::flipBytes(Int32 value) } -#if defined(POCO_HAVE_INT64) inline UInt64 ByteOrder::flipBytes(UInt64 value) { # if defined(POCO_HAVE_MSC_BYTESWAP) @@ -162,7 +147,6 @@ inline Int64 ByteOrder::flipBytes(Int64 value) { return Int64(flipBytes(UInt64(value))); } -#endif // POCO_HAVE_INT64 // @@ -180,7 +164,6 @@ inline Int64 ByteOrder::flipBytes(Int64 value) } -#if defined(POCO_HAVE_INT64) # define POCO_IMPLEMENT_BYTEORDER_NOOP(op) \ POCO_IMPLEMENT_BYTEORDER_NOOP_(op, Int16) \ POCO_IMPLEMENT_BYTEORDER_NOOP_(op, UInt16) \ @@ -195,18 +178,6 @@ inline Int64 ByteOrder::flipBytes(Int64 value) POCO_IMPLEMENT_BYTEORDER_FLIP_(op, UInt32) \ POCO_IMPLEMENT_BYTEORDER_FLIP_(op, Int64) \ POCO_IMPLEMENT_BYTEORDER_FLIP_(op, UInt64) -#else -# define POCO_IMPLEMENT_BYTEORDER_NOOP(op) \ - POCO_IMPLEMENT_BYTEORDER_NOOP_(op, Int16) \ - POCO_IMPLEMENT_BYTEORDER_NOOP_(op, UInt16) \ - POCO_IMPLEMENT_BYTEORDER_NOOP_(op, Int32) \ - POCO_IMPLEMENT_BYTEORDER_NOOP_(op, UInt32) -# define POCO_IMPLEMENT_BYTEORDER_FLIP(op) \ - POCO_IMPLEMENT_BYTEORDER_FLIP_(op, Int16) \ - POCO_IMPLEMENT_BYTEORDER_FLIP_(op, UInt16) \ - POCO_IMPLEMENT_BYTEORDER_FLIP_(op, Int32) \ - POCO_IMPLEMENT_BYTEORDER_FLIP_(op, UInt32) -#endif #if defined(POCO_ARCH_BIG_ENDIAN) diff --git a/base/poco/Foundation/include/Poco/NumberFormatter.h b/base/poco/Foundation/include/Poco/NumberFormatter.h index e246ca16ec3..a320b576083 100644 --- a/base/poco/Foundation/include/Poco/NumberFormatter.h +++ b/base/poco/Foundation/include/Poco/NumberFormatter.h @@ -151,7 +151,6 @@ public: /// If prefix is true, "0x" prefix is prepended to the /// resulting string. -#ifdef POCO_HAVE_INT64 # ifdef POCO_LONG_IS_64_BIT @@ -255,7 +254,6 @@ public: # endif // ifdef POCO_LONG_IS_64_BIT -#endif // ifdef POCO_HAVE_INT64 static std::string format(float value); /// Formats a float value in decimal floating-point notation, @@ -380,7 +378,6 @@ public: /// right justified and zero-padded in a field having at least the /// specified width. -#ifdef POCO_HAVE_INT64 # ifdef POCO_LONG_IS_64_BIT @@ -472,7 +469,6 @@ public: # endif // ifdef POCO_LONG_IS_64_BIT -#endif // ifdef POCO_HAVE_INT64 static void append(std::string & str, float value); /// Formats a float value in decimal floating-point notation, @@ -673,7 +669,6 @@ inline std::string NumberFormatter::formatHex(unsigned long value, int width, bo } -#ifdef POCO_HAVE_INT64 # ifdef POCO_LONG_IS_64_BIT @@ -843,7 +838,6 @@ inline std::string NumberFormatter::formatHex(UInt64 value, int width, bool pref # endif // ifdef POCO_LONG_IS_64_BIT -#endif // ifdef POCO_HAVE_INT64 inline std::string NumberFormatter::format(float value) diff --git a/base/poco/Foundation/include/Poco/NumberParser.h b/base/poco/Foundation/include/Poco/NumberParser.h index de813e37dae..32f8c0dc989 100644 --- a/base/poco/Foundation/include/Poco/NumberParser.h +++ b/base/poco/Foundation/include/Poco/NumberParser.h @@ -80,7 +80,6 @@ public: /// Returns true if a valid integer has been found, false otherwise. /// If parsing was not successful, value is undefined. -#if defined(POCO_HAVE_INT64) static Int64 parse64(const std::string & s, char thousandSeparator = ','); /// Parses a 64-bit integer value in decimal notation from the given string. @@ -118,7 +117,6 @@ public: /// Returns true if a valid integer has been found, false otherwise. /// If parsing was not successful, value is undefined. -#endif // defined(POCO_HAVE_INT64) static double parseFloat(const std::string & s, char decimalSeparator = '.', char thousandSeparator = ','); /// Parses a double value in decimal floating point notation diff --git a/base/poco/Foundation/include/Poco/StreamCopier.h b/base/poco/Foundation/include/Poco/StreamCopier.h index 72b19306388..c24e73d88dd 100644 --- a/base/poco/Foundation/include/Poco/StreamCopier.h +++ b/base/poco/Foundation/include/Poco/StreamCopier.h @@ -38,7 +38,6 @@ public: /// /// Returns the number of bytes copied. -#if defined(POCO_HAVE_INT64) static Poco::UInt64 copyStream64(std::istream & istr, std::ostream & ostr, std::size_t bufferSize = 8192); /// Writes all bytes readable from istr to ostr, using an internal buffer. /// @@ -46,14 +45,12 @@ public: /// /// Note: the only difference to copyStream() is that a 64-bit unsigned /// integer is used to count the number of bytes copied. -#endif static std::streamsize copyStreamUnbuffered(std::istream & istr, std::ostream & ostr); /// Writes all bytes readable from istr to ostr. /// /// Returns the number of bytes copied. -#if defined(POCO_HAVE_INT64) static Poco::UInt64 copyStreamUnbuffered64(std::istream & istr, std::ostream & ostr); /// Writes all bytes readable from istr to ostr. /// @@ -61,14 +58,12 @@ public: /// /// Note: the only difference to copyStreamUnbuffered() is that a 64-bit unsigned /// integer is used to count the number of bytes copied. -#endif static std::streamsize copyToString(std::istream & istr, std::string & str, std::size_t bufferSize = 8192); /// Appends all bytes readable from istr to the given string, using an internal buffer. /// /// Returns the number of bytes copied. -#if defined(POCO_HAVE_INT64) static Poco::UInt64 copyToString64(std::istream & istr, std::string & str, std::size_t bufferSize = 8192); /// Appends all bytes readable from istr to the given string, using an internal buffer. /// @@ -76,7 +71,6 @@ public: /// /// Note: the only difference to copyToString() is that a 64-bit unsigned /// integer is used to count the number of bytes copied. -#endif }; diff --git a/base/poco/Foundation/include/Poco/Token.h b/base/poco/Foundation/include/Poco/Token.h index 2d62ed87de6..1aec9e620fe 100644 --- a/base/poco/Foundation/include/Poco/Token.h +++ b/base/poco/Foundation/include/Poco/Token.h @@ -84,13 +84,11 @@ public: virtual std::string asString() const; /// Returns a string representation of the token. -#if defined(POCO_HAVE_INT64) virtual Int64 asInteger64() const; /// Returns a 64-bit integer representation of the token. virtual UInt64 asUnsignedInteger64() const; /// Returns an unsigned 64-bit integer representation of the token. -#endif virtual int asInteger() const; /// Returns an integer representation of the token. diff --git a/base/poco/Foundation/include/Poco/Types.h b/base/poco/Foundation/include/Poco/Types.h index 156b3584d15..d10047344f6 100644 --- a/base/poco/Foundation/include/Poco/Types.h +++ b/base/poco/Foundation/include/Poco/Types.h @@ -46,7 +46,6 @@ typedef unsigned long UInt64; typedef signed long long Int64; typedef unsigned long long UInt64; # endif -# define POCO_HAVE_INT64 1 #endif diff --git a/base/poco/Foundation/src/BinaryReader.cpp b/base/poco/Foundation/src/BinaryReader.cpp index fb57371fbc3..f2961e03966 100644 --- a/base/poco/Foundation/src/BinaryReader.cpp +++ b/base/poco/Foundation/src/BinaryReader.cpp @@ -170,7 +170,7 @@ BinaryReader& BinaryReader::operator >> (double& value) } -#if defined(POCO_HAVE_INT64) && !defined(POCO_LONG_IS_64_BIT) +#if !defined(POCO_LONG_IS_64_BIT) BinaryReader& BinaryReader::operator >> (Int64& value) @@ -233,7 +233,6 @@ void BinaryReader::read7BitEncoded(UInt32& value) } -#if defined(POCO_HAVE_INT64) void BinaryReader::read7BitEncoded(UInt64& value) @@ -254,7 +253,6 @@ void BinaryReader::read7BitEncoded(UInt64& value) } -#endif void BinaryReader::readRaw(std::streamsize length, std::string& value) diff --git a/base/poco/Foundation/src/BinaryWriter.cpp b/base/poco/Foundation/src/BinaryWriter.cpp index 62e1adfe373..6db5ab7cb90 100644 --- a/base/poco/Foundation/src/BinaryWriter.cpp +++ b/base/poco/Foundation/src/BinaryWriter.cpp @@ -212,7 +212,7 @@ BinaryWriter& BinaryWriter::operator << (double value) } -#if defined(POCO_HAVE_INT64) && !defined(POCO_LONG_IS_64_BIT) +#if !defined(POCO_LONG_IS_64_BIT) BinaryWriter& BinaryWriter::operator << (Int64 value) @@ -303,7 +303,6 @@ void BinaryWriter::write7BitEncoded(UInt32 value) } -#if defined(POCO_HAVE_INT64) void BinaryWriter::write7BitEncoded(UInt64 value) @@ -319,7 +318,6 @@ void BinaryWriter::write7BitEncoded(UInt64 value) } -#endif void BinaryWriter::writeRaw(const std::string& rawData) diff --git a/base/poco/Foundation/src/NumberFormatter.cpp b/base/poco/Foundation/src/NumberFormatter.cpp index 5c8126e9b0a..0a9334059a9 100644 --- a/base/poco/Foundation/src/NumberFormatter.cpp +++ b/base/poco/Foundation/src/NumberFormatter.cpp @@ -234,7 +234,6 @@ void NumberFormatter::appendHex(std::string& str, unsigned long value, int width } -#ifdef POCO_HAVE_INT64 #ifdef POCO_LONG_IS_64_BIT @@ -424,7 +423,6 @@ void NumberFormatter::appendHex(std::string& str, UInt64 value, int width) #endif // ifdef POCO_LONG_IS_64_BIT -#endif // ifdef POCO_HAVE_INT64 void NumberFormatter::append(std::string& str, float value) diff --git a/base/poco/Foundation/src/NumberParser.cpp b/base/poco/Foundation/src/NumberParser.cpp index 56eeb167595..4081f3b2663 100644 --- a/base/poco/Foundation/src/NumberParser.cpp +++ b/base/poco/Foundation/src/NumberParser.cpp @@ -104,7 +104,6 @@ bool NumberParser::tryParseOct(const std::string& s, unsigned& value) } -#if defined(POCO_HAVE_INT64) Int64 NumberParser::parse64(const std::string& s, char thSep) @@ -173,7 +172,6 @@ bool NumberParser::tryParseOct64(const std::string& s, UInt64& value) } -#endif // defined(POCO_HAVE_INT64) double NumberParser::parseFloat(const std::string& s, char decSep, char thSep) diff --git a/base/poco/Foundation/src/StreamCopier.cpp b/base/poco/Foundation/src/StreamCopier.cpp index 6f34cc233a2..508d1e7b2ae 100644 --- a/base/poco/Foundation/src/StreamCopier.cpp +++ b/base/poco/Foundation/src/StreamCopier.cpp @@ -42,7 +42,6 @@ std::streamsize StreamCopier::copyStream(std::istream& istr, std::ostream& ostr, } -#if defined(POCO_HAVE_INT64) Poco::UInt64 StreamCopier::copyStream64(std::istream& istr, std::ostream& ostr, std::size_t bufferSize) { poco_assert (bufferSize > 0); @@ -64,7 +63,6 @@ Poco::UInt64 StreamCopier::copyStream64(std::istream& istr, std::ostream& ostr, } return len; } -#endif std::streamsize StreamCopier::copyToString(std::istream& istr, std::string& str, std::size_t bufferSize) @@ -90,7 +88,6 @@ std::streamsize StreamCopier::copyToString(std::istream& istr, std::string& str, } -#if defined(POCO_HAVE_INT64) Poco::UInt64 StreamCopier::copyToString64(std::istream& istr, std::string& str, std::size_t bufferSize) { poco_assert (bufferSize > 0); @@ -112,7 +109,6 @@ Poco::UInt64 StreamCopier::copyToString64(std::istream& istr, std::string& str, } return len; } -#endif std::streamsize StreamCopier::copyStreamUnbuffered(std::istream& istr, std::ostream& ostr) @@ -130,7 +126,6 @@ std::streamsize StreamCopier::copyStreamUnbuffered(std::istream& istr, std::ostr } -#if defined(POCO_HAVE_INT64) Poco::UInt64 StreamCopier::copyStreamUnbuffered64(std::istream& istr, std::ostream& ostr) { char c = 0; @@ -144,7 +139,6 @@ Poco::UInt64 StreamCopier::copyStreamUnbuffered64(std::istream& istr, std::ostre } return len; } -#endif } // namespace Poco diff --git a/base/poco/Foundation/src/Token.cpp b/base/poco/Foundation/src/Token.cpp index 98e8bb25e93..4e81c6ef885 100644 --- a/base/poco/Foundation/src/Token.cpp +++ b/base/poco/Foundation/src/Token.cpp @@ -54,7 +54,6 @@ std::string Token::asString() const } -#if defined(POCO_HAVE_INT64) Int64 Token::asInteger64() const { return NumberParser::parse64(_value); @@ -65,7 +64,6 @@ UInt64 Token::asUnsignedInteger64() const { return NumberParser::parseUnsigned64(_value); } -#endif int Token::asInteger() const diff --git a/base/poco/JSON/include/Poco/JSON/Handler.h b/base/poco/JSON/include/Poco/JSON/Handler.h index f9114a59221..c412a05003f 100644 --- a/base/poco/JSON/include/Poco/JSON/Handler.h +++ b/base/poco/JSON/include/Poco/JSON/Handler.h @@ -74,14 +74,12 @@ namespace JSON /// An unsigned value is read. This will only be triggered if the /// value cannot fit into a signed int. -#if defined(POCO_HAVE_INT64) virtual void value(Int64 v) = 0; /// A 64-bit integer value is read. virtual void value(UInt64 v) = 0; /// An unsigned 64-bit integer value is read. This will only be /// triggered if the value cannot fit into a signed 64-bit integer. -#endif virtual void value(const std::string & value) = 0; /// A string value is read. diff --git a/base/poco/JSON/include/Poco/JSON/ParseHandler.h b/base/poco/JSON/include/Poco/JSON/ParseHandler.h index 4669dc8638f..1b8ac3066d2 100644 --- a/base/poco/JSON/include/Poco/JSON/ParseHandler.h +++ b/base/poco/JSON/include/Poco/JSON/ParseHandler.h @@ -73,14 +73,12 @@ namespace JSON /// An unsigned value is read. This will only be triggered if the /// value cannot fit into a signed int. -#if defined(POCO_HAVE_INT64) virtual void value(Int64 v); /// A 64-bit integer value is read virtual void value(UInt64 v); /// An unsigned 64-bit integer value is read. This will only be /// triggered if the value cannot fit into a signed 64-bit integer. -#endif virtual void value(const std::string & s); /// A string value is read. @@ -126,7 +124,6 @@ namespace JSON } -#if defined(POCO_HAVE_INT64) inline void ParseHandler::value(Int64 v) { setValue(v); @@ -137,7 +134,6 @@ namespace JSON { setValue(v); } -#endif inline void ParseHandler::value(const std::string & s) diff --git a/base/poco/JSON/include/Poco/JSON/PrintHandler.h b/base/poco/JSON/include/Poco/JSON/PrintHandler.h index 34a991653ba..390f4d8bba9 100644 --- a/base/poco/JSON/include/Poco/JSON/PrintHandler.h +++ b/base/poco/JSON/include/Poco/JSON/PrintHandler.h @@ -81,13 +81,11 @@ namespace JSON /// An unsigned value is read. This will only be triggered if the /// value cannot fit into a signed int. -#if defined(POCO_HAVE_INT64) void value(Int64 v); /// A 64-bit integer value is read; it will be written to the output. void value(UInt64 v); /// An unsigned 64-bit integer value is read; it will be written to the output. -#endif void value(const std::string & value); /// A string value is read; it will be formatted and written to the output. diff --git a/base/poco/JSON/src/PrintHandler.cpp b/base/poco/JSON/src/PrintHandler.cpp index bf735d0869c..ea81cbdd1c0 100644 --- a/base/poco/JSON/src/PrintHandler.cpp +++ b/base/poco/JSON/src/PrintHandler.cpp @@ -154,7 +154,6 @@ void PrintHandler::value(unsigned v) } -#if defined(POCO_HAVE_INT64) void PrintHandler::value(Int64 v) { arrayValue(); @@ -169,7 +168,6 @@ void PrintHandler::value(UInt64 v) _out << v; _objStart = false; } -#endif void PrintHandler::value(const std::string& value) diff --git a/base/poco/Net/include/Poco/Net/HTTPFixedLengthStream.h b/base/poco/Net/include/Poco/Net/HTTPFixedLengthStream.h index dcdd1cfcaf8..4de211fdb92 100644 --- a/base/poco/Net/include/Poco/Net/HTTPFixedLengthStream.h +++ b/base/poco/Net/include/Poco/Net/HTTPFixedLengthStream.h @@ -43,11 +43,7 @@ namespace Net public: typedef HTTPBasicStreamBuf::openmode openmode; -#if defined(POCO_HAVE_INT64) typedef Poco::Int64 ContentLength; -#else - typedef std::streamsize ContentLength; -#endif HTTPFixedLengthStreamBuf(HTTPSession & session, ContentLength length, openmode mode); ~HTTPFixedLengthStreamBuf(); diff --git a/base/poco/Net/include/Poco/Net/HTTPMessage.h b/base/poco/Net/include/Poco/Net/HTTPMessage.h index 5c54bf7306b..0bef50803a8 100644 --- a/base/poco/Net/include/Poco/Net/HTTPMessage.h +++ b/base/poco/Net/include/Poco/Net/HTTPMessage.h @@ -56,7 +56,6 @@ namespace Net /// which may be UNKNOWN_CONTENT_LENGTH if /// no Content-Length header is present. -#if defined(POCO_HAVE_INT64) void setContentLength64(Poco::Int64 length); /// Sets the Content-Length header. /// @@ -73,7 +72,6 @@ namespace Net /// /// In contrast to getContentLength(), this method /// always returns a 64-bit integer for content length. -#endif // defined(POCO_HAVE_INT64) bool hasContentLength() const; /// Returns true iff a Content-Length header is present. diff --git a/base/poco/Net/src/HTTPClientSession.cpp b/base/poco/Net/src/HTTPClientSession.cpp index 323e9526df5..c5697b556d1 100644 --- a/base/poco/Net/src/HTTPClientSession.cpp +++ b/base/poco/Net/src/HTTPClientSession.cpp @@ -264,11 +264,7 @@ std::ostream& HTTPClientSession::sendRequest(HTTPRequest& request) { Poco::CountingOutputStream cs; request.write(cs); -#if POCO_HAVE_INT64 _pRequestStream = new HTTPFixedLengthOutputStream(*this, request.getContentLength64() + cs.chars()); -#else - _pRequestStream = new HTTPFixedLengthOutputStream(*this, request.getContentLength() + cs.chars()); -#endif request.write(*_pRequestStream); } else if ((method != HTTPRequest::HTTP_PUT && method != HTTPRequest::HTTP_POST && method != HTTPRequest::HTTP_PATCH) || request.has(HTTPRequest::UPGRADE)) @@ -334,11 +330,7 @@ std::istream& HTTPClientSession::receiveResponse(HTTPResponse& response) else if (response.getChunkedTransferEncoding()) _pResponseStream = new HTTPChunkedInputStream(*this); else if (response.hasContentLength()) -#if defined(POCO_HAVE_INT64) _pResponseStream = new HTTPFixedLengthInputStream(*this, response.getContentLength64()); -#else - _pResponseStream = new HTTPFixedLengthInputStream(*this, response.getContentLength()); -#endif else _pResponseStream = new HTTPInputStream(*this); diff --git a/base/poco/Net/src/HTTPMessage.cpp b/base/poco/Net/src/HTTPMessage.cpp index debda04c3b3..0cd234ee9cb 100644 --- a/base/poco/Net/src/HTTPMessage.cpp +++ b/base/poco/Net/src/HTTPMessage.cpp @@ -89,7 +89,6 @@ std::streamsize HTTPMessage::getContentLength() const } -#if defined(POCO_HAVE_INT64) void HTTPMessage::setContentLength64(Poco::Int64 length) { if (length != UNKNOWN_CONTENT_LENGTH) @@ -108,7 +107,6 @@ Poco::Int64 HTTPMessage::getContentLength64() const } else return UNKNOWN_CONTENT_LENGTH; } -#endif // defined(POCO_HAVE_INT64) void HTTPMessage::setTransferEncoding(const std::string& transferEncoding) diff --git a/base/poco/Net/src/HTTPServerRequestImpl.cpp b/base/poco/Net/src/HTTPServerRequestImpl.cpp index d8ea7398c9b..d893e49aafb 100644 --- a/base/poco/Net/src/HTTPServerRequestImpl.cpp +++ b/base/poco/Net/src/HTTPServerRequestImpl.cpp @@ -49,11 +49,7 @@ HTTPServerRequestImpl::HTTPServerRequestImpl(HTTPServerResponseImpl& response, H if (getChunkedTransferEncoding()) _pStream = new HTTPChunkedInputStream(session); else if (hasContentLength()) -#if defined(POCO_HAVE_INT64) _pStream = new HTTPFixedLengthInputStream(session, getContentLength64()); -#else - _pStream = new HTTPFixedLengthInputStream(session, getContentLength()); -#endif else if (getMethod() == HTTPRequest::HTTP_GET || getMethod() == HTTPRequest::HTTP_HEAD || getMethod() == HTTPRequest::HTTP_DELETE) _pStream = new HTTPFixedLengthInputStream(session, 0); else diff --git a/base/poco/Net/src/HTTPServerResponseImpl.cpp b/base/poco/Net/src/HTTPServerResponseImpl.cpp index fb6783c633e..55de22c876c 100644 --- a/base/poco/Net/src/HTTPServerResponseImpl.cpp +++ b/base/poco/Net/src/HTTPServerResponseImpl.cpp @@ -92,11 +92,7 @@ std::ostream& HTTPServerResponseImpl::send() { Poco::CountingOutputStream cs; write(cs); -#if defined(POCO_HAVE_INT64) _pStream = new HTTPFixedLengthOutputStream(_session, getContentLength64() + cs.chars()); -#else - _pStream = new HTTPFixedLengthOutputStream(_session, getContentLength() + cs.chars()); -#endif write(*_pStream); } else @@ -153,11 +149,7 @@ void HTTPServerResponseImpl::sendFile(const std::string& path, const std::string Timestamp dateTime = f.getLastModified(); File::FileSize length = f.getSize(); set("Last-Modified", DateTimeFormatter::format(dateTime, DateTimeFormat::HTTP_FORMAT)); -#if defined(POCO_HAVE_INT64) setContentLength64(length); -#else - setContentLength(static_cast(length)); -#endif setContentType(mediaType); setChunkedTransferEncoding(false); diff --git a/base/poco/Util/include/Poco/Util/AbstractConfiguration.h b/base/poco/Util/include/Poco/Util/AbstractConfiguration.h index a0e5e2c50dd..926ac3ba8a9 100644 --- a/base/poco/Util/include/Poco/Util/AbstractConfiguration.h +++ b/base/poco/Util/include/Poco/Util/AbstractConfiguration.h @@ -167,7 +167,6 @@ namespace Util /// If the value contains references to other properties (${}), these /// are expanded. -#if defined(POCO_HAVE_INT64) Int64 getInt64(const std::string & key) const; /// Returns the Int64 value of the property with the given name. @@ -205,7 +204,6 @@ namespace Util /// If the value contains references to other properties (${}), these /// are expanded. -#endif // defined(POCO_HAVE_INT64) double getDouble(const std::string & key) const; /// Returns the double value of the property with the given name. @@ -255,7 +253,6 @@ namespace Util /// Sets the property with the given key to the given value. /// An already existing value for the key is overwritten. -#if defined(POCO_HAVE_INT64) virtual void setInt64(const std::string & key, Int64 value); /// Sets the property with the given key to the given value. @@ -265,7 +262,6 @@ namespace Util /// Sets the property with the given key to the given value. /// An already existing value for the key is overwritten. -#endif // defined(POCO_HAVE_INT64) virtual void setDouble(const std::string & key, double value); /// Sets the property with the given key to the given value. @@ -335,12 +331,10 @@ namespace Util static int parseInt(const std::string & value); static unsigned parseUInt(const std::string & value); -#if defined(POCO_HAVE_INT64) static Int64 parseInt64(const std::string & value); static UInt64 parseUInt64(const std::string & value); -#endif // defined(POCO_HAVE_INT64) static bool parseBool(const std::string & value); void setRawWithEvent(const std::string & key, std::string value); diff --git a/base/poco/Util/include/Poco/Util/WinRegistryKey.h b/base/poco/Util/include/Poco/Util/WinRegistryKey.h index b28f6aefb37..9aa5e35ed8a 100644 --- a/base/poco/Util/include/Poco/Util/WinRegistryKey.h +++ b/base/poco/Util/include/Poco/Util/WinRegistryKey.h @@ -123,7 +123,6 @@ namespace Util /// /// Throws a NotFoundException if the value does not exist. -#if defined(POCO_HAVE_INT64) void setInt64(const std::string & name, Poco::Int64 value); /// Sets the numeric (REG_QWORD) value with the given name. @@ -135,7 +134,6 @@ namespace Util /// /// Throws a NotFoundException if the value does not exist. -#endif // POCO_HAVE_INT64 void deleteValue(const std::string & name); /// Deletes the value with the given name. diff --git a/base/poco/Util/src/AbstractConfiguration.cpp b/base/poco/Util/src/AbstractConfiguration.cpp index 95e8da68a57..2c892decd9a 100644 --- a/base/poco/Util/src/AbstractConfiguration.cpp +++ b/base/poco/Util/src/AbstractConfiguration.cpp @@ -163,7 +163,6 @@ unsigned AbstractConfiguration::getUInt(const std::string& key, unsigned default } -#if defined(POCO_HAVE_INT64) Int64 AbstractConfiguration::getInt64(const std::string& key) const @@ -214,7 +213,6 @@ UInt64 AbstractConfiguration::getUInt64(const std::string& key, UInt64 defaultVa } -#endif // defined(POCO_HAVE_INT64) double AbstractConfiguration::getDouble(const std::string& key) const @@ -283,7 +281,6 @@ void AbstractConfiguration::setUInt(const std::string& key, unsigned int value) } -#if defined(POCO_HAVE_INT64) void AbstractConfiguration::setInt64(const std::string& key, Int64 value) @@ -302,7 +299,6 @@ void AbstractConfiguration::setUInt64(const std::string& key, UInt64 value) } -#endif // defined(POCO_HAVE_INT64) void AbstractConfiguration::setDouble(const std::string& key, double value) From 7f5fb77ed51e6dc8beb842dded898b891972e51d Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 22 Feb 2023 15:09:48 +0100 Subject: [PATCH 104/229] Increase table retries in cluster copier tests (#46590) --- programs/copier/ClusterCopier.cpp | 4 ++-- tests/integration/test_cluster_copier/test.py | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/programs/copier/ClusterCopier.cpp b/programs/copier/ClusterCopier.cpp index 48a3578dd7b..bc882719a08 100644 --- a/programs/copier/ClusterCopier.cpp +++ b/programs/copier/ClusterCopier.cpp @@ -908,7 +908,7 @@ bool ClusterCopier::tryProcessTable(const ConnectionTimeouts & timeouts, TaskTab /// Exit if success if (task_status != TaskStatus::Finished) { - LOG_WARNING(log, "Create destination Tale Failed "); + LOG_WARNING(log, "Create destination table failed "); return false; } @@ -1473,7 +1473,7 @@ TaskStatus ClusterCopier::processPartitionPieceTaskImpl( if (count != 0) { - LOG_INFO(log, "Partition {} piece {}is not empty. In contains {} rows.", task_partition.name, current_piece_number, count); + LOG_INFO(log, "Partition {} piece {} is not empty. In contains {} rows.", task_partition.name, current_piece_number, count); Coordination::Stat stat_shards{}; zookeeper->get(partition_piece.getPartitionPieceShardsPath(), &stat_shards); diff --git a/tests/integration/test_cluster_copier/test.py b/tests/integration/test_cluster_copier/test.py index 0aadcadc064..b261f7e3a39 100644 --- a/tests/integration/test_cluster_copier/test.py +++ b/tests/integration/test_cluster_copier/test.py @@ -565,13 +565,20 @@ def test_copy_with_recovering(started_cluster, use_sample_offset): str(COPYING_FAIL_PROBABILITY), "--experimental-use-sample-offset", "1", + "--max-table-tries", + "10", ], ) else: execute_task( started_cluster, Task1(started_cluster), - ["--copy-fault-probability", str(COPYING_FAIL_PROBABILITY)], + [ + "--copy-fault-probability", + str(COPYING_FAIL_PROBABILITY), + "--max-table-tries", + "10", + ], ) @@ -606,7 +613,12 @@ def test_copy_month_to_week_partition_with_recovering(started_cluster): execute_task( started_cluster, Task2(started_cluster, "test2"), - ["--copy-fault-probability", str(COPYING_FAIL_PROBABILITY)], + [ + "--copy-fault-probability", + str(COPYING_FAIL_PROBABILITY), + "--max-table-tries", + "10", + ], ) From 9ab4944b9ed0d0d126688d153477e5bbfd0d0fdd Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 22 Feb 2023 13:55:55 +0100 Subject: [PATCH 105/229] Handle input_format_null_as_default for nested types Signed-off-by: Azat Khuzhin --- .../Serializations/SerializationArray.cpp | 6 ++- .../Serializations/SerializationMap.cpp | 6 ++- .../Serializations/SerializationNullable.cpp | 6 +-- .../Serializations/SerializationTuple.cpp | 6 ++- ...nore_unknown_keys_in_named_tuple.reference | 8 ++-- ...json_ignore_unknown_keys_in_named_tuple.sh | 23 ++++------ ..._as_default_null_as_empty_nested.reference | 42 +++++++++++++++++++ ...t_null_as_default_null_as_empty_nested.sql | 25 +++++++++++ 8 files changed, 94 insertions(+), 28 deletions(-) create mode 100644 tests/queries/0_stateless/02573_insert_null_as_default_null_as_empty_nested.reference create mode 100644 tests/queries/0_stateless/02573_insert_null_as_default_null_as_empty_nested.sql diff --git a/src/DataTypes/Serializations/SerializationArray.cpp b/src/DataTypes/Serializations/SerializationArray.cpp index 24aa9e8320d..73b232690c7 100644 --- a/src/DataTypes/Serializations/SerializationArray.cpp +++ b/src/DataTypes/Serializations/SerializationArray.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -510,7 +511,10 @@ void SerializationArray::deserializeTextJSON(IColumn & column, ReadBuffer & istr deserializeTextImpl(column, istr, [&](IColumn & nested_column) { - nested->deserializeTextJSON(nested_column, istr, settings); + if (settings.null_as_default) + SerializationNullable::deserializeTextJSONImpl(nested_column, istr, settings, nested); + else + nested->deserializeTextJSON(nested_column, istr, settings); }, false); } diff --git a/src/DataTypes/Serializations/SerializationMap.cpp b/src/DataTypes/Serializations/SerializationMap.cpp index 98067077178..34da0f11cae 100644 --- a/src/DataTypes/Serializations/SerializationMap.cpp +++ b/src/DataTypes/Serializations/SerializationMap.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -211,7 +212,10 @@ void SerializationMap::deserializeTextJSON(IColumn & column, ReadBuffer & istr, deserializeTextImpl(column, istr, [&settings](ReadBuffer & buf, const SerializationPtr & subcolumn_serialization, IColumn & subcolumn) { - subcolumn_serialization->deserializeTextJSON(subcolumn, buf, settings); + if (settings.null_as_default) + SerializationNullable::deserializeTextJSONImpl(subcolumn, buf, settings, subcolumn_serialization); + else + subcolumn_serialization->deserializeTextJSON(subcolumn, buf, settings); }); } diff --git a/src/DataTypes/Serializations/SerializationNullable.cpp b/src/DataTypes/Serializations/SerializationNullable.cpp index 8b0bdc05d00..20188f7cec5 100644 --- a/src/DataTypes/Serializations/SerializationNullable.cpp +++ b/src/DataTypes/Serializations/SerializationNullable.cpp @@ -219,13 +219,9 @@ static ReturnType safeDeserialize( /// Deserialize value into non-nullable column. In case of NULL, insert default value and return false. template , ReturnType>* = nullptr> static ReturnType safeDeserialize( - IColumn & column, const ISerialization & nested, + IColumn & column, const ISerialization &, CheckForNull && check_for_null, DeserializeNested && deserialize_nested) { - assert(!dynamic_cast(&column)); - assert(!dynamic_cast(&nested)); - UNUSED(nested); - bool insert_default = check_for_null(); if (insert_default) column.insertDefault(); diff --git a/src/DataTypes/Serializations/SerializationTuple.cpp b/src/DataTypes/Serializations/SerializationTuple.cpp index ce15e099222..ef11b3e4660 100644 --- a/src/DataTypes/Serializations/SerializationTuple.cpp +++ b/src/DataTypes/Serializations/SerializationTuple.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -231,7 +232,10 @@ void SerializationTuple::deserializeTextJSON(IColumn & column, ReadBuffer & istr seen_elements[element_pos] = 1; auto & element_column = extractElementColumn(column, element_pos); - elems[element_pos]->deserializeTextJSON(element_column, istr, settings); + if (settings.null_as_default) + SerializationNullable::deserializeTextJSONImpl(element_column, istr, settings, elems[element_pos]); + else + elems[element_pos]->deserializeTextJSON(element_column, istr, settings); skipWhitespaceIfAny(istr); ++processed; diff --git a/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.reference b/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.reference index a1b4e2b5a83..b7edddf46e0 100644 --- a/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.reference +++ b/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.reference @@ -3,11 +3,11 @@ INCORRECT_DATA NOT_FOUND_COLUMN_IN_BLOCK (1) { - "row_1": {"type":"CreateEvent","actor":{"login":"foobar"},"repo":{"name":"ClickHouse\/ClickHouse"},"created_at":"2023-01-26 10:48:02","payload":{"updated_at":"1970-01-01 00:00:00","action":"","comment":{"id":"0","path":"","position":0,"line":0,"user":{"login":""},"diff_hunk":"","original_position":0,"commit_id":"","original_commit_id":""},"review":{"body":"","author_association":"","state":""},"ref":"backport","ref_type":"branch","issue":{"number":0,"title":"","labels":[],"state":"","locked":0,"assignee":{"login":""},"assignees":[],"comment":"","closed_at":"1970-01-01 00:00:00"},"pull_request":{"merged_at":null,"merge_commit_sha":"","requested_reviewers":[],"requested_teams":[],"head":{"ref":"","sha":""},"base":{"ref":"","sha":""},"merged":0,"mergeable":0,"rebaseable":0,"mergeable_state":"","merged_by":null,"review_comments":0,"maintainer_can_modify":0,"commits":0,"additions":0,"deletions":0,"changed_files":0},"size":0,"distinct_size":0,"member":{"login":""},"release":{"tag_name":"","name":""}}} + "row_1": {"type":"CreateEvent","actor":{"login":"foobar"},"repo":{"name":"ClickHouse\/ClickHouse"},"created_at":"2023-01-26 10:48:02","payload":{"updated_at":"1970-01-01 00:00:00","action":"","comment":{"id":"0","path":"","position":0,"line":0,"user":{"login":""},"diff_hunk":"","original_position":0,"commit_id":"","original_commit_id":""},"review":{"body":"","author_association":"","state":""},"ref":"backport","ref_type":"branch","issue":{"number":0,"title":"","labels":[],"state":"","locked":0,"assignee":{"login":""},"assignees":[],"comment":"","closed_at":"1970-01-01 00:00:00"},"pull_request":{"merged_at":"1970-01-01 00:00:00","merge_commit_sha":"","requested_reviewers":[],"requested_teams":[],"head":{"ref":"","sha":""},"base":{"ref":"","sha":""},"merged":0,"mergeable":0,"rebaseable":0,"mergeable_state":"","merged_by":{"login":""},"review_comments":0,"maintainer_can_modify":0,"commits":0,"additions":0,"deletions":0,"changed_files":0},"size":0,"distinct_size":0,"member":{"login":""},"release":{"tag_name":"","name":""}}} } { - "row_1": {"labels":[],"merged_by":""}, + "row_1": {"labels":[],"merged_by":""}, "row_2": {"labels":[],"merged_by":"foobar"}, - "row_3": {"labels":[],"merged_by":""}, - "row_4": {"labels":["backport"],"merged_by":""} + "row_3": {"labels":[],"merged_by":""}, + "row_4": {"labels":["backport"],"merged_by":""} } diff --git a/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.sh b/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.sh index f37a36fa192..eccac543215 100755 --- a/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.sh +++ b/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.sh @@ -60,7 +60,7 @@ gharchive_structure=( closed_at DateTime('UTC') ), pull_request Tuple( - merged_at Nullable(DateTime('UTC')), + merged_at DateTime('UTC'), merge_commit_sha String, requested_reviewers Nested( login String @@ -80,16 +80,9 @@ gharchive_structure=( mergeable UInt8, rebaseable UInt8, mergeable_state String, - merged_by Nullable(String), - /* NOTE: correct type is Tuple, however Tuple cannot be Nullable, - * so you still have to use Nullable(String) and rely on - * input_format_json_read_objects_as_strings, but see also - * https://github.com/ClickHouse/ClickHouse/issues/36464 - */ - /* merged_by Tuple( - * login String - * ), - */ + merged_by Tuple( + login String + ), review_comments UInt32, maintainer_can_modify UInt8, commits UInt32, @@ -122,12 +115,10 @@ EOL # NOTE: due to [1] we cannot use dot.dot notation, only tupleElement() # # [1]: https://github.com/ClickHouse/ClickHouse/issues/24607 -$CLICKHOUSE_LOCAL "${gharchive_settings[@]}" --structure="${gharchive_structure[*]}" -q " - WITH - tupleElement(tupleElement(payload, 'pull_request'), 'merged_by') AS merged_by_ +$CLICKHOUSE_LOCAL --allow_experimental_analyzer=1 "${gharchive_settings[@]}" --structure="${gharchive_structure[*]}" -q " SELECT - tupleElement(tupleElement(tupleElement(payload, 'issue'), 'labels'), 'name') AS labels, - if(merged_by_ IS NULL, '', JSONExtractString(merged_by_, 'login')) AS merged_by + payload.issue.labels.name AS labels, + payload.pull_request.merged_by.login AS merged_by FROM table " < Date: Wed, 22 Feb 2023 15:51:13 +0100 Subject: [PATCH 106/229] Analyzer AutoFinalOnQueryPass fix --- src/Analyzer/Passes/AutoFinalOnQueryPass.cpp | 87 ++++++++++++-------- src/Analyzer/Passes/AutoFinalOnQueryPass.h | 16 +++- src/Analyzer/TableExpressionModifiers.h | 6 ++ src/Analyzer/TableFunctionNode.h | 6 ++ src/Analyzer/TableNode.h | 6 ++ 5 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/Analyzer/Passes/AutoFinalOnQueryPass.cpp b/src/Analyzer/Passes/AutoFinalOnQueryPass.cpp index 10efebe0731..fdf818681d7 100644 --- a/src/Analyzer/Passes/AutoFinalOnQueryPass.cpp +++ b/src/Analyzer/Passes/AutoFinalOnQueryPass.cpp @@ -1,8 +1,11 @@ #include "AutoFinalOnQueryPass.h" -#include -#include #include + +#include +#include +#include +#include #include namespace DB @@ -10,52 +13,64 @@ namespace DB namespace { - class AutoFinalOnQueryPassVisitor : public InDepthQueryTreeVisitorWithContext + +class AutoFinalOnQueryPassVisitor : public InDepthQueryTreeVisitorWithContext +{ +public: + using Base = InDepthQueryTreeVisitorWithContext; + using Base::Base; + + void visitImpl(QueryTreeNodePtr & node) { - public: - using Base = InDepthQueryTreeVisitorWithContext; - using Base::Base; + const auto & context = getContext(); + if (!context->getSettingsRef().final) + return; - void visitImpl(QueryTreeNodePtr & node) + const auto * query_node = node->as(); + if (!query_node) + return; + + auto table_expressions = extractTableExpressions(query_node->getJoinTree()); + for (auto & table_expression : table_expressions) + applyFinalIfNeeded(table_expression); + } +private: + static void applyFinalIfNeeded(QueryTreeNodePtr & node) + { + auto * table_node = node->as(); + auto * table_function_node = node->as(); + if (!table_node && !table_function_node) + return; + + const auto & storage = table_node ? table_node->getStorage() : table_function_node->getStorage(); + bool is_final_supported = storage && storage->supportsFinal() && !storage->isRemote(); + if (!is_final_supported) + return; + + TableExpressionModifiers table_expression_modifiers_with_final(true /*has_final*/, {}, {}); + + if (table_node) { - if (auto * table_node = node->as()) - { - if (autoFinalOnQuery(*table_node, table_node->getStorage(), getContext())) - { - auto modifier = TableExpressionModifiers(true, std::nullopt, std::nullopt); - table_node->setTableExpressionModifiers(modifier); - } - } + if (table_node->hasTableExpressionModifiers()) + table_node->getTableExpressionModifiers()->setHasFinal(true); + else + table_node->setTableExpressionModifiers(table_expression_modifiers_with_final); } - - private: - static bool autoFinalOnQuery(TableNode & table_node, StoragePtr storage, ContextPtr context) + else if (table_function_node) { - bool is_auto_final_setting_on = context->getSettingsRef().final; - bool is_final_supported = storage && storage->supportsFinal() && !storage->isRemote(); - bool is_query_already_final = table_node.hasTableExpressionModifiers() ? table_node.getTableExpressionModifiers().has_value() : false; - - return is_auto_final_setting_on && !is_query_already_final && is_final_supported; + if (table_function_node->hasTableExpressionModifiers()) + table_function_node->getTableExpressionModifiers()->setHasFinal(true); + else + table_function_node->setTableExpressionModifiers(table_expression_modifiers_with_final); } + } +}; - }; - -} - -String AutoFinalOnQueryPass::getName() -{ - return "AutoFinalOnQueryPass"; -} - -String AutoFinalOnQueryPass::getDescription() -{ - return "Automatically applies final modifier to queries if it is supported and if user level final setting is set."; } void AutoFinalOnQueryPass::run(QueryTreeNodePtr query_tree_node, ContextPtr context) { auto visitor = AutoFinalOnQueryPassVisitor(std::move(context)); - visitor.visit(query_tree_node); } diff --git a/src/Analyzer/Passes/AutoFinalOnQueryPass.h b/src/Analyzer/Passes/AutoFinalOnQueryPass.h index eacbe0f8235..3489597108c 100644 --- a/src/Analyzer/Passes/AutoFinalOnQueryPass.h +++ b/src/Analyzer/Passes/AutoFinalOnQueryPass.h @@ -7,13 +7,23 @@ namespace DB { - +/** Automatically applies final modifier to table expressions in queries if it is supported and if user level final setting is set. + * + * Example: SELECT id, value FROM test_table; + * Result: SELECT id, value FROM test_table FINAL; + */ class AutoFinalOnQueryPass final : public IQueryTreePass { public: - String getName() override; + String getName() override + { + return "AutoFinalOnQueryPass"; + } - String getDescription() override; + String getDescription() override + { + return "Automatically applies final modifier to table expressions in queries if it is supported and if user level final setting is set"; + } void run(QueryTreeNodePtr query_tree_node, ContextPtr context) override; }; diff --git a/src/Analyzer/TableExpressionModifiers.h b/src/Analyzer/TableExpressionModifiers.h index f61c2a61610..9b76c9bc0fd 100644 --- a/src/Analyzer/TableExpressionModifiers.h +++ b/src/Analyzer/TableExpressionModifiers.h @@ -28,6 +28,12 @@ public: return has_final; } + /// Set has final value + void setHasFinal(bool value) + { + has_final = value; + } + /// Returns true if sample size ratio is specified, false otherwise bool hasSampleSizeRatio() const { diff --git a/src/Analyzer/TableFunctionNode.h b/src/Analyzer/TableFunctionNode.h index 292ab740c5b..a88630ffd00 100644 --- a/src/Analyzer/TableFunctionNode.h +++ b/src/Analyzer/TableFunctionNode.h @@ -116,6 +116,12 @@ public: return table_expression_modifiers; } + /// Get table expression modifiers + std::optional & getTableExpressionModifiers() + { + return table_expression_modifiers; + } + /// Set table expression modifiers void setTableExpressionModifiers(TableExpressionModifiers table_expression_modifiers_value) { diff --git a/src/Analyzer/TableNode.h b/src/Analyzer/TableNode.h index 4965de535df..6d47f87c78b 100644 --- a/src/Analyzer/TableNode.h +++ b/src/Analyzer/TableNode.h @@ -68,6 +68,12 @@ public: return table_expression_modifiers; } + /// Get table expression modifiers + std::optional & getTableExpressionModifiers() + { + return table_expression_modifiers; + } + /// Set table expression modifiers void setTableExpressionModifiers(TableExpressionModifiers table_expression_modifiers_value) { From cdbff57e6c91930b7b4eb9535f6b4d17e17641be Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky <43110995+evillique@users.noreply.github.com> Date: Wed, 22 Feb 2023 15:58:06 +0100 Subject: [PATCH 107/229] Ask for password interactively --- programs/client/Client.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp index 3be96a4b0a0..af66a4ac61d 100644 --- a/programs/client/Client.cpp +++ b/programs/client/Client.cpp @@ -327,7 +327,29 @@ try showClientVersion(); } - connect(); + try + { + connect(); + } + catch (const Exception & e) + { + if (e.code() == DB::ErrorCodes::AUTHENTICATION_FAILED) + { + if (!config().getString("password", "").empty()) + throw; + + if (!is_interactive) + throw; + + String prompt = fmt::format("Password for user ({}): ", config().getString("user", "")); + String password; + if (auto * result = readpassphrase(prompt, buf, sizeof(buf), 0)) + password = result; + + config().setString("password", password); + connect(); + } + } /// Show warnings at the beginning of connection. if (is_interactive && !config().has("no-warnings")) From 986dd728705230e8debd5d88289193e50ff284b2 Mon Sep 17 00:00:00 2001 From: avogar Date: Wed, 22 Feb 2023 15:18:13 +0000 Subject: [PATCH 108/229] Fix possible clickhouse-local abort on JSONEachRow schema inference --- src/Processors/Formats/ISchemaReader.cpp | 4 +++- ...local_desc_abort_on_twitter_json.reference | 1 + .../02669_local_desc_abort_on_twitter_json.sh | 8 ++++++++ .../0_stateless/data_json/twitter.jsonl | Bin 0 -> 9940434 bytes 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.reference create mode 100755 tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.sh create mode 100644 tests/queries/0_stateless/data_json/twitter.jsonl diff --git a/src/Processors/Formats/ISchemaReader.cpp b/src/Processors/Formats/ISchemaReader.cpp index 48cb093f0ab..48dcdc657e6 100644 --- a/src/Processors/Formats/ISchemaReader.cpp +++ b/src/Processors/Formats/ISchemaReader.cpp @@ -223,8 +223,10 @@ NamesAndTypesList IRowWithNamesSchemaReader::readSchema() break; std::unordered_set names_set; /// We should check for duplicate column names in current row - for (auto & [name, new_type] : new_names_and_types) + for (auto & new_name_and_type : new_names_and_types) { + auto & name = new_name_and_type.name; + auto & new_type = new_name_and_type.type; if (names_set.contains(name)) throw Exception(ErrorCodes::INCORRECT_DATA, "Duplicate column name found while schema inference: \"{}\"", name); names_set.insert(name); diff --git a/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.reference b/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.reference new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.reference @@ -0,0 +1 @@ +1 diff --git a/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.sh b/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.sh new file mode 100755 index 00000000000..e4f738f18ff --- /dev/null +++ b/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_LOCAL -q "desc file('$CUR_DIR/data_json/twitter.jsonl')" 2>&1 | grep -c "ONLY_NULLS_WHILE_READING_SCHEMA" + diff --git a/tests/queries/0_stateless/data_json/twitter.jsonl b/tests/queries/0_stateless/data_json/twitter.jsonl new file mode 100644 index 0000000000000000000000000000000000000000..4c5eaabcb1b1478b4b126b2168b2353668b1fe53 GIT binary patch literal 9940434 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
EN1Dr1ePMli1QRC- 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 z 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