From c855cf705774b5e6c786858e6f6c33c6f157f822 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 2 Jun 2021 19:56:24 +0300 Subject: [PATCH 001/672] Part 1. --- src/Storages/MergeTree/KeyCondition.cpp | 198 +++++++++++++++++++----- src/Storages/MergeTree/KeyCondition.h | 16 +- 2 files changed, 171 insertions(+), 43 deletions(-) diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 268c45c305f..38c849c728c 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -114,6 +114,130 @@ static String firstStringThatIsGreaterThanAllStringsWithPrefix(const String & pr return res; } +class KeyCondition::Tree +{ +public: + explicit Tree(const IAST * ast_) : ast(ast_) {} + explicit Tree(const ActionsDAG::Node * dag_) : dag(dag_) {} + + std::string getColumnName() const + { + if (ast) + return ast->getColumnNameWithoutAlias(); + else + getColumnNameWithoutAlias(dag); + } + + // size_t numChildren() const + // { + // if (ast) + // return ast->children.size(); + // else + // return dag->children.size(); + // } + + // Tree getChildrenAt(size_t idx) const + // { + // if (ast) + // return Tree(ast->children[idx].get()); + // else + // return Tree(dag->children[idx]); + // } + + bool isFunction() const + { + if (ast) + return typeid_cast(ast); + else + return dag->type == ActionsDAG::ActionType::FUNCTION; + } + + bool isConstant() const + { + if (ast) + return typeid_cast(ast); + else + return dag->type == ActionsDAG::ActionType::COLUMN; + } + + ColumnWithTypeAndName getConstant() const + { + if (!isConstant()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "KeyCondition::Tree node is not a constant"); + + ColumnWithTypeAndName res; + + if (ast) + { + const auto * literal = assert_cast(ast); + res.type = applyVisitor(FieldToDataType(), literal->value); + res.column = res.type->createColumnConst(0, literal->value); + + } + else + { + res.type = dag->result_type; + res.column = dag->column; + } + + return res; + } + + FunctionTree asFunction() const; + +protected: + const IAST * ast = nullptr; + const ActionsDAG::Node * dag = nullptr; +}; + +class KeyCondition::FunctionTree : public KeyCondition::Tree +{ +public: + std::string getFunctionName() const + { + if (ast) + return assert_cast(ast)->name; + else + return dag->function_base->getName(); + } + + size_t numArguments() const + { + if (ast) + { + const auto * func = assert_cast(ast); + return func->arguments ? 0 : func->arguments->size(); + } + else + return dag->children.size(); + } + + Tree getArgumentAt(size_t idx) const + { + if (ast) + return Tree(assert_cast(ast)->arguments->children[idx].get()); + else + return Tree(dag->children[idx]); + } + +private: + using Tree::Tree; + + friend class Tree; +}; + + +KeyCondition::FunctionTree KeyCondition::Tree::asFunction() const +{ + if (!isFunction()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "KeyCondition::Tree node is not a function"); + + if (ast) + return KeyCondition::FunctionTree(ast); + else + return KeyCondition::FunctionTree(dag); +} + /// A dictionary containing actions to the corresponding functions to turn them into `RPNElement` const KeyCondition::AtomMap KeyCondition::atom_map @@ -560,18 +684,19 @@ static FieldRef applyFunction(const FunctionBasePtr & func, const DataTypePtr & return {field.columns, field.row_idx, result_idx}; } -void KeyCondition::traverseAST(const ASTPtr & node, ContextPtr context, Block & block_with_constants) +void KeyCondition::traverseAST(const Tree & node, ContextPtr context, Block & block_with_constants) { RPNElement element; - if (const auto * func = node->as()) + if (node.isFunction()) { + auto func = node.asFunction(); if (tryParseLogicalOperatorFromAST(func, element)) { - auto & args = func->arguments->children; - for (size_t i = 0, size = args.size(); i < size; ++i) + size_t num_args = func.numArguments(); + for (size_t i = 0; i < num_args; ++i) { - traverseAST(args[i], context, block_with_constants); + traverseAST(func.getArgumentAt(i), context, block_with_constants); /** The first part of the condition is for the correct support of `and` and `or` functions of arbitrary arity * - in this case `n - 1` elements are added (where `n` is the number of arguments). @@ -968,13 +1093,13 @@ private: bool KeyCondition::isKeyPossiblyWrappedByMonotonicFunctions( - const ASTPtr & node, + const Tree & node, ContextPtr context, size_t & out_key_column_num, DataTypePtr & out_key_res_column_type, MonotonicFunctionsChain & out_functions_chain) { - std::vector chain_not_tested_for_monotonicity; + std::vector chain_not_tested_for_monotonicity; DataTypePtr key_column_type; if (!isKeyPossiblyWrappedByMonotonicFunctionsImpl(node, out_key_column_num, key_column_type, chain_not_tested_for_monotonicity)) @@ -982,14 +1107,14 @@ bool KeyCondition::isKeyPossiblyWrappedByMonotonicFunctions( for (auto it = chain_not_tested_for_monotonicity.rbegin(); it != chain_not_tested_for_monotonicity.rend(); ++it) { - const auto & args = (*it)->arguments->children; - auto func_builder = FunctionFactory::instance().tryGet((*it)->name, context); + auto func = *it; + auto func_builder = FunctionFactory::instance().tryGet(func.getFunctionName(), context); if (!func_builder) return false; ColumnsWithTypeAndName arguments; ColumnWithTypeAndName const_arg; FunctionWithOptionalConstArg::Kind kind = FunctionWithOptionalConstArg::Kind::NO_CONST; - if (args.size() == 2) + if (func.numArguments() == 2) { if (const auto * arg_left = args[0]->as()) { @@ -1029,10 +1154,10 @@ bool KeyCondition::isKeyPossiblyWrappedByMonotonicFunctions( } bool KeyCondition::isKeyPossiblyWrappedByMonotonicFunctionsImpl( - const ASTPtr & node, + const Tree & node, size_t & out_key_column_num, DataTypePtr & out_key_column_type, - std::vector & out_functions_chain) + std::vector & out_functions_chain) { /** By itself, the key column can be a functional expression. for example, `intHash32(UserID)`. * Therefore, use the full name of the expression for search. @@ -1040,7 +1165,7 @@ bool KeyCondition::isKeyPossiblyWrappedByMonotonicFunctionsImpl( const auto & sample_block = key_expr->getSampleBlock(); // Key columns should use canonical names for index analysis - String name = node->getColumnNameWithoutAlias(); + String name = node.getColumnName(); auto it = key_columns.find(name); if (key_columns.end() != it) @@ -1050,31 +1175,30 @@ bool KeyCondition::isKeyPossiblyWrappedByMonotonicFunctionsImpl( return true; } - if (const auto * func = node->as()) + if (node.isFunction()) { - if (!func->arguments) - return false; + auto func = node.asFunction(); - const auto & args = func->arguments->children; - if (args.size() > 2 || args.empty()) + size_t num_args = func.numArguments(); + if (num_args > 2 || num_args == 0) return false; out_functions_chain.push_back(func); bool ret = false; - if (args.size() == 2) + if (num_args == 2) { - if (args[0]->as()) + if (func.getArgumentAt(0).isConstant()) { - ret = isKeyPossiblyWrappedByMonotonicFunctionsImpl(args[1], out_key_column_num, out_key_column_type, out_functions_chain); + ret = isKeyPossiblyWrappedByMonotonicFunctionsImpl(func.getArgumentAt(1), out_key_column_num, out_key_column_type, out_functions_chain); } - else if (args[1]->as()) + else if (func.getArgumentAt(1).isConstant()) { - ret = isKeyPossiblyWrappedByMonotonicFunctionsImpl(args[0], out_key_column_num, out_key_column_type, out_functions_chain); + ret = isKeyPossiblyWrappedByMonotonicFunctionsImpl(func.getArgumentAt(0), out_key_column_num, out_key_column_type, out_functions_chain); } } else { - ret = isKeyPossiblyWrappedByMonotonicFunctionsImpl(args[0], out_key_column_num, out_key_column_type, out_functions_chain); + ret = isKeyPossiblyWrappedByMonotonicFunctionsImpl(func.getArgumentAt(0), out_key_column_num, out_key_column_type, out_functions_chain); } return ret; } @@ -1099,7 +1223,7 @@ static void castValueToType(const DataTypePtr & desired_type, Field & src_value, } -bool KeyCondition::tryParseAtomFromAST(const ASTPtr & node, ContextPtr context, Block & block_with_constants, RPNElement & out) +bool KeyCondition::tryParseAtomFromAST(const Tree & node, ContextPtr context, Block & block_with_constants, RPNElement & out) { /** Functions < > = != <= >= in `notIn`, where one argument is a constant, and the other is one of columns of key, * or itself, wrapped in a chain of possibly-monotonic functions, @@ -1107,27 +1231,28 @@ bool KeyCondition::tryParseAtomFromAST(const ASTPtr & node, ContextPtr context, */ Field const_value; DataTypePtr const_type; - if (const auto * func = node->as()) + if (node.isFunction()) { - const ASTs & args = func->arguments->children; + auto func = node.asFunction(); + size_t num_args = func.numArguments(); DataTypePtr key_expr_type; /// Type of expression containing key column size_t key_column_num = -1; /// Number of a key column (inside key_column_names array) MonotonicFunctionsChain chain; - std::string func_name = func->name; + std::string func_name = func.getFunctionName(); if (atom_map.find(func_name) == std::end(atom_map)) return false; - if (args.size() == 1) + if (num_args == 1) { - if (!(isKeyPossiblyWrappedByMonotonicFunctions(args[0], context, key_column_num, key_expr_type, chain))) + if (!(isKeyPossiblyWrappedByMonotonicFunctions(func.getArgumentAt(0), context, key_column_num, key_expr_type, chain))) return false; if (key_column_num == static_cast(-1)) throw Exception("`key_column_num` wasn't initialized. It is a bug.", ErrorCodes::LOGICAL_ERROR); } - else if (args.size() == 2) + else if (num_args == 2) { size_t key_arg_pos; /// Position of argument with key column (non-const argument) bool is_set_const = false; @@ -1315,25 +1440,24 @@ bool KeyCondition::tryParseAtomFromAST(const ASTPtr & node, ContextPtr context, return false; } -bool KeyCondition::tryParseLogicalOperatorFromAST(const ASTFunction * func, RPNElement & out) +bool KeyCondition::tryParseLogicalOperatorFromAST(const FunctionTree & func, RPNElement & out) { /// Functions AND, OR, NOT. /// Also a special function `indexHint` - works as if instead of calling a function there are just parentheses /// (or, the same thing - calling the function `and` from one argument). - const ASTs & args = func->arguments->children; - if (func->name == "not") + if (func.getFunctionName() == "not") { - if (args.size() != 1) + if (func.numArguments() != 1) return false; out.function = RPNElement::FUNCTION_NOT; } else { - if (func->name == "and" || func->name == "indexHint") + if (func.getFunctionName() == "and" || func.getFunctionName() == "indexHint") out.function = RPNElement::FUNCTION_AND; - else if (func->name == "or") + else if (func.getFunctionName() == "or") out.function = RPNElement::FUNCTION_OR; else return false; diff --git a/src/Storages/MergeTree/KeyCondition.h b/src/Storages/MergeTree/KeyCondition.h index fc28d4a93c9..8bc9d13ef3c 100644 --- a/src/Storages/MergeTree/KeyCondition.h +++ b/src/Storages/MergeTree/KeyCondition.h @@ -378,6 +378,10 @@ public: static const AtomMap atom_map; private: + + class Tree; + class FunctionTree; + BoolMask checkInRange( size_t used_key_size, const FieldRef * left_key, @@ -386,9 +390,9 @@ private: bool right_bounded, BoolMask initial_mask) const; - void traverseAST(const ASTPtr & node, ContextPtr context, Block & block_with_constants); - bool tryParseAtomFromAST(const ASTPtr & node, ContextPtr context, Block & block_with_constants, RPNElement & out); - static bool tryParseLogicalOperatorFromAST(const ASTFunction * func, RPNElement & out); + void traverseAST(const Tree & node, ContextPtr context, Block & block_with_constants); + bool tryParseAtomFromAST(const Tree & node, ContextPtr context, Block & block_with_constants, RPNElement & out); + static bool tryParseLogicalOperatorFromAST(const FunctionTree & func, RPNElement & out); /** Is node the key column * or expression in which column of key is wrapped by chain of functions, @@ -397,17 +401,17 @@ private: * and fills chain of possibly-monotonic functions. */ bool isKeyPossiblyWrappedByMonotonicFunctions( - const ASTPtr & node, + const Tree & node, ContextPtr context, size_t & out_key_column_num, DataTypePtr & out_key_res_column_type, MonotonicFunctionsChain & out_functions_chain); bool isKeyPossiblyWrappedByMonotonicFunctionsImpl( - const ASTPtr & node, + const Tree & node, size_t & out_key_column_num, DataTypePtr & out_key_column_type, - std::vector & out_functions_chain); + std::vector & out_functions_chain); bool canConstantBeWrappedByMonotonicFunctions( const ASTPtr & node, From 38966e3e6b8f5a65949841545cbd6813ee39869a Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Thu, 3 Jun 2021 15:26:02 +0300 Subject: [PATCH 002/672] Part 2. --- src/Storages/MergeTree/KeyCondition.cpp | 173 ++++++++++++++++-------- src/Storages/MergeTree/KeyCondition.h | 2 +- 2 files changed, 120 insertions(+), 55 deletions(-) diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 38c849c728c..3d96ce37c85 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -183,6 +184,99 @@ public: return res; } + bool getConstant(Block & block_with_constants, Field & out_value, DataTypePtr & out_type) + { + if (ast) + { + // Constant expr should use alias names if any + String column_name = ast->getColumnName(); + + if (const auto * lit = ast->as()) + { + /// By default block_with_constants has only one column named "_dummy". + /// If block contains only constants it's may not be preprocessed by + // ExpressionAnalyzer, so try to look up in the default column. + if (!block_with_constants.has(column_name)) + column_name = "_dummy"; + + /// Simple literal + out_value = lit->value; + out_type = block_with_constants.getByName(column_name).type; + return true; + } + else if (block_with_constants.has(column_name) && isColumnConst(*block_with_constants.getByName(column_name).column)) + { + /// An expression which is dependent on constants only + const auto & expr_info = block_with_constants.getByName(column_name); + out_value = (*expr_info.column)[0]; + out_type = expr_info.type; + return true; + } + } + else + { + if (dag->column && isColumnConst(*dag->column)) + { + out_value = (*dag->column)[0]; + out_type = dag->result_type; + return true; + } + } + + return false; + } + + ConstSetPtr tryGetPreparedSet( + const PreparedSets & sets, + const std::vector & indexes_mapping, + const DataTypes & data_types) const + { + if (ast) + { + if (ast->as() || ast->as()) + { + auto set_it = sets.find(PreparedSetKey::forSubquery(*ast)); + if (set_it != sets.end()) + return set_it->second; + } + else + { + /// We have `PreparedSetKey::forLiteral` but it is useless here as we don't have enough information + /// about types in left argument of the IN operator. Instead, we manually iterate through all the sets + /// and find the one for the right arg based on the AST structure (getTreeHash), after that we check + /// that the types it was prepared with are compatible with the types of the primary key. + auto set_ast_hash = ast->getTreeHash(); + auto set_it = std::find_if( + sets.begin(), sets.end(), + [&](const auto & candidate_entry) + { + if (candidate_entry.first.ast_hash != set_ast_hash) + return false; + + for (size_t i = 0; i < indexes_mapping.size(); ++i) + if (!candidate_entry.second->areTypesEqual(indexes_mapping[i].tuple_index, data_types[i])) + return false; + + return true; + }); + if (set_it != sets.end()) + return set_it->second; + } + } + else + { + if (dag->column) + { + const auto * col_set = typeid_cast(dag->column.get()); + auto set = col_set->getData(); + if (set->isCreated()) + return set; + } + } + + return nullptr; + } + FunctionTree asFunction() const; protected: @@ -929,18 +1023,18 @@ bool KeyCondition::canConstantBeWrappedByFunctions( } bool KeyCondition::tryPrepareSetIndex( - const ASTs & args, + const FunctionTree & func, ContextPtr context, RPNElement & out, size_t & out_key_column_num) { - const ASTPtr & left_arg = args[0]; + const auto & left_arg = func.getArgumentAt(0); out_key_column_num = 0; std::vector indexes_mapping; DataTypes data_types; - auto get_key_tuple_position_mapping = [&](const ASTPtr & node, size_t tuple_index) + auto get_key_tuple_position_mapping = [&](const Tree & node, size_t tuple_index) { MergeTreeSetIndex::KeyTuplePositionMapping index_mapping; index_mapping.tuple_index = tuple_index; @@ -956,13 +1050,17 @@ bool KeyCondition::tryPrepareSetIndex( }; size_t left_args_count = 1; - const auto * left_arg_tuple = left_arg->as(); - if (left_arg_tuple && left_arg_tuple->name == "tuple") + if (left_arg.isFunction()) { - const auto & tuple_elements = left_arg_tuple->arguments->children; - left_args_count = tuple_elements.size(); - for (size_t i = 0; i < left_args_count; ++i) - get_key_tuple_position_mapping(tuple_elements[i], i); + /// Note: in case of ActionsDAG, tuple may be a constant. + /// In this case, there is no keys in tuple. So, we don't have to check it. + auto left_arg_tuple = left_arg.asFunction(); + if (left_arg_tuple.getFunctionName() == "tuple") + { + left_args_count = left_arg_tuple.numArguments(); + for (size_t i = 0; i < left_args_count; ++i) + get_key_tuple_position_mapping(left_arg_tuple.getArgumentAt(i), i); + } } else get_key_tuple_position_mapping(left_arg, 0); @@ -970,42 +1068,11 @@ bool KeyCondition::tryPrepareSetIndex( if (indexes_mapping.empty()) return false; - const ASTPtr & right_arg = args[1]; + const auto right_arg = func.getArgumentAt(1); - SetPtr prepared_set; - if (right_arg->as() || right_arg->as()) - { - auto set_it = prepared_sets.find(PreparedSetKey::forSubquery(*right_arg)); - if (set_it == prepared_sets.end()) - return false; - - prepared_set = set_it->second; - } - else - { - /// We have `PreparedSetKey::forLiteral` but it is useless here as we don't have enough information - /// about types in left argument of the IN operator. Instead, we manually iterate through all the sets - /// and find the one for the right arg based on the AST structure (getTreeHash), after that we check - /// that the types it was prepared with are compatible with the types of the primary key. - auto set_ast_hash = right_arg->getTreeHash(); - auto set_it = std::find_if( - prepared_sets.begin(), prepared_sets.end(), - [&](const auto & candidate_entry) - { - if (candidate_entry.first.ast_hash != set_ast_hash) - return false; - - for (size_t i = 0; i < indexes_mapping.size(); ++i) - if (!candidate_entry.second->areTypesEqual(indexes_mapping[i].tuple_index, data_types[i])) - return false; - - return true; - }); - if (set_it == prepared_sets.end()) - return false; - - prepared_set = set_it->second; - } + auto prepared_set = right_arg.tryGetPreparedSet(prepared_sets, indexes_mapping, data_types); + if (!prepared_set) + return false; /// The index can be prepared if the elements of the set were saved in advance. if (!prepared_set->hasExplicitSetElements()) @@ -1107,28 +1174,26 @@ bool KeyCondition::isKeyPossiblyWrappedByMonotonicFunctions( for (auto it = chain_not_tested_for_monotonicity.rbegin(); it != chain_not_tested_for_monotonicity.rend(); ++it) { - auto func = *it; - auto func_builder = FunctionFactory::instance().tryGet(func.getFunctionName(), context); + auto function = *it; + auto func_builder = FunctionFactory::instance().tryGet(function.getFunctionName(), context); if (!func_builder) return false; ColumnsWithTypeAndName arguments; ColumnWithTypeAndName const_arg; FunctionWithOptionalConstArg::Kind kind = FunctionWithOptionalConstArg::Kind::NO_CONST; - if (func.numArguments() == 2) + if (function.numArguments() == 2) { - if (const auto * arg_left = args[0]->as()) + if (function.getArgumentAt(0).isConstant()) { - auto left_arg_type = applyVisitor(FieldToDataType(), arg_left->value); - const_arg = { left_arg_type->createColumnConst(0, arg_left->value), left_arg_type, "" }; + const_arg = function.getArgumentAt(0).getConstant(); arguments.push_back(const_arg); arguments.push_back({ nullptr, key_column_type, "" }); kind = FunctionWithOptionalConstArg::Kind::LEFT_CONST; } - else if (const auto * arg_right = args[1]->as()) + else if (function.getArgumentAt(1).isConstant()) { arguments.push_back({ nullptr, key_column_type, "" }); - auto right_arg_type = applyVisitor(FieldToDataType(), arg_right->value); - const_arg = { right_arg_type->createColumnConst(0, arg_right->value), right_arg_type, "" }; + const_arg = function.getArgumentAt(1).getConstant(); arguments.push_back(const_arg); kind = FunctionWithOptionalConstArg::Kind::RIGHT_CONST; } @@ -1277,7 +1342,7 @@ bool KeyCondition::tryParseAtomFromAST(const Tree & node, ContextPtr context, Bl if (functionIsInOrGlobalInOperator(func_name)) { - if (tryPrepareSetIndex(args, context, out, key_column_num)) + if (tryPrepareSetIndex(func, context, out, key_column_num)) { key_arg_pos = 0; is_set_const = true; diff --git a/src/Storages/MergeTree/KeyCondition.h b/src/Storages/MergeTree/KeyCondition.h index 8bc9d13ef3c..f34c2a950f8 100644 --- a/src/Storages/MergeTree/KeyCondition.h +++ b/src/Storages/MergeTree/KeyCondition.h @@ -433,7 +433,7 @@ private: /// that will filter values (possibly tuples) by the content of 'prepared_set', /// do it and return true. bool tryPrepareSetIndex( - const ASTs & args, + const FunctionTree & func, ContextPtr context, RPNElement & out, size_t & out_key_column_num); From dee032c89975be004c036c9240847ae4722cdb9d Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Thu, 3 Jun 2021 15:27:38 +0300 Subject: [PATCH 003/672] Part 2. --- src/Storages/MergeTree/KeyCondition.cpp | 35 ++++--------------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 3d96ce37c85..40529296aaf 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -666,32 +666,7 @@ bool KeyCondition::addCondition(const String & column, const Range & range) */ bool KeyCondition::getConstant(const ASTPtr & expr, Block & block_with_constants, Field & out_value, DataTypePtr & out_type) { - // Constant expr should use alias names if any - String column_name = expr->getColumnName(); - - if (const auto * lit = expr->as()) - { - /// By default block_with_constants has only one column named "_dummy". - /// If block contains only constants it's may not be preprocessed by - // ExpressionAnalyzer, so try to look up in the default column. - if (!block_with_constants.has(column_name)) - column_name = "_dummy"; - - /// Simple literal - out_value = lit->value; - out_type = block_with_constants.getByName(column_name).type; - return true; - } - else if (block_with_constants.has(column_name) && isColumnConst(*block_with_constants.getByName(column_name).column)) - { - /// An expression which is dependent on constants only - const auto & expr_info = block_with_constants.getByName(column_name); - out_value = (*expr_info.column)[0]; - out_type = expr_info.type; - return true; - } - else - return false; + return Tree(expr.get()).getConstant(block_with_constants, out_value, out_type); } @@ -1350,22 +1325,22 @@ bool KeyCondition::tryParseAtomFromAST(const Tree & node, ContextPtr context, Bl else return false; } - else if (getConstant(args[1], block_with_constants, const_value, const_type)) + else if (func.getArgumentAt(1).getConstant(block_with_constants, const_value, const_type)) { - if (isKeyPossiblyWrappedByMonotonicFunctions(args[0], context, key_column_num, key_expr_type, chain)) + if (isKeyPossiblyWrappedByMonotonicFunctions(func.getArgumentAt(0), context, key_column_num, key_expr_type, chain)) { key_arg_pos = 0; } else if ( !strict_condition - && canConstantBeWrappedByMonotonicFunctions(args[0], key_column_num, key_expr_type, const_value, const_type)) + && canConstantBeWrappedByMonotonicFunctions(func.getArgumentAt(0), key_column_num, key_expr_type, const_value, const_type)) { key_arg_pos = 0; is_constant_transformed = true; } else if ( single_point && func_name == "equals" && !strict_condition - && canConstantBeWrappedByFunctions(args[0], key_column_num, key_expr_type, const_value, const_type)) + && canConstantBeWrappedByFunctions(func.getArgumentAt(0), key_column_num, key_expr_type, const_value, const_type)) { key_arg_pos = 0; is_constant_transformed = true; From eef6c73030f317a77dec089b6caadbed7b8b7b03 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 21 Jun 2021 19:17:05 +0300 Subject: [PATCH 004/672] Use DAG in KeyCondition --- .../QueryPlan/ReadFromMergeTree.cpp | 2 +- src/Storages/MergeTree/KeyCondition.cpp | 275 +++++++++++++++--- src/Storages/MergeTree/KeyCondition.h | 20 +- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 4 +- .../MergeTree/MergeTreeIndexMinMax.cpp | 2 +- src/Storages/MergeTree/PartitionPruner.h | 3 +- 6 files changed, 257 insertions(+), 49 deletions(-) diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index fd5de98b4c0..c9204f41ce4 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -788,7 +788,7 @@ ReadFromMergeTree::AnalysisResult ReadFromMergeTree::selectRangesToRead(MergeTre // Build and check if primary key is used when necessary const auto & primary_key = metadata_snapshot->getPrimaryKey(); Names primary_key_columns = primary_key.column_names; - KeyCondition key_condition(query_info, context, primary_key_columns, primary_key.expression); + KeyCondition key_condition(query_info.query, query_info.syntax_analyzer_result, query_info.sets, context, primary_key_columns, primary_key.expression); if (settings.force_primary_key && key_condition.alwaysUnknownOrTrue()) { diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 82ca403154f..16807d0361c 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -116,6 +116,54 @@ static String firstStringThatIsGreaterThanAllStringsWithPrefix(const String & pr return res; } +static void appendColumnNameWithoutAlias(const ActionsDAG::Node & node, WriteBuffer & out, bool legacy = false) +{ + switch (node.type) + { + case (ActionsDAG::ActionType::INPUT): + writeString(node.result_name, out); + break; + case (ActionsDAG::ActionType::COLUMN): + writeString(node.result_name, out); + break; + case (ActionsDAG::ActionType::ALIAS): + appendColumnNameWithoutAlias(*node.children.front(), out, legacy); + break; + case (ActionsDAG::ActionType::ARRAY_JOIN): + writeCString("arrayJoin(", out); + appendColumnNameWithoutAlias(*node.children.front(), out, legacy); + writeChar(')', out); + break; + case (ActionsDAG::ActionType::FUNCTION): + { + auto name = node.function_base->getName(); + if (legacy && name == "modulo") + writeCString("moduleLegacy", out); + else + writeString(name, out); + + writeChar('(', out); + bool first = true; + for (const auto * arg : node.children) + { + if (!first) + writeCString(", ", out); + first = false; + + appendColumnNameWithoutAlias(*arg, out, legacy); + } + writeChar(')', out); + } + } +} + +static std::string getColumnNameWithoutAlias(const ActionsDAG::Node & node, bool legacy = false) +{ + WriteBufferFromOwnString out; + appendColumnNameWithoutAlias(node, out, legacy); + return std::move(out.str()); +} + class KeyCondition::Tree { public: @@ -127,24 +175,20 @@ public: if (ast) return ast->getColumnNameWithoutAlias(); else - getColumnNameWithoutAlias(dag); + return getColumnNameWithoutAlias(*dag); } - // size_t numChildren() const - // { - // if (ast) - // return ast->children.size(); - // else - // return dag->children.size(); - // } - - // Tree getChildrenAt(size_t idx) const - // { - // if (ast) - // return Tree(ast->children[idx].get()); - // else - // return Tree(dag->children[idx]); - // } + std::string getColumnNameLegacy() const + { + if (ast) + { + auto adjusted_ast = ast->clone(); + KeyDescription::moduloToModuloLegacyRecursive(adjusted_ast); + return adjusted_ast->getColumnNameWithoutAlias(); + } + else + return getColumnNameWithoutAlias(*dag, true); + } bool isFunction() const { @@ -185,7 +229,7 @@ public: return res; } - bool getConstant(Block & block_with_constants, Field & out_value, DataTypePtr & out_type) + bool getConstant(const Block & block_with_constants, Field & out_value, DataTypePtr & out_type) const { if (ast) { @@ -584,6 +628,123 @@ ASTPtr cloneASTWithInversionPushDown(const ASTPtr node, const bool need_inversio return need_inversion ? makeASTFunction("not", cloned_node) : cloned_node; } +static const ActionsDAG::Node & cloneASTWithInversionPushDown( + const ActionsDAG::Node & node, + ActionsDAG & inverted_dag, + std::unordered_map to_inverted, + const ContextPtr & context, + const bool need_inversion) +{ + { + auto it = to_inverted.find(&node); + if (it != to_inverted.end()) + return *it->second; + } + + const ActionsDAG::Node * res = nullptr; + + switch (node.type) + { + case (ActionsDAG::ActionType::INPUT): + /// Should be already added + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot clone input in cloneASTWithInversionPushDown"); + case (ActionsDAG::ActionType::COLUMN): + { + res = &inverted_dag.addInput({node.column, node.result_type, node.result_name}); + break; + } + case (ActionsDAG::ActionType::ALIAS): + { + /// Ignore aliases + const auto & alias = cloneASTWithInversionPushDown(*node.children.front(), inverted_dag, to_inverted, context, need_inversion); + to_inverted[&node] = &alias; + return alias; + } + case (ActionsDAG::ActionType::ARRAY_JOIN): + { + const auto & arg = cloneASTWithInversionPushDown(*node.children.front(), inverted_dag, to_inverted, context, false); + res = &inverted_dag.addArrayJoin(arg, node.result_name); + break; + } + case (ActionsDAG::ActionType::FUNCTION): + { + auto name = node.function_base->getName(); + if (name == "not") + { + const auto & arg = cloneASTWithInversionPushDown(*node.children.front(), inverted_dag, to_inverted, context, !need_inversion); + to_inverted[&node] = &arg; + return arg; + } + + if (isLogicalOperator(name)) + { + ActionsDAG::NodeRawConstPtrs children(node.children); + + for (auto & arg : children) + arg = &cloneASTWithInversionPushDown(*arg, inverted_dag, to_inverted, context, need_inversion); + + FunctionOverloadResolverPtr function_builder; + if (name == "indexHint") + function_builder = node.function_builder; + else if (name == "and") + function_builder = FunctionFactory::instance().get("or", context); + else if (name == "or") + function_builder = FunctionFactory::instance().get("and", context); + + assert(function_builder); + + const auto & func = inverted_dag.addFunction(function_builder, children, ""); + to_inverted[&node] = &func; + return func; + } + + ActionsDAG::NodeRawConstPtrs children(node.children); + + for (auto & arg : children) + arg = &cloneASTWithInversionPushDown(*arg, inverted_dag, to_inverted, context, false); + + auto it = inverse_relations.find(name); + if (it != inverse_relations.end()) + { + const auto & func_name = need_inversion ? it->second : it->first; + auto function_builder = FunctionFactory::instance().get(func_name, context); + const auto & func = inverted_dag.addFunction(function_builder, children, ""); + to_inverted[&node] = &func; + return func; + } + + res = &inverted_dag.addFunction(node.function_builder, children, ""); + } + } + + if (need_inversion) + res = &inverted_dag.addFunction(FunctionFactory::instance().get("not", context), {res}, ""); + + to_inverted[&node] = res; + return *res; +} + +static ActionsDAGPtr cloneASTWithInversionPushDown(const ActionsDAG & dag, const ContextPtr & context) +{ + auto res = std::make_shared(dag.getRequiredColumns()); + + std::unordered_map to_inverted; + const auto & res_inputs = res->getInputs(); + auto it = res_inputs.begin(); + for (const auto * input : dag.getInputs()) + { + to_inverted[input] = *it; + ++it; + } + + ActionsDAG::NodeRawConstPtrs index; + index.reserve(dag.getIndex().size()); + for (const auto * node : dag.getIndex()) + index.push_back(&cloneASTWithInversionPushDown(*node, *res, to_inverted, context, false)); + + return res; +} + inline bool Range::equals(const Field & lhs, const Field & rhs) { return applyVisitor(FieldVisitorAccurateEquals(), lhs, rhs); } inline bool Range::less(const Field & lhs, const Field & rhs) { return applyVisitor(FieldVisitorAccurateLess(), lhs, rhs); } @@ -617,7 +778,9 @@ static NameSet getAllSubexpressionNames(const ExpressionActions & key_expr) } KeyCondition::KeyCondition( - const SelectQueryInfo & query_info, + const ASTPtr & query, + TreeRewriterResultPtr syntax_analyzer_result, + PreparedSets prepared_sets_, ContextPtr context, const Names & key_column_names, const ExpressionActionsPtr & key_expr_, @@ -625,7 +788,7 @@ KeyCondition::KeyCondition( bool strict_) : key_expr(key_expr_) , key_subexpr_names(getAllSubexpressionNames(*key_expr)) - , prepared_sets(query_info.sets) + , prepared_sets(std::move(prepared_sets_)) , single_point(single_point_) , strict(strict_) { @@ -639,9 +802,9 @@ KeyCondition::KeyCondition( /** Evaluation of expressions that depend only on constants. * For the index to be used, if it is written, for example `WHERE Date = toDate(now())`. */ - Block block_with_constants = getBlockWithConstants(query_info.query, query_info.syntax_analyzer_result, context); + Block block_with_constants = getBlockWithConstants(query, syntax_analyzer_result, context); - const ASTSelectQuery & select = query_info.query->as(); + const ASTSelectQuery & select = query->as(); if (select.where() || select.prewhere()) { ASTPtr filter_query; @@ -657,7 +820,43 @@ KeyCondition::KeyCondition( * To overcome the problem, before parsing the AST we transform it to its semantically equivalent form where all NOT's * are pushed down and applied (when possible) to leaf nodes. */ - traverseAST(cloneASTWithInversionPushDown(filter_query), context, block_with_constants); + auto ast = cloneASTWithInversionPushDown(filter_query); + traverseAST(Tree(ast.get()), context, block_with_constants); + } + else + { + rpn.emplace_back(RPNElement::FUNCTION_UNKNOWN); + } +} + +KeyCondition::KeyCondition( + const ActionsDAG & dag, + PreparedSets prepared_sets_, + ContextPtr context, + const Names & key_column_names, + const ExpressionActionsPtr & key_expr_, + bool single_point_, + bool strict_) + : key_expr(key_expr_) + , key_subexpr_names(getAllSubexpressionNames(*key_expr)) + , prepared_sets(std::move(prepared_sets_)) + , single_point(single_point_) + , strict(strict_) +{ + for (size_t i = 0, size = key_column_names.size(); i < size; ++i) + { + std::string name = key_column_names[i]; + if (!key_columns.count(name)) + key_columns[name] = i; + } + + auto inverted_dag = cloneASTWithInversionPushDown(dag, context); + + if (!inverted_dag->getIndex().empty()) + { + Block empty; + for (const auto * node : inverted_dag->getIndex()) + traverseAST(Tree(node), context, empty); } else { @@ -797,13 +996,13 @@ void KeyCondition::traverseAST(const Tree & node, ContextPtr context, Block & bl } bool KeyCondition::canConstantBeWrappedByMonotonicFunctions( - const ASTPtr & node, + const Tree & node, size_t & out_key_column_num, DataTypePtr & out_key_column_type, Field & out_value, DataTypePtr & out_type) { - String expr_name = node->getColumnNameWithoutAlias(); + String expr_name = node.getColumnName(); if (key_subexpr_names.count(expr_name) == 0) return false; @@ -904,9 +1103,9 @@ bool KeyCondition::canConstantBeWrappedByMonotonicFunctions( /// Looking for possible transformation of `column = constant` into `partition_expr = function(constant)` bool KeyCondition::canConstantBeWrappedByFunctions( - const ASTPtr & ast, size_t & out_key_column_num, DataTypePtr & out_key_column_type, Field & out_value, DataTypePtr & out_type) + const Tree & node, size_t & out_key_column_num, DataTypePtr & out_key_column_type, Field & out_value, DataTypePtr & out_type) { - String expr_name = ast->getColumnNameWithoutAlias(); + String expr_name = node.getColumnName(); if (key_subexpr_names.count(expr_name) == 0) { @@ -919,9 +1118,7 @@ bool KeyCondition::canConstantBeWrappedByFunctions( /// The case `f(modulo(...))` for totally monotonic `f ` is consedered to be rare. /// /// Note: for negative values, we can filter more partitions then needed. - auto adjusted_ast = ast->clone(); - KeyDescription::moduloToModuloLegacyRecursive(adjusted_ast); - expr_name = adjusted_ast->getColumnName(); + expr_name = node.getColumnNameLegacy(); if (key_subexpr_names.count(expr_name) == 0) return false; @@ -933,14 +1130,14 @@ bool KeyCondition::canConstantBeWrappedByFunctions( if (out_value.isNull()) return false; - for (const auto & node : key_expr->getNodes()) + for (const auto & dag_node : key_expr->getNodes()) { - auto it = key_columns.find(node.result_name); + auto it = key_columns.find(dag_node.result_name); if (it != key_columns.end()) { std::stack chain; - const auto * cur_node = &node; + const auto * cur_node = &dag_node; bool is_valid_chain = true; while (is_valid_chain) @@ -1280,7 +1477,7 @@ bool KeyCondition::isKeyPossiblyWrappedByMonotonicFunctionsImpl( } -static void castValueToType(const DataTypePtr & desired_type, Field & src_value, const DataTypePtr & src_type, const ASTPtr & node) +static void castValueToType(const DataTypePtr & desired_type, Field & src_value, const DataTypePtr & src_type, const KeyCondition::Tree & node) { try { @@ -1290,7 +1487,7 @@ static void castValueToType(const DataTypePtr & desired_type, Field & src_value, { throw Exception("Key expression contains comparison between inconvertible types: " + desired_type->getName() + " and " + src_type->getName() + - " inside " + queryToString(node), + " inside " + node.getColumnName(), ErrorCodes::BAD_TYPE_OF_FIELD); } } @@ -1381,22 +1578,22 @@ bool KeyCondition::tryParseAtomFromAST(const Tree & node, ContextPtr context, Bl else return false; } - else if (getConstant(args[0], block_with_constants, const_value, const_type)) + else if (func.getArgumentAt(0).getConstant(block_with_constants, const_value, const_type)) { - if (isKeyPossiblyWrappedByMonotonicFunctions(args[1], context, key_column_num, key_expr_type, chain)) + if (isKeyPossiblyWrappedByMonotonicFunctions(func.getArgumentAt(1), context, key_column_num, key_expr_type, chain)) { key_arg_pos = 1; } else if ( !strict_condition - && canConstantBeWrappedByMonotonicFunctions(args[1], key_column_num, key_expr_type, const_value, const_type)) + && canConstantBeWrappedByMonotonicFunctions(func.getArgumentAt(1), key_column_num, key_expr_type, const_value, const_type)) { key_arg_pos = 1; is_constant_transformed = true; } else if ( single_point && func_name == "equals" && !strict_condition - && canConstantBeWrappedByFunctions(args[1], key_column_num, key_expr_type, const_value, const_type)) + && canConstantBeWrappedByFunctions(func.getArgumentAt(1), key_column_num, key_expr_type, const_value, const_type)) { key_arg_pos = 0; is_constant_transformed = true; @@ -1490,7 +1687,7 @@ bool KeyCondition::tryParseAtomFromAST(const Tree & node, ContextPtr context, Bl return atom_it->second(out, const_value); } - else if (getConstant(node, block_with_constants, const_value, const_type)) + else if (node.getConstant(block_with_constants, const_value, const_type)) { /// For cases where it says, for example, `WHERE 0 AND something` diff --git a/src/Storages/MergeTree/KeyCondition.h b/src/Storages/MergeTree/KeyCondition.h index 6e1af69fd37..f0ffbf9e6bf 100644 --- a/src/Storages/MergeTree/KeyCondition.h +++ b/src/Storages/MergeTree/KeyCondition.h @@ -228,7 +228,18 @@ class KeyCondition public: /// Does not take into account the SAMPLE section. all_columns - the set of all columns of the table. KeyCondition( - const SelectQueryInfo & query_info, + const ASTPtr & query, + TreeRewriterResultPtr syntax_analyzer_result, + PreparedSets prepared_sets_, + ContextPtr context, + const Names & key_column_names, + const ExpressionActionsPtr & key_expr, + bool single_point_ = false, + bool strict_ = false); + + KeyCondition( + const ActionsDAG & dag, + PreparedSets prepared_sets_, ContextPtr context, const Names & key_column_names, const ExpressionActionsPtr & key_expr, @@ -377,11 +388,10 @@ private: public: static const AtomMap atom_map; -private: - class Tree; class FunctionTree; +private: BoolMask checkInRange( size_t used_key_size, const FieldRef * left_key, @@ -414,14 +424,14 @@ private: std::vector & out_functions_chain); bool canConstantBeWrappedByMonotonicFunctions( - const ASTPtr & node, + const Tree & node, size_t & out_key_column_num, DataTypePtr & out_key_column_type, Field & out_value, DataTypePtr & out_type); bool canConstantBeWrappedByFunctions( - const ASTPtr & ast, size_t & out_key_column_num, DataTypePtr & out_key_column_type, Field & out_value, DataTypePtr & out_type); + const Tree & node, size_t & out_key_column_num, DataTypePtr & out_key_column_type, Field & out_value, DataTypePtr & out_type); /// If it's possible to make an RPNElement /// that will filter values (possibly tuples) by the content of 'prepared_set', diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index ae3b533918d..695b7509a59 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -677,7 +677,7 @@ void MergeTreeDataSelectExecutor::filterPartsByPartition( minmax_columns_types = data.getMinMaxColumnsTypes(partition_key); minmax_idx_condition.emplace( - query_info, context, minmax_columns_names, data.getMinMaxExpr(partition_key, ExpressionActionsSettings::fromContext(context))); + query_info.query, query_info.syntax_analyzer_result, query_info.sets, context, minmax_columns_names, data.getMinMaxExpr(partition_key, ExpressionActionsSettings::fromContext(context))); partition_pruner.emplace(metadata_snapshot, query_info, context, false /* strict */); if (settings.force_index_by_date && (minmax_idx_condition->alwaysUnknownOrTrue() && partition_pruner->isUseless())) @@ -1116,7 +1116,7 @@ size_t MergeTreeDataSelectExecutor::estimateNumMarksToRead( const auto & primary_key = metadata_snapshot->getPrimaryKey(); Names primary_key_columns = primary_key.column_names; - KeyCondition key_condition(query_info, context, primary_key_columns, primary_key.expression); + KeyCondition key_condition(query_info.query, query_info.syntax_analyzer_result, query_info.sets, context, primary_key_columns, primary_key.expression); if (key_condition.alwaysUnknownOrTrue()) { diff --git a/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp b/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp index 099d561cf80..40ccb48a72e 100644 --- a/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp @@ -140,7 +140,7 @@ MergeTreeIndexConditionMinMax::MergeTreeIndexConditionMinMax( const SelectQueryInfo & query, ContextPtr context) : index_data_types(index.data_types) - , condition(query, context, index.column_names, index.expression) + , condition(query.query, query.syntax_analyzer_result, query.sets, context, index.column_names, index.expression) { } diff --git a/src/Storages/MergeTree/PartitionPruner.h b/src/Storages/MergeTree/PartitionPruner.h index fbed0e6ab99..3af52fd9a38 100644 --- a/src/Storages/MergeTree/PartitionPruner.h +++ b/src/Storages/MergeTree/PartitionPruner.h @@ -27,7 +27,8 @@ public: PartitionPruner(const StorageMetadataPtr & metadata, const SelectQueryInfo & query_info, ContextPtr context, bool strict) : partition_key(MergeTreePartition::adjustPartitionKey(metadata, context)) , partition_condition( - query_info, context, partition_key.column_names, partition_key.expression, true /* single_point */, strict) + query_info.query, query_info.syntax_analyzer_result, query_info.sets, + context, partition_key.column_names, partition_key.expression, true /* single_point */, strict) , useless(strict ? partition_condition.anyUnknownOrAlwaysTrue() : partition_condition.alwaysUnknownOrTrue()) { } From 68176f064bdbc21356e5d5485752e394b1e32e2f Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 21 Jun 2021 20:28:15 +0300 Subject: [PATCH 005/672] Fix some tests. --- src/Storages/MergeTree/KeyCondition.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 16807d0361c..9bc01375c3e 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -345,7 +345,7 @@ public: if (ast) { const auto * func = assert_cast(ast); - return func->arguments ? 0 : func->arguments->size(); + return func->arguments ? func->arguments->children.size() : 0; } else return dag->children.size(); @@ -1266,6 +1266,8 @@ bool KeyCondition::tryPrepareSetIndex( for (size_t i = 0; i < left_args_count; ++i) get_key_tuple_position_mapping(left_arg_tuple.getArgumentAt(i), i); } + else + get_key_tuple_position_mapping(left_arg, 0); } else get_key_tuple_position_mapping(left_arg, 0); From d15d16fee0956f55a106199fc3028dc8106576ff Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 22 Jun 2021 10:26:45 +0300 Subject: [PATCH 006/672] Fix build. --- src/Storages/MergeTree/KeyCondition.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 9bc01375c3e..05b5399a84f 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -120,9 +120,7 @@ static void appendColumnNameWithoutAlias(const ActionsDAG::Node & node, WriteBuf { switch (node.type) { - case (ActionsDAG::ActionType::INPUT): - writeString(node.result_name, out); - break; + case (ActionsDAG::ActionType::INPUT): [[fallthrough]]; case (ActionsDAG::ActionType::COLUMN): writeString(node.result_name, out); break; @@ -167,8 +165,8 @@ static std::string getColumnNameWithoutAlias(const ActionsDAG::Node & node, bool class KeyCondition::Tree { public: - explicit Tree(const IAST * ast_) : ast(ast_) {} - explicit Tree(const ActionsDAG::Node * dag_) : dag(dag_) {} + explicit Tree(const IAST * ast_) : ast(ast_) { assert(ast); } + explicit Tree(const ActionsDAG::Node * dag_) : dag(dag_) { assert(dag); } std::string getColumnName() const { From 21e39e10ea93dcd7624a1ae122fa6c7f793bda9e Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 22 Jun 2021 13:28:56 +0300 Subject: [PATCH 007/672] Update KeyCondition constructor --- src/Interpreters/ActionsDAG.cpp | 3 ++ src/Interpreters/ActionsDAG.h | 6 ++++ src/Storages/MergeTree/KeyCondition.cpp | 42 ++++++++++++++----------- src/Storages/MergeTree/KeyCondition.h | 3 +- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/Interpreters/ActionsDAG.cpp b/src/Interpreters/ActionsDAG.cpp index 9fa48f6ceab..8b1c5afa014 100644 --- a/src/Interpreters/ActionsDAG.cpp +++ b/src/Interpreters/ActionsDAG.cpp @@ -161,6 +161,9 @@ const ActionsDAG::Node & ActionsDAG::addArrayJoin(const Node & child, std::strin if (!array_type) throw Exception("ARRAY JOIN requires array argument", ErrorCodes::TYPE_MISMATCH); + if (result_name.empty()) + result_name = "arrayJoin(" + child.result_name + ")"; + Node node; node.type = ActionType::ARRAY_JOIN; node.result_type = array_type->getNestedType(); diff --git a/src/Interpreters/ActionsDAG.h b/src/Interpreters/ActionsDAG.h index 9bea1d1c040..e06662af725 100644 --- a/src/Interpreters/ActionsDAG.h +++ b/src/Interpreters/ActionsDAG.h @@ -279,4 +279,10 @@ private: static ActionsDAGPtr cloneActionsForConjunction(NodeRawConstPtrs conjunction, const ColumnsWithTypeAndName & all_inputs); }; +/// This is an ugly way to bypass impossibility to forward declare ActionDAG::Node. +struct ActionDAGNodes +{ + ActionsDAG::NodeRawConstPtrs nodes; +}; + } diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 05b5399a84f..fd9e846cf16 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -644,11 +644,14 @@ static const ActionsDAG::Node & cloneASTWithInversionPushDown( switch (node.type) { case (ActionsDAG::ActionType::INPUT): - /// Should be already added - throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot clone input in cloneASTWithInversionPushDown"); + { + /// Note: inputs order is not important here. Will match columns by names. + res = &inverted_dag.addInput({node.column, node.result_type, node.result_name}); + break; + } case (ActionsDAG::ActionType::COLUMN): { - res = &inverted_dag.addInput({node.column, node.result_type, node.result_name}); + res = &inverted_dag.addColumn({node.column, node.result_type, node.result_name}); break; } case (ActionsDAG::ActionType::ALIAS): @@ -661,7 +664,7 @@ static const ActionsDAG::Node & cloneASTWithInversionPushDown( case (ActionsDAG::ActionType::ARRAY_JOIN): { const auto & arg = cloneASTWithInversionPushDown(*node.children.front(), inverted_dag, to_inverted, context, false); - res = &inverted_dag.addArrayJoin(arg, node.result_name); + res = &inverted_dag.addArrayJoin(arg, ""); break; } case (ActionsDAG::ActionType::FUNCTION): @@ -691,6 +694,8 @@ static const ActionsDAG::Node & cloneASTWithInversionPushDown( assert(function_builder); + /// We match columns by name, so it is important to fill name correctly. + /// So, use empty string to make it automatically. const auto & func = inverted_dag.addFunction(function_builder, children, ""); to_inverted[&node] = &func; return func; @@ -722,23 +727,22 @@ static const ActionsDAG::Node & cloneASTWithInversionPushDown( return *res; } -static ActionsDAGPtr cloneASTWithInversionPushDown(const ActionsDAG & dag, const ContextPtr & context) +static ActionsDAGPtr cloneASTWithInversionPushDown(ActionsDAG::NodeRawConstPtrs nodes, const ContextPtr & context) { - auto res = std::make_shared(dag.getRequiredColumns()); + auto res = std::make_shared(); std::unordered_map to_inverted; - const auto & res_inputs = res->getInputs(); - auto it = res_inputs.begin(); - for (const auto * input : dag.getInputs()) + + for (auto & node : nodes) + node = &cloneASTWithInversionPushDown(*node, *res, to_inverted, context, false); + + if (nodes.size() > 1) { - to_inverted[input] = *it; - ++it; + auto function_builder = FunctionFactory::instance().get("and", context); + nodes = {&res->addFunction(function_builder, std::move(nodes), "")}; } - ActionsDAG::NodeRawConstPtrs index; - index.reserve(dag.getIndex().size()); - for (const auto * node : dag.getIndex()) - index.push_back(&cloneASTWithInversionPushDown(*node, *res, to_inverted, context, false)); + res->getIndex().swap(nodes); return res; } @@ -828,7 +832,7 @@ KeyCondition::KeyCondition( } KeyCondition::KeyCondition( - const ActionsDAG & dag, + ActionDAGNodes dag_nodes, PreparedSets prepared_sets_, ContextPtr context, const Names & key_column_names, @@ -848,10 +852,10 @@ KeyCondition::KeyCondition( key_columns[name] = i; } - auto inverted_dag = cloneASTWithInversionPushDown(dag, context); - - if (!inverted_dag->getIndex().empty()) + if (!dag_nodes.nodes.empty()) { + auto inverted_dag = cloneASTWithInversionPushDown(std::move(dag_nodes.nodes), context); + Block empty; for (const auto * node : inverted_dag->getIndex()) traverseAST(Tree(node), context, empty); diff --git a/src/Storages/MergeTree/KeyCondition.h b/src/Storages/MergeTree/KeyCondition.h index f0ffbf9e6bf..8c54d9bb751 100644 --- a/src/Storages/MergeTree/KeyCondition.h +++ b/src/Storages/MergeTree/KeyCondition.h @@ -18,6 +18,7 @@ class IFunction; using FunctionBasePtr = std::shared_ptr; class ExpressionActions; using ExpressionActionsPtr = std::shared_ptr; +struct ActionDAGNodes; /** A field, that can be stored in two representations: * - A standalone field. @@ -238,7 +239,7 @@ public: bool strict_ = false); KeyCondition( - const ActionsDAG & dag, + ActionDAGNodes dag_nodes, PreparedSets prepared_sets_, ContextPtr context, const Names & key_column_names, From 47f130d39c7f654960386efadbaa43854ebac5a4 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 22 Jun 2021 16:54:00 +0300 Subject: [PATCH 008/672] Try use expression for KeyCondition from query plan. --- src/Core/Settings.h | 1 + .../QueryPlan/Optimizations/Optimizations.h | 2 + .../optimizePrimaryKeyCondition.cpp | 53 +++++++++++++++++++ src/Processors/QueryPlan/QueryPlan.cpp | 1 + .../QueryPlan/ReadFromMergeTree.cpp | 32 +++++++++-- src/Processors/QueryPlan/ReadFromMergeTree.h | 9 ++++ src/Storages/MergeTree/KeyCondition.cpp | 16 ++++-- 7 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 84e7500b064..a99f06904fd 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -473,6 +473,7 @@ class IColumn; M(Bool, query_plan_enable_optimizations, true, "Apply optimizations to query plan", 0) \ M(UInt64, query_plan_max_optimizations_to_apply, 10000, "Limit the total number of optimizations applied to query plan. If zero, ignored. If limit reached, throw exception", 0) \ M(Bool, query_plan_filter_push_down, true, "Allow to push down filter by predicate query plan step", 0) \ + M(Bool, query_plan_optimize_primary_key, true, "Analyze primary key using query plan (instead of AST)", 0) \ \ M(UInt64, limit, 0, "Limit on read rows from the most 'end' result for select query, default 0 means no limit length", 0) \ M(UInt64, offset, 0, "Offset on read rows from the most 'end' result for select query", 0) \ diff --git a/src/Processors/QueryPlan/Optimizations/Optimizations.h b/src/Processors/QueryPlan/Optimizations/Optimizations.h index 10bc6293537..9808f8c1850 100644 --- a/src/Processors/QueryPlan/Optimizations/Optimizations.h +++ b/src/Processors/QueryPlan/Optimizations/Optimizations.h @@ -12,6 +12,8 @@ namespace QueryPlanOptimizations /// This is the main function which optimizes the whole QueryPlan tree. void optimizeTree(const QueryPlanOptimizationSettings & settings, QueryPlan::Node & root, QueryPlan::Nodes & nodes); +void optimizePrimaryKeyCondition(QueryPlan::Node & root); + /// Optimization is a function applied to QueryPlan::Node. /// It can read and update subtree of specified node. /// It return the number of updated layers of subtree if some change happened. diff --git a/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp b/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp new file mode 100644 index 00000000000..5724e50f893 --- /dev/null +++ b/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include + +namespace DB::QueryPlanOptimizations +{ + +void optimizePrimaryKeyCondition(QueryPlan::Node & root) +{ + struct Frame + { + QueryPlan::Node * node = nullptr; + size_t next_child = 0; + }; + + std::stack stack; + stack.push({.node = &root}); + + while (!stack.empty()) + { + auto & frame = stack.top(); + + /// Traverse all children first. + if (frame.next_child < frame.node->children.size()) + { + stack.push({.node = frame.node->children[frame.next_child]}); + + ++frame.next_child; + continue; + } + + if (auto * filter_step = typeid_cast(frame.node->step.get())) + { + auto * child = frame.node->children.at(0); + if (typeid_cast(child->step.get())) + { + auto * child_child = child->children.at(0); + if (auto * read_from_merge_tree = typeid_cast(child_child->step.get())) + read_from_merge_tree->addFilter(filter_step->getExpression(), filter_step->getFilterColumnName()); + } + else if (auto * read_from_merge_tree = typeid_cast(child->step.get())) + read_from_merge_tree->addFilter(filter_step->getExpression(), filter_step->getFilterColumnName()); + + } + + stack.pop(); + } +} + +} diff --git a/src/Processors/QueryPlan/QueryPlan.cpp b/src/Processors/QueryPlan/QueryPlan.cpp index 44c5c48975c..c13b866d078 100644 --- a/src/Processors/QueryPlan/QueryPlan.cpp +++ b/src/Processors/QueryPlan/QueryPlan.cpp @@ -432,6 +432,7 @@ void QueryPlan::explainPipeline(WriteBuffer & buffer, const ExplainPipelineOptio void QueryPlan::optimize(const QueryPlanOptimizationSettings & optimization_settings) { QueryPlanOptimizations::optimizeTree(optimization_settings, *root, nodes); + QueryPlanOptimizations::optimizePrimaryKeyCondition(*root); } } diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index c9204f41ce4..dc0eed5bb73 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -788,16 +788,38 @@ ReadFromMergeTree::AnalysisResult ReadFromMergeTree::selectRangesToRead(MergeTre // Build and check if primary key is used when necessary const auto & primary_key = metadata_snapshot->getPrimaryKey(); Names primary_key_columns = primary_key.column_names; - KeyCondition key_condition(query_info.query, query_info.syntax_analyzer_result, query_info.sets, context, primary_key_columns, primary_key.expression); + std::optional key_condition; - if (settings.force_primary_key && key_condition.alwaysUnknownOrTrue()) + if (settings.query_plan_optimize_primary_key) + { + ActionDAGNodes nodes; + if (prewhere_info) + { + const auto & node = prewhere_info->prewhere_actions->getActionsDAG().findInIndex(prewhere_info->prewhere_column_name); + nodes.nodes.push_back(&node); + } + + if (added_filter) + { + const auto & node = added_filter->findInIndex(added_filter_column_name); + nodes.nodes.push_back(&node); + } + + key_condition.emplace(std::move(nodes), query_info.sets, context, primary_key_columns, primary_key.expression); + } + else + { + key_condition.emplace(query_info.query, query_info.syntax_analyzer_result, query_info.sets, context, primary_key_columns, primary_key.expression); + } + + if (settings.force_primary_key && key_condition->alwaysUnknownOrTrue()) { throw Exception( ErrorCodes::INDEX_NOT_USED, "Primary key ({}) is not used and setting 'force_primary_key' is set.", fmt::join(primary_key_columns, ", ")); } - LOG_DEBUG(log, "Key condition: {}", key_condition.toString()); + LOG_DEBUG(log, "Key condition: {}", key_condition->toString()); const auto & select = query_info.query->as(); @@ -806,7 +828,7 @@ ReadFromMergeTree::AnalysisResult ReadFromMergeTree::selectRangesToRead(MergeTre max_block_numbers_to_read.get(), log, result.index_stats); result.sampling = MergeTreeDataSelectExecutor::getSampling( - select, metadata_snapshot->getColumns().getAllPhysical(), parts, key_condition, + select, metadata_snapshot->getColumns().getAllPhysical(), parts, *key_condition, data, metadata_snapshot, context, sample_factor_column_queried, log); if (result.sampling.read_nothing) @@ -823,7 +845,7 @@ ReadFromMergeTree::AnalysisResult ReadFromMergeTree::selectRangesToRead(MergeTre metadata_snapshot, query_info, context, - key_condition, + *key_condition, reader_settings, log, requested_num_streams, diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.h b/src/Processors/QueryPlan/ReadFromMergeTree.h index 6e1efffdb02..259cb32d82a 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.h +++ b/src/Processors/QueryPlan/ReadFromMergeTree.h @@ -80,6 +80,12 @@ public: void describeActions(JSONBuilder::JSONMap & map) const override; void describeIndexes(JSONBuilder::JSONMap & map) const override; + void addFilter(ActionsDAGPtr expression, std::string column_name) + { + added_filter = std::move(expression); + added_filter_column_name = std::move(column_name); + } + private: const MergeTreeReaderSettings reader_settings; @@ -91,6 +97,9 @@ private: SelectQueryInfo query_info; PrewhereInfoPtr prewhere_info; + ActionsDAGPtr added_filter; + std::string added_filter_column_name; + StorageMetadataPtr metadata_snapshot; StorageMetadataPtr metadata_snapshot_base; diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index fd9e846cf16..60cf6b82abd 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -201,7 +201,7 @@ public: if (ast) return typeid_cast(ast); else - return dag->type == ActionsDAG::ActionType::COLUMN; + return dag->column && isColumnConst(*dag->column); } ColumnWithTypeAndName getConstant() const @@ -310,10 +310,16 @@ public: { if (dag->column) { - const auto * col_set = typeid_cast(dag->column.get()); - auto set = col_set->getData(); - if (set->isCreated()) - return set; + const IColumn * col = dag->column.get(); + if (const auto * col_const = typeid_cast(col)) + col = &col_const->getDataColumn(); + + if (const auto * col_set = typeid_cast(col)) + { + auto set = col_set->getData(); + if (set->isCreated()) + return set; + } } } From f5f57781b718542a84a81b5b386614fccd325dd6 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 22 Jun 2021 17:45:22 +0300 Subject: [PATCH 009/672] Fix build after merge. --- src/Processors/QueryPlan/ReadFromMergeTree.cpp | 2 +- src/Storages/MergeTree/KeyCondition.cpp | 4 ++++ src/Storages/MergeTree/KeyCondition.h | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index dc0eed5bb73..84520b714f7 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -805,7 +805,7 @@ ReadFromMergeTree::AnalysisResult ReadFromMergeTree::selectRangesToRead(MergeTre nodes.nodes.push_back(&node); } - key_condition.emplace(std::move(nodes), query_info.sets, context, primary_key_columns, primary_key.expression); + key_condition.emplace(std::move(nodes), query_info.syntax_analyzer_result, query_info.sets, context, primary_key_columns, primary_key.expression); } else { diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index ac60b32246c..996c6bc01ee 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -842,6 +842,7 @@ KeyCondition::KeyCondition( KeyCondition::KeyCondition( ActionDAGNodes dag_nodes, + TreeRewriterResultPtr syntax_analyzer_result, PreparedSets prepared_sets_, ContextPtr context, const Names & key_column_names, @@ -861,6 +862,9 @@ KeyCondition::KeyCondition( key_columns[name] = i; } + for (const auto & [name, _] : syntax_analyzer_result->array_join_result_to_source) + array_joined_columns.insert(name); + if (!dag_nodes.nodes.empty()) { auto inverted_dag = cloneASTWithInversionPushDown(std::move(dag_nodes.nodes), context); diff --git a/src/Storages/MergeTree/KeyCondition.h b/src/Storages/MergeTree/KeyCondition.h index c01e32cf2da..e3ac56b2e3b 100644 --- a/src/Storages/MergeTree/KeyCondition.h +++ b/src/Storages/MergeTree/KeyCondition.h @@ -240,6 +240,7 @@ public: KeyCondition( ActionDAGNodes dag_nodes, + TreeRewriterResultPtr syntax_analyzer_result, PreparedSets prepared_sets_, ContextPtr context, const Names & key_column_names, From a45290bfb363f7c6b2902417999bcd29652f2c6e Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 22 Jun 2021 18:52:14 +0300 Subject: [PATCH 010/672] Fix some tests. --- src/Storages/MergeTree/KeyCondition.cpp | 4 +++- tests/queries/0_stateless/00160_merge_and_index_in_in.sql | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 996c6bc01ee..07298593abb 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -683,7 +683,7 @@ static const ActionsDAG::Node & cloneASTWithInversionPushDown( return arg; } - if (isLogicalOperator(name)) + if (isLogicalOperator(name) && need_inversion) { ActionsDAG::NodeRawConstPtrs children(node.children); @@ -869,6 +869,8 @@ KeyCondition::KeyCondition( { auto inverted_dag = cloneASTWithInversionPushDown(std::move(dag_nodes.nodes), context); + // std::cerr << "========== inverted dag: " << inverted_dag->dumpDAG() << std::endl; + Block empty; for (const auto * node : inverted_dag->getIndex()) traverseAST(Tree(node), context, empty); diff --git a/tests/queries/0_stateless/00160_merge_and_index_in_in.sql b/tests/queries/0_stateless/00160_merge_and_index_in_in.sql index bdab3f7640d..3ed829c4d59 100644 --- a/tests/queries/0_stateless/00160_merge_and_index_in_in.sql +++ b/tests/queries/0_stateless/00160_merge_and_index_in_in.sql @@ -9,6 +9,7 @@ SET max_block_size = 1000000; INSERT INTO mt_00160 (x) SELECT number AS x FROM system.numbers LIMIT 100000; SELECT *, b FROM mt_00160 WHERE x IN (12345, 67890) AND NOT ignore(blockSize() < 10 AS b) ORDER BY x; +SET query_plan_optimize_primary_key = 0; -- Need separate query plan step for merge SELECT *, b FROM merge_00160 WHERE x IN (12345, 67890) AND NOT ignore(blockSize() < 10 AS b) ORDER BY x; DROP TABLE merge_00160; From c22f856d3620ad8e5d2b082da767ea34cead7879 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 23 Jun 2021 15:19:22 +0300 Subject: [PATCH 011/672] Fix indexHint --- src/CMakeLists.txt | 4 +- src/Functions/IFunctionAdaptors.h | 2 + src/Functions/indexHint.cpp | 54 +-------------- src/Functions/indexHint.h | 67 +++++++++++++++++++ src/Interpreters/ActionsVisitor.cpp | 38 ++++++++++- src/Processors/ya.make | 1 + src/Storages/MergeTree/KeyCondition.cpp | 28 ++++++-- .../0_stateless/01739_index_hint.reference | 4 +- .../queries/0_stateless/01739_index_hint.sql | 2 +- .../01783_merge_engine_join_key_condition.sql | 1 + 10 files changed, 138 insertions(+), 63 deletions(-) create mode 100644 src/Functions/indexHint.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 88a6113b8fa..791085eee92 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -107,8 +107,8 @@ endif() list (APPEND clickhouse_common_io_sources ${CONFIG_BUILD}) list (APPEND clickhouse_common_io_headers ${CONFIG_VERSION} ${CONFIG_COMMON}) -list (APPEND dbms_sources Functions/IFunction.cpp Functions/FunctionFactory.cpp Functions/FunctionHelpers.cpp Functions/extractTimeZoneFromFunctionArguments.cpp Functions/replicate.cpp Functions/FunctionsLogical.cpp) -list (APPEND dbms_headers Functions/IFunction.h Functions/FunctionFactory.h Functions/FunctionHelpers.h Functions/extractTimeZoneFromFunctionArguments.h Functions/replicate.h Functions/FunctionsLogical.h) +list (APPEND dbms_sources Functions/IFunction.cpp Functions/FunctionFactory.cpp Functions/FunctionHelpers.cpp Functions/extractTimeZoneFromFunctionArguments.cpp Functions/replicate.cpp Functions/FunctionsLogical.cpp Functions/indexHint.cpp) +list (APPEND dbms_headers Functions/IFunction.h Functions/FunctionFactory.h Functions/FunctionHelpers.h Functions/extractTimeZoneFromFunctionArguments.h Functions/replicate.h Functions/FunctionsLogical.h Functions/indexHint.h) list (APPEND dbms_sources AggregateFunctions/IAggregateFunction.cpp diff --git a/src/Functions/IFunctionAdaptors.h b/src/Functions/IFunctionAdaptors.h index 6a865af0dd3..e032f2ca90d 100644 --- a/src/Functions/IFunctionAdaptors.h +++ b/src/Functions/IFunctionAdaptors.h @@ -133,6 +133,8 @@ public: void getLambdaArgumentTypesImpl(DataTypes & arguments) const override { function->getLambdaArgumentTypes(arguments); } + const IFunction * getFunction() const { return function.get(); } + private: std::shared_ptr function; }; diff --git a/src/Functions/indexHint.cpp b/src/Functions/indexHint.cpp index f3c856c38ce..8637a6762b9 100644 --- a/src/Functions/indexHint.cpp +++ b/src/Functions/indexHint.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -6,58 +6,6 @@ namespace DB { - -/** The `indexHint` function takes any number of any arguments and always returns one. - * - * This function has a special meaning (see ExpressionAnalyzer, KeyCondition) - * - the expressions inside it are not evaluated; - * - but when analyzing the index (selecting ranges for reading), this function is treated the same way, - * as if instead of using it the expression itself would be. - * - * Example: WHERE something AND indexHint(CounterID = 34) - * - do not read or calculate CounterID = 34, but select ranges in which the CounterID = 34 expression can be true. - * - * The function can be used for debugging purposes, as well as for (hidden from the user) query conversions. - */ -class FunctionIndexHint : public IFunction -{ -public: - static constexpr auto name = "indexHint"; - static FunctionPtr create(ContextPtr) - { - return std::make_shared(); - } - - bool isVariadic() const override - { - return true; - } - size_t getNumberOfArguments() const override - { - return 0; - } - - bool useDefaultImplementationForNulls() const override { return false; } - - bool isSuitableForConstantFolding() const override { return false; } - - String getName() const override - { - return name; - } - DataTypePtr getReturnTypeImpl(const DataTypes & /*arguments*/) const override - { - return std::make_shared(); - } - - ColumnPtr executeImpl(const ColumnsWithTypeAndName &, const DataTypePtr &, size_t input_rows_count) const override - { - return DataTypeUInt8().createColumnConst(input_rows_count, 1u); - } - -}; - - void registerFunctionIndexHint(FunctionFactory & factory) { factory.registerFunction(); diff --git a/src/Functions/indexHint.h b/src/Functions/indexHint.h new file mode 100644 index 00000000000..db838df7763 --- /dev/null +++ b/src/Functions/indexHint.h @@ -0,0 +1,67 @@ +#include +#include +#include + + +namespace DB +{ + +class ActionsDAG; +using ActionsDAGPtr = std::shared_ptr; + +/** The `indexHint` function takes any number of any arguments and always returns one. + * + * This function has a special meaning (see ExpressionAnalyzer, KeyCondition) + * - the expressions inside it are not evaluated; + * - but when analyzing the index (selecting ranges for reading), this function is treated the same way, + * as if instead of using it the expression itself would be. + * + * Example: WHERE something AND indexHint(CounterID = 34) + * - do not read or calculate CounterID = 34, but select ranges in which the CounterID = 34 expression can be true. + * + * The function can be used for debugging purposes, as well as for (hidden from the user) query conversions. + */ +class FunctionIndexHint : public IFunction +{ +public: + static constexpr auto name = "indexHint"; + static FunctionPtr create(ContextPtr) + { + return std::make_shared(); + } + + bool isVariadic() const override + { + return true; + } + size_t getNumberOfArguments() const override + { + return 0; + } + + bool useDefaultImplementationForNulls() const override { return false; } + + bool isSuitableForConstantFolding() const override { return false; } + + String getName() const override + { + return name; + } + DataTypePtr getReturnTypeImpl(const DataTypes & /*arguments*/) const override + { + return std::make_shared(); + } + + ColumnPtr executeImpl(const ColumnsWithTypeAndName &, const DataTypePtr &, size_t input_rows_count) const override + { + return DataTypeUInt8().createColumnConst(input_rows_count, 1u); + } + + void setActions(ActionsDAGPtr actions_) { actions = std::move(actions_); } + const ActionsDAGPtr & getActions() const { return actions; } + +private: + ActionsDAGPtr actions; +}; + +} diff --git a/src/Interpreters/ActionsVisitor.cpp b/src/Interpreters/ActionsVisitor.cpp index 20f54e9b66e..b74ee4ba02a 100644 --- a/src/Interpreters/ActionsVisitor.cpp +++ b/src/Interpreters/ActionsVisitor.cpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -818,8 +819,43 @@ void ActionsMatcher::visit(const ASTFunction & node, const ASTPtr & ast, Data & /// A special function `indexHint`. Everything that is inside it is not calculated if (node.name == "indexHint") { + if (data.only_consts) + return; + + /// Here we create a separate DAG for indexHint condition. + /// It will be used only for index analysis. + Data index_hint_data( + data.getContext(), + data.set_size_limit, + data.subquery_depth, + data.source_columns, + std::make_shared(data.source_columns), + data.prepared_sets, + data.subqueries_for_sets, + data.no_subqueries, + data.no_makeset, + data.only_consts, + /*create_source_for_in*/ false); + + NamesWithAliases args; + + if (node.arguments) + { + for (const auto & arg : node.arguments->children) + { + visit(arg, index_hint_data); + args.push_back({arg->getColumnNameWithoutAlias(), {}}); + } + } + + auto dag = index_hint_data.getActions(); + dag->project(args); + + auto index_hint = std::make_shared(); + index_hint->setActions(std::move(dag)); + // Arguments are removed. We add function instead of constant column to avoid constant folding. - data.addFunction(FunctionFactory::instance().get("indexHint", data.getContext()), {}, column_name); + data.addFunction(std::make_unique(index_hint), {}, column_name); return; } diff --git a/src/Processors/ya.make b/src/Processors/ya.make index 86a40685d1f..f86577bde99 100644 --- a/src/Processors/ya.make +++ b/src/Processors/ya.make @@ -119,6 +119,7 @@ SRCS( QueryPlan/Optimizations/liftUpArrayJoin.cpp QueryPlan/Optimizations/limitPushDown.cpp QueryPlan/Optimizations/mergeExpressions.cpp + QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp QueryPlan/Optimizations/optimizeTree.cpp QueryPlan/Optimizations/splitFilter.cpp QueryPlan/PartialSortingStep.cpp diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 07298593abb..9bc26b8ba9b 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -683,7 +684,27 @@ static const ActionsDAG::Node & cloneASTWithInversionPushDown( return arg; } - if (isLogicalOperator(name) && need_inversion) + if (name == "indexHint") + { + ActionsDAG::NodeRawConstPtrs children; + if (const auto * adaptor = typeid_cast(node.function_builder.get())) + { + if (const auto * index_hint = typeid_cast(adaptor->getFunction())) + { + const auto & index_hint_dag = index_hint->getActions(); + children = index_hint_dag->getIndex(); + + for (auto & arg : children) + arg = &cloneASTWithInversionPushDown(*arg, inverted_dag, to_inverted, context, need_inversion); + } + } + + const auto & func = inverted_dag.addFunction(node.function_builder, children, ""); + to_inverted[&node] = &func; + return func; + } + + if (need_inversion && (name == "and" || name == "or")) { ActionsDAG::NodeRawConstPtrs children(node.children); @@ -691,9 +712,8 @@ static const ActionsDAG::Node & cloneASTWithInversionPushDown( arg = &cloneASTWithInversionPushDown(*arg, inverted_dag, to_inverted, context, need_inversion); FunctionOverloadResolverPtr function_builder; - if (name == "indexHint") - function_builder = node.function_builder; - else if (name == "and") + + if (name == "and") function_builder = FunctionFactory::instance().get("or", context); else if (name == "or") function_builder = FunctionFactory::instance().get("and", context); diff --git a/tests/queries/0_stateless/01739_index_hint.reference b/tests/queries/0_stateless/01739_index_hint.reference index 6aa40c5d302..e0877a10007 100644 --- a/tests/queries/0_stateless/01739_index_hint.reference +++ b/tests/queries/0_stateless/01739_index_hint.reference @@ -30,6 +30,6 @@ SELECT count() FROM XXXX WHERE indexHint(t = 42); drop table if exists XXXX; create table XXXX (t Int64, f Float64) Engine=MergeTree order by t settings index_granularity=8192; insert into XXXX select number*60, 0 from numbers(100000); -SELECT count() FROM XXXX WHERE indexHint(t = toDateTime(0)); -100000 +SELECT count() >= 1 FROM XXXX WHERE indexHint(t = toDateTime(0)); +1 drop table XXXX; diff --git a/tests/queries/0_stateless/01739_index_hint.sql b/tests/queries/0_stateless/01739_index_hint.sql index 28395c2dc1d..99cd7d001a4 100644 --- a/tests/queries/0_stateless/01739_index_hint.sql +++ b/tests/queries/0_stateless/01739_index_hint.sql @@ -30,6 +30,6 @@ create table XXXX (t Int64, f Float64) Engine=MergeTree order by t settings inde insert into XXXX select number*60, 0 from numbers(100000); -SELECT count() FROM XXXX WHERE indexHint(t = toDateTime(0)); +SELECT count() >= 1 FROM XXXX WHERE indexHint(t = toDateTime(0)); drop table XXXX; diff --git a/tests/queries/0_stateless/01783_merge_engine_join_key_condition.sql b/tests/queries/0_stateless/01783_merge_engine_join_key_condition.sql index 372c1bd3572..606597850ab 100644 --- a/tests/queries/0_stateless/01783_merge_engine_join_key_condition.sql +++ b/tests/queries/0_stateless/01783_merge_engine_join_key_condition.sql @@ -11,6 +11,7 @@ CREATE TABLE t2 (Id Int32, Val Int32, X Int32) Engine=Memory; INSERT INTO t2 values (4, 3, 4); SET force_primary_key = 1, force_index_by_date=1; +SET query_plan_optimize_primary_key = 0; SELECT * FROM foo_merge WHERE Val = 3 AND Id = 3; SELECT count(), X FROM foo_merge JOIN t2 USING Val WHERE Val = 3 AND Id = 3 AND t2.X == 4 GROUP BY X; From 4186bc29fdb474e1dc534371bf2afbb7f0862534 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 29 Jun 2021 20:01:22 +0300 Subject: [PATCH 012/672] Fix build. --- src/Processors/QueryPlan/ReadFromMergeTree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index 6e453fd97e9..06dfc6aa22f 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -796,7 +796,7 @@ ReadFromMergeTree::AnalysisResult ReadFromMergeTree::selectRangesToRead(MergeTre ActionDAGNodes nodes; if (prewhere_info) { - const auto & node = prewhere_info->prewhere_actions->getActionsDAG().findInIndex(prewhere_info->prewhere_column_name); + const auto & node = prewhere_info->prewhere_actions->findInIndex(prewhere_info->prewhere_column_name); nodes.nodes.push_back(&node); } From 8a94a10b83fb51f472c16481c0bc003e4cb04652 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 14 Jul 2021 12:08:30 +0300 Subject: [PATCH 013/672] Fix style. --- src/Functions/indexHint.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Functions/indexHint.h b/src/Functions/indexHint.h index db838df7763..0dc44e613ef 100644 --- a/src/Functions/indexHint.h +++ b/src/Functions/indexHint.h @@ -1,3 +1,4 @@ +#pragma once #include #include #include From 9e37df7989e1efef12a7810090f93d91a92e6fe0 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 11 May 2022 16:09:14 +0200 Subject: [PATCH 014/672] better table with DDDL status --- src/Interpreters/executeDDLQueryOnCluster.cpp | 150 +++++++++++++----- 1 file changed, 112 insertions(+), 38 deletions(-) diff --git a/src/Interpreters/executeDDLQueryOnCluster.cpp b/src/Interpreters/executeDDLQueryOnCluster.cpp index b280d98b5d3..ce9137ccea2 100644 --- a/src/Interpreters/executeDDLQueryOnCluster.cpp +++ b/src/Interpreters/executeDDLQueryOnCluster.cpp @@ -12,9 +12,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -203,10 +205,24 @@ public: private: static Strings getChildrenAllowNoNode(const std::shared_ptr & zookeeper, const String & node_path); + static Block getSampleBlock(ContextPtr context_, bool hosts_to_wait); + Strings getNewAndUpdate(const Strings & current_list_of_finished_hosts); std::pair parseHostAndPort(const String & host_id) const; + Chunk generateChunkWithUnfinishedHosts() const; + + enum ReplicatedDatabaseQueryStatus + { + /// Query is (successfully) finished + OK = 0, + /// Query is not finished yet, but replica is currently executing it + IN_PROGRESS = 1, + /// Replica is not available or busy with previous queries. It will process query asynchronously + QUEUED = 2, + }; + String node_path; ContextPtr context; Stopwatch watch; @@ -222,7 +238,7 @@ private: std::unique_ptr first_exception; Int64 timeout_seconds = 120; - bool by_hostname = true; + bool is_replicated_database = false; bool throw_on_timeout = true; bool timeout_exceeded = false; }; @@ -243,7 +259,7 @@ BlockIO getDistributedDDLStatus(const String & node_path, const DDLLogEntry & en return io; } -static Block getSampleBlock(ContextPtr context_, bool hosts_to_wait) +Block DDLQueryStatusSource::getSampleBlock(ContextPtr context_, bool hosts_to_wait) { auto output_mode = context_->getSettingsRef().distributed_ddl_output_mode; @@ -254,19 +270,38 @@ static Block getSampleBlock(ContextPtr context_, bool hosts_to_wait) return std::make_shared(type); }; - Block res = Block{ - {std::make_shared(), "host"}, - {std::make_shared(), "port"}, - {maybe_make_nullable(std::make_shared()), "status"}, - {maybe_make_nullable(std::make_shared()), "error"}, - {std::make_shared(), "num_hosts_remaining"}, - {std::make_shared(), "num_hosts_active"}, + auto get_status_enum = []() + { + return std::make_shared( + DataTypeEnum8::Values + { + {"OK", static_cast(OK)}, + {"IN_PROGRESS", static_cast(IN_PROGRESS)}, + {"QUEUED", static_cast(QUEUED)}, + }); }; if (hosts_to_wait) - res.erase("port"); - - return res; + { + return Block{ + {std::make_shared(), "shard"}, + {std::make_shared(), "replica"}, + {get_status_enum(), "status"}, + {std::make_shared(), "num_hosts_remaining"}, + {std::make_shared(), "num_hosts_active"}, + }; + } + else + { + return Block{ + {std::make_shared(), "host"}, + {std::make_shared(), "port"}, + {maybe_make_nullable(std::make_shared()), "status"}, + {maybe_make_nullable(std::make_shared()), "error"}, + {std::make_shared(), "num_hosts_remaining"}, + {std::make_shared(), "num_hosts_active"}, + }; + } } DDLQueryStatusSource::DDLQueryStatusSource( @@ -283,7 +318,7 @@ DDLQueryStatusSource::DDLQueryStatusSource( if (hosts_to_wait) { waiting_hosts = NameSet(hosts_to_wait->begin(), hosts_to_wait->end()); - by_hostname = false; + is_replicated_database = true; } else { @@ -299,7 +334,7 @@ std::pair DDLQueryStatusSource::parseHostAndPort(const String & { String host = host_id; UInt16 port = 0; - if (by_hostname) + if (!is_replicated_database) { auto host_and_port = Cluster::Address::fromString(host_id); host = host_and_port.first; @@ -308,6 +343,43 @@ std::pair DDLQueryStatusSource::parseHostAndPort(const String & return {host, port}; } +Chunk DDLQueryStatusSource::generateChunkWithUnfinishedHosts() const +{ + NameSet unfinished_hosts = waiting_hosts; + for (const auto & host_id : finished_hosts) + unfinished_hosts.erase(host_id); + + NameSet active_hosts_set = NameSet{current_active_hosts.begin(), current_active_hosts.end()}; + + /// Query is not finished on the rest hosts, so fill the corresponding rows with NULLs. + MutableColumns columns = output.getHeader().cloneEmptyColumns(); + for (const String & host_id : unfinished_hosts) + { + size_t num = 0; + if (is_replicated_database) + { + auto [shard, replica] = DatabaseReplicated::parseFullReplicaName(host_id); + columns[num++]->insert(shard); + columns[num++]->insert(replica); + if (active_hosts_set.contains(host_id)) + columns[num++]->insert(IN_PROGRESS); + else + columns[num++]->insert(QUEUED); + } + else + { + auto [host, port] = parseHostAndPort(host_id); + columns[num++]->insert(host); + columns[num++]->insert(port); + columns[num++]->insert(Field{}); + columns[num++]->insert(Field{}); + } + columns[num++]->insert(unfinished_hosts.size()); + columns[num++]->insert(current_active_hosts.size()); + } + return Chunk(std::move(columns), unfinished_hosts.size()); +} + Chunk DDLQueryStatusSource::generate() { bool all_hosts_finished = num_hosts_finished >= waiting_hosts.size(); @@ -342,30 +414,16 @@ Chunk DDLQueryStatusSource::generate() first_exception = std::make_unique( fmt::format(msg_format, node_path, timeout_seconds, num_unfinished_hosts, num_active_hosts), ErrorCodes::TIMEOUT_EXCEEDED); + + /// For Replicated database print a list of unfinished hosts as well. Will return empty block on next iteration. + if (is_replicated_database) + return generateChunkWithUnfinishedHosts(); return {}; } LOG_INFO(log, msg_format, node_path, timeout_seconds, num_unfinished_hosts, num_active_hosts); - NameSet unfinished_hosts = waiting_hosts; - for (const auto & host_id : finished_hosts) - unfinished_hosts.erase(host_id); - - /// Query is not finished on the rest hosts, so fill the corresponding rows with NULLs. - MutableColumns columns = output.getHeader().cloneEmptyColumns(); - for (const String & host_id : unfinished_hosts) - { - auto [host, port] = parseHostAndPort(host_id); - size_t num = 0; - columns[num++]->insert(host); - if (by_hostname) - columns[num++]->insert(port); - columns[num++]->insert(Field{}); - columns[num++]->insert(Field{}); - columns[num++]->insert(num_unfinished_hosts); - columns[num++]->insert(num_active_hosts); - } - return Chunk(std::move(columns), unfinished_hosts.size()); + return generateChunkWithUnfinishedHosts(); } if (num_hosts_finished != 0 || try_number != 0) @@ -404,11 +462,15 @@ Chunk DDLQueryStatusSource::generate() status.tryDeserializeText(status_data); } - auto [host, port] = parseHostAndPort(host_id); if (status.code != 0 && !first_exception && context->getSettingsRef().distributed_ddl_output_mode != DistributedDDLOutputMode::NEVER_THROW) { + /// Replicated database retries in case of error, it should not write error status. + if (is_replicated_database) + throw Exception(ErrorCodes::LOGICAL_ERROR, "There was an error on {}: {} (probably it's a bug)", host_id, status.message); + + auto [host, port] = parseHostAndPort(host_id); first_exception = std::make_unique( fmt::format("There was an error on [{}:{}]: {}", host, port, status.message), status.code); } @@ -416,11 +478,23 @@ Chunk DDLQueryStatusSource::generate() ++num_hosts_finished; size_t num = 0; - columns[num++]->insert(host); - if (by_hostname) + if (is_replicated_database) + { + if (status.code != 0) + throw Exception(ErrorCodes::LOGICAL_ERROR, "There was an error on {}: {} (probably it's a bug)", host_id, status.message); + auto [shard, replica] = DatabaseReplicated::parseFullReplicaName(host_id); + columns[num++]->insert(shard); + columns[num++]->insert(replica); + columns[num++]->insert(OK); + } + else + { + auto [host, port] = parseHostAndPort(host_id); + columns[num++]->insert(host); columns[num++]->insert(port); - columns[num++]->insert(status.code); - columns[num++]->insert(status.message); + columns[num++]->insert(status.code); + columns[num++]->insert(status.message); + } columns[num++]->insert(waiting_hosts.size() - num_hosts_finished); columns[num++]->insert(current_active_hosts.size()); } From 281027e49020c0ea94ab6cce8765088d55d49fdb Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 13 May 2022 15:15:39 +0200 Subject: [PATCH 015/672] do not fail to start if replica seems dropped --- src/Databases/DatabaseReplicated.cpp | 41 +++++--- src/Databases/DatabaseReplicated.h | 3 +- src/Databases/DatabaseReplicatedWorker.cpp | 4 +- tests/clickhouse-test | 2 - ...create_drop_replicated_db_stress.reference | 0 .../01111_create_drop_replicated_db_stress.sh | 95 +++++++++++++++++++ 6 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 tests/queries/0_stateless/01111_create_drop_replicated_db_stress.reference create mode 100755 tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh diff --git a/src/Databases/DatabaseReplicated.cpp b/src/Databases/DatabaseReplicated.cpp index 47a7041b3f4..2239ef86fab 100644 --- a/src/Databases/DatabaseReplicated.cpp +++ b/src/Databases/DatabaseReplicated.cpp @@ -227,7 +227,7 @@ void DatabaseReplicated::fillClusterAuthInfo(String collection_name, const Poco: cluster_auth_info.cluster_secure_connection = config_ref.getBool(config_prefix + ".cluster_secure_connection", false); } -void DatabaseReplicated::tryConnectToZooKeeperAndInitDatabase(bool force_attach) +void DatabaseReplicated::tryConnectToZooKeeperAndInitDatabase(bool force_attach, bool is_create_query) { try { @@ -249,16 +249,36 @@ void DatabaseReplicated::tryConnectToZooKeeperAndInitDatabase(bool force_attach) String replica_host_id; if (current_zookeeper->tryGet(replica_path, replica_host_id)) { + if (replica_host_id == DROPPED_MARK && !is_create_query) + { + LOG_WARNING(log, "Database {} exists locally, but marked dropped in ZooKeeper {}. " + "Will not try to start it up", getDatabaseName(), replica_path); + is_probably_dropped = true; + return; + } + String host_id = getHostID(getContext(), db_uuid); - if (replica_host_id != host_id) - throw Exception(ErrorCodes::REPLICA_IS_ALREADY_EXIST, - "Replica {} of shard {} of replicated database at {} already exists. Replica host ID: '{}', current host ID: '{}'", - replica_name, shard_name, zookeeper_path, replica_host_id, host_id); + if (is_create_query || replica_host_id != host_id) + { + throw Exception( + ErrorCodes::REPLICA_IS_ALREADY_EXIST, + "Replica {} of shard {} of replicated database at {} already exists. Replica host ID: '{}', current host ID: '{}'", + replica_name, shard_name, zookeeper_path, replica_host_id, host_id); + } + } + else if (is_create_query) + { + /// Create new replica. Throws if replica with the same name already exists + createReplicaNodesInZooKeeper(current_zookeeper); } else { - /// Throws if replica with the same name already exists - createReplicaNodesInZooKeeper(current_zookeeper); + /// It's not CREATE query, but replica does not exist. Probably it was dropped. + /// Do not create anything, continue as readonly. + LOG_WARNING(log, "Database {} exists locally, but its replica does not exist in ZooKeeper {}. " + "Assuming it was dropped, will not try to start it up", getDatabaseName(), replica_path); + is_probably_dropped = true; + return; } is_readonly = false; @@ -351,7 +371,7 @@ void DatabaseReplicated::createReplicaNodesInZooKeeper(const zkutil::ZooKeeperPt void DatabaseReplicated::beforeLoadingMetadata(ContextMutablePtr /*context*/, bool /*force_restore*/, bool force_attach) { - tryConnectToZooKeeperAndInitDatabase(force_attach); + tryConnectToZooKeeperAndInitDatabase(force_attach, /* is_create_query */ !force_attach); } void DatabaseReplicated::loadStoredObjects( @@ -365,6 +385,8 @@ void DatabaseReplicated::startupTables(ThreadPool & thread_pool, bool force_rest { DatabaseAtomic::startupTables(thread_pool, force_restore, force_attach); ddl_worker = std::make_unique(this, getContext()); + if (is_probably_dropped) + return; ddl_worker->startup(); } @@ -527,9 +549,6 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep else LOG_WARNING(log, "Will recover replica with staled log pointer {} from log pointer {}", our_log_ptr, max_log_ptr); - if (new_replica && !empty()) - throw Exception(ErrorCodes::LOGICAL_ERROR, "It's new replica, but database is not empty"); - auto table_name_to_metadata = tryGetConsistentMetadataSnapshot(current_zookeeper, max_log_ptr); /// For ReplicatedMergeTree tables we can compare only UUIDs to ensure that it's the same table. diff --git a/src/Databases/DatabaseReplicated.h b/src/Databases/DatabaseReplicated.h index 72a4f0d00bb..c71fe2ca6d8 100644 --- a/src/Databases/DatabaseReplicated.h +++ b/src/Databases/DatabaseReplicated.h @@ -75,7 +75,7 @@ public: friend struct DatabaseReplicatedTask; friend class DatabaseReplicatedDDLWorker; private: - void tryConnectToZooKeeperAndInitDatabase(bool force_attach); + void tryConnectToZooKeeperAndInitDatabase(bool force_attach, bool is_create_query); bool createDatabaseNodesInZooKeeper(const ZooKeeperPtr & current_zookeeper); void createReplicaNodesInZooKeeper(const ZooKeeperPtr & current_zookeeper); @@ -116,6 +116,7 @@ private: zkutil::ZooKeeperPtr getZooKeeper() const; std::atomic_bool is_readonly = true; + std::atomic_bool is_probably_dropped = false; std::atomic_bool is_recovering = false; std::unique_ptr ddl_worker; UInt32 max_log_ptr_at_creation = 0; diff --git a/src/Databases/DatabaseReplicatedWorker.cpp b/src/Databases/DatabaseReplicatedWorker.cpp index 96b4e273ce7..f2857daa9be 100644 --- a/src/Databases/DatabaseReplicatedWorker.cpp +++ b/src/Databases/DatabaseReplicatedWorker.cpp @@ -32,9 +32,10 @@ bool DatabaseReplicatedDDLWorker::initializeMainThread() { try { + assert(!database->is_probably_dropped); auto zookeeper = getAndSetZooKeeper(); if (database->is_readonly) - database->tryConnectToZooKeeperAndInitDatabase(false); + database->tryConnectToZooKeeperAndInitDatabase(/* force_attach */ false, /* is_create_query */ false); initializeReplication(); initialized = true; return true; @@ -108,7 +109,6 @@ bool DatabaseReplicatedDDLWorker::waitForReplicaToProcessAllEntries(UInt64 timeo std::unique_lock lock{mutex}; bool processed = wait_current_task_change.wait_for(lock, std::chrono::milliseconds(timeout_ms), [&]() { - assert(zookeeper->expired() || current_task <= max_log); return zookeeper->expired() || current_task == max_log || stop_flag; }); diff --git a/tests/clickhouse-test b/tests/clickhouse-test index cff6c2de799..43b96465a4c 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -54,9 +54,7 @@ MESSAGES_TO_RETRY = [ "ConnectionPoolWithFailover: Connection failed at try", "DB::Exception: New table appeared in database being dropped or detached. Try again", "is already started to be removing by another replica right now", - "DB::Exception: Cannot enqueue query", "line 1: wait_for: No record of process", # Something weird from bash internals, let's just retry - "is executing longer than distributed_ddl_task_timeout", # FIXME ] MAX_RETRIES = 3 diff --git a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.reference b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh new file mode 100755 index 00000000000..baa5bb98c88 --- /dev/null +++ b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# Tags: race, zookeeper, no-backward-compatibility-check + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + + +function create_db() +{ + SHARD=$(($RANDOM % 2)) + REPLICA=$(($RANDOM % 2)) + SUFFIX=$(($RANDOM % 16)) + # Multiple database replicas on one server are actually not supported (until we have namespaces). + # So CREATE TABLE queries will fail on all replicas except one. But it's still makes sense for a stress test. + $CLICKHOUSE_CLIENT --allow_experimental_database_replicated=1 --query \ + "create database if not exists ${CLICKHOUSE_DATABASE}_repl_$SUFFIX engine=Replicated('/test/01111/$CLICKHOUSE_TEST_ZOOKEEPER_PREFIX', '$SHARD', '$REPLICA')" \ + 2>&1| grep -Fa "Exception: " | grep -Fv "REPLICA_IS_ALREADY_EXIST" | grep -Fiv "Will not try to start it up" | grep -Fv "Coordination::Exception" + sleep 0.$RANDOM +} + +function drop_db() +{ + database=$($CLICKHOUSE_CLIENT -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") + if [[ "$database" == "$CLICKHOUSE_DATABASE" ]]; then return; fi + if [ -z "$database" ]; then return; fi + $CLICKHOUSE_CLIENT -n --query \ + "drop database if exists $database" 2>&1| grep -Fa "Exception: " + sleep 0.$RANDOM +} + +function sync_db() +{ + database=$($CLICKHOUSE_CLIENT -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") + if [ -z "$database" ]; then return; fi + $CLICKHOUSE_CLIENT --receive_timeout=1 -q \ + "system sync database replica $database" 2>&1| grep -Fa "Exception: " | grep -Fv TIMEOUT_EXCEEDED | grep -Fv "only with Replicated engine" | grep -Fv UNKNOWN_DATABASE + sleep 0.$RANDOM +} + +function create_table() +{ + database=$($CLICKHOUSE_CLIENT -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") + if [ -z "$database" ]; then return; fi + $CLICKHOUSE_CLIENT --distributed_ddl_task_timeout=0 -q \ + "create table $database.rmt_$RANDOM (n int) engine=ReplicatedMergeTree order by tuple()" \ + 2>&1| grep -Fa "Exception: " | grep -Fv "Macro 'uuid' and empty arguments" | grep -Fv "Cannot enqueue query" | grep -Fv "ZooKeeper session expired" | grep -Fv UNKNOWN_DATABASE + sleep 0.$RANDOM +} + +function alter_table() +{ + table=$($CLICKHOUSE_CLIENT -q "select database || '.' || name from system.tables where database like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") + if [ -z "$table" ]; then return; fi + $CLICKHOUSE_CLIENT --distributed_ddl_task_timeout=0 -q \ + "alter table $table update n = n + (select max(n) from merge(REGEXP('${CLICKHOUSE_DATABASE}.*'), '.*')) where 1 settings allow_nondeterministic_mutations=1" \ + 2>&1| grep -Fa "Exception: " | grep -Fv "Cannot enqueue query" | grep -Fv "ZooKeeper session expired" | grep -Fv UNKNOWN_DATABASE | grep -Fv UNKNOWN_TABLE | grep -Fv TABLE_IS_READ_ONLY + sleep 0.$RANDOM +} + +function insert() +{ + table=$($CLICKHOUSE_CLIENT -q "select database || '.' || name from system.tables where database like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") + if [ -z "$table" ]; then return; fi + $CLICKHOUSE_CLIENT -q \ + "insert into $table values ($RANDOM)" 2>&1| grep -Fa "Exception: " | grep -Fv UNKNOWN_DATABASE | grep -Fv UNKNOWN_TABLE | grep -Fv TABLE_IS_READ_ONLY +} + + + +export -f create_db +export -f drop_db +export -f sync_db +export -f create_table +export -f alter_table +export -f insert + +TIMEOUT=30 + +clickhouse_client_loop_timeout $TIMEOUT create_db & +clickhouse_client_loop_timeout $TIMEOUT sync_db & +clickhouse_client_loop_timeout $TIMEOUT create_table & +clickhouse_client_loop_timeout $TIMEOUT alter_table & +clickhouse_client_loop_timeout $TIMEOUT insert & + +sleep 1 # give other queries a head start +clickhouse_client_loop_timeout $TIMEOUT drop_db & + +wait + +readarray -t databases_arr < <(${CLICKHOUSE_CLIENT} -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}_%'") +for db in "${databases_arr[@]}" +do + $CLICKHOUSE_CLIENT -q "drop database $db" +done From 894acf0c5c880b97a4d16e73e1cbdc284733385e Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 29 Jun 2022 16:27:21 +0200 Subject: [PATCH 016/672] fix tests --- src/Databases/DatabaseReplicated.cpp | 8 ++++---- src/Databases/DatabaseReplicatedWorker.cpp | 6 +++--- src/Interpreters/DDLTask.cpp | 2 +- src/Interpreters/DDLWorker.cpp | 20 +++++++++---------- .../test_replicated_database/test.py | 12 +++++------ .../01111_create_drop_replicated_db_stress.sh | 2 +- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Databases/DatabaseReplicated.cpp b/src/Databases/DatabaseReplicated.cpp index 2f78c17ae72..884789c6de0 100644 --- a/src/Databases/DatabaseReplicated.cpp +++ b/src/Databases/DatabaseReplicated.cpp @@ -325,7 +325,7 @@ bool DatabaseReplicated::createDatabaseNodesInZooKeeper(const zkutil::ZooKeeperP /// Other codes are unexpected, will throw zkutil::KeeperMultiException::check(res, ops, responses); - assert(false); + chassert(false); __builtin_unreachable(); } @@ -533,7 +533,7 @@ static UUID getTableUUIDIfReplicated(const String & metadata, ContextPtr context return UUIDHelpers::Nil; if (!startsWith(create.storage->engine->name, "Replicated") || !endsWith(create.storage->engine->name, "MergeTree")) return UUIDHelpers::Nil; - assert(create.uuid != UUIDHelpers::Nil); + chassert(create.uuid != UUIDHelpers::Nil); return create.uuid; } @@ -767,8 +767,8 @@ std::map DatabaseReplicated::tryGetConsistentMetadataSnapshot(co } else { - assert(max_log_ptr == new_max_log_ptr); - assert(table_names.size() != table_name_to_metadata.size()); + chassert(max_log_ptr == new_max_log_ptr); + chassert(table_names.size() != table_name_to_metadata.size()); LOG_DEBUG(log, "Cannot get metadata of some tables due to ZooKeeper error, will retry"); } } diff --git a/src/Databases/DatabaseReplicatedWorker.cpp b/src/Databases/DatabaseReplicatedWorker.cpp index f2857daa9be..d114c6a6028 100644 --- a/src/Databases/DatabaseReplicatedWorker.cpp +++ b/src/Databases/DatabaseReplicatedWorker.cpp @@ -32,7 +32,7 @@ bool DatabaseReplicatedDDLWorker::initializeMainThread() { try { - assert(!database->is_probably_dropped); + chassert(!database->is_probably_dropped); auto zookeeper = getAndSetZooKeeper(); if (database->is_readonly) database->tryConnectToZooKeeperAndInitDatabase(/* force_attach */ false, /* is_create_query */ false); @@ -94,7 +94,7 @@ bool DatabaseReplicatedDDLWorker::waitForReplicaToProcessAllEntries(UInt64 timeo const auto max_log_ptr_path = database->zookeeper_path + "/max_log_ptr"; UInt32 our_log_ptr = parse(zookeeper->get(our_log_ptr_path)); UInt32 max_log_ptr = parse(zookeeper->get(max_log_ptr_path)); - assert(our_log_ptr <= max_log_ptr); + chassert(our_log_ptr <= max_log_ptr); /// max_log_ptr is the number of the last successfully executed request on the initiator /// The log could contain other entries which are not committed yet @@ -206,7 +206,7 @@ String DatabaseReplicatedDDLWorker::tryEnqueueAndExecuteEntry(DDLLogEntry & entr auto task = std::make_unique(entry_name, entry_path, database); task->entry = entry; task->parseQueryFromEntry(context); - assert(!task->entry.query.empty()); + chassert(!task->entry.query.empty()); assert(!zookeeper->exists(task->getFinishedNodePath())); task->is_initial_query = true; diff --git a/src/Interpreters/DDLTask.cpp b/src/Interpreters/DDLTask.cpp index 71fcd7a1884..c0456f0eaa9 100644 --- a/src/Interpreters/DDLTask.cpp +++ b/src/Interpreters/DDLTask.cpp @@ -361,7 +361,7 @@ void DatabaseReplicatedTask::parseQueryFromEntry(ContextPtr context) if (auto * ddl_query = dynamic_cast(query.get())) { /// Update database name with actual name of local database - assert(!ddl_query->database); + chassert(!ddl_query->database); ddl_query->setDatabase(database->getDatabaseName()); } } diff --git a/src/Interpreters/DDLWorker.cpp b/src/Interpreters/DDLWorker.cpp index 51932ad051b..eeab557c4b1 100644 --- a/src/Interpreters/DDLWorker.cpp +++ b/src/Interpreters/DDLWorker.cpp @@ -249,7 +249,7 @@ void DDLWorker::scheduleTasks(bool reinitialized) auto & task = *task_it; if (task->completely_processed) { - assert(task->was_executed); + chassert(task->was_executed); /// Status must be written (but finished/ node may not exist if entry was deleted). /// If someone is deleting entry concurrently, then /active status dir must not exist. assert(zookeeper->exists(task->getFinishedNodePath()) || !zookeeper->exists(fs::path(task->entry_path) / "active")); @@ -310,7 +310,7 @@ void DDLWorker::scheduleTasks(bool reinitialized) if (first_failed_task_name) { /// If we had failed tasks, then we should start from the first failed task. - assert(reinitialized); + chassert(reinitialized); begin_node = std::lower_bound(queue_nodes.begin(), queue_nodes.end(), first_failed_task_name); } else @@ -503,7 +503,7 @@ void DDLWorker::updateMaxDDLEntryID(const String & entry_name) void DDLWorker::processTask(DDLTaskBase & task, const ZooKeeperPtr & zookeeper) { LOG_DEBUG(log, "Processing task {} ({})", task.entry_name, task.entry.query); - assert(!task.completely_processed); + chassert(!task.completely_processed); String active_node_path = task.getActiveNodePath(); String finished_node_path = task.getFinishedNodePath(); @@ -521,14 +521,14 @@ void DDLWorker::processTask(DDLTaskBase & task, const ZooKeeperPtr & zookeeper) { if (create_active_res != Coordination::Error::ZNONODE && create_active_res != Coordination::Error::ZNODEEXISTS) { - assert(Coordination::isHardwareError(create_active_res)); + chassert(Coordination::isHardwareError(create_active_res)); throw Coordination::Exception(create_active_res, active_node_path); } /// Status dirs were not created in enqueueQuery(...) or someone is removing entry if (create_active_res == Coordination::Error::ZNONODE) { - assert(dynamic_cast(&task) == nullptr); + chassert(dynamic_cast(&task) == nullptr); if (task.was_executed) { /// Special case: @@ -804,7 +804,7 @@ bool DDLWorker::tryExecuteQueryOnLeaderReplica( } } - assert(!(executed_by_us && executed_by_other_leader)); + chassert(!(executed_by_us && executed_by_other_leader)); /// Not executed by leader so was not executed at all if (!executed_by_us && !executed_by_other_leader) @@ -895,9 +895,9 @@ void DDLWorker::cleanupQueue(Int64, const ZooKeeperPtr & zookeeper) /// Possible rare case: initiator node has lost connection after enqueueing entry and failed to create status dirs. /// No one has started to process the entry, so node_path/active and node_path/finished nodes were never created, node_path has no children. /// Entry became outdated, but we cannot remove remove it in a transaction with node_path/finished. - assert(res[0]->error == Coordination::Error::ZOK && res[1]->error == Coordination::Error::ZNONODE); + chassert(res[0]->error == Coordination::Error::ZOK && res[1]->error == Coordination::Error::ZNONODE); rm_entry_res = zookeeper->tryRemove(node_path); - assert(rm_entry_res != Coordination::Error::ZNOTEMPTY); + chassert(rm_entry_res != Coordination::Error::ZNOTEMPTY); continue; } zkutil::KeeperMultiException::check(rm_entry_res, ops, res); @@ -998,7 +998,7 @@ String DDLWorker::enqueueQuery(DDLLogEntry & entry) bool DDLWorker::initializeMainThread() { - assert(!initialized); + chassert(!initialized); setThreadName("DDLWorker"); LOG_DEBUG(log, "Initializing DDLWorker thread"); @@ -1017,7 +1017,7 @@ bool DDLWorker::initializeMainThread() { /// A logical error. LOG_ERROR(log, "ZooKeeper error: {}. Failed to start DDLWorker.", getCurrentExceptionMessage(true)); - assert(false); /// Catch such failures in tests with debug build + chassert(false); /// Catch such failures in tests with debug build } tryLogCurrentException(__PRETTY_FUNCTION__); diff --git a/tests/integration/test_replicated_database/test.py b/tests/integration/test_replicated_database/test.py index 11ca0d2f962..01aedba66c8 100644 --- a/tests/integration/test_replicated_database/test.py +++ b/tests/integration/test_replicated_database/test.py @@ -417,7 +417,7 @@ def test_alters_from_different_replicas(started_cluster): "distributed_ddl_task_timeout": 5, "distributed_ddl_output_mode": "null_status_on_timeout", } - assert "shard1|replica2\t\\N\t\\N" in main_node.query( + assert "shard1\treplica2\tQUEUED\t" in main_node.query( "ALTER TABLE testdb.concurrent_test ADD COLUMN Added2 UInt32;", settings=settings, ) @@ -425,7 +425,7 @@ def test_alters_from_different_replicas(started_cluster): "distributed_ddl_task_timeout": 5, "distributed_ddl_output_mode": "never_throw", } - assert "shard1|replica2\t\\N\t\\N" in competing_node.query( + assert "shard1\treplica2\tQUEUED\t" in competing_node.query( "ALTER TABLE testdb.concurrent_test ADD COLUMN Added1 UInt32 AFTER Added0;", settings=settings, ) @@ -495,11 +495,11 @@ def test_alters_from_different_replicas(started_cluster): ) res = main_node.query("ALTER TABLE testdb.concurrent_test DELETE WHERE UserID % 2") assert ( - "shard1|replica1" in res - and "shard1|replica2" in res - and "shard1|replica3" in res + "shard1\treplica1\tOK" in res + and "shard1\treplica2\tOK" in res + and "shard1\treplica3\tOK" in res ) - assert "shard2|replica1" in res and "shard2|replica2" in res + assert "shard2\treplica1\tOK" in res and "shard2\treplica2\tOK" in res expected = ( "1\t1\tmain_node\n" diff --git a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh index baa5bb98c88..293b5deb78a 100755 --- a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh +++ b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh @@ -43,7 +43,7 @@ function create_table() database=$($CLICKHOUSE_CLIENT -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") if [ -z "$database" ]; then return; fi $CLICKHOUSE_CLIENT --distributed_ddl_task_timeout=0 -q \ - "create table $database.rmt_$RANDOM (n int) engine=ReplicatedMergeTree order by tuple()" \ + "create table $database.rmt_$RANDOM (n int) engine=ReplicatedMergeTree order by tuple() -- suppress CLICKHOUSE_TEST_ZOOKEEPER_PREFIX" \ 2>&1| grep -Fa "Exception: " | grep -Fv "Macro 'uuid' and empty arguments" | grep -Fv "Cannot enqueue query" | grep -Fv "ZooKeeper session expired" | grep -Fv UNKNOWN_DATABASE sleep 0.$RANDOM } From b7529986c0bc4ffe3ad4ebbbb6cf366091168374 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Fri, 1 Jul 2022 12:09:32 +0000 Subject: [PATCH 017/672] Use separate counter for RSS in global memory tracker. --- src/Common/MemoryTracker.cpp | 61 ++++++++++++++---------- src/Common/MemoryTracker.h | 6 ++- src/Interpreters/AsynchronousMetrics.cpp | 19 +++++--- src/Interpreters/AsynchronousMetrics.h | 1 + 4 files changed, 53 insertions(+), 34 deletions(-) diff --git a/src/Common/MemoryTracker.cpp b/src/Common/MemoryTracker.cpp index 51f4c83dc23..ba097568477 100644 --- a/src/Common/MemoryTracker.cpp +++ b/src/Common/MemoryTracker.cpp @@ -88,6 +88,7 @@ static constexpr size_t log_peak_memory_usage_every = 1ULL << 30; MemoryTracker total_memory_tracker(nullptr, VariableContext::Global); +std::atomic MemoryTracker::rss; MemoryTracker::MemoryTracker(VariableContext level_) : parent(&total_memory_tracker), level(level_) {} MemoryTracker::MemoryTracker(MemoryTracker * parent_, VariableContext level_) : parent(parent_), level(level_) {} @@ -131,6 +132,16 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT if (MemoryTrackerBlockerInThread::isBlocked(level)) { + if (level == VariableContext::Global) + { + /// For global memory tracker always update memory usage. + amount.fetch_add(size, std::memory_order_relaxed); + + auto metric_loaded = metric.load(std::memory_order_relaxed); + if (metric_loaded != CurrentMetrics::end()) + CurrentMetrics::add(metric_loaded, size); + } + /// Since the MemoryTrackerBlockerInThread should respect the level, we should go to the next parent. if (auto * loaded_next = parent.load(std::memory_order_relaxed)) loaded_next->allocImpl(size, throw_if_memory_exceeded, @@ -151,24 +162,6 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT Int64 current_hard_limit = hard_limit.load(std::memory_order_relaxed); Int64 current_profiler_limit = profiler_limit.load(std::memory_order_relaxed); - /// Cap the limit to the total_memory_tracker, since it may include some drift - /// for user-level memory tracker. - /// - /// And since total_memory_tracker is reset to the process resident - /// memory peridically (in AsynchronousMetrics::update()), any limit can be - /// capped to it, to avoid possible drift. - if (unlikely(current_hard_limit - && will_be > current_hard_limit - && level == VariableContext::User)) - { - Int64 total_amount = total_memory_tracker.get(); - if (amount > total_amount) - { - set(total_amount); - will_be = size + total_amount; - } - } - #ifdef MEMORY_TRACKER_DEBUG_CHECKS if (unlikely(memory_tracker_always_throw_logical_error_on_allocation)) { @@ -214,6 +207,16 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT allocation_traced = true; } + bool used_rss_counter = false; + if (level == VariableContext::Global) + { + if (Int64 current_rss = rss.load(std::memory_order_relaxed); unlikely(current_rss + size > will_be)) + { + used_rss_counter = true; + will_be = current_rss + size; + } + } + if (unlikely(current_hard_limit && will_be > current_hard_limit) && memoryTrackerCanThrow(level, false) && throw_if_memory_exceeded) { OvercommitResult overcommit_result = OvercommitResult::NONE; @@ -228,9 +231,10 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT const auto * description = description_ptr.load(std::memory_order_relaxed); throw DB::Exception( DB::ErrorCodes::MEMORY_LIMIT_EXCEEDED, - "Memory limit{}{} exceeded: would use {} (attempt to allocate chunk of {} bytes), maximum: {}. OvercommitTracker decision: {}.", + "Memory limit{}{} {}exceeded: would use {} (attempt to allocate chunk of {} bytes), maximum: {}. OvercommitTracker decision: {}.", description ? " " : "", description ? description : "", + used_rss_counter ? "(RSS) " : "", formatReadableSizeWithBinarySuffix(will_be), size, formatReadableSizeWithBinarySuffix(current_hard_limit), @@ -303,6 +307,16 @@ void MemoryTracker::free(Int64 size) { if (MemoryTrackerBlockerInThread::isBlocked(level)) { + if (level == VariableContext::Global) + { + /// For global memory tracker always update memory usage. + amount.fetch_sub(size, std::memory_order_relaxed); + + auto metric_loaded = metric.load(std::memory_order_relaxed); + if (metric_loaded != CurrentMetrics::end()) + CurrentMetrics::add(metric_loaded, size); + } + /// Since the MemoryTrackerBlockerInThread should respect the level, we should go to the next parent. if (auto * loaded_next = parent.load(std::memory_order_relaxed)) loaded_next->free(size); @@ -317,7 +331,7 @@ void MemoryTracker::free(Int64 size) } Int64 accounted_size = size; - if (level == VariableContext::Thread) + if (level == VariableContext::Thread || level == VariableContext::Global) { /// Could become negative if memory allocated in this thread is freed in another one amount.fetch_sub(accounted_size, std::memory_order_relaxed); @@ -391,12 +405,9 @@ void MemoryTracker::reset() } -void MemoryTracker::set(Int64 to) +void MemoryTracker::setRSS(Int64 to) { - amount.store(to, std::memory_order_relaxed); - - bool log_memory_usage = true; - updatePeak(to, log_memory_usage); + rss.store(to, std::memory_order_relaxed); } diff --git a/src/Common/MemoryTracker.h b/src/Common/MemoryTracker.h index 58bd3a460bd..b66706aafe8 100644 --- a/src/Common/MemoryTracker.h +++ b/src/Common/MemoryTracker.h @@ -56,6 +56,8 @@ private: std::atomic hard_limit {0}; std::atomic profiler_limit {0}; + static std::atomic rss; + Int64 profiler_step = 0; /// To test exception safety of calling code, memory tracker throws an exception on each memory allocation with specified probability. @@ -212,8 +214,8 @@ public: /// Reset the accumulated data. void reset(); - /// Reset current counter to a new value. - void set(Int64 to); + /// Update RSS. + static void setRSS(Int64 to); /// Prints info about peak memory consumption into log. void logPeakMemoryUsage() const; diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index 37ed418ec2a..9275c1d6840 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -670,21 +670,26 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti { Int64 amount = total_memory_tracker.get(); Int64 peak = total_memory_tracker.getPeak(); - Int64 new_amount = data.resident; + Int64 rss = data.resident; - Int64 difference = new_amount - amount; + Int64 rss_drift = rss - amount; + Int64 difference = rss_drift - last_logged_rss_drift; /// Log only if difference is high. This is for convenience. The threshold is arbitrary. if (difference >= 1048576 || difference <= -1048576) + { LOG_TRACE(log, - "MemoryTracking: was {}, peak {}, will set to {} (RSS), difference: {}", + "MemoryTracking: allocated {}, peak {}, RSS {}, difference: {}", ReadableSize(amount), ReadableSize(peak), - ReadableSize(new_amount), - ReadableSize(difference)); + ReadableSize(rss), + ReadableSize(rss_drift)); - total_memory_tracker.set(new_amount); - CurrentMetrics::set(CurrentMetrics::MemoryTracking, new_amount); + last_logged_rss_drift = rss_drift; + } + + total_memory_tracker.setRSS(rss); + // CurrentMetrics::set(CurrentMetrics::MemoryTracking, new_amount); } } #endif diff --git a/src/Interpreters/AsynchronousMetrics.h b/src/Interpreters/AsynchronousMetrics.h index e4bcb2890f3..3ba84219cb2 100644 --- a/src/Interpreters/AsynchronousMetrics.h +++ b/src/Interpreters/AsynchronousMetrics.h @@ -78,6 +78,7 @@ private: #if defined(OS_LINUX) || defined(OS_FREEBSD) MemoryStatisticsOS memory_stat; + Int64 last_logged_rss_drift = 0; #endif #if defined(OS_LINUX) From 3a94ae8f67a0f399e0058f0bef3e32e8b10504e0 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 6 Jul 2022 18:50:00 +0200 Subject: [PATCH 018/672] normalize CREATE ON CLUSTER on initiator --- src/Core/Settings.h | 2 +- src/Interpreters/DDLTask.cpp | 11 +++++--- src/Interpreters/DDLTask.h | 4 +++ src/Interpreters/InterpreterCreateQuery.cpp | 25 +++++++++++++++---- src/Interpreters/InterpreterCreateQuery.h | 2 ++ ...e_table_on_cluster_normalization.reference | 3 +++ ..._create_table_on_cluster_normalization.sql | 18 +++++++++++++ 7 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference create mode 100644 tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 332b7364605..03c0613075e 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -529,7 +529,7 @@ static constexpr UInt64 operator""_GiB(unsigned long long value) M(Bool, database_replicated_always_detach_permanently, false, "Execute DETACH TABLE as DETACH TABLE PERMANENTLY if database engine is Replicated", 0) \ M(Bool, database_replicated_allow_only_replicated_engine, false, "Allow to create only Replicated tables in database with engine Replicated", 0) \ M(DistributedDDLOutputMode, distributed_ddl_output_mode, DistributedDDLOutputMode::THROW, "Format of distributed DDL query result", 0) \ - M(UInt64, distributed_ddl_entry_format_version, 2, "Version of DDL entry to write into ZooKeeper", 0) \ + M(UInt64, distributed_ddl_entry_format_version, 3, "Compatibility version of distributed DDL (ON CLUSTER) queries", 0) \ \ M(UInt64, external_storage_max_read_rows, 0, "Limit maximum number of rows when table with external engine should flush history data. Now supported only for MySQL table engine, database engine, dictionary and MaterializedMySQL. If equal to 0, this setting is disabled", 0) \ M(UInt64, external_storage_max_read_bytes, 0, "Limit maximum number of bytes when table with external engine should flush history data. Now supported only for MySQL table engine, database engine, dictionary and MaterializedMySQL. If equal to 0, this setting is disabled", 0) \ diff --git a/src/Interpreters/DDLTask.cpp b/src/Interpreters/DDLTask.cpp index c0456f0eaa9..55a4dbf9ff4 100644 --- a/src/Interpreters/DDLTask.cpp +++ b/src/Interpreters/DDLTask.cpp @@ -58,7 +58,12 @@ void DDLLogEntry::assertVersion() const void DDLLogEntry::setSettingsIfRequired(ContextPtr context) { version = context->getSettingsRef().distributed_ddl_entry_format_version; - if (version == 2) + + /// NORMALIZE_CREATE_ON_INITIATOR_VERSION does not affect entry format in ZooKeeper + if (version == NORMALIZE_CREATE_ON_INITIATOR_VERSION) + version = SETTINGS_IN_ZK_VERSION; + + if (version == SETTINGS_IN_ZK_VERSION) settings.emplace(context->getSettingsRef().changes()); } @@ -69,7 +74,7 @@ String DDLLogEntry::toString() const wb << "version: " << version << "\n"; wb << "query: " << escape << query << "\n"; - bool write_hosts = version == 1 || !hosts.empty(); + bool write_hosts = version == OLDEST_VERSION || !hosts.empty(); if (write_hosts) { Strings host_id_strings(hosts.size()); @@ -79,7 +84,7 @@ String DDLLogEntry::toString() const wb << "initiator: " << initiator << "\n"; - bool write_settings = 1 <= version && settings && !settings->empty(); + bool write_settings = SETTINGS_IN_ZK_VERSION <= version && settings && !settings->empty(); if (write_settings) { ASTSetQuery ast; diff --git a/src/Interpreters/DDLTask.h b/src/Interpreters/DDLTask.h index d3728918a2d..6b78f347235 100644 --- a/src/Interpreters/DDLTask.h +++ b/src/Interpreters/DDLTask.h @@ -66,6 +66,10 @@ struct HostID struct DDLLogEntry { + static constexpr const UInt64 OLDEST_VERSION = 1; + static constexpr const UInt64 SETTINGS_IN_ZK_VERSION = 2; + static constexpr const UInt64 NORMALIZE_CREATE_ON_INITIATOR_VERSION = 3; + UInt64 version = 1; String query; std::vector hosts; diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index 7a00bbf524c..b3a618a8bb6 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -1017,6 +1017,9 @@ BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create) } } + if (!create.cluster.empty()) + return executeQueryOnCluster(create); + bool if_not_exists = create.if_not_exists; // Table SQL definition is available even if the table is detached (even permanently) @@ -1153,6 +1156,9 @@ BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create) } } + if (!create.cluster.empty()) + return executeQueryOnCluster(create); + if (create.replace_table) return doCreateOrReplaceTable(create, properties); @@ -1543,16 +1549,25 @@ void InterpreterCreateQuery::prepareOnClusterQuery(ASTCreateQuery & create, Cont } } +BlockIO InterpreterCreateQuery::executeQueryOnCluster(ASTCreateQuery & create) +{ + prepareOnClusterQuery(create, getContext(), create.cluster); + DDLQueryOnClusterParams params; + params.access_to_check = getRequiredAccess(); + return executeDDLQueryOnCluster(query_ptr, getContext(), params); +} + BlockIO InterpreterCreateQuery::execute() { FunctionNameNormalizer().visit(query_ptr.get()); auto & create = query_ptr->as(); + + bool is_create_database = create.database && !create.table; if (!create.cluster.empty()) { - prepareOnClusterQuery(create, getContext(), create.cluster); - DDLQueryOnClusterParams params; - params.access_to_check = getRequiredAccess(); - return executeDDLQueryOnCluster(query_ptr, getContext(), params); + auto on_cluster_version = getContext()->getSettingsRef().distributed_ddl_entry_format_version; + if (is_create_database || on_cluster_version < DDLLogEntry::NORMALIZE_CREATE_ON_INITIATOR_VERSION) + return executeQueryOnCluster(create); } getContext()->checkAccess(getRequiredAccess()); @@ -1560,7 +1575,7 @@ BlockIO InterpreterCreateQuery::execute() ASTQueryWithOutput::resetOutputASTIfExist(create); /// CREATE|ATTACH DATABASE - if (create.database && !create.table) + if (is_create_database) return createDatabase(create); else return createTable(create); diff --git a/src/Interpreters/InterpreterCreateQuery.h b/src/Interpreters/InterpreterCreateQuery.h index b6c8e10668a..984310b2952 100644 --- a/src/Interpreters/InterpreterCreateQuery.h +++ b/src/Interpreters/InterpreterCreateQuery.h @@ -100,6 +100,8 @@ private: /// It's used to prevent automatic schema inference while table creation on each server startup. void addColumnsDescriptionToCreateQueryIfNecessary(ASTCreateQuery & create, const StoragePtr & storage); + BlockIO executeQueryOnCluster(ASTCreateQuery & create); + ASTPtr query_ptr; /// Skip safety threshold when loading tables. diff --git a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference new file mode 100644 index 00000000000..4c3a8cd6257 --- /dev/null +++ b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference @@ -0,0 +1,3 @@ +1 2 3 4 +5 6 7 8 +CREATE TABLE default.t_l5ydey\n(\n `c_qv5rv` Int32,\n `c_rutjs4` Int32,\n `c_wmj` Int32,\n `c_m3` String\n)\nENGINE = Distributed(\'test_cluster\', \'default\', \'local_t_l5ydey\', rand()) \ No newline at end of file diff --git a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql new file mode 100644 index 00000000000..e62fcea91f2 --- /dev/null +++ b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql @@ -0,0 +1,18 @@ + +create table local_t_l5ydey on cluster test_shard_localhost ( + c_qv5rv INTEGER , + c_rutjs4 INTEGER , + c_wmj INTEGER , + c_m3 TEXT NOT NULL, + primary key(c_qv5rv) +) engine=ReplicatedMergeTree('/clickhouse/tables/test_{database}/{shard}/local_t_l5ydey', '{replica}'); + +create table t_l5ydey on cluster test_shard_localhost as local_t_l5ydey + engine=Distributed('test_cluster', currentDatabase(),'local_t_l5ydey', rand()); + +insert into local_t_l5ydey values (1, 2, 3, '4'); +insert into t_l5ydey values (5, 6, 7, '8'); +system flush distributed t_l5ydey; + +select * from t_l5ydey order by c_qv5rv; +show create t_l5ydey; From 30e9104b6e0000cd03815c93191fc0c351c43b0b Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 6 Jul 2022 19:54:05 +0200 Subject: [PATCH 019/672] ignore cluster if equals to db name --- src/Interpreters/InterpreterAlterQuery.cpp | 2 +- src/Interpreters/InterpreterCreateQuery.cpp | 2 +- src/Interpreters/InterpreterDropQuery.cpp | 2 +- src/Interpreters/executeDDLQueryOnCluster.cpp | 22 +++++ src/Interpreters/executeDDLQueryOnCluster.h | 2 + ...tem_parts_race_condition_drop_zookeeper.sh | 2 +- .../01111_create_drop_replicated_db_stress.sh | 96 +++++++++++-------- ...e_table_on_cluster_normalization.reference | 2 +- ..._create_table_on_cluster_normalization.sql | 4 + 9 files changed, 87 insertions(+), 47 deletions(-) diff --git a/src/Interpreters/InterpreterAlterQuery.cpp b/src/Interpreters/InterpreterAlterQuery.cpp index 056a3d9f7b4..762afd28923 100644 --- a/src/Interpreters/InterpreterAlterQuery.cpp +++ b/src/Interpreters/InterpreterAlterQuery.cpp @@ -66,7 +66,7 @@ BlockIO InterpreterAlterQuery::executeToTable(const ASTAlterQuery & alter) { BlockIO res; - if (!alter.cluster.empty()) + if (!alter.cluster.empty() && !maybeRemoveOnCluster(query_ptr, getContext())) { DDLQueryOnClusterParams params; params.access_to_check = getRequiredAccess(); diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index b3a618a8bb6..b29ac5dc6d4 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -1563,7 +1563,7 @@ BlockIO InterpreterCreateQuery::execute() auto & create = query_ptr->as(); bool is_create_database = create.database && !create.table; - if (!create.cluster.empty()) + if (!create.cluster.empty() && !maybeRemoveOnCluster(query_ptr, getContext())) { auto on_cluster_version = getContext()->getSettingsRef().distributed_ddl_entry_format_version; if (is_create_database || on_cluster_version < DDLLogEntry::NORMALIZE_CREATE_ON_INITIATOR_VERSION) diff --git a/src/Interpreters/InterpreterDropQuery.cpp b/src/Interpreters/InterpreterDropQuery.cpp index ac731ec6f4b..29091546197 100644 --- a/src/Interpreters/InterpreterDropQuery.cpp +++ b/src/Interpreters/InterpreterDropQuery.cpp @@ -54,7 +54,7 @@ InterpreterDropQuery::InterpreterDropQuery(const ASTPtr & query_ptr_, ContextMut BlockIO InterpreterDropQuery::execute() { auto & drop = query_ptr->as(); - if (!drop.cluster.empty()) + if (!drop.cluster.empty() && !maybeRemoveOnCluster(query_ptr, getContext())) { DDLQueryOnClusterParams params; params.access_to_check = getRequiredAccessForDDLOnCluster(); diff --git a/src/Interpreters/executeDDLQueryOnCluster.cpp b/src/Interpreters/executeDDLQueryOnCluster.cpp index 3163b4bdbec..d1f4068c477 100644 --- a/src/Interpreters/executeDDLQueryOnCluster.cpp +++ b/src/Interpreters/executeDDLQueryOnCluster.cpp @@ -538,4 +538,26 @@ Strings DDLQueryStatusSource::getNewAndUpdate(const Strings & current_list_of_fi } +bool maybeRemoveOnCluster(const ASTPtr & query_ptr, ContextPtr context) +{ + const auto * query = dynamic_cast(query_ptr.get()); + if (!query || !query->table) + return false; + + String database_name = query->getDatabase(); + if (database_name.empty()) + database_name = context->getCurrentDatabase(); + + auto * query_on_cluster = dynamic_cast(query_ptr.get()); + if (database_name != query_on_cluster->cluster) + return false; + + auto db = DatabaseCatalog::instance().tryGetDatabase(database_name); + if (!db || db->getEngineName() != "Replicated") + return false; + + query_on_cluster->cluster.clear(); + return true; +} + } diff --git a/src/Interpreters/executeDDLQueryOnCluster.h b/src/Interpreters/executeDDLQueryOnCluster.h index 3004fe2ff2e..8df199f0ede 100644 --- a/src/Interpreters/executeDDLQueryOnCluster.h +++ b/src/Interpreters/executeDDLQueryOnCluster.h @@ -45,4 +45,6 @@ BlockIO executeDDLQueryOnCluster(const ASTPtr & query_ptr, ContextPtr context, c BlockIO getDistributedDDLStatus( const String & node_path, const DDLLogEntry & entry, ContextPtr context, const std::optional & hosts_to_wait = {}); +bool maybeRemoveOnCluster(const ASTPtr & query_ptr, ContextPtr context); + } diff --git a/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh b/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh index d5d43d3c293..5ccef802c0c 100755 --- a/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh +++ b/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh @@ -58,7 +58,7 @@ function thread6() $CLICKHOUSE_CLIENT -n -q "DROP TABLE IF EXISTS alter_table_$REPLICA; CREATE TABLE alter_table_$REPLICA (a UInt8, b Int16, c Float32, d String, e Array(UInt8), f Nullable(UUID), g Tuple(UInt8, UInt16)) ENGINE = ReplicatedMergeTree('/clickhouse/tables/$CLICKHOUSE_TEST_ZOOKEEPER_PREFIX/alter_table', 'r_$REPLICA') ORDER BY a PARTITION BY b % 10 SETTINGS old_parts_lifetime = 1, cleanup_delay_period = 0, cleanup_delay_period_random_add = 0;"; sleep 0.$RANDOM; - done + done } diff --git a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh index 293b5deb78a..942958bf461 100755 --- a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh +++ b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh @@ -8,62 +8,74 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) function create_db() { - SHARD=$(($RANDOM % 2)) - REPLICA=$(($RANDOM % 2)) - SUFFIX=$(($RANDOM % 16)) - # Multiple database replicas on one server are actually not supported (until we have namespaces). - # So CREATE TABLE queries will fail on all replicas except one. But it's still makes sense for a stress test. - $CLICKHOUSE_CLIENT --allow_experimental_database_replicated=1 --query \ - "create database if not exists ${CLICKHOUSE_DATABASE}_repl_$SUFFIX engine=Replicated('/test/01111/$CLICKHOUSE_TEST_ZOOKEEPER_PREFIX', '$SHARD', '$REPLICA')" \ - 2>&1| grep -Fa "Exception: " | grep -Fv "REPLICA_IS_ALREADY_EXIST" | grep -Fiv "Will not try to start it up" | grep -Fv "Coordination::Exception" - sleep 0.$RANDOM + while true; do + SHARD=$(($RANDOM % 2)) + REPLICA=$(($RANDOM % 2)) + SUFFIX=$(($RANDOM % 16)) + # Multiple database replicas on one server are actually not supported (until we have namespaces). + # So CREATE TABLE queries will fail on all replicas except one. But it's still makes sense for a stress test. + $CLICKHOUSE_CLIENT --allow_experimental_database_replicated=1 --query \ + "create database if not exists ${CLICKHOUSE_DATABASE}_repl_$SUFFIX engine=Replicated('/test/01111/$CLICKHOUSE_TEST_ZOOKEEPER_PREFIX', '$SHARD', '$REPLICA')" \ + 2>&1| grep -Fa "Exception: " | grep -Fv "REPLICA_IS_ALREADY_EXIST" | grep -Fiv "Will not try to start it up" | grep -Fv "Coordination::Exception" + sleep 0.$RANDOM + done } function drop_db() { - database=$($CLICKHOUSE_CLIENT -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") - if [[ "$database" == "$CLICKHOUSE_DATABASE" ]]; then return; fi - if [ -z "$database" ]; then return; fi - $CLICKHOUSE_CLIENT -n --query \ - "drop database if exists $database" 2>&1| grep -Fa "Exception: " - sleep 0.$RANDOM + while true; do + database=$($CLICKHOUSE_CLIENT -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") + if [[ "$database" == "$CLICKHOUSE_DATABASE" ]]; then return; fi + if [ -z "$database" ]; then return; fi + $CLICKHOUSE_CLIENT -n --query \ + "drop database if exists $database" 2>&1| grep -Fa "Exception: " + sleep 0.$RANDOM + done } function sync_db() { - database=$($CLICKHOUSE_CLIENT -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") - if [ -z "$database" ]; then return; fi - $CLICKHOUSE_CLIENT --receive_timeout=1 -q \ - "system sync database replica $database" 2>&1| grep -Fa "Exception: " | grep -Fv TIMEOUT_EXCEEDED | grep -Fv "only with Replicated engine" | grep -Fv UNKNOWN_DATABASE - sleep 0.$RANDOM + while true; do + database=$($CLICKHOUSE_CLIENT -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") + if [ -z "$database" ]; then return; fi + $CLICKHOUSE_CLIENT --receive_timeout=1 -q \ + "system sync database replica $database" 2>&1| grep -Fa "Exception: " | grep -Fv TIMEOUT_EXCEEDED | grep -Fv "only with Replicated engine" | grep -Fv UNKNOWN_DATABASE + sleep 0.$RANDOM + done } function create_table() { - database=$($CLICKHOUSE_CLIENT -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") - if [ -z "$database" ]; then return; fi - $CLICKHOUSE_CLIENT --distributed_ddl_task_timeout=0 -q \ - "create table $database.rmt_$RANDOM (n int) engine=ReplicatedMergeTree order by tuple() -- suppress CLICKHOUSE_TEST_ZOOKEEPER_PREFIX" \ - 2>&1| grep -Fa "Exception: " | grep -Fv "Macro 'uuid' and empty arguments" | grep -Fv "Cannot enqueue query" | grep -Fv "ZooKeeper session expired" | grep -Fv UNKNOWN_DATABASE - sleep 0.$RANDOM + while true; do + database=$($CLICKHOUSE_CLIENT -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") + if [ -z "$database" ]; then return; fi + $CLICKHOUSE_CLIENT --distributed_ddl_task_timeout=0 -q \ + "create table $database.rmt_$RANDOM (n int) engine=ReplicatedMergeTree order by tuple() -- suppress CLICKHOUSE_TEST_ZOOKEEPER_PREFIX" \ + 2>&1| grep -Fa "Exception: " | grep -Fv "Macro 'uuid' and empty arguments" | grep -Fv "Cannot enqueue query" | grep -Fv "ZooKeeper session expired" | grep -Fv UNKNOWN_DATABASE + sleep 0.$RANDOM + done } function alter_table() { - table=$($CLICKHOUSE_CLIENT -q "select database || '.' || name from system.tables where database like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") - if [ -z "$table" ]; then return; fi - $CLICKHOUSE_CLIENT --distributed_ddl_task_timeout=0 -q \ - "alter table $table update n = n + (select max(n) from merge(REGEXP('${CLICKHOUSE_DATABASE}.*'), '.*')) where 1 settings allow_nondeterministic_mutations=1" \ - 2>&1| grep -Fa "Exception: " | grep -Fv "Cannot enqueue query" | grep -Fv "ZooKeeper session expired" | grep -Fv UNKNOWN_DATABASE | grep -Fv UNKNOWN_TABLE | grep -Fv TABLE_IS_READ_ONLY - sleep 0.$RANDOM + while true; do + table=$($CLICKHOUSE_CLIENT -q "select database || '.' || name from system.tables where database like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") + if [ -z "$table" ]; then return; fi + $CLICKHOUSE_CLIENT --distributed_ddl_task_timeout=0 -q \ + "alter table $table on cluster $database update n = n + (select max(n) from merge(REGEXP('${CLICKHOUSE_DATABASE}.*'), '.*')) where 1 settings allow_nondeterministic_mutations=1" \ + 2>&1| grep -Fa "Exception: " | grep -Fv "Cannot enqueue query" | grep -Fv "ZooKeeper session expired" | grep -Fv UNKNOWN_DATABASE | grep -Fv UNKNOWN_TABLE | grep -Fv TABLE_IS_READ_ONLY + sleep 0.$RANDOM + done } function insert() { - table=$($CLICKHOUSE_CLIENT -q "select database || '.' || name from system.tables where database like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") - if [ -z "$table" ]; then return; fi - $CLICKHOUSE_CLIENT -q \ - "insert into $table values ($RANDOM)" 2>&1| grep -Fa "Exception: " | grep -Fv UNKNOWN_DATABASE | grep -Fv UNKNOWN_TABLE | grep -Fv TABLE_IS_READ_ONLY + while true; do + table=$($CLICKHOUSE_CLIENT -q "select database || '.' || name from system.tables where database like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") + if [ -z "$table" ]; then return; fi + $CLICKHOUSE_CLIENT -q \ + "insert into $table values ($RANDOM)" 2>&1| grep -Fa "Exception: " | grep -Fv UNKNOWN_DATABASE | grep -Fv UNKNOWN_TABLE | grep -Fv TABLE_IS_READ_ONLY + done } @@ -77,14 +89,14 @@ export -f insert TIMEOUT=30 -clickhouse_client_loop_timeout $TIMEOUT create_db & -clickhouse_client_loop_timeout $TIMEOUT sync_db & -clickhouse_client_loop_timeout $TIMEOUT create_table & -clickhouse_client_loop_timeout $TIMEOUT alter_table & -clickhouse_client_loop_timeout $TIMEOUT insert & +timeout $TIMEOUT bash -c create_db & +timeout $TIMEOUT bash -c sync_db & +timeout $TIMEOUT bash -c create_table & +timeout $TIMEOUT bash -c alter_table & +timeout $TIMEOUT bash -c insert & sleep 1 # give other queries a head start -clickhouse_client_loop_timeout $TIMEOUT drop_db & +timeout $TIMEOUT bash -c drop_db & wait diff --git a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference index 4c3a8cd6257..e32b948cc9d 100644 --- a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference +++ b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference @@ -1,3 +1,3 @@ 1 2 3 4 5 6 7 8 -CREATE TABLE default.t_l5ydey\n(\n `c_qv5rv` Int32,\n `c_rutjs4` Int32,\n `c_wmj` Int32,\n `c_m3` String\n)\nENGINE = Distributed(\'test_cluster\', \'default\', \'local_t_l5ydey\', rand()) \ No newline at end of file +CREATE TABLE default.t_l5ydey\n(\n `c_qv5rv` Int32,\n `c_rutjs4` Int32,\n `c_wmj` Int32,\n `c_m3` String\n)\nENGINE = Distributed(\'test_cluster\', \'default\', \'local_t_l5ydey\', rand()) diff --git a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql index e62fcea91f2..79fd57e034b 100644 --- a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql +++ b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql @@ -1,3 +1,7 @@ +-- Tags: no-replicated-database +-- Tag no-replicated-database: ON CLUSTER is not allowed + +set distributed_ddl_entry_format_version=3; create table local_t_l5ydey on cluster test_shard_localhost ( c_qv5rv INTEGER , From d274b05fac7957067bf7ff37e4120c2b8ea2c4fd Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Thu, 7 Jul 2022 20:19:15 +0000 Subject: [PATCH 020/672] improvements --- docker/packager/packager | 2 +- tests/integration/helpers/cluster.py | 175 ++++++++++-------- tests/integration/pytest.ini | 2 +- tests/integration/runner | 1 + .../configs/users.d/network.xml | 3 - .../test_allowed_client_hosts/test.py | 29 +-- .../test_allowed_url_from_config/test.py | 15 ++ .../test_dictionaries_redis/test.py | 38 ++-- .../test.py | 33 ++-- tests/integration/test_merge_tree_s3/test.py | 15 +- .../test_merge_tree_s3_restore/test.py | 24 +-- .../test.py | 17 +- .../test.py | 2 +- tests/integration/test_storage_s3/test.py | 27 +-- 14 files changed, 214 insertions(+), 169 deletions(-) diff --git a/docker/packager/packager b/docker/packager/packager index 7c0f046b76c..98c8e49385a 100755 --- a/docker/packager/packager +++ b/docker/packager/packager @@ -62,7 +62,7 @@ def pre_build(repo_path: str, env_variables: List[str]): f"git -C {repo_path} fetch --no-recurse-submodules " "--no-tags origin master:master" ) - logging.info("Getting master branch for performance artifact: ''%s'", cmd) + logging.info("Getting master branch for performance artifact: '%s'", cmd) subprocess.check_call(cmd, shell=True) diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 5983c886680..f5c546ff264 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -237,14 +237,22 @@ def enable_consistent_hash_plugin(rabbitmq_id): return p.returncode == 0 -def get_instances_dir(): - if ( - "INTEGRATION_TESTS_RUN_ID" in os.environ - and os.environ["INTEGRATION_TESTS_RUN_ID"] - ): - return "_instances_" + shlex.quote(os.environ["INTEGRATION_TESTS_RUN_ID"]) - else: - return "_instances" +def get_instances_dir(name): + instances_dir_name = "_instances" + + worker_name = os.environ.get("PYTEST_XDIST_WORKER", "") + run_id = os.environ.get("INTEGRATION_TESTS_RUN_ID", "") + + if worker_name: + instances_dir_name += "_" + worker_name + + if name: + instances_dir_name += "_" + name + + if run_id: + instances_dir_name += "_" + shlex.quote(run_id) + + return instances_dir_name class ClickHouseCluster: @@ -270,6 +278,7 @@ class ClickHouseCluster: zookeeper_keyfile=None, zookeeper_certfile=None, ): + logging.debug(f"INIT CALLED") for param in list(os.environ.keys()): logging.debug("ENV %40s %s" % (param, os.environ[param])) self.base_path = base_path @@ -306,19 +315,8 @@ class ClickHouseCluster: ) # docker-compose removes everything non-alphanumeric from project names so we do it too. self.project_name = re.sub(r"[^a-z0-9]", "", project_name.lower()) - instances_dir_name = "_instances" - if self.name: - instances_dir_name += "_" + self.name - - if ( - "INTEGRATION_TESTS_RUN_ID" in os.environ - and os.environ["INTEGRATION_TESTS_RUN_ID"] - ): - instances_dir_name += "_" + shlex.quote( - os.environ["INTEGRATION_TESTS_RUN_ID"] - ) - - self.instances_dir = p.join(self.base_dir, instances_dir_name) + self.instances_dir_name = get_instances_dir(self.name) + self.instances_dir = p.join(self.base_dir, self.instances_dir_name) self.docker_logs_path = p.join(self.instances_dir, "docker.log") self.env_file = p.join(self.instances_dir, DEFAULT_ENV_NAME) self.env_variables = {} @@ -536,8 +534,37 @@ class ClickHouseCluster: self.is_up = False self.env = os.environ.copy() logging.debug(f"CLUSTER INIT base_config_dir:{self.base_config_dir}") + if p.exists(self.instances_dir): + shutil.rmtree(self.instances_dir, ignore_errors=True) + logging.debug(f"Removed :{self.instances_dir}") + os.mkdir(self.instances_dir) + + + def print_all_docker_pieces(self): + res_networks = subprocess.check_output( + f"docker network ls --filter name='{self.project_name}*'", + shell=True, + universal_newlines=True, + ) + logging.debug(f"Docker networks for project {self.project_name} are {res_networks}") + res_containers = subprocess.check_output( + f"docker container ls -a --filter name='{self.project_name}*'", + shell=True, + universal_newlines=True, + ) + logging.debug(f"Docker containers for project {self.project_name} are {res_containers}") + res_volumes = subprocess.check_output( + f"docker volume ls --filter name='{self.project_name}*'", + shell=True, + universal_newlines=True, + ) + logging.debug(f"Docker volumes for project {self.project_name} are {res_volumes}") + def cleanup(self): + logging.debug('Cleanup called') + self.print_all_docker_pieces() + if ( os.environ and "DISABLE_CLEANUP" in os.environ @@ -549,12 +576,16 @@ class ClickHouseCluster: # Just in case kill unstopped containers from previous launch try: unstopped_containers = self.get_running_containers() - if unstopped_containers: + logging.debug(f"Unstopped containers: {unstopped_containers}") + if len(unstopped_containers): logging.debug( f"Trying to kill unstopped containers: {unstopped_containers}" ) for id in unstopped_containers: - run_and_check(f"docker kill {id}", shell=True, nothrow=True) + try: + run_and_check(f"docker kill {id}", shell=True, nothrow=True) + except: + pass run_and_check(f"docker rm {id}", shell=True, nothrow=True) unstopped_containers = self.get_running_containers() if unstopped_containers: @@ -563,26 +594,33 @@ class ClickHouseCluster: logging.debug(f"Unstopped containers killed.") else: logging.debug(f"No running containers for project: {self.project_name}") + except Exception as ex: + logging.debug(f"Got exception removing containers {str(ex)}") + + # # Just in case remove unused networks + try: + logging.debug("Trying to prune unused networks...") + + list_networks = subprocess.check_output( + f"docker network ls -q --filter name='{self.project_name}'", + shell=True, + universal_newlines=True, + ).splitlines() + if list_networks: + logging.debug(f"Trying to remove networks: {list_networks}") + subprocess.check_call(f"docker network rm {' '.join(list_networks)}", shell=True) + logging.debug(f"Networks removed: {list_networks}") except: pass - # # Just in case remove unused networks - # try: - # logging.debug("Trying to prune unused networks...") - - # run_and_check(['docker', 'network', 'prune', '-f']) - # logging.debug("Networks pruned") - # except: - # pass - # Remove unused images - # try: - # logging.debug("Trying to prune unused images...") + try: + logging.debug("Trying to prune unused images...") - # run_and_check(['docker', 'image', 'prune', '-f']) - # logging.debug("Images pruned") - # except: - # pass + run_and_check(['docker', 'image', 'prune', '-f']) + logging.debug("Images pruned") + except: + pass # Remove unused volumes try: @@ -626,7 +664,7 @@ class ClickHouseCluster: shell=True, ) containers = dict( - line.split(":", 1) for line in containers.decode("utf8").splitlines() + line.split(":", 1) for line in containers.splitlines() ) return containers @@ -1767,7 +1805,7 @@ class ClickHouseCluster: errors += [str(ex)] time.sleep(0.5) - run_and_check(["docker-compose", "ps", "--services", "--all"]) + run_and_check(["docker", "ps", "--all"]) logging.error("Can't connect to MySQL:{}".format(errors)) raise Exception("Cannot wait MySQL container") @@ -1789,7 +1827,7 @@ class ClickHouseCluster: logging.debug("Can't connect to MySQL 8 " + str(ex)) time.sleep(0.5) - run_and_check(["docker-compose", "ps", "--services", "--all"]) + run_and_check(["docker", "ps", "--all"]) raise Exception("Cannot wait MySQL 8 container") def wait_mysql_cluster_to_start(self, timeout=180): @@ -1814,7 +1852,7 @@ class ClickHouseCluster: errors += [str(ex)] time.sleep(0.5) - run_and_check(["docker-compose", "ps", "--services", "--all"]) + run_and_check(["docker", "ps", "--all"]) logging.error("Can't connect to MySQL:{}".format(errors)) raise Exception("Cannot wait MySQL container") @@ -2087,7 +2125,7 @@ class ClickHouseCluster: logging.debug("Can't connect to MeiliSearch " + str(ex)) time.sleep(1) - def wait_minio_to_start(self, timeout=180, secure=False): + def wait_minio_to_start(self, timeout=10, secure=False): self.minio_ip = self.get_instance_ip(self.minio_host) self.minio_redirect_ip = self.get_instance_ip(self.minio_redirect_host) @@ -2129,6 +2167,14 @@ class ClickHouseCluster: logging.debug("Can't connect to Minio: %s", str(ex)) time.sleep(1) + try: + with open(os.path.join(self.minio_dir, "docker.log"), "w+") as f: + subprocess.check_call( # STYLE_CHECK_ALLOW_SUBPROCESS_CHECK_CALL + self.base_minio_cmd + ["logs"], stdout=f + ) + except Exception as e: + logging.debug("Unable to get logs from docker.") + raise Exception("Can't wait Minio to start") def wait_azurite_to_start(self, timeout=180): @@ -2199,15 +2245,13 @@ class ClickHouseCluster: raise Exception("Can't wait Cassandra to start") - def start(self, destroy_dirs=True): + def start(self): pytest_xdist_logging_to_separate_files.setup() logging.info("Running tests in {}".format(self.base_path)) - logging.debug( - "Cluster start called. is_up={}, destroy_dirs={}".format( - self.is_up, destroy_dirs - ) - ) + logging.debug(f"Cluster start called. is_up={self.is_up}") + self.print_all_docker_pieces() + if self.is_up: return @@ -2217,23 +2261,9 @@ class ClickHouseCluster: logging.warning("Cleanup failed:{e}") try: - # clickhouse_pull_cmd = self.base_cmd + ['pull'] - # print(f"Pulling images for {self.base_cmd}") - # retry_exception(10, 5, subprocess_check_call, Exception, clickhouse_pull_cmd) - - if destroy_dirs and p.exists(self.instances_dir): - logging.debug(f"Removing instances dir {self.instances_dir}") - shutil.rmtree(self.instances_dir) - for instance in list(self.instances.values()): - logging.debug( - ( - "Setup directory for instance: {} destroy_dirs: {}".format( - instance.name, destroy_dirs - ) - ) - ) - instance.create_dir(destroy_dir=destroy_dirs) + logging.debug(f"Setup directory for instance: {instance.name}") + instance.create_dir() _create_env_file(os.path.join(self.env_file), self.env_variables) self.docker_client = docker.DockerClient( @@ -2627,13 +2657,9 @@ class ClickHouseCluster: def pause_container(self, instance_name): subprocess_check_call(self.base_cmd + ["pause", instance_name]) - # subprocess_check_call(self.base_cmd + ['kill', '-s SIGSTOP', instance_name]) - def unpause_container(self, instance_name): subprocess_check_call(self.base_cmd + ["unpause", instance_name]) - # subprocess_check_call(self.base_cmd + ['kill', '-s SIGCONT', instance_name]) - def open_bash_shell(self, instance_name): os.system(" ".join(self.base_cmd + ["exec", instance_name, "/bin/bash"])) @@ -3687,14 +3713,9 @@ class ClickHouseInstance: ["bash", "-c", f"sed -i 's/{replace}/{replacement}/g' {path_to_config}"] ) - def create_dir(self, destroy_dir=True): + def create_dir(self): """Create the instance directory and all the needed files there.""" - if destroy_dir: - self.destroy_dir() - elif p.exists(self.path): - return - os.makedirs(self.path) instance_config_dir = p.abspath(p.join(self.path, "configs")) @@ -3953,10 +3974,6 @@ class ClickHouseInstance: ) ) - def destroy_dir(self): - if p.exists(self.path): - shutil.rmtree(self.path) - def wait_for_path_exists(self, path, seconds): while seconds > 0: seconds -= 1 diff --git a/tests/integration/pytest.ini b/tests/integration/pytest.ini index 2a57ea5a229..772c96f7361 100644 --- a/tests/integration/pytest.ini +++ b/tests/integration/pytest.ini @@ -1,5 +1,5 @@ [pytest] -python_files = test*.py +python_files = test_*/test*.py norecursedirs = _instances* timeout = 900 junit_duration_report = call diff --git a/tests/integration/runner b/tests/integration/runner index cd07875ad1d..f7d9387f72b 100755 --- a/tests/integration/runner +++ b/tests/integration/runner @@ -383,6 +383,7 @@ if __name__ == "__main__": --volume=/run:/run/host:ro \ {dockerd_internal_volume} -e DOCKER_CLIENT_TIMEOUT=300 -e COMPOSE_HTTP_TIMEOUT=600 \ -e XTABLES_LOCKFILE=/run/host/xtables.lock \ + -e PYTHONUNBUFFERED=1 \ {env_tags} {env_cleanup} -e PYTEST_OPTS='{parallel} {opts} {tests_list} -vvv' {img} {command}".format( net=net, tty=tty, diff --git a/tests/integration/test_allowed_client_hosts/configs/users.d/network.xml b/tests/integration/test_allowed_client_hosts/configs/users.d/network.xml index 1207e2703de..6c55d61481a 100644 --- a/tests/integration/test_allowed_client_hosts/configs/users.d/network.xml +++ b/tests/integration/test_allowed_client_hosts/configs/users.d/network.xml @@ -26,9 +26,6 @@ 127.0.0.1 clientA1.com clientA3.com - clientB\d+\.ru - clientC\d+\.ru$ - ^clientD\d+\.ru$ diff --git a/tests/integration/test_allowed_client_hosts/test.py b/tests/integration/test_allowed_client_hosts/test.py index db2ba464b38..dda3439be14 100644 --- a/tests/integration/test_allowed_client_hosts/test.py +++ b/tests/integration/test_allowed_client_hosts/test.py @@ -1,4 +1,5 @@ import pytest +import logging from helpers.cluster import ClickHouseCluster cluster = ClickHouseCluster(__file__) @@ -7,16 +8,6 @@ server = cluster.add_instance("server", user_configs=["configs/users.d/network.x clientA1 = cluster.add_instance("clientA1", hostname="clientA1.com") clientA2 = cluster.add_instance("clientA2", hostname="clientA2.com") clientA3 = cluster.add_instance("clientA3", hostname="clientA3.com") -clientB1 = cluster.add_instance("clientB1", hostname="clientB001.ru") -clientB2 = cluster.add_instance("clientB2", hostname="clientB002.ru") -clientB3 = cluster.add_instance("clientB3", hostname="xxx.clientB003.rutracker.com") -clientC1 = cluster.add_instance("clientC1", hostname="clientC01.ru") -clientC2 = cluster.add_instance("clientC2", hostname="xxx.clientC02.ru") -clientC3 = cluster.add_instance("clientC3", hostname="xxx.clientC03.rutracker.com") -clientD1 = cluster.add_instance("clientD1", hostname="clientD0001.ru") -clientD2 = cluster.add_instance("clientD2", hostname="xxx.clientD0002.ru") -clientD3 = cluster.add_instance("clientD3", hostname="clientD0003.ru") - def check_clickhouse_is_ok(client_node, server_node): assert ( @@ -29,6 +20,13 @@ def check_clickhouse_is_ok(client_node, server_node): def query_from_one_node_to_another(client_node, server_node, query): check_clickhouse_is_ok(client_node, server_node) + res1 = client_node.exec_in_container(["ip", "address", "show"]) + res2 = client_node.exec_in_container(["host", "clientA1.com"]) + res3 = client_node.exec_in_container(["host", "clientA2.com"]) + res4 = client_node.exec_in_container(["host", "clientA3.com"]) + + logging.debug(f"IP: {res1}, A1 {res2}, A2 {res3}, A3 {res4}") + return client_node.exec_in_container( [ "bash", @@ -55,6 +53,13 @@ def setup_nodes(): ) query(server, "INSERT INTO test_allowed_client_hosts VALUES (5)") + s = query(server, "SELECT fqdn(), hostName()") + a1 = query(clientA1, "SELECT fqdn(), hostName()") + a2 = query(clientA2, "SELECT fqdn(), hostName()") + a3 = query(clientA3, "SELECT fqdn(), hostName()") + + logging.debug(f"s:{s}, a1:{a1}, a2:{a2}, a3:{a3}") + yield cluster finally: @@ -63,7 +68,6 @@ def setup_nodes(): def test_allowed_host(): expected_to_pass = [clientA1, clientA3] - expected_to_fail = [clientA2] # Reverse DNS lookup currently isn't working as expected in this test. # For example, it gives something like "vitbartestallowedclienthosts_clientB1_1.vitbartestallowedclienthosts_default" instead of "clientB001.ru". @@ -79,6 +83,9 @@ def test_allowed_host(): == "5\n" ) +def test_denied_host(): + expected_to_fail = [clientA2] + for client_node in expected_to_fail: with pytest.raises(Exception, match=r"default: Authentication failed"): query_from_one_node_to_another( diff --git a/tests/integration/test_allowed_url_from_config/test.py b/tests/integration/test_allowed_url_from_config/test.py index da9d4404c82..3106cf12702 100644 --- a/tests/integration/test_allowed_url_from_config/test.py +++ b/tests/integration/test_allowed_url_from_config/test.py @@ -49,6 +49,8 @@ def test_config_with_hosts(start_cluster): assert "not allowed" in node1.query_and_get_error( "CREATE TABLE table_test_1_4 (word String) Engine=URL('https://yandex2.ru', CSV)" ) + node1.query("DROP TABLE table_test_1_1") + node1.query("DROP TABLE table_test_1_2") def test_config_with_only_primary_hosts(start_cluster): @@ -86,6 +88,11 @@ def test_config_with_only_primary_hosts(start_cluster): "CREATE TABLE table_test_2_6 (word String) Engine=URL('https://yandex2.ru', CSV)" ) + node2.query("DROP TABLE table_test_2_1") + node2.query("DROP TABLE table_test_2_2") + node2.query("DROP TABLE table_test_2_3") + node2.query("DROP TABLE table_test_2_4") + def test_config_with_only_regexp_hosts(start_cluster): assert ( @@ -106,6 +113,8 @@ def test_config_with_only_regexp_hosts(start_cluster): assert "not allowed" in node3.query_and_get_error( "CREATE TABLE table_test_3_4 (word String) Engine=URL('https://yandex2.ru', CSV)" ) + node3.query("DROP TABLE table_test_3_1") + node3.query("DROP TABLE table_test_3_2") def test_config_without_allowed_hosts_section(start_cluster): @@ -139,6 +148,11 @@ def test_config_without_allowed_hosts_section(start_cluster): ) == "" ) + node4.query("DROP TABLE table_test_4_1") + node4.query("DROP TABLE table_test_4_2") + node4.query("DROP TABLE table_test_4_3") + node4.query("DROP TABLE table_test_4_4") + node4.query("DROP TABLE table_test_4_5") def test_config_without_allowed_hosts(start_cluster): @@ -267,6 +281,7 @@ def test_redirect(start_cluster): assert "not allowed" in node7.query_and_get_error( "SET max_http_get_redirects=1; SELECT * from table_test_7_1" ) + node7.query("DROP TABLE table_test_7_1") def test_HDFS(start_cluster): diff --git a/tests/integration/test_dictionaries_redis/test.py b/tests/integration/test_dictionaries_redis/test.py index bc8170ab08d..e7b5fcb7b80 100644 --- a/tests/integration/test_dictionaries_redis/test.py +++ b/tests/integration/test_dictionaries_redis/test.py @@ -1,14 +1,14 @@ import os import shutil import pytest +import logging from helpers.cluster import ClickHouseCluster from helpers.dictionary import Field, Row, Dictionary, DictionaryStructure, Layout from helpers.external_sources import SourceRedis -cluster = None +cluster = ClickHouseCluster(__file__) SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) dict_configs_path = os.path.join(SCRIPT_DIR, "configs/dictionaries") -node = None KEY_FIELDS = { "simple": [Field("KeyField", "UInt64", is_key=True, default_value_for_get=9999999)], @@ -70,8 +70,6 @@ DICTIONARIES = [] def get_dict(source, layout, fields, suffix_name=""): - global dict_configs_path - structure = DictionaryStructure(layout, fields) dict_name = source.name + "_" + layout.name + "_" + suffix_name dict_path = os.path.join(dict_configs_path, dict_name + ".xml") @@ -82,13 +80,9 @@ def get_dict(source, layout, fields, suffix_name=""): return dictionary -def setup_module(module): +def generate_dict_configs(): global DICTIONARIES global cluster - global node - global dict_configs_path - - cluster = ClickHouseCluster(__file__) if os.path.exists(dict_configs_path): shutil.rmtree(dict_configs_path) @@ -126,9 +120,7 @@ def setup_module(module): for source in sources: for layout in LAYOUTS: if not source.compatible_with_layout(layout): - print( - "Source", source.name, "incompatible with layout", layout.name - ) + logging.debug(f"Source {source.name} incompatible with layout {layout.name}") continue fields = KEY_FIELDS[layout.layout_type] + [field] @@ -137,7 +129,9 @@ def setup_module(module): main_configs = [] dictionaries = [] for fname in os.listdir(dict_configs_path): - dictionaries.append(os.path.join(dict_configs_path, fname)) + path = os.path.join(dict_configs_path, fname) + logging.debug(f"Found dictionary {path}") + dictionaries.append(path) node = cluster.add_instance( "node", main_configs=main_configs, dictionaries=dictionaries, with_redis=True @@ -147,13 +141,15 @@ def setup_module(module): @pytest.fixture(scope="module", autouse=True) def started_cluster(): try: + generate_dict_configs() + cluster.start() assert len(FIELDS) == len(VALUES) for dicts in DICTIONARIES: for dictionary in dicts: - print("Preparing", dictionary.name) + logging.debug(f"Preparing {dictionary.name}") dictionary.prepare_source(cluster) - print("Prepared") + logging.debug(f"Prepared {dictionary.name}") yield cluster @@ -161,14 +157,19 @@ def started_cluster(): cluster.shutdown() -@pytest.mark.parametrize("id", list(range(len(FIELDS)))) +def get_entity_id(entity): + return FIELDS[entity].name + + +@pytest.mark.parametrize("id", list(range(len(FIELDS))), ids=get_entity_id) def test_redis_dictionaries(started_cluster, id): - print("id:", id) + logging.debug(f"Run test with id: {id}") dicts = DICTIONARIES[id] values = VALUES[id] field = FIELDS[id] + node = started_cluster.instances["node"] node.query("system reload dictionaries") for dct in dicts: @@ -193,10 +194,9 @@ def test_redis_dictionaries(started_cluster, id): for query in dct.get_select_get_or_default_queries(field, row): queries_with_answers.append((query, field.default_value_for_get)) - node.query("system reload dictionary {}".format(dct.name)) + node.query(f"system reload dictionary {dct.name}") for query, answer in queries_with_answers: - print(query) assert node.query(query) == str(answer) + "\n" # Checks, that dictionaries can be reloaded. diff --git a/tests/integration/test_merge_tree_azure_blob_storage/test.py b/tests/integration/test_merge_tree_azure_blob_storage/test.py index bc549210b39..668432d4a69 100644 --- a/tests/integration/test_merge_tree_azure_blob_storage/test.py +++ b/tests/integration/test_merge_tree_azure_blob_storage/test.py @@ -3,16 +3,11 @@ import time import os import pytest -from helpers.cluster import ClickHouseCluster, get_instances_dir +from helpers.cluster import ClickHouseCluster from helpers.utility import generate_values, replace_config, SafeThread SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) -CONFIG_PATH = os.path.join( - SCRIPT_DIR, - "./{}/node/configs/config.d/storage_conf.xml".format(get_instances_dir()), -) - NODE_NAME = "node" TABLE_NAME = "blob_storage_table" AZURE_BLOB_STORAGE_DISK = "blob_storage_disk" @@ -51,7 +46,7 @@ def azure_query(node, query, try_num=3): return node.query(query) except Exception as ex: retriable_errors = [ - "DB::Exception: Azure::Core::Http::TransportException: Connection was closed by the server while trying to read a response", + "DB::Exception: Azure::Core::Http::TransportException: Connection was closed by the server while trying to read a response" ] retry = False for error in retriable_errors: @@ -160,13 +155,7 @@ def test_inserts_selects(cluster): ) -@pytest.mark.parametrize( - "merge_vertical", - [ - (True), - (False), - ], -) +@pytest.mark.parametrize("merge_vertical", [(True), (False)]) def test_insert_same_partition_and_merge(cluster, merge_vertical): settings = {} if merge_vertical: @@ -498,6 +487,12 @@ def test_freeze_unfreeze(cluster): def test_apply_new_settings(cluster): node = cluster.instances[NODE_NAME] create_table(node, TABLE_NAME) + config_path = os.path.join( + SCRIPT_DIR, + "./{}/node/configs/config.d/storage_conf.xml".format( + cluster.instances_dir_name + ), + ) azure_query( node, f"INSERT INTO {TABLE_NAME} VALUES {generate_values('2020-01-03', 4096)}" @@ -505,7 +500,7 @@ def test_apply_new_settings(cluster): # Force multi-part upload mode. replace_config( - CONFIG_PATH, + config_path, "33554432", "4096", ) @@ -522,10 +517,16 @@ def test_apply_new_settings(cluster): def test_restart_during_load(cluster): node = cluster.instances[NODE_NAME] create_table(node, TABLE_NAME) + config_path = os.path.join( + SCRIPT_DIR, + "./{}/node/configs/config.d/storage_conf.xml".format( + cluster.instances_dir_name + ), + ) # Force multi-part upload mode. replace_config( - CONFIG_PATH, "false", "" + config_path, "false", "" ) azure_query( diff --git a/tests/integration/test_merge_tree_s3/test.py b/tests/integration/test_merge_tree_s3/test.py index f5a9bf153b7..4c0ea40f637 100644 --- a/tests/integration/test_merge_tree_s3/test.py +++ b/tests/integration/test_merge_tree_s3/test.py @@ -3,15 +3,11 @@ import time import os import pytest -from helpers.cluster import ClickHouseCluster, get_instances_dir +from helpers.cluster import ClickHouseCluster from helpers.utility import generate_values, replace_config, SafeThread SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) -CONFIG_PATH = os.path.join( - SCRIPT_DIR, - "./{}/node/configs/config.d/storage_conf.xml".format(get_instances_dir()), -) @pytest.fixture(scope="module") @@ -562,6 +558,13 @@ def test_s3_disk_apply_new_settings(cluster, node_name): node = cluster.instances[node_name] create_table(node, "s3_test") + config_path = os.path.join( + SCRIPT_DIR, + "./{}/node/configs/config.d/storage_conf.xml".format( + cluster.instances_dir_name + ), + ) + def get_s3_requests(): node.query("SYSTEM FLUSH LOGS") return int( @@ -578,7 +581,7 @@ def test_s3_disk_apply_new_settings(cluster, node_name): # Force multi-part upload mode. replace_config( - CONFIG_PATH, + config_path, "33554432", "0", ) diff --git a/tests/integration/test_merge_tree_s3_restore/test.py b/tests/integration/test_merge_tree_s3_restore/test.py index f4acc4ac91e..0652c31951d 100644 --- a/tests/integration/test_merge_tree_s3_restore/test.py +++ b/tests/integration/test_merge_tree_s3_restore/test.py @@ -5,28 +5,22 @@ import string import time import pytest -from helpers.cluster import ClickHouseCluster, get_instances_dir +from helpers.cluster import ClickHouseCluster SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) -NOT_RESTORABLE_CONFIG_PATH = os.path.join( - SCRIPT_DIR, - "./{}/node_not_restorable/configs/config.d/storage_conf_not_restorable.xml".format( - get_instances_dir() - ), -) COMMON_CONFIGS = [ "configs/config.d/bg_processing_pool_conf.xml", "configs/config.d/clusters.xml", ] -def replace_config(old, new): - config = open(NOT_RESTORABLE_CONFIG_PATH, "r") +def replace_config(path, old, new): + config = open(path, "r") config_lines = config.readlines() config.close() config_lines = [line.replace(old, new) for line in config_lines] - config = open(NOT_RESTORABLE_CONFIG_PATH, "w") + config = open(path, "w") config.writelines(config_lines) config.close() @@ -507,6 +501,12 @@ def test_restore_mutations(cluster, db_atomic): def test_migrate_to_restorable_schema(cluster): db_atomic = True node = cluster.instances["node_not_restorable"] + config_path = os.path.join( + SCRIPT_DIR, + "./{}/node_not_restorable/configs/config.d/storage_conf_not_restorable.xml".format( + cluster.instances_dir_name + ), + ) create_table(node, "test", db_atomic=db_atomic) uuid = get_table_uuid(node, db_atomic, "test") @@ -525,7 +525,9 @@ def test_migrate_to_restorable_schema(cluster): ) replace_config( - "false", "true" + config_path, + "false", + "true", ) node.restart_clickhouse() diff --git a/tests/integration/test_reload_max_table_size_to_drop/test.py b/tests/integration/test_reload_max_table_size_to_drop/test.py index da7dba12fa0..90e60e5cfa4 100644 --- a/tests/integration/test_reload_max_table_size_to_drop/test.py +++ b/tests/integration/test_reload_max_table_size_to_drop/test.py @@ -2,18 +2,13 @@ import os import time import pytest -from helpers.cluster import ClickHouseCluster, get_instances_dir +from helpers.cluster import ClickHouseCluster cluster = ClickHouseCluster(__file__) node = cluster.add_instance("node", main_configs=["configs/max_table_size_to_drop.xml"]) SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) -CONFIG_PATH = os.path.join( - SCRIPT_DIR, - "./{}/node/configs/config.d/max_table_size_to_drop.xml".format(get_instances_dir()), -) - @pytest.fixture(scope="module") def start_cluster(): @@ -29,6 +24,12 @@ def start_cluster(): def test_reload_max_table_size_to_drop(start_cluster): node.query("INSERT INTO test VALUES (now(), 0)") + config_path = os.path.join( + SCRIPT_DIR, + "./{}/node/configs/config.d/max_table_size_to_drop.xml".format( + start_cluster.instances_dir_name + ), + ) time.sleep(5) # wait for data part commit @@ -37,14 +38,14 @@ def test_reload_max_table_size_to_drop(start_cluster): assert out == "" assert err != "" - config = open(CONFIG_PATH, "r") + config = open(config_path, "r") config_lines = config.readlines() config.close() config_lines = [ line.replace("1", "1000000") for line in config_lines ] - config = open(CONFIG_PATH, "w") + config = open(config_path, "w") config.writelines(config_lines) config.close() diff --git a/tests/integration/test_replicated_merge_tree_s3_restore/test.py b/tests/integration/test_replicated_merge_tree_s3_restore/test.py index d743dedbdde..fc13c8a1184 100644 --- a/tests/integration/test_replicated_merge_tree_s3_restore/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_restore/test.py @@ -5,7 +5,7 @@ import string import time import pytest -from helpers.cluster import ClickHouseCluster, get_instances_dir +from helpers.cluster import ClickHouseCluster COMMON_CONFIGS = ["configs/config.d/clusters.xml"] diff --git a/tests/integration/test_storage_s3/test.py b/tests/integration/test_storage_s3/test.py index 5dd09ddd362..7d22cb24a5c 100644 --- a/tests/integration/test_storage_s3/test.py +++ b/tests/integration/test_storage_s3/test.py @@ -9,7 +9,7 @@ import time import helpers.client import pytest -from helpers.cluster import ClickHouseCluster, ClickHouseInstance, get_instances_dir +from helpers.cluster import ClickHouseCluster, ClickHouseInstance from helpers.network import PartitionManager from helpers.test_tools import exec_query_with_retry @@ -17,11 +17,6 @@ MINIO_INTERNAL_PORT = 9001 SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) -CONFIG_PATH = os.path.join( - SCRIPT_DIR, "./{}/dummy/configs/config.d/defaultS3.xml".format(get_instances_dir()) -) - - # Creates S3 bucket for tests and allows anonymous read-write access to it. def prepare_s3_bucket(started_cluster): # Allows read-write access for bucket without authorization. @@ -724,17 +719,24 @@ def run_s3_mocks(started_cluster): logging.info("S3 mocks started") -def replace_config(old, new): - config = open(CONFIG_PATH, "r") +def replace_config(path, old, new): + config = open(path, "r") config_lines = config.readlines() config.close() config_lines = [line.replace(old, new) for line in config_lines] - config = open(CONFIG_PATH, "w") + config = open(path, "w") config.writelines(config_lines) config.close() def test_custom_auth_headers(started_cluster): + config_path = os.path.join( + SCRIPT_DIR, + "./{}/dummy/configs/config.d/defaultS3.xml".format( + started_cluster.instances_dir_name + ), + ) + table_format = "column1 UInt32, column2 UInt32, column3 UInt32" filename = "test.csv" get_query = "select * from s3('http://resolver:8080/{bucket}/{file}', 'CSV', '{table_format}')".format( @@ -758,6 +760,7 @@ def test_custom_auth_headers(started_cluster): assert run_query(instance, "SELECT * FROM test") == "1\t2\t3\n" replace_config( + config_path, "
Authorization: Bearer TOKEN", "
Authorization: Bearer INVALID_TOKEN", ) @@ -765,6 +768,7 @@ def test_custom_auth_headers(started_cluster): ret, err = instance.query_and_get_answer_with_error("SELECT * FROM test") assert ret == "" and err != "" replace_config( + config_path, "
Authorization: Bearer INVALID_TOKEN", "
Authorization: Bearer TOKEN", ) @@ -805,10 +809,7 @@ def test_infinite_redirect(started_cluster): @pytest.mark.parametrize( "extension,method", - [ - pytest.param("bin", "gzip", id="bin"), - pytest.param("gz", "auto", id="gz"), - ], + [pytest.param("bin", "gzip", id="bin"), pytest.param("gz", "auto", id="gz")], ) def test_storage_s3_get_gzip(started_cluster, extension, method): bucket = started_cluster.minio_bucket From e898f65cc523b8b06b1e903ea113818419ca8b68 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Thu, 7 Jul 2022 20:42:41 +0000 Subject: [PATCH 021/672] black --- tests/integration/helpers/cluster.py | 28 ++++++++++--------- .../test_allowed_client_hosts/test.py | 2 ++ .../test_dictionaries_redis/test.py | 4 ++- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index f5c546ff264..32a4e663975 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -539,30 +539,34 @@ class ClickHouseCluster: logging.debug(f"Removed :{self.instances_dir}") os.mkdir(self.instances_dir) - def print_all_docker_pieces(self): res_networks = subprocess.check_output( f"docker network ls --filter name='{self.project_name}*'", shell=True, universal_newlines=True, ) - logging.debug(f"Docker networks for project {self.project_name} are {res_networks}") + logging.debug( + f"Docker networks for project {self.project_name} are {res_networks}" + ) res_containers = subprocess.check_output( f"docker container ls -a --filter name='{self.project_name}*'", shell=True, universal_newlines=True, ) - logging.debug(f"Docker containers for project {self.project_name} are {res_containers}") + logging.debug( + f"Docker containers for project {self.project_name} are {res_containers}" + ) res_volumes = subprocess.check_output( f"docker volume ls --filter name='{self.project_name}*'", shell=True, universal_newlines=True, ) - logging.debug(f"Docker volumes for project {self.project_name} are {res_volumes}") - + logging.debug( + f"Docker volumes for project {self.project_name} are {res_volumes}" + ) def cleanup(self): - logging.debug('Cleanup called') + logging.debug("Cleanup called") self.print_all_docker_pieces() if ( @@ -608,7 +612,7 @@ class ClickHouseCluster: ).splitlines() if list_networks: logging.debug(f"Trying to remove networks: {list_networks}") - subprocess.check_call(f"docker network rm {' '.join(list_networks)}", shell=True) + run_and_check(f"docker network rm {' '.join(list_networks)}") logging.debug(f"Networks removed: {list_networks}") except: pass @@ -617,7 +621,7 @@ class ClickHouseCluster: try: logging.debug("Trying to prune unused images...") - run_and_check(['docker', 'image', 'prune', '-f']) + run_and_check(["docker", "image", "prune", "-f"]) logging.debug("Images pruned") except: pass @@ -663,9 +667,7 @@ class ClickHouseCluster: f"docker container list --all --filter name='{filter_name}' --format '{format}'", shell=True, ) - containers = dict( - line.split(":", 1) for line in containers.splitlines() - ) + containers = dict(line.split(":", 1) for line in containers.splitlines()) return containers def copy_file_from_container_to_container( @@ -3551,14 +3553,14 @@ class ClickHouseInstance: "bash", "-c", "echo 'ATTACH DATABASE system ENGINE=Ordinary' > /var/lib/clickhouse/metadata/system.sql", - ], + ] ) self.exec_in_container( [ "bash", "-c", "echo 'ATTACH DATABASE system ENGINE=Ordinary' > /var/lib/clickhouse/metadata/default.sql", - ], + ] ) self.exec_in_container( ["bash", "-c", "{} --daemon".format(self.clickhouse_start_command)], diff --git a/tests/integration/test_allowed_client_hosts/test.py b/tests/integration/test_allowed_client_hosts/test.py index dda3439be14..1fc9d0432cb 100644 --- a/tests/integration/test_allowed_client_hosts/test.py +++ b/tests/integration/test_allowed_client_hosts/test.py @@ -9,6 +9,7 @@ clientA1 = cluster.add_instance("clientA1", hostname="clientA1.com") clientA2 = cluster.add_instance("clientA2", hostname="clientA2.com") clientA3 = cluster.add_instance("clientA3", hostname="clientA3.com") + def check_clickhouse_is_ok(client_node, server_node): assert ( client_node.exec_in_container( @@ -83,6 +84,7 @@ def test_allowed_host(): == "5\n" ) + def test_denied_host(): expected_to_fail = [clientA2] diff --git a/tests/integration/test_dictionaries_redis/test.py b/tests/integration/test_dictionaries_redis/test.py index e7b5fcb7b80..c2dc73db782 100644 --- a/tests/integration/test_dictionaries_redis/test.py +++ b/tests/integration/test_dictionaries_redis/test.py @@ -120,7 +120,9 @@ def generate_dict_configs(): for source in sources: for layout in LAYOUTS: if not source.compatible_with_layout(layout): - logging.debug(f"Source {source.name} incompatible with layout {layout.name}") + logging.debug( + f"Source {source.name} incompatible with layout {layout.name}" + ) continue fields = KEY_FIELDS[layout.layout_type] + [field] From 63fc95903c3bc35e066fedf4ee6d51c59ba050a1 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 7 Jul 2022 23:59:15 +0200 Subject: [PATCH 022/672] fix tests --- src/Interpreters/InterpreterCreateQuery.cpp | 5 +++++ tests/config/users.d/database_replicated.xml | 1 - .../0_stateless/01111_create_drop_replicated_db_stress.sh | 2 +- tests/queries/0_stateless/01152_cross_replication.sql | 2 ++ .../0_stateless/01175_distributed_ddl_output_mode_long.sh | 1 - .../0_stateless/02232_allow_only_replicated_engine.sh | 6 +++--- .../02400_create_table_on_cluster_normalization.reference | 2 +- .../02400_create_table_on_cluster_normalization.sql | 4 +--- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index b29ac5dc6d4..a8f8a3f3e27 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -1517,6 +1517,11 @@ void InterpreterCreateQuery::prepareOnClusterQuery(ASTCreateQuery & create, Cont if (cluster->maybeCrossReplication()) { + auto on_cluster_version = local_context->getSettingsRef().distributed_ddl_entry_format_version; + if (DDLLogEntry::NORMALIZE_CREATE_ON_INITIATOR_VERSION <= on_cluster_version) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Value {} of setting distributed_ddl_entry_format_version " + "is incompatible with cross-replication", on_cluster_version); + /// Check that {uuid} macro is not used in zookeeper_path for ReplicatedMergeTree. /// Otherwise replicas will generate different paths. if (!create.storage) diff --git a/tests/config/users.d/database_replicated.xml b/tests/config/users.d/database_replicated.xml index 279591494c1..c833ce3e343 100644 --- a/tests/config/users.d/database_replicated.xml +++ b/tests/config/users.d/database_replicated.xml @@ -6,7 +6,6 @@ 120 120 1 - 2 diff --git a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh index 942958bf461..9a3f97e81c0 100755 --- a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh +++ b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh @@ -50,7 +50,7 @@ function create_table() database=$($CLICKHOUSE_CLIENT -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}%' order by rand() limit 1") if [ -z "$database" ]; then return; fi $CLICKHOUSE_CLIENT --distributed_ddl_task_timeout=0 -q \ - "create table $database.rmt_$RANDOM (n int) engine=ReplicatedMergeTree order by tuple() -- suppress CLICKHOUSE_TEST_ZOOKEEPER_PREFIX" \ + "create table $database.rmt_$RANDOM (n int) engine=ReplicatedMergeTree order by tuple() -- suppress $CLICKHOUSE_TEST_ZOOKEEPER_PREFIX" \ 2>&1| grep -Fa "Exception: " | grep -Fv "Macro 'uuid' and empty arguments" | grep -Fv "Cannot enqueue query" | grep -Fv "ZooKeeper session expired" | grep -Fv UNKNOWN_DATABASE sleep 0.$RANDOM done diff --git a/tests/queries/0_stateless/01152_cross_replication.sql b/tests/queries/0_stateless/01152_cross_replication.sql index 60b2c34be07..5d013400539 100644 --- a/tests/queries/0_stateless/01152_cross_replication.sql +++ b/tests/queries/0_stateless/01152_cross_replication.sql @@ -8,6 +8,8 @@ DROP TABLE IF EXISTS demo_loan_01568_dist; CREATE DATABASE shard_0; CREATE DATABASE shard_1; +CREATE TABLE demo_loan_01568 ON CLUSTER test_cluster_two_shards_different_databases ( `id` Int64 COMMENT 'id', `date_stat` Date COMMENT 'date of stat', `customer_no` String COMMENT 'customer no', `loan_principal` Float64 COMMENT 'loan principal' ) ENGINE=ReplacingMergeTree() ORDER BY id PARTITION BY toYYYYMM(date_stat); -- { serverError 48 } +SET distributed_ddl_entry_format_version = 2; CREATE TABLE demo_loan_01568 ON CLUSTER test_cluster_two_shards_different_databases ( `id` Int64 COMMENT 'id', `date_stat` Date COMMENT 'date of stat', `customer_no` String COMMENT 'customer no', `loan_principal` Float64 COMMENT 'loan principal' ) ENGINE=ReplacingMergeTree() ORDER BY id PARTITION BY toYYYYMM(date_stat); -- { serverError 371 } SET distributed_ddl_output_mode='throw'; CREATE TABLE shard_0.demo_loan_01568 ON CLUSTER test_cluster_two_shards_different_databases ( `id` Int64 COMMENT 'id', `date_stat` Date COMMENT 'date of stat', `customer_no` String COMMENT 'customer no', `loan_principal` Float64 COMMENT 'loan principal' ) ENGINE=ReplacingMergeTree() ORDER BY id PARTITION BY toYYYYMM(date_stat); diff --git a/tests/queries/0_stateless/01175_distributed_ddl_output_mode_long.sh b/tests/queries/0_stateless/01175_distributed_ddl_output_mode_long.sh index e632841bd01..c18514d0ecc 100755 --- a/tests/queries/0_stateless/01175_distributed_ddl_output_mode_long.sh +++ b/tests/queries/0_stateless/01175_distributed_ddl_output_mode_long.sh @@ -38,7 +38,6 @@ LOG_COMMENT="${CLICKHOUSE_LOG_COMMENT}_$RAND_COMMENT" CLICKHOUSE_CLIENT_WITH_SETTINGS=${CLICKHOUSE_CLIENT/--log_comment ${CLICKHOUSE_LOG_COMMENT}/--log_comment ${LOG_COMMENT}} CLICKHOUSE_CLIENT_WITH_SETTINGS+=" --output_format_parallel_formatting=0 " -CLICKHOUSE_CLIENT_WITH_SETTINGS+=" --distributed_ddl_entry_format_version=2 " CLIENT=${CLICKHOUSE_CLIENT_WITH_SETTINGS} CLIENT+=" --distributed_ddl_task_timeout=$TIMEOUT " diff --git a/tests/queries/0_stateless/02232_allow_only_replicated_engine.sh b/tests/queries/0_stateless/02232_allow_only_replicated_engine.sh index c84b1ab0e55..3ff2dabfa43 100755 --- a/tests/queries/0_stateless/02232_allow_only_replicated_engine.sh +++ b/tests/queries/0_stateless/02232_allow_only_replicated_engine.sh @@ -11,8 +11,8 @@ ${CLICKHOUSE_CLIENT} -q "CREATE USER user_${CLICKHOUSE_DATABASE} settings databa ${CLICKHOUSE_CLIENT} -q "GRANT CREATE TABLE ON ${CLICKHOUSE_DATABASE}_db.* TO user_${CLICKHOUSE_DATABASE}" ${CLICKHOUSE_CLIENT} --allow_experimental_database_replicated=1 --query "CREATE DATABASE ${CLICKHOUSE_DATABASE}_db engine = Replicated('/clickhouse/databases/${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}/${CLICKHOUSE_DATABASE}_db', '{shard}', '{replica}')" ${CLICKHOUSE_CLIENT} --distributed_ddl_output_mode=none --user "user_${CLICKHOUSE_DATABASE}" --query "CREATE TABLE ${CLICKHOUSE_DATABASE}_db.tab_memory (x UInt32) engine = Memory;" -${CLICKHOUSE_CLIENT} --distributed_ddl_output_mode=none --user "user_${CLICKHOUSE_DATABASE}" -n --query "set distributed_ddl_entry_format_version=2; CREATE TABLE ${CLICKHOUSE_DATABASE}_db.tab_mt (x UInt32) engine = MergeTree order by x;" 2>&1 | grep -o "Only tables with a Replicated engine" -${CLICKHOUSE_CLIENT} --distributed_ddl_output_mode=none -n --query "set distributed_ddl_entry_format_version=2; CREATE TABLE ${CLICKHOUSE_DATABASE}_db.tab_mt (x UInt32) engine = MergeTree order by x;" -${CLICKHOUSE_CLIENT} --distributed_ddl_output_mode=none --user "user_${CLICKHOUSE_DATABASE}" -n --query "set distributed_ddl_entry_format_version=2; CREATE TABLE ${CLICKHOUSE_DATABASE}_db.tab_rmt (x UInt32) engine = ReplicatedMergeTree order by x;" +${CLICKHOUSE_CLIENT} --distributed_ddl_output_mode=none --user "user_${CLICKHOUSE_DATABASE}" -n --query "CREATE TABLE ${CLICKHOUSE_DATABASE}_db.tab_mt (x UInt32) engine = MergeTree order by x;" 2>&1 | grep -o "Only tables with a Replicated engine" +${CLICKHOUSE_CLIENT} --distributed_ddl_output_mode=none -n --query "CREATE TABLE ${CLICKHOUSE_DATABASE}_db.tab_mt (x UInt32) engine = MergeTree order by x;" +${CLICKHOUSE_CLIENT} --distributed_ddl_output_mode=none --user "user_${CLICKHOUSE_DATABASE}" -n --query "CREATE TABLE ${CLICKHOUSE_DATABASE}_db.tab_rmt (x UInt32) engine = ReplicatedMergeTree order by x;" ${CLICKHOUSE_CLIENT} --query "DROP DATABASE ${CLICKHOUSE_DATABASE}_db" ${CLICKHOUSE_CLIENT} -q "DROP USER user_${CLICKHOUSE_DATABASE}" diff --git a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference index e32b948cc9d..2c9562f362f 100644 --- a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference +++ b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference @@ -1,3 +1,3 @@ 1 2 3 4 5 6 7 8 -CREATE TABLE default.t_l5ydey\n(\n `c_qv5rv` Int32,\n `c_rutjs4` Int32,\n `c_wmj` Int32,\n `c_m3` String\n)\nENGINE = Distributed(\'test_cluster\', \'default\', \'local_t_l5ydey\', rand()) +CREATE TABLE default.t_l5ydey\n(\n `c_qv5rv` Int32,\n `c_rutjs4` Int32,\n `c_wmj` Int32,\n `c_m3` String\n)\nENGINE = Distributed(\'test_shard_localhost\', \'default\', \'local_t_l5ydey\', rand()) diff --git a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql index 79fd57e034b..2468c064dd2 100644 --- a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql +++ b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql @@ -1,8 +1,6 @@ -- Tags: no-replicated-database -- Tag no-replicated-database: ON CLUSTER is not allowed -set distributed_ddl_entry_format_version=3; - create table local_t_l5ydey on cluster test_shard_localhost ( c_qv5rv INTEGER , c_rutjs4 INTEGER , @@ -12,7 +10,7 @@ create table local_t_l5ydey on cluster test_shard_localhost ( ) engine=ReplicatedMergeTree('/clickhouse/tables/test_{database}/{shard}/local_t_l5ydey', '{replica}'); create table t_l5ydey on cluster test_shard_localhost as local_t_l5ydey - engine=Distributed('test_cluster', currentDatabase(),'local_t_l5ydey', rand()); + engine=Distributed('test_shard_localhost', currentDatabase(),'local_t_l5ydey', rand()); insert into local_t_l5ydey values (1, 2, 3, '4'); insert into t_l5ydey values (5, 6, 7, '8'); From 12f4a489577e00095ed50d00478408bcfd434ad9 Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Fri, 8 Jul 2022 06:48:05 +0000 Subject: [PATCH 023/672] Extend LUT range to 1900..2300 --- src/Common/DateLUTImpl.h | 76 ++++++++++++++++++++++++++++---------- src/Functions/makeDate.cpp | 7 ++-- 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/src/Common/DateLUTImpl.h b/src/Common/DateLUTImpl.h index 4bc9614abcb..209afc9e6f0 100644 --- a/src/Common/DateLUTImpl.h +++ b/src/Common/DateLUTImpl.h @@ -10,20 +10,27 @@ #include -#define DATE_LUT_MIN_YEAR 1925 /// 1925 since wast majority of timezones changed to 15-minute aligned offsets somewhere in 1924 or earlier. -#define DATE_LUT_MAX_YEAR 2283 /// Last supported year (complete) +#define DATE_LUT_MIN_YEAR 1900 /// 1900 since majority of financial organizations consider 1900 as an initial year. +// #define DATE_LUT_MAX_YEAR 2258 /// Last supported year (complete) +#define DATE_LUT_MAX_YEAR 2300 /// Last supported year (complete) #define DATE_LUT_YEARS (1 + DATE_LUT_MAX_YEAR - DATE_LUT_MIN_YEAR) /// Number of years in lookup table -#define DATE_LUT_SIZE 0x20000 +// #define DATE_LUT_SIZE 0x20000 +#define DATE_LUT_SIZE 0x23C1E + #define DATE_LUT_MAX (0xFFFFFFFFU - 86400) #define DATE_LUT_MAX_DAY_NUM 0xFFFF + +#define DAYNUM_OFFSET_EPOCH 25567 + /// Max int value of Date32, DATE LUT cache size minus daynum_offset_epoch -#define DATE_LUT_MAX_EXTEND_DAY_NUM (DATE_LUT_SIZE - 16436) +// #define DATE_LUT_MAX_EXTEND_DAY_NUM (DATE_LUT_SIZE - (Time)DAYNUM_OFFSET_EPOCH) +#define DATE_LUT_MAX_EXTEND_DAY_NUM (DATE_LUT_SIZE - 25567) /// A constant to add to time_t so every supported time point becomes non-negative and still has the same remainder of division by 3600. /// If we treat "remainder of division" operation in the sense of modular arithmetic (not like in C++). -#define DATE_LUT_ADD ((1970 - DATE_LUT_MIN_YEAR) * 366 * 86400) +#define DATE_LUT_ADD ((1970 - DATE_LUT_MIN_YEAR) * 366L * 86400) #if defined(__PPC__) @@ -64,62 +71,88 @@ private: // Same as above but select different function overloads for zero saturation. STRONG_TYPEDEF(UInt32, LUTIndexWithSaturation) + static inline LUTIndex normalizeLUTIndex(UInt32 index) + { + if (index >= DATE_LUT_SIZE) + LUTIndex(DATE_LUT_SIZE - 1); + return LUTIndex{index}; + } + + static inline LUTIndex normalizeLUTIndex(Int64 index) + { + if (index < 0 ) + return LUTIndex(0); + if (index >= DATE_LUT_SIZE) + LUTIndex(DATE_LUT_SIZE - 1); + return LUTIndex{index}; + } + template friend inline LUTIndex operator+(const LUTIndex & index, const T v) { - return LUTIndex{(index.toUnderType() + UInt32(v)) & date_lut_mask}; + return normalizeLUTIndex(index.toUnderType() + UInt32(v)); + //return LUTIndex{(index.toUnderType() + UInt32(v)) & date_lut_mask}; } template friend inline LUTIndex operator+(const T v, const LUTIndex & index) { - return LUTIndex{(v + index.toUnderType()) & date_lut_mask}; + return normalizeLUTIndex(v + index.toUnderType()); + //return LUTIndex{(v + index.toUnderType()) & date_lut_mask}; } friend inline LUTIndex operator+(const LUTIndex & index, const LUTIndex & v) { - return LUTIndex{(index.toUnderType() + v.toUnderType()) & date_lut_mask}; + return normalizeLUTIndex(static_cast(index.toUnderType() + v.toUnderType())); + //return LUTIndex{(index.toUnderType() + v.toUnderType()) & date_lut_mask}; } template friend inline LUTIndex operator-(const LUTIndex & index, const T v) { - return LUTIndex{(index.toUnderType() - UInt32(v)) & date_lut_mask}; + return normalizeLUTIndex(static_cast(index.toUnderType() - UInt32(v))); + //return LUTIndex{(index.toUnderType() - UInt32(v)) & date_lut_mask}; } template friend inline LUTIndex operator-(const T v, const LUTIndex & index) { - return LUTIndex{(v - index.toUnderType()) & date_lut_mask}; + return normalizeLUTIndex(static_cast(v - index.toUnderType())); + //return LUTIndex{(v - index.toUnderType()) & date_lut_mask}; } friend inline LUTIndex operator-(const LUTIndex & index, const LUTIndex & v) { - return LUTIndex{(index.toUnderType() - v.toUnderType()) & date_lut_mask}; + return normalizeLUTIndex(static_cast(index.toUnderType() - v.toUnderType())); + //return LUTIndex{(index.toUnderType() - v.toUnderType()) & date_lut_mask}; } template friend inline LUTIndex operator*(const LUTIndex & index, const T v) { - return LUTIndex{(index.toUnderType() * UInt32(v)) & date_lut_mask}; + return normalizeLUTIndex(index.toUnderType() * UInt32(v)); + // return LUTIndex{(index.toUnderType() * UInt32(v)) /*& date_lut_mask*/}; } template friend inline LUTIndex operator*(const T v, const LUTIndex & index) { - return LUTIndex{(v * index.toUnderType()) & date_lut_mask}; + return normalizeLUTIndex(v * index.toUnderType()); + // return LUTIndex{(v * index.toUnderType()) /*& date_lut_mask*/}; } template friend inline LUTIndex operator/(const LUTIndex & index, const T v) { - return LUTIndex{(index.toUnderType() / UInt32(v)) & date_lut_mask}; + return normalizeLUTIndex(index.toUnderType() / UInt32(v)); + // return LUTIndex{(index.toUnderType() / UInt32(v)) /*& date_lut_mask*/}; } template friend inline LUTIndex operator/(const T v, const LUTIndex & index) { - return LUTIndex{(UInt32(v) / index.toUnderType()) & date_lut_mask}; + return normalizeLUTIndex(UInt32(v) / index.toUnderType()); + // return LUTIndex{(UInt32(v) / index.toUnderType()) /*& date_lut_mask*/}; } public: @@ -170,12 +203,13 @@ public: private: /// Mask is all-ones to allow efficient protection against overflow. - static constexpr UInt32 date_lut_mask = 0x1ffff; - static_assert(date_lut_mask == DATE_LUT_SIZE - 1); + // static constexpr UInt32 date_lut_mask = 0x1ffff; + // static_assert(date_lut_mask == DATE_LUT_SIZE - 1); /// Offset to epoch in days (ExtendedDayNum) of the first day in LUT. /// "epoch" is the Unix Epoch (starts at unix timestamp zero) - static constexpr UInt32 daynum_offset_epoch = 16436; + // static constexpr UInt32 daynum_offset_epoch = DAYNUM_OFFSET_EPOCH; + static constexpr UInt32 daynum_offset_epoch = 25567; static_assert(daynum_offset_epoch == (1970 - DATE_LUT_MIN_YEAR) * 365 + (1970 - DATE_LUT_MIN_YEAR / 4 * 4) / 4); /// Lookup table is indexed by LUTIndex. @@ -232,12 +266,14 @@ private: static inline LUTIndex toLUTIndex(DayNum d) { - return LUTIndex{(d + daynum_offset_epoch) & date_lut_mask}; + return normalizeLUTIndex(d + daynum_offset_epoch); + // return LUTIndex{(d + daynum_offset_epoch) /*& date_lut_mask*/}; } static inline LUTIndex toLUTIndex(ExtendedDayNum d) { - return LUTIndex{static_cast(d + daynum_offset_epoch) & date_lut_mask}; + return normalizeLUTIndex(static_cast(d + daynum_offset_epoch)); + // return LUTIndex{static_cast(d + daynum_offset_epoch) /*& date_lut_mask*/}; } inline LUTIndex toLUTIndex(Time t) const diff --git a/src/Functions/makeDate.cpp b/src/Functions/makeDate.cpp index dbf29322787..d97af38dbea 100644 --- a/src/Functions/makeDate.cpp +++ b/src/Functions/makeDate.cpp @@ -164,9 +164,10 @@ struct MakeDate32Traits using ReturnDataType = DataTypeDate32; using ReturnColumnType = ColumnInt32; - static constexpr auto MIN_YEAR = 1925; - static constexpr auto MAX_YEAR = 2283; - static constexpr auto MAX_DATE = YearMonthDayToSingleInt(MAX_YEAR, 11, 11); + static constexpr auto MIN_YEAR = 1900; //1925; + static constexpr auto MAX_YEAR = 2300; //2283; + // static constexpr auto MAX_DATE = YearMonthDayToSingleInt(MAX_YEAR, 11, 11); + static constexpr auto MAX_DATE = YearMonthDayToSingleInt(MAX_YEAR, 12, 31); }; /// Common implementation for makeDateTime, makeDateTime64 From 6d51dd3ab8ae758675b8cbabe9b8c58e6139c11b Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 8 Jul 2022 11:15:03 +0300 Subject: [PATCH 024/672] Update 02400_create_table_on_cluster_normalization.reference --- .../02400_create_table_on_cluster_normalization.reference | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference index 2c9562f362f..c00653f2bb3 100644 --- a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference +++ b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.reference @@ -1,3 +1,5 @@ +localhost 9000 0 0 0 +localhost 9000 0 0 0 1 2 3 4 5 6 7 8 CREATE TABLE default.t_l5ydey\n(\n `c_qv5rv` Int32,\n `c_rutjs4` Int32,\n `c_wmj` Int32,\n `c_m3` String\n)\nENGINE = Distributed(\'test_shard_localhost\', \'default\', \'local_t_l5ydey\', rand()) From 8ef2d87adfe95f6eb02ddfe445086e183904b7b9 Mon Sep 17 00:00:00 2001 From: Ilya Yatsishin <2159081+qoega@users.noreply.github.com> Date: Mon, 11 Jul 2022 15:09:42 +0200 Subject: [PATCH 025/672] Update tests/integration/helpers/cluster.py --- tests/integration/helpers/cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 32a4e663975..4c6304632e7 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -2127,7 +2127,7 @@ class ClickHouseCluster: logging.debug("Can't connect to MeiliSearch " + str(ex)) time.sleep(1) - def wait_minio_to_start(self, timeout=10, secure=False): + def wait_minio_to_start(self, timeout=180, secure=False): self.minio_ip = self.get_instance_ip(self.minio_host) self.minio_redirect_ip = self.get_instance_ip(self.minio_redirect_host) From 36e34d8cc610e3afc25b5f5bc79e3775ee93be75 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 13 Jul 2022 14:53:23 +0000 Subject: [PATCH 026/672] Respect remote_url_allow_hosts in relevant dictionary sources. --- src/Dictionaries/CassandraDictionarySource.h | 1 + src/Dictionaries/ClickHouseDictionarySource.cpp | 3 +++ src/Dictionaries/HTTPDictionarySource.cpp | 6 ++++-- src/Dictionaries/MongoDBDictionarySource.cpp | 5 ++++- src/Dictionaries/MySQLDictionarySource.cpp | 9 +++++++++ src/Dictionaries/PostgreSQLDictionarySource.cpp | 10 +++++++++- 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/Dictionaries/CassandraDictionarySource.h b/src/Dictionaries/CassandraDictionarySource.h index e73383aa75c..2591b33c638 100644 --- a/src/Dictionaries/CassandraDictionarySource.h +++ b/src/Dictionaries/CassandraDictionarySource.h @@ -8,6 +8,7 @@ #include "IDictionarySource.h" #include "ExternalQueryBuilder.h" #include +#include #include #include diff --git a/src/Dictionaries/ClickHouseDictionarySource.cpp b/src/Dictionaries/ClickHouseDictionarySource.cpp index 1a3f3f0edc4..b616979ba2e 100644 --- a/src/Dictionaries/ClickHouseDictionarySource.cpp +++ b/src/Dictionaries/ClickHouseDictionarySource.cpp @@ -284,6 +284,9 @@ void registerDictionarySourceClickHouse(DictionarySourceFactory & factory) else { context = Context::createCopy(global_context); + + if (created_from_ddl) + context->getRemoteHostFilter().checkHostAndPort(configuration.host, toString(configuration.port)); } context->applySettingsChanges(readSettingsFromDictionaryConfig(config, config_prefix)); diff --git a/src/Dictionaries/HTTPDictionarySource.cpp b/src/Dictionaries/HTTPDictionarySource.cpp index d45213fa7f8..95a891e18ce 100644 --- a/src/Dictionaries/HTTPDictionarySource.cpp +++ b/src/Dictionaries/HTTPDictionarySource.cpp @@ -32,8 +32,7 @@ HTTPDictionarySource::HTTPDictionarySource( const Configuration & configuration_, const Poco::Net::HTTPBasicCredentials & credentials_, Block & sample_block_, - ContextPtr context_, - bool created_from_ddl) + ContextPtr context_) : log(&Poco::Logger::get("HTTPDictionarySource")) , update_time(std::chrono::system_clock::from_time_t(0)) , dict_struct(dict_struct_) @@ -303,6 +302,9 @@ void registerDictionarySourceHTTP(DictionarySourceFactory & factory) auto context = copyContextAndApplySettingsFromDictionaryConfig(global_context, config, config_prefix); + if (created_from_ddl) + context->getRemoteHostFilter().checkURL(Poco::URI(configuration.url)); + return std::make_unique(dict_struct, configuration, credentials, sample_block, context, created_from_ddl); }; factory.registerSource("http", create_table_source); diff --git a/src/Dictionaries/MongoDBDictionarySource.cpp b/src/Dictionaries/MongoDBDictionarySource.cpp index 6a1bfceaec7..1ede0ec5045 100644 --- a/src/Dictionaries/MongoDBDictionarySource.cpp +++ b/src/Dictionaries/MongoDBDictionarySource.cpp @@ -20,7 +20,7 @@ void registerDictionarySourceMongoDB(DictionarySourceFactory & factory) Block & sample_block, ContextPtr context, const std::string & /* default_database */, - bool /* created_from_ddl */) + bool created_from_ddl) { const auto config_prefix = root_config_prefix + ".mongodb"; ExternalDataSourceConfiguration configuration; @@ -39,6 +39,9 @@ void registerDictionarySourceMongoDB(DictionarySourceFactory & factory) configuration.database = config.getString(config_prefix + ".db", ""); } + if (created_from_ddl) + context->getRemoteHostFilter().checkHostAndPort(configuration.host, toString(configuration.port)); + return std::make_unique(dict_struct, config.getString(config_prefix + ".uri", ""), configuration.host, diff --git a/src/Dictionaries/MySQLDictionarySource.cpp b/src/Dictionaries/MySQLDictionarySource.cpp index a5807f58f8a..6fb4dffc5e7 100644 --- a/src/Dictionaries/MySQLDictionarySource.cpp +++ b/src/Dictionaries/MySQLDictionarySource.cpp @@ -78,6 +78,9 @@ void registerDictionarySourceMysql(DictionarySourceFactory & factory) : std::nullopt; if (named_collection) { + if (created_from_ddl) + global_context->getRemoteHostFilter().checkHostAndPort(configuration.host, toString(configuration.port)); + mysql_settings.applyChanges(named_collection->settings_changes); configuration.set(named_collection->configuration); configuration.addresses = {std::make_pair(configuration.host, configuration.port)}; @@ -90,6 +93,12 @@ void registerDictionarySourceMysql(DictionarySourceFactory & factory) } else { + if (created_from_ddl) + { + for (auto & address : configuration.addresses) + global_context->getRemoteHostFilter().checkHostAndPort(address.first, toString(address.second)); + } + configuration.database = config.getString(settings_config_prefix + ".db", ""); configuration.table = config.getString(settings_config_prefix + ".table", ""); pool = std::make_shared(mysqlxx::PoolFactory::instance().get(config, settings_config_prefix)); diff --git a/src/Dictionaries/PostgreSQLDictionarySource.cpp b/src/Dictionaries/PostgreSQLDictionarySource.cpp index 42884278e7d..bec8283e724 100644 --- a/src/Dictionaries/PostgreSQLDictionarySource.cpp +++ b/src/Dictionaries/PostgreSQLDictionarySource.cpp @@ -185,13 +185,21 @@ void registerDictionarySourcePostgreSQL(DictionarySourceFactory & factory) Block & sample_block, ContextPtr context, const std::string & /* default_database */, - bool /* created_from_ddl */) -> DictionarySourcePtr + bool created_from_ddl) -> DictionarySourcePtr { #if USE_LIBPQXX const auto settings_config_prefix = config_prefix + ".postgresql"; auto has_config_key = [](const String & key) { return dictionary_allowed_keys.contains(key) || key.starts_with("replica"); }; auto configuration = getExternalDataSourceConfigurationByPriority(config, settings_config_prefix, context, has_config_key); const auto & settings = context->getSettingsRef(); + + if (created_from_ddl) + { + for (const auto & replicas : configuration.replicas_configurations) + for (const auto & replica : replicas.second) + context->getRemoteHostFilter().checkHostAndPort(replica.host, toString(replica.port)); + } + auto pool = std::make_shared( configuration.replicas_configurations, settings.postgresql_connection_pool_size, From 937a9e9d9fecd06520ab89d5b59bffe6c716d72b Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 13 Jul 2022 15:05:49 +0000 Subject: [PATCH 027/672] Fixing build/ --- src/Dictionaries/HTTPDictionarySource.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Dictionaries/HTTPDictionarySource.cpp b/src/Dictionaries/HTTPDictionarySource.cpp index 95a891e18ce..265c8f7cb99 100644 --- a/src/Dictionaries/HTTPDictionarySource.cpp +++ b/src/Dictionaries/HTTPDictionarySource.cpp @@ -41,9 +41,6 @@ HTTPDictionarySource::HTTPDictionarySource( , context(context_) , timeouts(ConnectionTimeouts::getHTTPTimeouts(context)) { - if (created_from_ddl) - context->getRemoteHostFilter().checkURL(Poco::URI(configuration.url)); - credentials.setUsername(credentials_.getUsername()); credentials.setPassword(credentials_.getPassword()); } From 5005700eee75ed791cf21479d470c088dfb6b72e Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 13 Jul 2022 15:33:18 +0000 Subject: [PATCH 028/672] Fixing build --- src/Dictionaries/HTTPDictionarySource.cpp | 2 +- src/Dictionaries/HTTPDictionarySource.h | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Dictionaries/HTTPDictionarySource.cpp b/src/Dictionaries/HTTPDictionarySource.cpp index 265c8f7cb99..39e034a071f 100644 --- a/src/Dictionaries/HTTPDictionarySource.cpp +++ b/src/Dictionaries/HTTPDictionarySource.cpp @@ -302,7 +302,7 @@ void registerDictionarySourceHTTP(DictionarySourceFactory & factory) if (created_from_ddl) context->getRemoteHostFilter().checkURL(Poco::URI(configuration.url)); - return std::make_unique(dict_struct, configuration, credentials, sample_block, context, created_from_ddl); + return std::make_unique(dict_struct, configuration, credentials, sample_block, context); }; factory.registerSource("http", create_table_source); } diff --git a/src/Dictionaries/HTTPDictionarySource.h b/src/Dictionaries/HTTPDictionarySource.h index 71351cd9987..86e3836f2dc 100644 --- a/src/Dictionaries/HTTPDictionarySource.h +++ b/src/Dictionaries/HTTPDictionarySource.h @@ -37,8 +37,7 @@ public: const Configuration & configuration, const Poco::Net::HTTPBasicCredentials & credentials_, Block & sample_block_, - ContextPtr context_, - bool created_from_ddl); + ContextPtr context_); HTTPDictionarySource(const HTTPDictionarySource & other); HTTPDictionarySource & operator=(const HTTPDictionarySource &) = delete; From 7cde9d3b404024ebc2539a9034deca66c12f52be Mon Sep 17 00:00:00 2001 From: avogar Date: Wed, 13 Jul 2022 15:57:55 +0000 Subject: [PATCH 029/672] Add new features in schema inference --- src/Core/Settings.h | 4 + src/DataTypes/transformTypesRecursively.cpp | 181 ++++++++++++ src/DataTypes/transformTypesRecursively.h | 17 ++ src/Formats/EscapingRuleUtils.cpp | 278 +++++++++++++++++- src/Formats/EscapingRuleUtils.h | 17 ++ src/Formats/FormatFactory.cpp | 4 + src/Formats/FormatSettings.h | 4 + src/Formats/JSONUtils.cpp | 115 +++++--- src/Formats/JSONUtils.h | 6 +- src/Formats/ReadSchemaUtils.cpp | 23 +- src/Processors/Formats/ISchemaReader.cpp | 76 +++-- src/Processors/Formats/ISchemaReader.h | 20 +- .../Impl/CustomSeparatedRowInputFormat.cpp | 5 + .../Impl/CustomSeparatedRowInputFormat.h | 2 + .../Impl/JSONColumnsBlockInputFormatBase.cpp | 12 +- .../Impl/JSONColumnsBlockInputFormatBase.h | 2 +- .../Impl/JSONCompactEachRowRowInputFormat.cpp | 13 +- .../Impl/JSONCompactEachRowRowInputFormat.h | 2 + .../Impl/JSONEachRowRowInputFormat.cpp | 18 +- .../Formats/Impl/JSONEachRowRowInputFormat.h | 1 + .../Formats/Impl/MySQLDumpRowInputFormat.cpp | 2 +- .../Formats/Impl/MySQLDumpRowInputFormat.h | 1 - .../Formats/Impl/RegexpRowInputFormat.cpp | 7 +- .../Formats/Impl/RegexpRowInputFormat.h | 4 +- .../Formats/Impl/TSKVRowInputFormat.cpp | 3 +- .../Formats/Impl/TSKVRowInputFormat.h | 1 - .../Formats/Impl/TemplateRowInputFormat.cpp | 6 +- .../Formats/Impl/TemplateRowInputFormat.h | 3 +- .../Formats/Impl/ValuesBlockInputFormat.cpp | 2 +- .../Formats/Impl/ValuesBlockInputFormat.h | 1 - .../RowInputFormatWithNamesAndTypes.cpp | 4 +- .../Formats/RowInputFormatWithNamesAndTypes.h | 6 +- ...02247_read_bools_as_numbers_json.reference | 12 + .../02247_read_bools_as_numbers_json.sh | 19 ++ .../02268_json_maps_and_objects.reference | 3 + .../02268_json_maps_and_objects.sql | 3 + .../02325_dates_schema_inference.reference | 60 ++++ .../02325_dates_schema_inference.sql | 68 +++++ ...om_json_strings_schema_inference.reference | 17 ++ ...ers_from_json_strings_schema_inference.sql | 19 ++ ..._infer_integers_schema_inference.reference | 36 +++ ...27_try_infer_integers_schema_inference.sql | 43 +++ 42 files changed, 994 insertions(+), 126 deletions(-) create mode 100644 src/DataTypes/transformTypesRecursively.cpp create mode 100644 src/DataTypes/transformTypesRecursively.h create mode 100644 tests/queries/0_stateless/02325_dates_schema_inference.reference create mode 100644 tests/queries/0_stateless/02325_dates_schema_inference.sql create mode 100644 tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.reference create mode 100644 tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.sql create mode 100644 tests/queries/0_stateless/02327_try_infer_integers_schema_inference.reference create mode 100644 tests/queries/0_stateless/02327_try_infer_integers_schema_inference.sql diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 5597d9076a4..b0e7f554717 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -686,6 +686,10 @@ static constexpr UInt64 operator""_GiB(unsigned long long value) M(Bool, input_format_arrow_skip_columns_with_unsupported_types_in_schema_inference, false, "Allow to skip columns with unsupported types while schema inference for format Arrow", 0) \ M(String, column_names_for_schema_inference, "", "The list of column names to use in schema inference for formats without column names. The format: 'column1,column2,column3,...'", 0) \ M(Bool, input_format_json_read_bools_as_numbers, true, "Allow to parse bools as numbers in JSON input formats", 0) \ + M(Bool, input_format_json_try_infer_numbers_from_strings, false, "Try to infer numbers from string fields while schema inference", 0) \ + M(Bool, input_format_try_infer_integers, false, "Try to infer numbers from string fields while schema inference in text formats", 0) \ + M(Bool, input_format_try_infer_dates, false, "Try to infer dates from string fields while schema inference in text formats", 0) \ + M(Bool, input_format_try_infer_datetimes, false, "Try to infer datetimes from string fields while schema inference in text formats", 0) \ M(Bool, input_format_protobuf_flatten_google_wrappers, false, "Enable Google wrappers for regular non-nested columns, e.g. google.protobuf.StringValue 'str' for String column 'str'. For Nullable columns empty wrappers are recognized as defaults, and missing as nulls", 0) \ M(Bool, output_format_protobuf_nullables_with_google_wrappers, false, "When serializing Nullable columns with Google wrappers, serialize default values as empty wrappers. If turned off, default and null values are not serialized", 0) \ M(UInt64, input_format_csv_skip_first_lines, 0, "Skip specified number of lines at the beginning of data in CSV format", 0) \ diff --git a/src/DataTypes/transformTypesRecursively.cpp b/src/DataTypes/transformTypesRecursively.cpp new file mode 100644 index 00000000000..2f1b689a233 --- /dev/null +++ b/src/DataTypes/transformTypesRecursively.cpp @@ -0,0 +1,181 @@ +#include +#include +#include +#include +#include + + +namespace DB +{ + +void transformTypesRecursively(DataTypes & types, std::function transform_simple_types, std::function transform_complex_types) +{ + { + /// Arrays + bool have_array = false; + bool all_arrays = true; + DataTypes nested_types; + for (const auto & type : types) + { + if (const DataTypeArray * type_array = typeid_cast(type.get())) + { + have_array = true; + nested_types.push_back(type_array->getNestedType()); + } + else + all_arrays = false; + } + + if (have_array) + { + if (all_arrays) + { + transformTypesRecursively(nested_types, transform_simple_types, transform_complex_types); + for (size_t i = 0; i != types.size(); ++i) + types[i] = std::make_shared(nested_types[i]); + } + + if (transform_complex_types) + transform_complex_types(types); + + return; + } + } + + { + /// Tuples + bool have_tuple = false; + bool all_tuples = true; + size_t tuple_size = 0; + + std::vector nested_types; + + for (const auto & type : types) + { + if (const DataTypeTuple * type_tuple = typeid_cast(type.get())) + { + if (!have_tuple) + { + tuple_size = type_tuple->getElements().size(); + nested_types.resize(tuple_size); + for (size_t elem_idx = 0; elem_idx < tuple_size; ++elem_idx) + nested_types[elem_idx].reserve(types.size()); + } + else if (tuple_size != type_tuple->getElements().size()) + return; + + have_tuple = true; + + for (size_t elem_idx = 0; elem_idx < tuple_size; ++elem_idx) + nested_types[elem_idx].emplace_back(type_tuple->getElements()[elem_idx]); + } + else + all_tuples = false; + } + + if (have_tuple) + { + if (all_tuples) + { + std::vector transposed_nested_types(types.size()); + for (size_t elem_idx = 0; elem_idx < tuple_size; ++elem_idx) + { + transformTypesRecursively(nested_types[elem_idx], transform_simple_types, transform_complex_types); + for (size_t i = 0; i != types.size(); ++i) + transposed_nested_types[i].push_back(nested_types[elem_idx][i]); + } + + for (size_t i = 0; i != types.size(); ++i) + types[i] = std::make_shared(transposed_nested_types[i]); + + if (transform_complex_types) + transform_complex_types(types); + } + + if (transform_complex_types) + transform_complex_types(types); + + return; + } + } + + { + /// Maps + bool have_maps = false; + bool all_maps = true; + DataTypes key_types; + DataTypes value_types; + key_types.reserve(types.size()); + value_types.reserve(types.size()); + + for (const auto & type : types) + { + if (const DataTypeMap * type_map = typeid_cast(type.get())) + { + have_maps = true; + key_types.emplace_back(type_map->getKeyType()); + value_types.emplace_back(type_map->getValueType()); + } + else + all_maps = false; + } + + if (have_maps) + { + if (all_maps) + { + transformTypesRecursively(key_types, transform_simple_types, transform_complex_types); + transformTypesRecursively(value_types, transform_simple_types, transform_complex_types); + + for (size_t i = 0; i != types.size(); ++i) + types[i] = std::make_shared(key_types[i], value_types[i]); + } + + if (transform_complex_types) + transform_complex_types(types); + + return; + } + } + + { + /// Nullable + bool have_nullable = false; + std::vector is_nullable; + is_nullable.reserve(types.size()); + DataTypes nested_types; + nested_types.reserve(types.size()); + for (const auto & type : types) + { + if (const DataTypeNullable * type_nullable = typeid_cast(type.get())) + { + have_nullable = true; + is_nullable.push_back(1); + nested_types.push_back(type_nullable->getNestedType()); + } + else + { + is_nullable.push_back(0); + nested_types.push_back(type); + } + } + + if (have_nullable) + { + transformTypesRecursively(nested_types, transform_simple_types, transform_complex_types); + for (size_t i = 0; i != types.size(); ++i) + { + if (is_nullable[i]) + types[i] = makeNullable(nested_types[i]); + else + types[i] = nested_types[i]; + } + + return; + } + } + + transform_simple_types(types); +} + +} diff --git a/src/DataTypes/transformTypesRecursively.h b/src/DataTypes/transformTypesRecursively.h new file mode 100644 index 00000000000..5cb8f095494 --- /dev/null +++ b/src/DataTypes/transformTypesRecursively.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace DB +{ + +/// Function that applies custom transformation functions to provided types recursively. +/// Implementation is similar to function getLeastSuperType: +/// If all types are Array/Map/Tuple/Nullable, this function will be called to nested types. +/// If not all types are the same complex type (Array/Map/Tuple), this function won't be called to nested types. +/// Function transform_simple_types will be applied to resulting simple types after all recursive calls. +/// Function transform_complex_types will be applied to complex types (Array/Map/Tuple) after recursive call to their nested types. +void transformTypesRecursively(DataTypes & types, std::function transform_simple_types, std::function transform_complex_types); + +} diff --git a/src/Formats/EscapingRuleUtils.cpp b/src/Formats/EscapingRuleUtils.cpp index 5aab8909a0c..0ae7918f682 100644 --- a/src/Formats/EscapingRuleUtils.cpp +++ b/src/Formats/EscapingRuleUtils.cpp @@ -9,8 +9,12 @@ #include #include #include -#include +#include +#include #include +#include +#include +#include #include #include #include @@ -255,7 +259,215 @@ String readStringByEscapingRule(ReadBuffer & buf, FormatSettings::EscapingRule e return readByEscapingRule(buf, escaping_rule, format_settings); } -static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBuffer & buf) +void transformInferredTypesIfNeededImpl(DataTypes & types, const FormatSettings & settings, bool is_json, const std::unordered_set * numbers_parsed_from_json_strings = nullptr) +{ + /// Do nothing if we didn't try to infer something special. + if (!settings.try_infer_integers && !settings.try_infer_dates && !settings.try_infer_datetimes && !is_json) + return; + + auto transform_simple_types = [&](DataTypes & data_types) + { + /// If we have floats and integers convert them all to float. + if (settings.try_infer_integers) + { + bool have_floats = false; + bool have_integers = false; + for (const auto & type : data_types) + { + have_floats |= isFloat(type); + have_integers |= isInteger(type) && !isBool(type); + } + + if (have_floats && have_integers) + { + for (auto & type : data_types) + { + if (isInteger(type)) + type = std::make_shared(); + } + } + } + + /// If we have date/datetimes and smth else, convert them to string. + /// If we have only dates and datetimes, convert dates to datetime. + if (settings.try_infer_dates || settings.try_infer_datetimes) + { + bool have_dates = false; + bool have_datetimes = false; + bool all_dates_or_datetimes = true; + + for (const auto & type : data_types) + { + have_dates |= isDate(type); + have_datetimes |= isDateTime64(type); + all_dates_or_datetimes &= isDate(type) || isDateTime64(type); + } + + if (!all_dates_or_datetimes && (have_dates || have_datetimes)) + { + for (auto & type : data_types) + { + if (isDate(type) || isDateTime64(type)) + type = std::make_shared(); + } + } + else if (have_dates && have_datetimes) + { + for (auto & type : data_types) + { + if (isDate(type)) + type = std::make_shared(9); + } + } + } + + if (!is_json) + return; + + /// Check settings specific for JSON formats. + + /// If we have numbers and strings, convert numbers to strings. + /// (Actually numbers could not be parsed from + if (settings.json.try_infer_numbers_from_strings) + { + bool have_strings = false; + bool have_numbers = false; + for (const auto & type : data_types) + { + have_strings |= isString(type); + have_numbers |= isNumber(type); + } + + if (have_strings && have_numbers) + { + for (auto & type : data_types) + { + if (isNumber(type) && (!numbers_parsed_from_json_strings || numbers_parsed_from_json_strings->contains(type.get()))) + type = std::make_shared(); + } + } + } + + if (settings.json.read_bools_as_numbers) + { + bool have_floats = false; + bool have_integers = false; + bool have_bools = false; + for (const auto & type : data_types) + { + have_floats |= isFloat(type); + have_integers |= isInteger(type) && !isBool(type); + have_bools |= isBool(type); + } + + if (have_bools && (have_integers || have_floats)) + { + for (auto & type : data_types) + { + if (isBool(type)) + { + if (have_integers) + type = std::make_shared(); + else + type = std::make_shared(); + } + } + } + } + }; + + auto transform_complex_types = [&](DataTypes & data_types) + { + if (!is_json) + return; + + bool have_maps = false; + bool have_objects = false; + bool are_maps_equal = true; + DataTypePtr first_map_type; + for (const auto & type : data_types) + { + if (isMap(type)) + { + if (!have_maps) + { + first_map_type = type; + have_maps = true; + } + else + { + are_maps_equal &= type->equals(*first_map_type); + if (!type->equals(*first_map_type)) + LOG_DEBUG(&Poco::Logger::get("SchemaInference"), "Maps {} and {} are different", type->getName(), first_map_type->getName()); + } + } + else if (isObject(type)) + { + have_objects = true; + } + } + + if (have_maps && (have_objects || !are_maps_equal)) + { + for (auto & type : data_types) + { + if (isMap(type)) + type = std::make_shared("json", true); + } + } + }; + + transformTypesRecursively(types, transform_simple_types, transform_complex_types); +} + +void transformInferredTypesIfNeeded(DataTypes & types, const FormatSettings & settings, FormatSettings::EscapingRule escaping_rule) +{ + transformInferredTypesIfNeededImpl(types, settings, escaping_rule == FormatSettings::EscapingRule::JSON); +} + +void transformInferredTypesIfNeeded(DataTypePtr & first, DataTypePtr & second, const FormatSettings & settings, FormatSettings::EscapingRule escaping_rule) +{ + DataTypes types = {first, second}; + transformInferredTypesIfNeeded(types, settings, escaping_rule); + first = std::move(types[0]); + second = std::move(types[1]); +} + +void transformInferredJSONTypesIfNeeded(DataTypes & types, const FormatSettings & settings, const std::unordered_set * numbers_parsed_from_json_strings) +{ + transformInferredTypesIfNeededImpl(types, settings, true, numbers_parsed_from_json_strings); +} + +void transformInferredJSONTypesIfNeeded(DataTypePtr & first, DataTypePtr & second, const FormatSettings & settings) +{ + DataTypes types = {first, second}; + transformInferredJSONTypesIfNeeded(types, settings); + first = std::move(types[0]); + second = std::move(types[1]); +} + +DataTypePtr tryInferDateOrDateTime(const std::string_view & field, const FormatSettings & settings) +{ + if (settings.try_infer_dates) + { + ReadBufferFromString buf(field); + DayNum tmp; + if (tryReadDateText(tmp, buf) && buf.eof()) + return makeNullable(std::make_shared()); + } + + if (settings.try_infer_datetimes) + { + ReadBufferFromString buf(field); + DateTime64 tmp; + if (tryReadDateTime64Text(tmp, 9, buf) && buf.eof()) + return makeNullable(std::make_shared(9)); + } + + return nullptr; +} + +static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBufferFromString & buf, const FormatSettings & settings) { if (buf.eof()) return nullptr; @@ -279,7 +491,7 @@ static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBuffer & buf) else first = false; - auto nested_type = determineDataTypeForSingleFieldImpl(buf); + auto nested_type = determineDataTypeForSingleFieldImpl(buf, settings); if (!nested_type) return nullptr; @@ -294,6 +506,8 @@ static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBuffer & buf) if (nested_types.empty()) return std::make_shared(std::make_shared()); + transformInferredTypesIfNeeded(nested_types, settings); + auto least_supertype = tryGetLeastSupertype(nested_types); if (!least_supertype) return nullptr; @@ -320,7 +534,7 @@ static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBuffer & buf) else first = false; - auto nested_type = determineDataTypeForSingleFieldImpl(buf); + auto nested_type = determineDataTypeForSingleFieldImpl(buf, settings); if (!nested_type) return nullptr; @@ -355,7 +569,7 @@ static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBuffer & buf) else first = false; - auto key_type = determineDataTypeForSingleFieldImpl(buf); + auto key_type = determineDataTypeForSingleFieldImpl(buf, settings); if (!key_type) return nullptr; @@ -366,7 +580,7 @@ static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBuffer & buf) return nullptr; skipWhitespaceIfAny(buf); - auto value_type = determineDataTypeForSingleFieldImpl(buf); + auto value_type = determineDataTypeForSingleFieldImpl(buf, settings); if (!value_type) return nullptr; @@ -382,6 +596,9 @@ static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBuffer & buf) if (key_types.empty()) return std::make_shared(std::make_shared(), std::make_shared()); + transformInferredTypesIfNeeded(key_types, settings); + transformInferredTypesIfNeeded(value_types, settings); + auto key_least_supertype = tryGetLeastSupertype(key_types); auto value_least_supertype = tryGetLeastSupertype(value_types); @@ -398,9 +615,11 @@ static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBuffer & buf) if (*buf.position() == '\'') { ++buf.position(); + String field; while (!buf.eof()) { char * next_pos = find_first_symbols<'\\', '\''>(buf.position(), buf.buffer().end()); + field.append(buf.position(), next_pos); buf.position() = next_pos; if (!buf.hasPendingData()) @@ -409,6 +628,7 @@ static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBuffer & buf) if (*buf.position() == '\'') break; + field.push_back(*buf.position()); if (*buf.position() == '\\') ++buf.position(); } @@ -417,6 +637,9 @@ static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBuffer & buf) return nullptr; ++buf.position(); + if (auto type = tryInferDateOrDateTime(field, settings)) + return type; + return std::make_shared(); } @@ -430,15 +653,29 @@ static DataTypePtr determineDataTypeForSingleFieldImpl(ReadBuffer & buf) /// Number Float64 tmp; + auto * pos_before_float = buf.position(); if (tryReadFloatText(tmp, buf)) + { + if (settings.try_infer_integers) + { + auto * float_end_pos = buf.position(); + buf.position() = pos_before_float; + Int64 tmp_int; + if (tryReadIntText(tmp_int, buf) && buf.position() == float_end_pos) + return std::make_shared(); + + buf.position() = float_end_pos; + } + return std::make_shared(); + } return nullptr; } -static DataTypePtr determineDataTypeForSingleField(ReadBuffer & buf) +static DataTypePtr determineDataTypeForSingleField(ReadBufferFromString & buf, const FormatSettings & settings) { - return makeNullableRecursivelyAndCheckForNothing(determineDataTypeForSingleFieldImpl(buf)); + return makeNullableRecursivelyAndCheckForNothing(determineDataTypeForSingleFieldImpl(buf, settings)); } DataTypePtr determineDataTypeByEscapingRule(const String & field, const FormatSettings & format_settings, FormatSettings::EscapingRule escaping_rule) @@ -448,11 +685,11 @@ DataTypePtr determineDataTypeByEscapingRule(const String & field, const FormatSe case FormatSettings::EscapingRule::Quoted: { ReadBufferFromString buf(field); - auto type = determineDataTypeForSingleField(buf); + auto type = determineDataTypeForSingleField(buf, format_settings); return buf.eof() ? type : nullptr; } case FormatSettings::EscapingRule::JSON: - return JSONUtils::getDataTypeFromField(field); + return JSONUtils::getDataTypeFromField(field, format_settings); case FormatSettings::EscapingRule::CSV: { if (!format_settings.csv.input_format_use_best_effort_in_schema_inference) @@ -466,9 +703,13 @@ DataTypePtr determineDataTypeByEscapingRule(const String & field, const FormatSe if (field.size() > 1 && ((field.front() == '\'' && field.back() == '\'') || (field.front() == '"' && field.back() == '"'))) { - ReadBufferFromString buf(std::string_view(field.data() + 1, field.size() - 2)); + auto data = std::string_view(field.data() + 1, field.size() - 2); + if (auto date_type = tryInferDateOrDateTime(data, format_settings)) + return date_type; + + ReadBufferFromString buf(data); /// Try to determine the type of value inside quotes - auto type = determineDataTypeForSingleField(buf); + auto type = determineDataTypeForSingleField(buf, format_settings); if (!type) return nullptr; @@ -481,6 +722,14 @@ DataTypePtr determineDataTypeByEscapingRule(const String & field, const FormatSe } /// Case when CSV value is not in quotes. Check if it's a number, and if not, determine it's as a string. + if (format_settings.try_infer_integers) + { + ReadBufferFromString buf(field); + Int64 tmp_int; + if (tryReadIntText(tmp_int, buf) && buf.eof()) + return makeNullable(std::make_shared()); + } + ReadBufferFromString buf(field); Float64 tmp; if (tryReadFloatText(tmp, buf) && buf.eof()) @@ -500,8 +749,11 @@ DataTypePtr determineDataTypeByEscapingRule(const String & field, const FormatSe if (field == format_settings.bool_false_representation || field == format_settings.bool_true_representation) return DataTypeFactory::instance().get("Nullable(Bool)"); + if (auto date_type = tryInferDateOrDateTime(field, format_settings)) + return date_type; + ReadBufferFromString buf(field); - auto type = determineDataTypeForSingleField(buf); + auto type = determineDataTypeForSingleField(buf, format_settings); if (!buf.eof()) return makeNullable(std::make_shared()); diff --git a/src/Formats/EscapingRuleUtils.h b/src/Formats/EscapingRuleUtils.h index 1ce04a8d1b7..ad4ce65a430 100644 --- a/src/Formats/EscapingRuleUtils.h +++ b/src/Formats/EscapingRuleUtils.h @@ -60,4 +60,21 @@ DataTypes determineDataTypesByEscapingRule(const std::vector & fields, c DataTypePtr getDefaultDataTypeForEscapingRule(FormatSettings::EscapingRule escaping_rule); DataTypes getDefaultDataTypeForEscapingRules(const std::vector & escaping_rules); +/// Try to infer Date or Datetime from string if corresponding settings are enabled. +DataTypePtr tryInferDateOrDateTime(const std::string_view & field, const FormatSettings & settings); + +/// Check if we need to transform types inferred from data and transform it if necessary. +/// It's used when we try to infer some not ordinary types from another types. +/// For example dates from strings, we should check if dates were inferred from all strings +/// in the same way and if not, transform inferred dates back to strings. +/// For example, if we have array of strings and we tried to infer dates from them, +/// to make the result type Array(Date) we should ensure that all strings were +/// successfully parsed as dated and if not, convert all dates back to strings and make result type Array(String). +void transformInferredTypesIfNeeded(DataTypes & types, const FormatSettings & settings, FormatSettings::EscapingRule escaping_rule = FormatSettings::EscapingRule::Escaped); +void transformInferredTypesIfNeeded(DataTypePtr & first, DataTypePtr & second, const FormatSettings & settings, FormatSettings::EscapingRule escaping_rule = FormatSettings::EscapingRule::Escaped); + +/// Same as transformInferredTypesIfNeeded but takes into account settings that are special for JSON formats. +void transformInferredJSONTypesIfNeeded(DataTypes & types, const FormatSettings & settings, const std::unordered_set * numbers_parsed_from_json_strings = nullptr); +void transformInferredJSONTypesIfNeeded(DataTypePtr & first, DataTypePtr & second, const FormatSettings & settings); + } diff --git a/src/Formats/FormatFactory.cpp b/src/Formats/FormatFactory.cpp index 756b33d3eb2..a2cd921c8f5 100644 --- a/src/Formats/FormatFactory.cpp +++ b/src/Formats/FormatFactory.cpp @@ -94,6 +94,7 @@ FormatSettings getFormatSettings(ContextPtr context, const Settings & settings) format_settings.json.quote_64bit_integers = settings.output_format_json_quote_64bit_integers; format_settings.json.quote_denormals = settings.output_format_json_quote_denormals; format_settings.json.read_bools_as_numbers = settings.input_format_json_read_bools_as_numbers; + format_settings.json.try_infer_numbers_from_strings = settings.input_format_json_try_infer_numbers_from_strings; format_settings.null_as_default = settings.input_format_null_as_default; format_settings.decimal_trailing_zeros = settings.output_format_decimal_trailing_zeros; format_settings.parquet.row_group_size = settings.output_format_parquet_row_group_size; @@ -163,6 +164,9 @@ FormatSettings getFormatSettings(ContextPtr context, const Settings & settings) format_settings.sql_insert.table_name = settings.output_format_sql_insert_table_name; format_settings.sql_insert.use_replace = settings.output_format_sql_insert_use_replace; format_settings.sql_insert.quote_names = settings.output_format_sql_insert_quote_names; + format_settings.try_infer_integers = settings.input_format_try_infer_integers; + format_settings.try_infer_dates = settings.input_format_try_infer_dates; + format_settings.try_infer_datetimes = settings.input_format_try_infer_datetimes; /// Validate avro_schema_registry_url with RemoteHostFilter when non-empty and in Server context if (format_settings.schema.is_server) diff --git a/src/Formats/FormatSettings.h b/src/Formats/FormatSettings.h index 70bf8979383..eb619ddfd79 100644 --- a/src/Formats/FormatSettings.h +++ b/src/Formats/FormatSettings.h @@ -38,6 +38,9 @@ struct FormatSettings UInt64 max_rows_to_read_for_schema_inference = 100; String column_names_for_schema_inference; + bool try_infer_integers = false; + bool try_infer_dates = false; + bool try_infer_datetimes = false; enum class DateTimeInputFormat { @@ -142,6 +145,7 @@ struct FormatSettings bool named_tuples_as_objects = false; bool serialize_as_strings = false; bool read_bools_as_numbers = true; + bool try_infer_numbers_from_strings = false; } json; struct diff --git a/src/Formats/JSONUtils.cpp b/src/Formats/JSONUtils.cpp index 1ac58760516..63c06a8615d 100644 --- a/src/Formats/JSONUtils.cpp +++ b/src/Formats/JSONUtils.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -121,7 +122,7 @@ namespace JSONUtils } template - DataTypePtr getDataTypeFromFieldImpl(const Element & field) + DataTypePtr getDataTypeFromFieldImpl(const Element & field, const FormatSettings & settings, std::unordered_set & numbers_parsed_from_json_strings) { if (field.isNull()) return nullptr; @@ -129,11 +130,48 @@ namespace JSONUtils if (field.isBool()) return DataTypeFactory::instance().get("Nullable(Bool)"); - if (field.isInt64() || field.isUInt64() || field.isDouble()) + if (field.isInt64() || field.isUInt64()) + { + if (settings.try_infer_integers) + return makeNullable(std::make_shared()); + + return makeNullable(std::make_shared()); + } + + if (field.isDouble()) return makeNullable(std::make_shared()); if (field.isString()) + { + if (auto date_type = tryInferDateOrDateTime(field.getString(), settings)) + return date_type; + + if (!settings.json.try_infer_numbers_from_strings) + return makeNullable(std::make_shared()); + + ReadBufferFromString buf(field.getString()); + + if (settings.try_infer_integers) + { + Int64 tmp_int; + if (tryReadIntText(tmp_int, buf) && buf.eof()) + { + auto type = std::make_shared(); + numbers_parsed_from_json_strings.insert(type.get()); + return makeNullable(type); + } + } + + Float64 tmp; + if (tryReadFloatText(tmp, buf) && buf.eof()) + { + auto type = std::make_shared(); + numbers_parsed_from_json_strings.insert(type.get()); + return makeNullable(type); + } + return makeNullable(std::make_shared()); + } if (field.isArray()) { @@ -145,20 +183,32 @@ namespace JSONUtils DataTypes nested_data_types; /// If this array contains fields with different types we will treat it as Tuple. - bool is_tuple = false; + bool are_types_the_same = true; for (const auto element : array) { - auto type = getDataTypeFromFieldImpl(element); + auto type = getDataTypeFromFieldImpl(element, settings, numbers_parsed_from_json_strings); if (!type) return nullptr; - if (!nested_data_types.empty() && type->getName() != nested_data_types.back()->getName()) - is_tuple = true; + if (!nested_data_types.empty() && !type->equals(*nested_data_types.back())) + are_types_the_same = false; nested_data_types.push_back(std::move(type)); } - if (is_tuple) + if (!are_types_the_same) + { + auto nested_types_copy = nested_data_types; + transformInferredJSONTypesIfNeeded(nested_types_copy, settings, &numbers_parsed_from_json_strings); + are_types_the_same = true; + for (size_t i = 1; i < nested_types_copy.size(); ++i) + are_types_the_same &= nested_types_copy[i]->equals(*nested_types_copy[i - 1]); + + if (are_types_the_same) + nested_data_types = std::move(nested_types_copy); + } + + if (!are_types_the_same) return std::make_shared(nested_data_types); return std::make_shared(nested_data_types.back()); @@ -167,38 +217,35 @@ namespace JSONUtils if (field.isObject()) { auto object = field.getObject(); - DataTypePtr value_type; - bool is_object = false; + DataTypes value_types; + bool have_object_value = false; for (const auto key_value_pair : object) { - auto type = getDataTypeFromFieldImpl(key_value_pair.second); + auto type = getDataTypeFromFieldImpl(key_value_pair.second, settings, numbers_parsed_from_json_strings); if (!type) continue; if (isObject(type)) { - is_object = true; + have_object_value = true; break; } - if (!value_type) - { - value_type = type; - } - else if (!value_type->equals(*type)) - { - is_object = true; - break; - } + value_types.push_back(type); } - if (is_object) + if (value_types.empty()) + return nullptr; + + transformInferredJSONTypesIfNeeded(value_types, settings, &numbers_parsed_from_json_strings); + bool are_types_equal = true; + for (size_t i = 1; i < value_types.size(); ++i) + are_types_equal &= value_types[i]->equals(*value_types[0]); + + if (have_object_value || !are_types_equal) return std::make_shared("json", true); - if (value_type) - return std::make_shared(std::make_shared(), value_type); - - return nullptr; + return std::make_shared(std::make_shared(), value_types[0]); } throw Exception{ErrorCodes::INCORRECT_DATA, "Unexpected JSON type"}; @@ -215,18 +262,19 @@ namespace JSONUtils #endif } - DataTypePtr getDataTypeFromField(const String & field) + DataTypePtr getDataTypeFromField(const String & field, const FormatSettings & settings) { auto [parser, element] = getJSONParserAndElement(); bool parsed = parser.parse(field, element); if (!parsed) throw Exception(ErrorCodes::INCORRECT_DATA, "Cannot parse JSON object here: {}", field); - return getDataTypeFromFieldImpl(element); + std::unordered_set numbers_parsed_from_json_strings; + return getDataTypeFromFieldImpl(element, settings, numbers_parsed_from_json_strings); } template - static DataTypes determineColumnDataTypesFromJSONEachRowDataImpl(ReadBuffer & in, bool /*json_strings*/, Extractor & extractor) + static DataTypes determineColumnDataTypesFromJSONEachRowDataImpl(ReadBuffer & in, const FormatSettings & settings, bool /*json_strings*/, Extractor & extractor) { String line = readJSONEachRowLineIntoStringImpl(in); auto [parser, element] = getJSONParserAndElement(); @@ -238,8 +286,9 @@ namespace JSONUtils DataTypes data_types; data_types.reserve(fields.size()); + std::unordered_set numbers_parsed_from_json_strings; for (const auto & field : fields) - data_types.push_back(getDataTypeFromFieldImpl(field)); + data_types.push_back(getDataTypeFromFieldImpl(field, settings, numbers_parsed_from_json_strings)); /// TODO: For JSONStringsEachRow/JSONCompactStringsEach all types will be strings. /// Should we try to parse data inside strings somehow in this case? @@ -284,11 +333,11 @@ namespace JSONUtils std::vector column_names; }; - NamesAndTypesList readRowAndGetNamesAndDataTypesForJSONEachRow(ReadBuffer & in, bool json_strings) + NamesAndTypesList readRowAndGetNamesAndDataTypesForJSONEachRow(ReadBuffer & in, const FormatSettings & settings, bool json_strings) { JSONEachRowFieldsExtractor extractor; auto data_types - = determineColumnDataTypesFromJSONEachRowDataImpl(in, json_strings, extractor); + = determineColumnDataTypesFromJSONEachRowDataImpl(in, settings, json_strings, extractor); NamesAndTypesList result; for (size_t i = 0; i != extractor.column_names.size(); ++i) result.emplace_back(extractor.column_names[i], data_types[i]); @@ -313,10 +362,10 @@ namespace JSONUtils } }; - DataTypes readRowAndGetDataTypesForJSONCompactEachRow(ReadBuffer & in, bool json_strings) + DataTypes readRowAndGetDataTypesForJSONCompactEachRow(ReadBuffer & in, const FormatSettings & settings, bool json_strings) { JSONCompactEachRowFieldsExtractor extractor; - return determineColumnDataTypesFromJSONEachRowDataImpl(in, json_strings, extractor); + return determineColumnDataTypesFromJSONEachRowDataImpl(in, settings, json_strings, extractor); } diff --git a/src/Formats/JSONUtils.h b/src/Formats/JSONUtils.h index f2aba3cbcb5..b4ab6a29c93 100644 --- a/src/Formats/JSONUtils.h +++ b/src/Formats/JSONUtils.h @@ -22,16 +22,16 @@ namespace JSONUtils /// Parse JSON from string and convert it's type to ClickHouse type. Make the result type always Nullable. /// JSON array with different nested types is treated as Tuple. /// If cannot convert (for example when field contains null), return nullptr. - DataTypePtr getDataTypeFromField(const String & field); + DataTypePtr getDataTypeFromField(const String & field, const FormatSettings & settings); /// Read row in JSONEachRow format and try to determine type for each field. /// Return list of names and types. /// If cannot determine the type of some field, return nullptr for it. - NamesAndTypesList readRowAndGetNamesAndDataTypesForJSONEachRow(ReadBuffer & in, bool json_strings); + NamesAndTypesList readRowAndGetNamesAndDataTypesForJSONEachRow(ReadBuffer & in, const FormatSettings & settings, bool json_strings); /// Read row in JSONCompactEachRow format and try to determine type for each field. /// If cannot determine the type of some field, return nullptr for it. - DataTypes readRowAndGetDataTypesForJSONCompactEachRow(ReadBuffer & in, bool json_strings); + DataTypes readRowAndGetDataTypesForJSONCompactEachRow(ReadBuffer & in, const FormatSettings & settings, bool json_strings); bool nonTrivialPrefixAndSuffixCheckerJSONEachRowImpl(ReadBuffer & buf); diff --git a/src/Formats/ReadSchemaUtils.cpp b/src/Formats/ReadSchemaUtils.cpp index 11a91bd50dc..b3934f422f8 100644 --- a/src/Formats/ReadSchemaUtils.cpp +++ b/src/Formats/ReadSchemaUtils.cpp @@ -75,8 +75,25 @@ ColumnsDescription readSchemaFromFormat( SchemaReaderPtr schema_reader; size_t max_rows_to_read = format_settings ? format_settings->max_rows_to_read_for_schema_inference : context->getSettingsRef().input_format_max_rows_to_read_for_schema_inference; size_t iterations = 0; - while ((buf = read_buffer_iterator())) + + while (true) { + try + { + buf = read_buffer_iterator(); + if (!buf) + break; + } + catch (...) + { + auto exception_message = getCurrentExceptionMessage(false); + throw Exception( + ErrorCodes::CANNOT_EXTRACT_TABLE_STRUCTURE, + "Cannot extract table structure from {} format file:\n{}\nYou can specify the structure manually", + format_name, + exception_message); + } + ++iterations; if (buf->eof()) @@ -118,14 +135,14 @@ ColumnsDescription readSchemaFromFormat( } if (!retry || !isRetryableSchemaInferenceError(getCurrentExceptionCode())) - throw Exception(ErrorCodes::CANNOT_EXTRACT_TABLE_STRUCTURE, "Cannot extract table structure from {} format file. Error: {}", format_name, exception_message); + throw Exception(ErrorCodes::CANNOT_EXTRACT_TABLE_STRUCTURE, "Cannot extract table structure from {} format file. Error: {}\nYou can specify the structure manually", format_name, exception_message); exception_messages += "\n" + exception_message; } } if (names_and_types.empty()) - throw Exception(ErrorCodes::CANNOT_EXTRACT_TABLE_STRUCTURE, "All attempts to extract table structure from files failed. Errors:{}", exception_messages); + throw Exception(ErrorCodes::CANNOT_EXTRACT_TABLE_STRUCTURE, "All attempts to extract table structure from files failed. Errors:{}\nYou can specify the structure manually", exception_messages); /// If we have "INSERT SELECT" query then try to order /// columns as they are ordered in table schema for formats diff --git a/src/Processors/Formats/ISchemaReader.cpp b/src/Processors/Formats/ISchemaReader.cpp index 5a6ebf00660..3df9ea70e34 100644 --- a/src/Processors/Formats/ISchemaReader.cpp +++ b/src/Processors/Formats/ISchemaReader.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -17,35 +18,38 @@ namespace ErrorCodes void chooseResultColumnType( DataTypePtr & type, - const DataTypePtr & new_type, - CommonDataTypeChecker common_type_checker, + DataTypePtr & new_type, + std::function transform_types_if_needed, const DataTypePtr & default_type, const String & column_name, size_t row) { if (!type) + { type = new_type; + return; + } + + if (!new_type || type->equals(*new_type)) + return; + + transform_types_if_needed(type, new_type); + if (type->equals(*new_type)) + return; /// If the new type and the previous type for this column are different, /// we will use default type if we have it or throw an exception. - if (new_type && !type->equals(*new_type)) + if (default_type) + type = default_type; + else { - DataTypePtr common_type; - if (common_type_checker) - common_type = common_type_checker(type, new_type); - - if (common_type) - type = common_type; - else if (default_type) - type = default_type; - else - throw Exception( - ErrorCodes::TYPE_MISMATCH, - "Automatically defined type {} for column {} in row {} differs from type defined by previous rows: {}", - type->getName(), - column_name, - row, - new_type->getName()); + throw Exception( + ErrorCodes::TYPE_MISMATCH, + "Automatically defined type {} for column {} in row {} differs from type defined by previous rows: {}", + type->getName(), + column_name, + row, + new_type->getName()); } } @@ -63,8 +67,8 @@ void checkResultColumnTypeAndAppend(NamesAndTypesList & result, DataTypePtr & ty result.emplace_back(name, type); } -IRowSchemaReader::IRowSchemaReader(ReadBuffer & in_, const FormatSettings & format_settings) - : ISchemaReader(in_) +IRowSchemaReader::IRowSchemaReader(ReadBuffer & in_, const FormatSettings & format_settings_) + : ISchemaReader(in_), format_settings(format_settings_) { if (!format_settings.column_names_for_schema_inference.empty()) { @@ -79,14 +83,14 @@ IRowSchemaReader::IRowSchemaReader(ReadBuffer & in_, const FormatSettings & form } } -IRowSchemaReader::IRowSchemaReader(ReadBuffer & in_, const FormatSettings & format_settings, DataTypePtr default_type_) - : IRowSchemaReader(in_, format_settings) +IRowSchemaReader::IRowSchemaReader(ReadBuffer & in_, const FormatSettings & format_settings_, DataTypePtr default_type_) + : IRowSchemaReader(in_, format_settings_) { default_type = default_type_; } -IRowSchemaReader::IRowSchemaReader(ReadBuffer & in_, const FormatSettings & format_settings, const DataTypes & default_types_) - : IRowSchemaReader(in_, format_settings) +IRowSchemaReader::IRowSchemaReader(ReadBuffer & in_, const FormatSettings & format_settings_, const DataTypes & default_types_) + : IRowSchemaReader(in_, format_settings_) { default_types = default_types_; } @@ -116,7 +120,8 @@ NamesAndTypesList IRowSchemaReader::readSchema() if (!new_data_types[i]) continue; - chooseResultColumnType(data_types[i], new_data_types[i], common_type_checker, getDefaultType(i), std::to_string(i + 1), rows_read); + auto transform_types_if_needed = [&](DataTypePtr & type, DataTypePtr & new_type){ transformTypesIfNeeded(type, new_type, i); }; + chooseResultColumnType(data_types[i], new_data_types[i], transform_types_if_needed, getDefaultType(i), std::to_string(i + 1), rows_read); } } @@ -156,8 +161,13 @@ DataTypePtr IRowSchemaReader::getDefaultType(size_t column) const return nullptr; } -IRowWithNamesSchemaReader::IRowWithNamesSchemaReader(ReadBuffer & in_, DataTypePtr default_type_) - : ISchemaReader(in_), default_type(default_type_) +void IRowSchemaReader::transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type, size_t) +{ + transformInferredTypesIfNeeded(type, new_type, format_settings); +} + +IRowWithNamesSchemaReader::IRowWithNamesSchemaReader(ReadBuffer & in_, const FormatSettings & format_settings_, DataTypePtr default_type_) + : ISchemaReader(in_), format_settings(format_settings_), default_type(default_type_) { } @@ -181,6 +191,7 @@ NamesAndTypesList IRowWithNamesSchemaReader::readSchema() names_order.push_back(name); } + auto transform_types_if_needed = [&](DataTypePtr & type, DataTypePtr & new_type){ transformTypesIfNeeded(type, new_type); }; for (rows_read = 1; rows_read < max_rows_to_read; ++rows_read) { auto new_names_and_types = readRowAndGetNamesAndDataTypes(eof); @@ -188,7 +199,7 @@ NamesAndTypesList IRowWithNamesSchemaReader::readSchema() /// We reached eof. break; - for (const auto & [name, new_type] : new_names_and_types) + for (auto & [name, new_type] : new_names_and_types) { auto it = names_to_types.find(name); /// If we didn't see this column before, just add it. @@ -200,7 +211,7 @@ NamesAndTypesList IRowWithNamesSchemaReader::readSchema() } auto & type = it->second; - chooseResultColumnType(type, new_type, common_type_checker, default_type, name, rows_read); + chooseResultColumnType(type, new_type, transform_types_if_needed, default_type, name, rows_read); } } @@ -219,4 +230,9 @@ NamesAndTypesList IRowWithNamesSchemaReader::readSchema() return result; } +void IRowWithNamesSchemaReader::transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type) +{ + transformInferredTypesIfNeeded(type, new_type, format_settings); +} + } diff --git a/src/Processors/Formats/ISchemaReader.h b/src/Processors/Formats/ISchemaReader.h index 00987540d04..02c42495b2a 100644 --- a/src/Processors/Formats/ISchemaReader.h +++ b/src/Processors/Formats/ISchemaReader.h @@ -53,8 +53,6 @@ public: NamesAndTypesList readSchema() override; - void setCommonTypeChecker(CommonDataTypeChecker checker) { common_type_checker = checker; } - protected: /// Read one row and determine types of columns in it. /// Return types in the same order in which the values were in the row. @@ -67,6 +65,10 @@ protected: void setMaxRowsToRead(size_t max_rows) override { max_rows_to_read = max_rows; } size_t getNumRowsRead() const override { return rows_read; } + FormatSettings format_settings; + + virtual void transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type, size_t column_idx); + private: DataTypePtr getDefaultType(size_t column) const; @@ -74,7 +76,6 @@ private: size_t rows_read = 0; DataTypePtr default_type; DataTypes default_types; - CommonDataTypeChecker common_type_checker; std::vector column_names; }; @@ -86,12 +87,10 @@ private: class IRowWithNamesSchemaReader : public ISchemaReader { public: - IRowWithNamesSchemaReader(ReadBuffer & in_, DataTypePtr default_type_ = nullptr); + IRowWithNamesSchemaReader(ReadBuffer & in_, const FormatSettings & format_settings_, DataTypePtr default_type_ = nullptr); NamesAndTypesList readSchema() override; bool hasStrictOrderOfColumns() const override { return false; } - void setCommonTypeChecker(CommonDataTypeChecker checker) { common_type_checker = checker; } - protected: /// Read one row and determine types of columns in it. /// Return list with names and types. @@ -102,11 +101,14 @@ protected: void setMaxRowsToRead(size_t max_rows) override { max_rows_to_read = max_rows; } size_t getNumRowsRead() const override { return rows_read; } + virtual void transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type); + + FormatSettings format_settings; + private: size_t max_rows_to_read; size_t rows_read = 0; DataTypePtr default_type; - CommonDataTypeChecker common_type_checker; }; /// Base class for schema inference for formats that don't need any data to @@ -122,8 +124,8 @@ public: void chooseResultColumnType( DataTypePtr & type, - const DataTypePtr & new_type, - CommonDataTypeChecker common_type_checker, + DataTypePtr & new_type, + std::function transform_types_if_needed, const DataTypePtr & default_type, const String & column_name, size_t row); diff --git a/src/Processors/Formats/Impl/CustomSeparatedRowInputFormat.cpp b/src/Processors/Formats/Impl/CustomSeparatedRowInputFormat.cpp index 61488a94ccd..e5397ca0757 100644 --- a/src/Processors/Formats/Impl/CustomSeparatedRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/CustomSeparatedRowInputFormat.cpp @@ -318,6 +318,11 @@ DataTypes CustomSeparatedSchemaReader::readRowAndGetDataTypes() return determineDataTypesByEscapingRule(fields, reader.getFormatSettings(), reader.getEscapingRule()); } +void CustomSeparatedSchemaReader::transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type, size_t) +{ + transformInferredTypesIfNeeded(type, new_type, format_settings, reader.getEscapingRule()); +} + void registerInputFormatCustomSeparated(FormatFactory & factory) { for (bool ignore_spaces : {false, true}) diff --git a/src/Processors/Formats/Impl/CustomSeparatedRowInputFormat.h b/src/Processors/Formats/Impl/CustomSeparatedRowInputFormat.h index d9e62a1b8e9..c7e332b983f 100644 --- a/src/Processors/Formats/Impl/CustomSeparatedRowInputFormat.h +++ b/src/Processors/Formats/Impl/CustomSeparatedRowInputFormat.h @@ -97,6 +97,8 @@ public: private: DataTypes readRowAndGetDataTypes() override; + void transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type, size_t) override; + PeekableReadBuffer buf; CustomSeparatedFormatReader reader; bool first_row = true; diff --git a/src/Processors/Formats/Impl/JSONColumnsBlockInputFormatBase.cpp b/src/Processors/Formats/Impl/JSONColumnsBlockInputFormatBase.cpp index cdde87f2cf6..7f9fbddd554 100644 --- a/src/Processors/Formats/Impl/JSONColumnsBlockInputFormatBase.cpp +++ b/src/Processors/Formats/Impl/JSONColumnsBlockInputFormatBase.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -181,13 +182,14 @@ JSONColumnsSchemaReaderBase::JSONColumnsSchemaReaderBase( { } -void JSONColumnsSchemaReaderBase::chooseResulType(DataTypePtr & type, const DataTypePtr & new_type, const String & column_name, size_t row) const +void JSONColumnsSchemaReaderBase::chooseResulType(DataTypePtr & type, DataTypePtr & new_type, const String & column_name, size_t row) const { - auto common_type_checker = [&](const DataTypePtr & first, const DataTypePtr & second) + auto convert_types_if_needed = [&](DataTypePtr & first, DataTypePtr & second) { - return JSONUtils::getCommonTypeForJSONFormats(first, second, format_settings.json.read_bools_as_numbers); + DataTypes types = {first, second}; + transformInferredJSONTypesIfNeeded(types, format_settings); }; - chooseResultColumnType(type, new_type, common_type_checker, nullptr, column_name, row); + chooseResultColumnType(type, new_type, convert_types_if_needed, nullptr, column_name, row); } NamesAndTypesList JSONColumnsSchemaReaderBase::readSchema() @@ -260,7 +262,7 @@ DataTypePtr JSONColumnsSchemaReaderBase::readColumnAndGetDataType(const String & } readJSONField(field, in); - DataTypePtr field_type = JSONUtils::getDataTypeFromField(field); + DataTypePtr field_type = JSONUtils::getDataTypeFromField(field, format_settings); chooseResulType(column_type, field_type, column_name, rows_read); ++rows_read; } diff --git a/src/Processors/Formats/Impl/JSONColumnsBlockInputFormatBase.h b/src/Processors/Formats/Impl/JSONColumnsBlockInputFormatBase.h index ac746a2e2d1..6769e60be22 100644 --- a/src/Processors/Formats/Impl/JSONColumnsBlockInputFormatBase.h +++ b/src/Processors/Formats/Impl/JSONColumnsBlockInputFormatBase.h @@ -83,7 +83,7 @@ private: DataTypePtr readColumnAndGetDataType(const String & column_name, size_t & rows_read, size_t max_rows_to_read); /// Choose result type for column from two inferred types from different rows. - void chooseResulType(DataTypePtr & type, const DataTypePtr & new_type, const String & column_name, size_t row) const; + void chooseResulType(DataTypePtr & type, DataTypePtr & new_type, const String & column_name, size_t row) const; const FormatSettings format_settings; std::unique_ptr reader; diff --git a/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.cpp b/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.cpp index 1bc5223a712..8ea379beae5 100644 --- a/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -187,11 +188,6 @@ JSONCompactEachRowRowSchemaReader::JSONCompactEachRowRowSchemaReader( : FormatWithNamesAndTypesSchemaReader(in_, format_settings_, with_names_, with_types_, &reader) , reader(in_, yield_strings_, format_settings_) { - bool allow_bools_as_numbers = format_settings_.json.read_bools_as_numbers; - setCommonTypeChecker([allow_bools_as_numbers](const DataTypePtr & first, const DataTypePtr & second) - { - return JSONUtils::getCommonTypeForJSONFormats(first, second, allow_bools_as_numbers); - }); } DataTypes JSONCompactEachRowRowSchemaReader::readRowAndGetDataTypes() @@ -210,7 +206,12 @@ DataTypes JSONCompactEachRowRowSchemaReader::readRowAndGetDataTypes() if (in.eof()) return {}; - return JSONUtils::readRowAndGetDataTypesForJSONCompactEachRow(in, reader.yieldStrings()); + return JSONUtils::readRowAndGetDataTypesForJSONCompactEachRow(in, format_settings, reader.yieldStrings()); +} + +void JSONCompactEachRowRowSchemaReader::transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type, size_t) +{ + transformInferredJSONTypesIfNeeded(type, new_type, format_settings); } void registerInputFormatJSONCompactEachRow(FormatFactory & factory) diff --git a/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.h b/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.h index 79c76214774..7be9ba9289b 100644 --- a/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.h +++ b/src/Processors/Formats/Impl/JSONCompactEachRowRowInputFormat.h @@ -80,6 +80,8 @@ public: private: DataTypes readRowAndGetDataTypes() override; + void transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type, size_t) override; + JSONCompactEachRowFormatReader reader; bool first_row = true; }; diff --git a/src/Processors/Formats/Impl/JSONEachRowRowInputFormat.cpp b/src/Processors/Formats/Impl/JSONEachRowRowInputFormat.cpp index 9eef72f95da..12415f897cb 100644 --- a/src/Processors/Formats/Impl/JSONEachRowRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/JSONEachRowRowInputFormat.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -306,18 +307,12 @@ void JSONEachRowRowInputFormat::readSuffix() assertEOF(*in); } -JSONEachRowSchemaReader::JSONEachRowSchemaReader(ReadBuffer & in_, bool json_strings_, const FormatSettings & format_settings) - : IRowWithNamesSchemaReader(in_) +JSONEachRowSchemaReader::JSONEachRowSchemaReader(ReadBuffer & in_, bool json_strings_, const FormatSettings & format_settings_) + : IRowWithNamesSchemaReader(in_, format_settings_) , json_strings(json_strings_) { - bool allow_bools_as_numbers = format_settings.json.read_bools_as_numbers; - setCommonTypeChecker([allow_bools_as_numbers](const DataTypePtr & first, const DataTypePtr & second) - { - return JSONUtils::getCommonTypeForJSONFormats(first, second, allow_bools_as_numbers); - }); } - NamesAndTypesList JSONEachRowSchemaReader::readRowAndGetNamesAndDataTypes(bool & eof) { if (first_row) @@ -350,7 +345,12 @@ NamesAndTypesList JSONEachRowSchemaReader::readRowAndGetNamesAndDataTypes(bool & return {}; } - return JSONUtils::readRowAndGetNamesAndDataTypesForJSONEachRow(in, json_strings); + return JSONUtils::readRowAndGetNamesAndDataTypesForJSONEachRow(in, format_settings, json_strings); +} + +void JSONEachRowSchemaReader::transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type) +{ + transformInferredJSONTypesIfNeeded(type, new_type, format_settings); } void registerInputFormatJSONEachRow(FormatFactory & factory) diff --git a/src/Processors/Formats/Impl/JSONEachRowRowInputFormat.h b/src/Processors/Formats/Impl/JSONEachRowRowInputFormat.h index 1da14a532de..325bee2fcbb 100644 --- a/src/Processors/Formats/Impl/JSONEachRowRowInputFormat.h +++ b/src/Processors/Formats/Impl/JSONEachRowRowInputFormat.h @@ -92,6 +92,7 @@ public: private: NamesAndTypesList readRowAndGetNamesAndDataTypes(bool & eof) override; + void transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type) override; bool json_strings; bool first_row = true; diff --git a/src/Processors/Formats/Impl/MySQLDumpRowInputFormat.cpp b/src/Processors/Formats/Impl/MySQLDumpRowInputFormat.cpp index 8e787edf8ab..8e1beb8ec89 100644 --- a/src/Processors/Formats/Impl/MySQLDumpRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/MySQLDumpRowInputFormat.cpp @@ -402,7 +402,7 @@ void MySQLDumpRowInputFormat::skipField() } MySQLDumpSchemaReader::MySQLDumpSchemaReader(ReadBuffer & in_, const FormatSettings & format_settings_) - : IRowSchemaReader(in_, format_settings_), format_settings(format_settings_), table_name(format_settings_.mysql_dump.table_name) + : IRowSchemaReader(in_, format_settings_), table_name(format_settings_.mysql_dump.table_name) { } diff --git a/src/Processors/Formats/Impl/MySQLDumpRowInputFormat.h b/src/Processors/Formats/Impl/MySQLDumpRowInputFormat.h index 2457f3d4762..6be20550e49 100644 --- a/src/Processors/Formats/Impl/MySQLDumpRowInputFormat.h +++ b/src/Processors/Formats/Impl/MySQLDumpRowInputFormat.h @@ -35,7 +35,6 @@ private: NamesAndTypesList readSchema() override; DataTypes readRowAndGetDataTypes() override; - const FormatSettings format_settings; String table_name; }; diff --git a/src/Processors/Formats/Impl/RegexpRowInputFormat.cpp b/src/Processors/Formats/Impl/RegexpRowInputFormat.cpp index d92f65f33d1..c6150863bd4 100644 --- a/src/Processors/Formats/Impl/RegexpRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/RegexpRowInputFormat.cpp @@ -133,7 +133,6 @@ RegexpSchemaReader::RegexpSchemaReader(ReadBuffer & in_, const FormatSettings & buf, format_settings_, getDefaultDataTypeForEscapingRule(format_settings_.regexp.escaping_rule)) - , format_settings(format_settings_) , field_extractor(format_settings) , buf(in_) { @@ -157,6 +156,12 @@ DataTypes RegexpSchemaReader::readRowAndGetDataTypes() return data_types; } +void RegexpSchemaReader::transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type, size_t) +{ + transformInferredTypesIfNeeded(type, new_type, format_settings, format_settings.regexp.escaping_rule); +} + + void registerInputFormatRegexp(FormatFactory & factory) { factory.registerInputFormat("Regexp", []( diff --git a/src/Processors/Formats/Impl/RegexpRowInputFormat.h b/src/Processors/Formats/Impl/RegexpRowInputFormat.h index 3cc6a3192fd..7fbb3fc320f 100644 --- a/src/Processors/Formats/Impl/RegexpRowInputFormat.h +++ b/src/Processors/Formats/Impl/RegexpRowInputFormat.h @@ -81,8 +81,10 @@ public: private: DataTypes readRowAndGetDataTypes() override; + void transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type, size_t) override; + + using EscapingRule = FormatSettings::EscapingRule; - const FormatSettings format_settings; RegexpFieldExtractor field_extractor; PeekableReadBuffer buf; }; diff --git a/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp b/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp index fe2c0c5ecdd..7393a1d6ce6 100644 --- a/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp @@ -214,8 +214,7 @@ void TSKVRowInputFormat::resetParser() } TSKVSchemaReader::TSKVSchemaReader(ReadBuffer & in_, const FormatSettings & format_settings_) - : IRowWithNamesSchemaReader(in_, getDefaultDataTypeForEscapingRule(FormatSettings::EscapingRule::Escaped)) - , format_settings(format_settings_) + : IRowWithNamesSchemaReader(in_, format_settings_, getDefaultDataTypeForEscapingRule(FormatSettings::EscapingRule::Escaped)) { } diff --git a/src/Processors/Formats/Impl/TSKVRowInputFormat.h b/src/Processors/Formats/Impl/TSKVRowInputFormat.h index bf8580bc6b7..5130ee5e827 100644 --- a/src/Processors/Formats/Impl/TSKVRowInputFormat.h +++ b/src/Processors/Formats/Impl/TSKVRowInputFormat.h @@ -61,7 +61,6 @@ public: private: NamesAndTypesList readRowAndGetNamesAndDataTypes(bool & eof) override; - const FormatSettings format_settings; bool first_row = true; }; diff --git a/src/Processors/Formats/Impl/TemplateRowInputFormat.cpp b/src/Processors/Formats/Impl/TemplateRowInputFormat.cpp index df4d49b172c..6e8bba89d8c 100644 --- a/src/Processors/Formats/Impl/TemplateRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/TemplateRowInputFormat.cpp @@ -458,7 +458,6 @@ TemplateSchemaReader::TemplateSchemaReader( , buf(in_) , format(format_) , row_format(row_format_) - , format_settings(format_settings_) , format_reader(buf, ignore_spaces_, format, row_format, row_between_delimiter, format_settings) { setColumnNames(row_format.column_names); @@ -494,6 +493,11 @@ DataTypes TemplateSchemaReader::readRowAndGetDataTypes() return data_types; } +void TemplateSchemaReader::transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type, size_t column_idx) +{ + transformInferredTypesIfNeeded(type, new_type, format_settings, row_format.escaping_rules[column_idx]); +} + static ParsedTemplateFormatString fillResultSetFormat(const FormatSettings & settings) { ParsedTemplateFormatString resultset_format; diff --git a/src/Processors/Formats/Impl/TemplateRowInputFormat.h b/src/Processors/Formats/Impl/TemplateRowInputFormat.h index ab7043f057e..740683ad95d 100644 --- a/src/Processors/Formats/Impl/TemplateRowInputFormat.h +++ b/src/Processors/Formats/Impl/TemplateRowInputFormat.h @@ -121,10 +121,11 @@ public: DataTypes readRowAndGetDataTypes() override; private: + void transformTypesIfNeeded(DataTypePtr & type, DataTypePtr & new_type, size_t column_idx) override; + PeekableReadBuffer buf; const ParsedTemplateFormatString format; const ParsedTemplateFormatString row_format; - FormatSettings format_settings; TemplateFormatReader format_reader; bool first_row = true; }; diff --git a/src/Processors/Formats/Impl/ValuesBlockInputFormat.cpp b/src/Processors/Formats/Impl/ValuesBlockInputFormat.cpp index 41f77f8bbf2..49b758b78c4 100644 --- a/src/Processors/Formats/Impl/ValuesBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/ValuesBlockInputFormat.cpp @@ -567,7 +567,7 @@ void ValuesBlockInputFormat::setReadBuffer(ReadBuffer & in_) } ValuesSchemaReader::ValuesSchemaReader(ReadBuffer & in_, const FormatSettings & format_settings_) - : IRowSchemaReader(buf, format_settings_), buf(in_), format_settings(format_settings_) + : IRowSchemaReader(buf, format_settings_), buf(in_) { } diff --git a/src/Processors/Formats/Impl/ValuesBlockInputFormat.h b/src/Processors/Formats/Impl/ValuesBlockInputFormat.h index 9653e431b4e..bf243c54bd7 100644 --- a/src/Processors/Formats/Impl/ValuesBlockInputFormat.h +++ b/src/Processors/Formats/Impl/ValuesBlockInputFormat.h @@ -103,7 +103,6 @@ private: DataTypes readRowAndGetDataTypes() override; PeekableReadBuffer buf; - const FormatSettings format_settings; ParserExpression parser; bool first_row = true; bool end_of_data = false; diff --git a/src/Processors/Formats/RowInputFormatWithNamesAndTypes.cpp b/src/Processors/Formats/RowInputFormatWithNamesAndTypes.cpp index a3dcbe914bb..9ff227e5dab 100644 --- a/src/Processors/Formats/RowInputFormatWithNamesAndTypes.cpp +++ b/src/Processors/Formats/RowInputFormatWithNamesAndTypes.cpp @@ -229,12 +229,12 @@ void RowInputFormatWithNamesAndTypes::setReadBuffer(ReadBuffer & in_) FormatWithNamesAndTypesSchemaReader::FormatWithNamesAndTypesSchemaReader( ReadBuffer & in_, - const FormatSettings & format_settings, + const FormatSettings & format_settings_, bool with_names_, bool with_types_, FormatWithNamesAndTypesReader * format_reader_, DataTypePtr default_type_) - : IRowSchemaReader(in_, format_settings, default_type_), with_names(with_names_), with_types(with_types_), format_reader(format_reader_) + : IRowSchemaReader(in_, format_settings_, default_type_), with_names(with_names_), with_types(with_types_), format_reader(format_reader_) { } diff --git a/src/Processors/Formats/RowInputFormatWithNamesAndTypes.h b/src/Processors/Formats/RowInputFormatWithNamesAndTypes.h index 9fc8b2083df..e6a587b446c 100644 --- a/src/Processors/Formats/RowInputFormatWithNamesAndTypes.h +++ b/src/Processors/Formats/RowInputFormatWithNamesAndTypes.h @@ -123,7 +123,7 @@ class FormatWithNamesAndTypesSchemaReader : public IRowSchemaReader public: FormatWithNamesAndTypesSchemaReader( ReadBuffer & in, - const FormatSettings & format_settings, + const FormatSettings & format_settings_, bool with_names_, bool with_types_, FormatWithNamesAndTypesReader * format_reader_, @@ -141,5 +141,9 @@ private: FormatWithNamesAndTypesReader * format_reader; }; +/// [2, 2, 4, 0] -> [2, 4, 4, 0] -> [4, 4, 0] -> [4, 4, 0, 0] +/// [2, 4, 4, 2] -> [2, 8, 2, 0] +/// [2, 2, 4, 4] -> [2, 4, 4, 4] -> [4, 4, 4, 0], -> [4, 4, 8, 0] -> [4, 8, 0, 0] + } diff --git a/tests/queries/0_stateless/02247_read_bools_as_numbers_json.reference b/tests/queries/0_stateless/02247_read_bools_as_numbers_json.reference index a7609bdd86b..b6d10581b16 100644 --- a/tests/queries/0_stateless/02247_read_bools_as_numbers_json.reference +++ b/tests/queries/0_stateless/02247_read_bools_as_numbers_json.reference @@ -7,6 +7,12 @@ x Nullable(Float64) x Nullable(Float64) 1 0.42 +x Array(Nullable(Float64)) +[1,0] +[0.42] +x Array(Array(Nullable(Float64))) +[[1,2,3],[1,0],[1,1,0]] +[[1,2,3]] c1 Nullable(Bool) true false @@ -16,3 +22,9 @@ c1 Nullable(Float64) c1 Nullable(Float64) 1 0.42 +c1 Array(Nullable(Float64)) +[1,0] +[0.42] +c1 Array(Array(Nullable(Float64))) +[[1,2,3],[1,0],[1,1,0]] +[[1,2,3]] diff --git a/tests/queries/0_stateless/02247_read_bools_as_numbers_json.sh b/tests/queries/0_stateless/02247_read_bools_as_numbers_json.sh index 10f050ea6d1..1b689aaf577 100755 --- a/tests/queries/0_stateless/02247_read_bools_as_numbers_json.sh +++ b/tests/queries/0_stateless/02247_read_bools_as_numbers_json.sh @@ -27,6 +27,16 @@ echo -e '{"x" : true} $CLICKHOUSE_CLIENT -q "desc file('$FILE_NAME', 'JSONEachRow')" $CLICKHOUSE_CLIENT -q "select * from file('$FILE_NAME', 'JSONEachRow')" +echo -e '{"x" : [true, false]} +{"x" : [0.42]}' > $DATA_FILE +$CLICKHOUSE_CLIENT -q "desc file('$FILE_NAME', 'JSONEachRow')" +$CLICKHOUSE_CLIENT -q "select * from file('$FILE_NAME', 'JSONEachRow')" + +echo -e '{"x" : [[1, 2, 3], [true, false], [1, true, false]]} +{"x" : [[1, 2, 3]]}' > $DATA_FILE +$CLICKHOUSE_CLIENT -q "desc file('$FILE_NAME', 'JSONEachRow')" +$CLICKHOUSE_CLIENT -q "select * from file('$FILE_NAME', 'JSONEachRow')" + echo -e '[true] [false]' > $DATA_FILE @@ -43,5 +53,14 @@ echo -e '[true] $CLICKHOUSE_CLIENT -q "desc file('$FILE_NAME', 'JSONCompactEachRow')" $CLICKHOUSE_CLIENT -q "select * from file('$FILE_NAME', 'JSONCompactEachRow')" +echo -e '[[true, false]] +[[0.42]]' > $DATA_FILE +$CLICKHOUSE_CLIENT -q "desc file('$FILE_NAME', 'JSONCompactEachRow')" +$CLICKHOUSE_CLIENT -q "select * from file('$FILE_NAME', 'JSONCompactEachRow')" + +echo -e '[[[1, 2, 3], [true, false], [1, true, false]]] +[[[1, 2, 3]]]' > $DATA_FILE +$CLICKHOUSE_CLIENT -q "desc file('$FILE_NAME', 'JSONCompactEachRow')" +$CLICKHOUSE_CLIENT -q "select * from file('$FILE_NAME', 'JSONCompactEachRow')" rm $DATA_FILE diff --git a/tests/queries/0_stateless/02268_json_maps_and_objects.reference b/tests/queries/0_stateless/02268_json_maps_and_objects.reference index cfdc6e7e55c..73a8a8f43cf 100644 --- a/tests/queries/0_stateless/02268_json_maps_and_objects.reference +++ b/tests/queries/0_stateless/02268_json_maps_and_objects.reference @@ -1,2 +1,5 @@ x Object(Nullable(\'json\')) x Object(Nullable(\'json\')) +x Array(Object(Nullable(\'json\'))) +x Array(Object(Nullable(\'json\'))) +x Tuple(Map(String, Nullable(String)), Map(String, Array(Nullable(Float64))), Array(Nullable(Float64))) diff --git a/tests/queries/0_stateless/02268_json_maps_and_objects.sql b/tests/queries/0_stateless/02268_json_maps_and_objects.sql index 83d8fbaac2d..8a9ede6876c 100644 --- a/tests/queries/0_stateless/02268_json_maps_and_objects.sql +++ b/tests/queries/0_stateless/02268_json_maps_and_objects.sql @@ -1,3 +1,6 @@ -- Tags: no-fasttest desc format(JSONEachRow, '{"x" : {"a" : "Some string"}}, {"x" : {"b" : [1, 2, 3]}}, {"x" : {"c" : {"d" : 10}}}'); desc format(JSONEachRow, '{"x" : {"a" : "Some string"}}, {"x" : {"b" : [1, 2, 3], "c" : {"42" : 42}}}'); +desc format(JSONEachRow, '{"x" : [{"a" : "Some string"}]}, {"x" : [{"b" : [1, 2, 3]}]}'); +desc format(JSONEachRow, '{"x" : [{"a" : "Some string"}, {"b" : [1, 2, 3]}]}'); +desc format(JSONEachRow, '{"x" : [{"a" : "Some string"}, {"b" : [1, 2, 3]}, [1, 2, 3]]}'); diff --git a/tests/queries/0_stateless/02325_dates_schema_inference.reference b/tests/queries/0_stateless/02325_dates_schema_inference.reference new file mode 100644 index 00000000000..3ac4ad88f1c --- /dev/null +++ b/tests/queries/0_stateless/02325_dates_schema_inference.reference @@ -0,0 +1,60 @@ +JSONEachRow +x Nullable(Date) +x Nullable(DateTime64(9)) +x Nullable(DateTime64(9)) +x Array(Nullable(Date)) +x Array(Nullable(DateTime64(9))) +x Array(Nullable(DateTime64(9))) +x Map(String, Nullable(DateTime64(9))) +x Array(Nullable(DateTime64(9))) +x Array(Nullable(DateTime64(9))) +x Nullable(DateTime64(9)) +x Array(Nullable(String)) +x Nullable(String) +x Array(Nullable(String)) +x Map(String, Array(Array(Nullable(String)))) +CSV +c1 Nullable(Date) +c1 Nullable(DateTime64(9)) +c1 Nullable(DateTime64(9)) +c1 Array(Nullable(Date)) +c1 Array(Nullable(DateTime64(9))) +c1 Array(Nullable(DateTime64(9))) +c1 Map(String, Nullable(DateTime64(9))) +c1 Array(Nullable(DateTime64(9))) +c1 Array(Nullable(DateTime64(9))) +c1 Nullable(DateTime64(9)) +c1 Array(Nullable(String)) +c1 Nullable(String) +c1 Array(Nullable(String)) +c1 Map(String, Array(Array(Nullable(String)))) +TSV +c1 Nullable(Date) +c1 Nullable(DateTime64(9)) +c1 Nullable(DateTime64(9)) +c1 Array(Nullable(Date)) +c1 Array(Nullable(DateTime64(9))) +c1 Array(Nullable(DateTime64(9))) +c1 Map(String, Nullable(DateTime64(9))) +c1 Array(Nullable(DateTime64(9))) +c1 Array(Nullable(DateTime64(9))) +c1 Nullable(DateTime64(9)) +c1 Array(Nullable(String)) +c1 Nullable(String) +c1 Array(Nullable(String)) +c1 Map(String, Array(Array(Nullable(String)))) +Values +c1 Nullable(Date) +c1 Nullable(DateTime64(9)) +c1 Nullable(DateTime64(9)) +c1 Array(Nullable(Date)) +c1 Array(Nullable(DateTime64(9))) +c1 Array(Nullable(DateTime64(9))) +c1 Map(String, Nullable(DateTime64(9))) +c1 Array(Nullable(DateTime64(9))) +c1 Array(Nullable(DateTime64(9))) +c1 Nullable(DateTime64(9)) +c1 Array(Nullable(String)) +c1 Nullable(String) +c1 Array(Nullable(String)) +c1 Map(String, Array(Array(Nullable(String)))) diff --git a/tests/queries/0_stateless/02325_dates_schema_inference.sql b/tests/queries/0_stateless/02325_dates_schema_inference.sql new file mode 100644 index 00000000000..4527d4d32f5 --- /dev/null +++ b/tests/queries/0_stateless/02325_dates_schema_inference.sql @@ -0,0 +1,68 @@ +set input_format_try_infer_dates=1; +set input_format_try_infer_datetimes=1; + +select 'JSONEachRow'; +desc format(JSONEachRow, '{"x" : "2020-01-01"}'); +desc format(JSONEachRow, '{"x" : "2020-01-01 00:00:00.00000"}'); +desc format(JSONEachRow, '{"x" : "2020-01-01 00:00:00"}'); +desc format(JSONEachRow, '{"x" : ["2020-01-01", "2020-01-02"]}'); +desc format(JSONEachRow, '{"x" : ["2020-01-01", "2020-01-01 00:00:00"]}'); +desc format(JSONEachRow, '{"x" : ["2020-01-01 00:00:00", "2020-01-01 00:00:00"]}'); +desc format(JSONEachRow, '{"x" : {"date1" : "2020-01-01 00:00:00", "date2" : "2020-01-01"}}'); +desc format(JSONEachRow, '{"x" : ["2020-01-01 00:00:00", "2020-01-01"]}\n{"x" : ["2020-01-01"]}'); +desc format(JSONEachRow, '{"x" : ["2020-01-01 00:00:00"]}\n{"x" : ["2020-01-01"]}'); +desc format(JSONEachRow, '{"x" : "2020-01-01 00:00:00"}\n{"x" : "2020-01-01"}'); +desc format(JSONEachRow, '{"x" : ["2020-01-01 00:00:00", "Some string"]}'); +desc format(JSONEachRow, '{"x" : "2020-01-01 00:00:00"}\n{"x" : "Some string"}'); +desc format(JSONEachRow, '{"x" : ["2020-01-01 00:00:00", "2020-01-01"]}\n{"x" : ["2020-01-01", "Some string"]}'); +desc format(JSONEachRow, '{"x" : {"key1" : [["2020-01-01 00:00:00"]], "key2" : [["2020-01-01"]]}}\n{"x" : {"key1" : [["2020-01-01"]], "key2" : [["Some string"]]}}'); + +select 'CSV'; +desc format(CSV, '"2020-01-01"'); +desc format(CSV, '"2020-01-01 00:00:00.00000"'); +desc format(CSV, '"2020-01-01 00:00:00"'); +desc format(CSV, '"[\'2020-01-01\', \'2020-01-02\']"'); +desc format(CSV, '"[\'2020-01-01\', \'2020-01-01 00:00:00\']"'); +desc format(CSV, '"[\'2020-01-01 00:00:00\', \'2020-01-01 00:00:00\']"'); +desc format(CSV, '"{\'date1\' : \'2020-01-01 00:00:00\', \'date2\' : \'2020-01-01\'}"'); +desc format(CSV, '"[\'2020-01-01 00:00:00\', \'2020-01-01\']"\n"[\'2020-01-01\']"'); +desc format(CSV, '"[\'2020-01-01 00:00:00\']"\n"[\'2020-01-01\']"'); +desc format(CSV, '"2020-01-01 00:00:00"\n"2020-01-01"'); +desc format(CSV, '"[\'2020-01-01 00:00:00\', \'Some string\']"'); +desc format(CSV, '"2020-01-01 00:00:00"\n"Some string"'); +desc format(CSV, '"[\'2020-01-01 00:00:00\', \'2020-01-01\']"\n"[\'2020-01-01\', \'Some string\']"'); +desc format(CSV, '"{\'key1\' : [[\'2020-01-01 00:00:00\']], \'key2\' : [[\'2020-01-01\']]}"\n"{\'key1\' : [[\'2020-01-01\']], \'key2\' : [[\'Some string\']]}"'); + +select 'TSV'; +desc format(TSV, '2020-01-01'); +desc format(TSV, '2020-01-01 00:00:00.00000'); +desc format(TSV, '2020-01-01 00:00:00'); +desc format(TSV, '[\'2020-01-01\', \'2020-01-02\']'); +desc format(TSV, '[\'2020-01-01\', \'2020-01-01 00:00:00\']'); +desc format(TSV, '[\'2020-01-01 00:00:00\', \'2020-01-01 00:00:00\']'); +desc format(TSV, '{\'date1\' : \'2020-01-01 00:00:00\', \'date2\' : \'2020-01-01\'}'); +desc format(TSV, '[\'2020-01-01 00:00:00\', \'2020-01-01\']\n[\'2020-01-01\']'); +desc format(TSV, '[\'2020-01-01 00:00:00\']\n[\'2020-01-01\']'); +desc format(TSV, '2020-01-01 00:00:00\n2020-01-01'); +desc format(TSV, '[\'2020-01-01 00:00:00\', \'Some string\']'); +desc format(TSV, '2020-01-01 00:00:00\nSome string'); +desc format(TSV, '[\'2020-01-01 00:00:00\', \'2020-01-01\']\n[\'2020-01-01\', \'Some string\']'); +desc format(TSV, '{\'key1\' : [[\'2020-01-01 00:00:00\']], \'key2\' : [[\'2020-01-01\']]}\n{\'key1\' : [[\'2020-01-01\']], \'key2\' : [[\'Some string\']]}'); + +select 'Values'; +desc format(Values, '(\'2020-01-01\')'); +desc format(Values, '(\'2020-01-01 00:00:00.00000\')'); +desc format(Values, '(\'2020-01-01 00:00:00\')'); +desc format(Values, '([\'2020-01-01\', \'2020-01-02\'])'); +desc format(Values, '([\'2020-01-01\', \'2020-01-01 00:00:00\'])'); +desc format(Values, '([\'2020-01-01 00:00:00\', \'2020-01-01 00:00:00\'])'); +desc format(Values, '({\'date1\' : \'2020-01-01 00:00:00\', \'date2\' : \'2020-01-01\'})'); +desc format(Values, '([\'2020-01-01 00:00:00\', \'2020-01-01\'])\n([\'2020-01-01\'])'); +desc format(Values, '([\'2020-01-01 00:00:00\']), ([\'2020-01-01\'])'); +desc format(Values, '(\'2020-01-01 00:00:00\')\n(\'2020-01-01\')'); +desc format(Values, '([\'2020-01-01 00:00:00\', \'Some string\'])'); +desc format(Values, '(\'2020-01-01 00:00:00\')\n(\'Some string\')'); +desc format(Values, '([\'2020-01-01 00:00:00\', \'2020-01-01\'])\n([\'2020-01-01\', \'Some string\'])'); +desc format(Values, '({\'key1\' : [[\'2020-01-01 00:00:00\']], \'key2\' : [[\'2020-01-01\']]})\n({\'key1\' : [[\'2020-01-01\']], \'key2\' : [[\'Some string\']]})'); + + diff --git a/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.reference b/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.reference new file mode 100644 index 00000000000..2972dd92756 --- /dev/null +++ b/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.reference @@ -0,0 +1,17 @@ +x Nullable(Float64) +x Array(Nullable(Float64)) +x Map(String, Nullable(Float64)) +x Map(String, Array(Nullable(Float64))) +x Nullable(Float64) +x Array(Nullable(Float64)) +x Map(String, Nullable(Float64)) +x Map(String, Array(Nullable(Float64))) +x Array(Nullable(String)) +x Map(String, Nullable(String)) +x Map(String, Array(Nullable(String))) +x Nullable(String) +x Array(Nullable(String)) +x Map(String, Nullable(String)) +x Map(String, Array(Nullable(String))) +x Tuple(Nullable(Float64), Nullable(String)) +x Object(Nullable(\'json\')) diff --git a/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.sql b/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.sql new file mode 100644 index 00000000000..d94e9b2dc23 --- /dev/null +++ b/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.sql @@ -0,0 +1,19 @@ +set input_format_json_try_infer_numbers_from_strings=1; + +desc format(JSONEachRow, '{"x" : "123"}'); +desc format(JSONEachRow, '{"x" : ["123", 123, 12.3]}'); +desc format(JSONEachRow, '{"x" : {"k1" : "123", "k2" : 123}}'); +desc format(JSONEachRow, '{"x" : {"k1" : ["123", "123"], "k2" : [123, 123]}}'); +desc format(JSONEachRow, '{"x" : "123"}\n{"x" : 123}'); +desc format(JSONEachRow, '{"x" : ["123", "456"]}\n{"x" : [123, 456]}'); +desc format(JSONEachRow, '{"x" : {"k1" : "123"}}\n{"x" : {"k2" : 123}}'); +desc format(JSONEachRow, '{"x" : {"k1" : ["123", "123"]}}\n{"x": {"k2" : [123, 123]}}'); +desc format(JSONEachRow, '{"x" : ["123", "Some string"]}'); +desc format(JSONEachRow, '{"x" : {"k1" : "123", "k2" : "Some string"}}'); +desc format(JSONEachRow, '{"x" : {"k1" : ["123", "123"], "k2" : ["Some string"]}}'); +desc format(JSONEachRow, '{"x" : "123"}\n{"x" : "Some string"}'); +desc format(JSONEachRow, '{"x" : ["123", "456"]}\n{"x" : ["Some string"]}'); +desc format(JSONEachRow, '{"x" : {"k1" : "123"}}\n{"x" : {"k2" : "Some string"}}'); +desc format(JSONEachRow, '{"x" : {"k1" : ["123", "123"]}}\n{"x": {"k2" : ["Some string"]}}'); +desc format(JSONEachRow, '{"x" : [123, "Some string"]}'); +desc format(JSONEachRow, '{"x" : {"a" : 123, "b" : "Some string"}}'); diff --git a/tests/queries/0_stateless/02327_try_infer_integers_schema_inference.reference b/tests/queries/0_stateless/02327_try_infer_integers_schema_inference.reference new file mode 100644 index 00000000000..a1cb9f8e5dc --- /dev/null +++ b/tests/queries/0_stateless/02327_try_infer_integers_schema_inference.reference @@ -0,0 +1,36 @@ +JSONEachRow +x Nullable(Int64) +x Array(Nullable(Int64)) +x Map(String, Array(Nullable(Int64))) +x Map(String, Array(Nullable(Int64))) +x Nullable(Float64) +x Nullable(Float64) +x Array(Nullable(Float64)) +x Map(String, Array(Nullable(Float64))) +CSV +c1 Nullable(Int64) +c1 Array(Nullable(Int64)) +c1 Map(String, Array(Nullable(Int64))) +c1 Map(String, Array(Nullable(Int64))) +c1 Nullable(Float64) +c1 Nullable(Float64) +c1 Array(Nullable(Float64)) +c1 Map(String, Array(Nullable(Float64))) +TSV +c1 Nullable(Int64) +c1 Array(Nullable(Int64)) +c1 Map(String, Array(Nullable(Int64))) +c1 Map(String, Array(Nullable(Int64))) +c1 Nullable(Float64) +c1 Nullable(Float64) +c1 Array(Nullable(Float64)) +c1 Map(String, Array(Nullable(Float64))) +Values +c1 Nullable(Int64) +c1 Array(Nullable(Int64)) +c1 Map(String, Array(Nullable(Int64))) +c1 Map(String, Array(Nullable(Int64))) +c1 Nullable(Float64) +c1 Nullable(Float64) +c1 Array(Nullable(Float64)) +c1 Map(String, Array(Nullable(Float64))) diff --git a/tests/queries/0_stateless/02327_try_infer_integers_schema_inference.sql b/tests/queries/0_stateless/02327_try_infer_integers_schema_inference.sql new file mode 100644 index 00000000000..6dc94a643a2 --- /dev/null +++ b/tests/queries/0_stateless/02327_try_infer_integers_schema_inference.sql @@ -0,0 +1,43 @@ +set input_format_try_infer_integers=1; + +select 'JSONEachRow'; +desc format(JSONEachRow, '{"x" : 123}'); +desc format(JSONEachRow, '{"x" : [123, 123]}'); +desc format(JSONEachRow, '{"x" : {"a" : [123, 123]}}'); +desc format(JSONEachRow, '{"x" : {"a" : [123, 123]}}\n{"x" : {"b" : [321, 321]}}'); +desc format(JSONEachRow, '{"x" : 123}\n{"x" : 123.123}'); +desc format(JSONEachRow, '{"x" : 123}\n{"x" : 1e2}'); +desc format(JSONEachRow, '{"x" : [123, 123]}\n{"x" : [321.321, 312]}'); +desc format(JSONEachRow, '{"x" : {"a" : [123, 123]}}\n{"x" : {"b" : [321.321, 123]}}'); + +select 'CSV'; +desc format(CSV, '123'); +desc format(CSV, '"[123, 123]"'); +desc format(CSV, '"{\'a\' : [123, 123]}"'); +desc format(CSV, '"{\'a\' : [123, 123]}"\n"{\'b\' : [321, 321]}"'); +desc format(CSV, '123\n123.123'); +desc format(CSV, '122\n1e2'); +desc format(CSV, '"[123, 123]"\n"[321.321, 312]"'); +desc format(CSV, '"{\'a\' : [123, 123]}"\n"{\'b\' : [321.321, 123]}"'); + +select 'TSV'; +desc format(TSV, '123'); +desc format(TSV, '[123, 123]'); +desc format(TSV, '{\'a\' : [123, 123]}'); +desc format(TSV, '{\'a\' : [123, 123]}\n{\'b\' : [321, 321]}'); +desc format(TSV, '123\n123.123'); +desc format(TSV, '122\n1e2'); +desc format(TSV, '[123, 123]\n[321.321, 312]'); +desc format(TSV, '{\'a\' : [123, 123]}\n{\'b\' : [321.321, 123]}'); + +select 'Values'; +desc format(Values, '(123)'); +desc format(Values, '([123, 123])'); +desc format(Values, '({\'a\' : [123, 123]})'); +desc format(Values, '({\'a\' : [123, 123]}), ({\'b\' : [321, 321]})'); +desc format(Values, '(123), (123.123)'); +desc format(Values, '(122), (1e2)'); +desc format(Values, '([123, 123])\n([321.321, 312])'); +desc format(Values, '({\'a\' : [123, 123]}), ({\'b\' : [321.321, 123]})'); + + From 2b7c6b7ecd1574cf87eff358daedc2025ada45c0 Mon Sep 17 00:00:00 2001 From: avogar Date: Wed, 13 Jul 2022 15:59:04 +0000 Subject: [PATCH 030/672] Remove logging --- src/Formats/EscapingRuleUtils.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Formats/EscapingRuleUtils.cpp b/src/Formats/EscapingRuleUtils.cpp index 0ae7918f682..3aceaeeff1b 100644 --- a/src/Formats/EscapingRuleUtils.cpp +++ b/src/Formats/EscapingRuleUtils.cpp @@ -397,8 +397,6 @@ void transformInferredTypesIfNeededImpl(DataTypes & types, const FormatSettings else { are_maps_equal &= type->equals(*first_map_type); - if (!type->equals(*first_map_type)) - LOG_DEBUG(&Poco::Logger::get("SchemaInference"), "Maps {} and {} are different", type->getName(), first_map_type->getName()); } } else if (isObject(type)) From 772c009f2fbe7e8991407d8f7f8dad0b6652dcaf Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Thu, 14 Jul 2022 18:44:43 +0000 Subject: [PATCH 031/672] Fixing build. --- src/Dictionaries/PostgreSQLDictionarySource.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dictionaries/PostgreSQLDictionarySource.cpp b/src/Dictionaries/PostgreSQLDictionarySource.cpp index bec8283e724..9153b9cb05d 100644 --- a/src/Dictionaries/PostgreSQLDictionarySource.cpp +++ b/src/Dictionaries/PostgreSQLDictionarySource.cpp @@ -185,7 +185,7 @@ void registerDictionarySourcePostgreSQL(DictionarySourceFactory & factory) Block & sample_block, ContextPtr context, const std::string & /* default_database */, - bool created_from_ddl) -> DictionarySourcePtr + [[maybe_unused]] bool created_from_ddl) -> DictionarySourcePtr { #if USE_LIBPQXX const auto settings_config_prefix = config_prefix + ".postgresql"; From 266039ea646d1519e81c5fae786f4022dc168b59 Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Thu, 14 Jul 2022 19:00:17 +0000 Subject: [PATCH 032/672] Correct gTests for DateLUT --- src/Common/DateLUTImpl.h | 2 +- src/Common/tests/gtest_DateLUTImpl.cpp | 9 +++++---- src/Functions/FunctionsConversion.h | 6 ++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Common/DateLUTImpl.h b/src/Common/DateLUTImpl.h index 209afc9e6f0..c8a0e149515 100644 --- a/src/Common/DateLUTImpl.h +++ b/src/Common/DateLUTImpl.h @@ -80,7 +80,7 @@ private: static inline LUTIndex normalizeLUTIndex(Int64 index) { - if (index < 0 ) + if unlikely(index < 0 ) return LUTIndex(0); if (index >= DATE_LUT_SIZE) LUTIndex(DATE_LUT_SIZE - 1); diff --git a/src/Common/tests/gtest_DateLUTImpl.cpp b/src/Common/tests/gtest_DateLUTImpl.cpp index d522448d337..2584d48a8d6 100644 --- a/src/Common/tests/gtest_DateLUTImpl.cpp +++ b/src/Common/tests/gtest_DateLUTImpl.cpp @@ -79,12 +79,13 @@ FailuresCount countFailures(const ::testing::TestResult & test_result) TEST(DateLUTTest, makeDayNumTest) { const DateLUTImpl & lut = DateLUT::instance("UTC"); - EXPECT_EQ(0, lut.makeDayNum(1924, 12, 31)); - EXPECT_EQ(-1, lut.makeDayNum(1924, 12, 31, -1)); + EXPECT_EQ(0, lut.makeDayNum(1899, 12, 31)); + EXPECT_EQ(-1, lut.makeDayNum(1899, 12, 31, -1)); + EXPECT_EQ(-25567, lut.makeDayNum(1900, 1, 1)); EXPECT_EQ(-16436, lut.makeDayNum(1925, 1, 1)); EXPECT_EQ(0, lut.makeDayNum(1970, 1, 1)); - EXPECT_EQ(114635, lut.makeDayNum(2283, 11, 11)); - EXPECT_EQ(114635, lut.makeDayNum(2500, 12, 25)); + EXPECT_EQ(120894, lut.makeDayNum(2399, 12, 31)); + EXPECT_EQ(120894, lut.makeDayNum(2500, 12, 25)); } diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index e0c42401207..9eac9945cb8 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -536,8 +536,10 @@ template struct ConvertImpl struct ConvertImpl : DateTimeTransformImpl> {}; -const time_t LUT_MIN_TIME = -1420070400l; // 1925-01-01 UTC -const time_t LUT_MAX_TIME = 9877248000l; // 2282-12-31 UTC + +const time_t LUT_MIN_TIME = -2208988800l; // 1900-01-01 UTC + +const time_t LUT_MAX_TIME = 10413792000l; // 2300-12-31 UTC /** Conversion of numeric to DateTime64 */ From 1d0818d9cf92ae3496b3e977a7fe306fea7772d3 Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Fri, 15 Jul 2022 10:33:52 +0000 Subject: [PATCH 033/672] Set max year to 2299; Code cleanup; Make working 02245_make_datetime64 test --- src/Common/DateLUTImpl.h | 21 +------ src/Common/tests/gtest_DateLUTImpl.cpp | 2 +- src/Functions/FunctionsConversion.h | 2 +- src/Functions/makeDate.cpp | 5 +- .../02245_make_datetime64.reference | 56 +++++++++---------- .../0_stateless/02245_make_datetime64.sql | 8 +-- 6 files changed, 39 insertions(+), 55 deletions(-) diff --git a/src/Common/DateLUTImpl.h b/src/Common/DateLUTImpl.h index c8a0e149515..98df35b2dfa 100644 --- a/src/Common/DateLUTImpl.h +++ b/src/Common/DateLUTImpl.h @@ -11,13 +11,10 @@ #define DATE_LUT_MIN_YEAR 1900 /// 1900 since majority of financial organizations consider 1900 as an initial year. -// #define DATE_LUT_MAX_YEAR 2258 /// Last supported year (complete) -#define DATE_LUT_MAX_YEAR 2300 /// Last supported year (complete) +#define DATE_LUT_MAX_YEAR 2299 /// Last supported year (complete) #define DATE_LUT_YEARS (1 + DATE_LUT_MAX_YEAR - DATE_LUT_MIN_YEAR) /// Number of years in lookup table -// #define DATE_LUT_SIZE 0x20000 -#define DATE_LUT_SIZE 0x23C1E - +#define DATE_LUT_SIZE 0x23AB1 #define DATE_LUT_MAX (0xFFFFFFFFU - 86400) #define DATE_LUT_MAX_DAY_NUM 0xFFFF @@ -91,68 +88,58 @@ private: friend inline LUTIndex operator+(const LUTIndex & index, const T v) { return normalizeLUTIndex(index.toUnderType() + UInt32(v)); - //return LUTIndex{(index.toUnderType() + UInt32(v)) & date_lut_mask}; } template friend inline LUTIndex operator+(const T v, const LUTIndex & index) { return normalizeLUTIndex(v + index.toUnderType()); - //return LUTIndex{(v + index.toUnderType()) & date_lut_mask}; } friend inline LUTIndex operator+(const LUTIndex & index, const LUTIndex & v) { return normalizeLUTIndex(static_cast(index.toUnderType() + v.toUnderType())); - //return LUTIndex{(index.toUnderType() + v.toUnderType()) & date_lut_mask}; } template friend inline LUTIndex operator-(const LUTIndex & index, const T v) { return normalizeLUTIndex(static_cast(index.toUnderType() - UInt32(v))); - //return LUTIndex{(index.toUnderType() - UInt32(v)) & date_lut_mask}; } template friend inline LUTIndex operator-(const T v, const LUTIndex & index) { return normalizeLUTIndex(static_cast(v - index.toUnderType())); - //return LUTIndex{(v - index.toUnderType()) & date_lut_mask}; } friend inline LUTIndex operator-(const LUTIndex & index, const LUTIndex & v) { return normalizeLUTIndex(static_cast(index.toUnderType() - v.toUnderType())); - //return LUTIndex{(index.toUnderType() - v.toUnderType()) & date_lut_mask}; } template friend inline LUTIndex operator*(const LUTIndex & index, const T v) { return normalizeLUTIndex(index.toUnderType() * UInt32(v)); - // return LUTIndex{(index.toUnderType() * UInt32(v)) /*& date_lut_mask*/}; } template friend inline LUTIndex operator*(const T v, const LUTIndex & index) { return normalizeLUTIndex(v * index.toUnderType()); - // return LUTIndex{(v * index.toUnderType()) /*& date_lut_mask*/}; } template friend inline LUTIndex operator/(const LUTIndex & index, const T v) { return normalizeLUTIndex(index.toUnderType() / UInt32(v)); - // return LUTIndex{(index.toUnderType() / UInt32(v)) /*& date_lut_mask*/}; } template friend inline LUTIndex operator/(const T v, const LUTIndex & index) { return normalizeLUTIndex(UInt32(v) / index.toUnderType()); - // return LUTIndex{(UInt32(v) / index.toUnderType()) /*& date_lut_mask*/}; } public: @@ -267,13 +254,11 @@ private: static inline LUTIndex toLUTIndex(DayNum d) { return normalizeLUTIndex(d + daynum_offset_epoch); - // return LUTIndex{(d + daynum_offset_epoch) /*& date_lut_mask*/}; } static inline LUTIndex toLUTIndex(ExtendedDayNum d) { return normalizeLUTIndex(static_cast(d + daynum_offset_epoch)); - // return LUTIndex{static_cast(d + daynum_offset_epoch) /*& date_lut_mask*/}; } inline LUTIndex toLUTIndex(Time t) const @@ -1098,7 +1083,7 @@ public: auto year_lut_index = (year - DATE_LUT_MIN_YEAR) * 12 + month - 1; UInt32 index = years_months_lut[year_lut_index].toUnderType() + day_of_month - 1; - /// When date is out of range, default value is DATE_LUT_SIZE - 1 (2283-11-11) + /// When date is out of range, default value is DATE_LUT_SIZE - 1 (2299-12-31) return LUTIndex{std::min(index, static_cast(DATE_LUT_SIZE - 1))}; } diff --git a/src/Common/tests/gtest_DateLUTImpl.cpp b/src/Common/tests/gtest_DateLUTImpl.cpp index 2584d48a8d6..95cad92efca 100644 --- a/src/Common/tests/gtest_DateLUTImpl.cpp +++ b/src/Common/tests/gtest_DateLUTImpl.cpp @@ -84,7 +84,7 @@ TEST(DateLUTTest, makeDayNumTest) EXPECT_EQ(-25567, lut.makeDayNum(1900, 1, 1)); EXPECT_EQ(-16436, lut.makeDayNum(1925, 1, 1)); EXPECT_EQ(0, lut.makeDayNum(1970, 1, 1)); - EXPECT_EQ(120894, lut.makeDayNum(2399, 12, 31)); + EXPECT_EQ(120894, lut.makeDayNum(2300, 12, 31)); EXPECT_EQ(120894, lut.makeDayNum(2500, 12, 25)); } diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index 9eac9945cb8..0b0a4e9f21b 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -539,7 +539,7 @@ template struct ConvertImpl Date: Fri, 15 Jul 2022 12:58:08 +0000 Subject: [PATCH 034/672] Correct 02243_make_date32 test --- .../0_stateless/02243_make_date32.reference | 7 +++---- tests/queries/0_stateless/02243_make_date32.sql | 15 +++++++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/tests/queries/0_stateless/02243_make_date32.reference b/tests/queries/0_stateless/02243_make_date32.reference index ac4b10d371a..b986cd285f4 100644 --- a/tests/queries/0_stateless/02243_make_date32.reference +++ b/tests/queries/0_stateless/02243_make_date32.reference @@ -28,10 +28,9 @@ Nullable(Date32) 1969-01-01 1969-12-01 1969-12-31 -2282-01-01 -2283-01-01 -2283-11-11 -1970-01-01 +2298-01-01 +2299-01-01 +2299-12-31 1970-01-01 1970-01-01 1970-01-01 diff --git a/tests/queries/0_stateless/02243_make_date32.sql b/tests/queries/0_stateless/02243_make_date32.sql index c01855546c5..2cf4ac6b358 100644 --- a/tests/queries/0_stateless/02243_make_date32.sql +++ b/tests/queries/0_stateless/02243_make_date32.sql @@ -39,14 +39,13 @@ select makeDate32(2150,1,1); select makeDate32(1969,1,1); select makeDate32(1969,12,1); select makeDate32(1969,12,31); -select makeDate32(2282,1,1); -select makeDate32(2283,1,1); -select makeDate32(2283,11,11); -select makeDate32(2283,11,12); -select makeDate32(2284,1,1); -select makeDate32(1924,1,1); -select makeDate32(1924,12,1); -select makeDate32(1924,12,31); +select makeDate32(2298,1,1); +select makeDate32(2299,1,1); +select makeDate32(2299,12,31); +select makeDate32(2300,1,1); +select makeDate32(1899,1,1); +select makeDate32(1899,12,1); +select makeDate32(1899,12,31); select makeDate32(1970,0,0); select makeDate32(1970,0,1); select makeDate32(1970,1,0); From 9eb7553fce3270472ba15c537ec470f3dae6257e Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 15 Jul 2022 18:03:24 +0200 Subject: [PATCH 035/672] Update tests/integration/helpers/cluster.py --- tests/integration/helpers/cluster.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 4c6304632e7..99b06e6ddb9 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -278,7 +278,6 @@ class ClickHouseCluster: zookeeper_keyfile=None, zookeeper_certfile=None, ): - logging.debug(f"INIT CALLED") for param in list(os.environ.keys()): logging.debug("ENV %40s %s" % (param, os.environ[param])) self.base_path = base_path From 17bc6f1db9edeb7338837825d5a501e897676b18 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 15 Jul 2022 18:11:14 +0200 Subject: [PATCH 036/672] fix tests --- tests/integration/test_distributed_ddl/test.py | 4 +++- .../configs/settings.xml | 7 +++++++ .../test.py | 3 +++ ...75_distributed_ddl_output_mode_long.reference | 16 ++++++++-------- 4 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 tests/integration/test_distributed_ddl_on_cross_replication/configs/settings.xml diff --git a/tests/integration/test_distributed_ddl/test.py b/tests/integration/test_distributed_ddl/test.py index 85d0a5f0999..9c66d4d344e 100755 --- a/tests/integration/test_distributed_ddl/test.py +++ b/tests/integration/test_distributed_ddl/test.py @@ -49,6 +49,7 @@ def test_default_database(test_cluster): test_cluster.ddl_check_query( instance, "CREATE TABLE null ON CLUSTER 'cluster2' (s String DEFAULT 'escape\t\nme') ENGINE = Null", + settings={"distributed_ddl_entry_format_version": 2} ) contents = instance.query( @@ -57,7 +58,8 @@ def test_default_database(test_cluster): assert TSV(contents) == TSV("ch1\tdefault\nch2\ttest2\nch3\tdefault\nch4\ttest2\n") test_cluster.ddl_check_query( - instance, "DROP TABLE IF EXISTS null ON CLUSTER cluster2" + instance, "DROP TABLE IF EXISTS null ON CLUSTER cluster2", + settings={"distributed_ddl_entry_format_version": 2} ) test_cluster.ddl_check_query( instance, "DROP DATABASE IF EXISTS test2 ON CLUSTER 'cluster'" diff --git a/tests/integration/test_distributed_ddl_on_cross_replication/configs/settings.xml b/tests/integration/test_distributed_ddl_on_cross_replication/configs/settings.xml new file mode 100644 index 00000000000..2387e2661e4 --- /dev/null +++ b/tests/integration/test_distributed_ddl_on_cross_replication/configs/settings.xml @@ -0,0 +1,7 @@ + + + + 2 + + + \ No newline at end of file diff --git a/tests/integration/test_distributed_ddl_on_cross_replication/test.py b/tests/integration/test_distributed_ddl_on_cross_replication/test.py index b89091d4034..a58fb212e27 100644 --- a/tests/integration/test_distributed_ddl_on_cross_replication/test.py +++ b/tests/integration/test_distributed_ddl_on_cross_replication/test.py @@ -7,18 +7,21 @@ cluster = ClickHouseCluster(__file__) node1 = cluster.add_instance( "node1", main_configs=["configs/remote_servers.xml"], + user_configs=[ "configs/settings.xml"], with_zookeeper=True, macros={"shard": 1, "replica": 1, "shard_bk": 3, "replica_bk": 2}, ) node2 = cluster.add_instance( "node2", main_configs=["configs/remote_servers.xml"], + user_configs=[ "configs/settings.xml"], with_zookeeper=True, macros={"shard": 2, "replica": 1, "shard_bk": 1, "replica_bk": 2}, ) node3 = cluster.add_instance( "node3", main_configs=["configs/remote_servers.xml"], + user_configs=[ "configs/settings.xml"], with_zookeeper=True, macros={"shard": 3, "replica": 1, "shard_bk": 2, "replica_bk": 2}, ) diff --git a/tests/queries/0_stateless/01175_distributed_ddl_output_mode_long.reference b/tests/queries/0_stateless/01175_distributed_ddl_output_mode_long.reference index bedf9e9a091..4397810b68d 100644 --- a/tests/queries/0_stateless/01175_distributed_ddl_output_mode_long.reference +++ b/tests/queries/0_stateless/01175_distributed_ddl_output_mode_long.reference @@ -27,19 +27,19 @@ localhost 9000 57 Code: 57. Error: Table default.never_throw already exists. (TA localhost 9000 0 1 0 localhost 1 \N \N 1 0 distributed_ddl_queue -2 localhost 9000 test_shard_localhost CREATE TABLE default.none ON CLUSTER test_shard_localhost (`n` int) ENGINE = Memory 1 localhost 9000 Finished 0 1 1 -2 localhost 9000 test_shard_localhost CREATE TABLE default.none ON CLUSTER test_shard_localhost (`n` int) ENGINE = Memory 1 localhost 9000 Finished 57 Code: 57. DB::Error: Table default.none already exists. (TABLE_ALREADY_EXISTS) 1 1 +2 localhost 9000 test_shard_localhost CREATE TABLE default.none ON CLUSTER test_shard_localhost (`n` Int32) ENGINE = Memory 1 localhost 9000 Finished 0 1 1 +2 localhost 9000 test_shard_localhost CREATE TABLE default.none ON CLUSTER test_shard_localhost (`n` Int32) ENGINE = Memory 1 localhost 9000 Finished 57 Code: 57. DB::Error: Table default.none already exists. (TABLE_ALREADY_EXISTS) 1 1 2 localhost 9000 test_unavailable_shard DROP TABLE IF EXISTS default.none ON CLUSTER test_unavailable_shard 1 localhost 1 Inactive \N \N \N \N 2 localhost 9000 test_unavailable_shard DROP TABLE IF EXISTS default.none ON CLUSTER test_unavailable_shard 1 localhost 9000 Finished 0 1 1 -2 localhost 9000 test_shard_localhost CREATE TABLE default.throw ON CLUSTER test_shard_localhost (`n` int) ENGINE = Memory 1 localhost 9000 Finished 0 1 1 -2 localhost 9000 test_shard_localhost CREATE TABLE default.throw ON CLUSTER test_shard_localhost (`n` int) ENGINE = Memory 1 localhost 9000 Finished 57 Code: 57. DB::Error: Table default.throw already exists. (TABLE_ALREADY_EXISTS) 1 1 +2 localhost 9000 test_shard_localhost CREATE TABLE default.throw ON CLUSTER test_shard_localhost (`n` Int32) ENGINE = Memory 1 localhost 9000 Finished 0 1 1 +2 localhost 9000 test_shard_localhost CREATE TABLE default.throw ON CLUSTER test_shard_localhost (`n` Int32) ENGINE = Memory 1 localhost 9000 Finished 57 Code: 57. DB::Error: Table default.throw already exists. (TABLE_ALREADY_EXISTS) 1 1 2 localhost 9000 test_unavailable_shard DROP TABLE IF EXISTS default.throw ON CLUSTER test_unavailable_shard 1 localhost 1 Inactive \N \N \N \N 2 localhost 9000 test_unavailable_shard DROP TABLE IF EXISTS default.throw ON CLUSTER test_unavailable_shard 1 localhost 9000 Finished 0 1 1 -2 localhost 9000 test_shard_localhost CREATE TABLE default.null_status ON CLUSTER test_shard_localhost (`n` int) ENGINE = Memory 1 localhost 9000 Finished 0 1 1 -2 localhost 9000 test_shard_localhost CREATE TABLE default.null_status ON CLUSTER test_shard_localhost (`n` int) ENGINE = Memory 1 localhost 9000 Finished 57 Code: 57. DB::Error: Table default.null_status already exists. (TABLE_ALREADY_EXISTS) 1 1 +2 localhost 9000 test_shard_localhost CREATE TABLE default.null_status ON CLUSTER test_shard_localhost (`n` Int32) ENGINE = Memory 1 localhost 9000 Finished 0 1 1 +2 localhost 9000 test_shard_localhost CREATE TABLE default.null_status ON CLUSTER test_shard_localhost (`n` Int32) ENGINE = Memory 1 localhost 9000 Finished 57 Code: 57. DB::Error: Table default.null_status already exists. (TABLE_ALREADY_EXISTS) 1 1 2 localhost 9000 test_unavailable_shard DROP TABLE IF EXISTS default.null_status ON CLUSTER test_unavailable_shard 1 localhost 1 Inactive \N \N \N \N 2 localhost 9000 test_unavailable_shard DROP TABLE IF EXISTS default.null_status ON CLUSTER test_unavailable_shard 1 localhost 9000 Finished 0 1 1 -2 localhost 9000 test_shard_localhost CREATE TABLE default.never_throw ON CLUSTER test_shard_localhost (`n` int) ENGINE = Memory 1 localhost 9000 Finished 0 1 1 -2 localhost 9000 test_shard_localhost CREATE TABLE default.never_throw ON CLUSTER test_shard_localhost (`n` int) ENGINE = Memory 1 localhost 9000 Finished 57 Code: 57. DB::Error: Table default.never_throw already exists. (TABLE_ALREADY_EXISTS) 1 1 +2 localhost 9000 test_shard_localhost CREATE TABLE default.never_throw ON CLUSTER test_shard_localhost (`n` Int32) ENGINE = Memory 1 localhost 9000 Finished 0 1 1 +2 localhost 9000 test_shard_localhost CREATE TABLE default.never_throw ON CLUSTER test_shard_localhost (`n` Int32) ENGINE = Memory 1 localhost 9000 Finished 57 Code: 57. DB::Error: Table default.never_throw already exists. (TABLE_ALREADY_EXISTS) 1 1 2 localhost 9000 test_unavailable_shard DROP TABLE IF EXISTS default.never_throw ON CLUSTER test_unavailable_shard 1 localhost 1 Inactive \N \N \N \N 2 localhost 9000 test_unavailable_shard DROP TABLE IF EXISTS default.never_throw ON CLUSTER test_unavailable_shard 1 localhost 9000 Finished 0 1 1 From 03194eaeb92c6225112db68b26e24b2f03a8116d Mon Sep 17 00:00:00 2001 From: root Date: Fri, 15 Jul 2022 10:01:30 -0700 Subject: [PATCH 037/672] Feature - Structured Logging Support resubmit --- programs/server/config.xml | 9 + src/Daemon/BaseDaemon.cpp | 204 +++++++++--------- src/Loggers/Loggers.cpp | 105 ++++++--- src/Loggers/OwnFormattingChannel.cpp | 12 +- src/Loggers/OwnFormattingChannel.h | 8 + src/Loggers/OwnJSONPatternFormatter.cpp | 123 +++++++++++ src/Loggers/OwnJSONPatternFormatter.h | 31 +++ .../test_structured_logging_json/__init__.py | 0 .../test_structured_logging_json/test.py | 57 +++++ 9 files changed, 415 insertions(+), 134 deletions(-) create mode 100644 src/Loggers/OwnJSONPatternFormatter.cpp create mode 100644 src/Loggers/OwnJSONPatternFormatter.h create mode 100644 tests/integration/test_structured_logging_json/__init__.py create mode 100644 tests/integration/test_structured_logging_json/test.py diff --git a/programs/server/config.xml b/programs/server/config.xml index 203684a9e00..f8e7fa9d8cf 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -60,6 +60,15 @@ --> + + true diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index 23835df87ea..f9b627aaf79 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -1,13 +1,14 @@ #ifdef HAS_RESERVED_IDENTIFIER -#pragma clang diagnostic ignored "-Wreserved-identifier" +# pragma clang diagnostic ignored "-Wreserved-identifier" #endif #include #include +#include #include -#include #include +#include #include #include #if defined(OS_LINUX) @@ -18,51 +19,52 @@ #include #include -#include -#include #include +#include #include +#include #include +#include +#include #include #include -#include -#include -#include #include -#include #include +#include #include +#include -#include -#include +#include #include #include +#include +#include #include +#include +#include +#include #include #include #include -#include -#include -#include #include #include #include -#include -#include +#include #include -#include +#include #include +#include #include #include #if defined(OS_DARWIN) -# pragma GCC diagnostic ignored "-Wunused-macros" +# pragma GCC diagnostic ignored "-Wunused-macros" // NOLINTNEXTLINE(bugprone-reserved-identifier) -# define _XOPEN_SOURCE 700 // ucontext is not available without _XOPEN_SOURCE +# define _XOPEN_SOURCE 700 // ucontext is not available without _XOPEN_SOURCE #endif #include @@ -92,19 +94,14 @@ static void call_default_signal_handler(int sig) DB::throwFromErrno("Cannot send signal.", DB::ErrorCodes::CANNOT_SEND_SIGNAL); } -static const size_t signal_pipe_buf_size = - sizeof(int) - + sizeof(siginfo_t) - + sizeof(ucontext_t*) - + sizeof(StackTrace) - + sizeof(UInt32) - + sizeof(void*); +static const size_t signal_pipe_buf_size + = sizeof(int) + sizeof(siginfo_t) + sizeof(ucontext_t *) + sizeof(StackTrace) + sizeof(UInt32) + sizeof(void *); -using signal_function = void(int, siginfo_t*, void*); +using signal_function = void(int, siginfo_t *, void *); static void writeSignalIDtoSignalPipe(int sig) { - auto saved_errno = errno; /// We must restore previous value of errno in signal handler. + auto saved_errno = errno; /// We must restore previous value of errno in signal handler. char buf[signal_pipe_buf_size]; DB::WriteBufferFromFileDescriptor out(signal_pipe.fds_rw[1], signal_pipe_buf_size, buf); @@ -133,7 +130,7 @@ static void terminateRequestedSignalHandler(int sig, siginfo_t *, void *) static void signalHandler(int sig, siginfo_t * info, void * context) { DENY_ALLOCATIONS_IN_SCOPE; - auto saved_errno = errno; /// We must restore previous value of errno in signal handler. + auto saved_errno = errno; /// We must restore previous value of errno in signal handler. char buf[signal_pipe_buf_size]; DB::WriteBufferFromFileDescriptorDiscardOnFailure out(signal_pipe.fds_rw[1], signal_pipe_buf_size, buf); @@ -153,7 +150,7 @@ static void signalHandler(int sig, siginfo_t * info, void * context) if (sig != SIGTSTP) /// This signal is used for debugging. { /// The time that is usually enough for separate thread to print info into log. - sleepForSeconds(20); /// FIXME: use some feedback from threads that process stacktrace + sleepForSeconds(20); /// FIXME: use some feedback from threads that process stacktrace call_default_signal_handler(sig); } @@ -162,8 +159,7 @@ static void signalHandler(int sig, siginfo_t * info, void * context) /// Avoid link time dependency on DB/Interpreters - will use this function only when linked. -__attribute__((__weak__)) void collectCrashLog( - Int32 signal, UInt64 thread_id, const String & query_id, const StackTrace & stack_trace); +__attribute__((__weak__)) void collectCrashLog(Int32 signal, UInt64 thread_id, const String & query_id, const StackTrace & stack_trace); /** The thread that read info about signal or std::terminate from pipe. @@ -181,16 +177,14 @@ public: SanitizerTrap = -3, }; - explicit SignalListener(BaseDaemon & daemon_) - : log(&Poco::Logger::get("BaseDaemon")) - , daemon(daemon_) - { - } + explicit SignalListener(BaseDaemon & daemon_) : log(&Poco::Logger::get("BaseDaemon")), daemon(daemon_) { } void run() override { static_assert(PIPE_BUF >= 512); - static_assert(signal_pipe_buf_size <= PIPE_BUF, "Only write of PIPE_BUF to pipe is atomic and the minimal known PIPE_BUF across supported platforms is 512"); + static_assert( + signal_pipe_buf_size <= PIPE_BUF, + "Only write of PIPE_BUF to pipe is atomic and the minimal known PIPE_BUF across supported platforms is 512"); char buf[signal_pipe_buf_size]; DB::ReadBufferFromFileDescriptor in(signal_pipe.fds_rw[0], signal_pipe_buf_size, buf); @@ -225,9 +219,7 @@ public: onTerminate(message, thread_num); } - else if (sig == SIGINT || - sig == SIGQUIT || - sig == SIGTERM) + else if (sig == SIGINT || sig == SIGQUIT || sig == SIGTERM) { daemon.handleSignal(sig); } @@ -264,8 +256,14 @@ private: { size_t pos = message.find('\n'); - LOG_FATAL(log, "(version {}{}, {}) (from thread {}) {}", - VERSION_STRING, VERSION_OFFICIAL, daemon.build_id_info, thread_num, message.substr(0, pos)); + LOG_FATAL( + log, + "(version {}{}, {}) (from thread {}) {}", + VERSION_STRING, + VERSION_OFFICIAL, + daemon.build_id_info, + thread_num, + message.substr(0, pos)); /// Print trace from std::terminate exception line-by-line to make it easy for grep. while (pos != std::string_view::npos) @@ -313,15 +311,29 @@ private: if (query_id.empty()) { - LOG_FATAL(log, "(version {}{}, {}) (from thread {}) (no query) Received signal {} ({})", - VERSION_STRING, VERSION_OFFICIAL, daemon.build_id_info, - thread_num, strsignal(sig), sig); + LOG_FATAL( + log, + "(version {}{}, {}) (from thread {}) (no query) Received signal {} ({})", + VERSION_STRING, + VERSION_OFFICIAL, + daemon.build_id_info, + thread_num, + strsignal(sig), + sig); } else { - LOG_FATAL(log, "(version {}{}, {}) (from thread {}) (query_id: {}) (query: {}) Received signal {} ({})", - VERSION_STRING, VERSION_OFFICIAL, daemon.build_id_info, - thread_num, query_id, query, strsignal(sig), sig); + LOG_FATAL( + log, + "(version {}{}, {}) (from thread {}) (query_id: {}) (query: {}) Received signal {} ({})", + VERSION_STRING, + VERSION_OFFICIAL, + daemon.build_id_info, + thread_num, + query_id, + query, + strsignal(sig), + sig); } String error_message; @@ -395,12 +407,7 @@ private: #if defined(SANITIZER) extern "C" void __sanitizer_set_death_callback(void (*)()); -/// Sanitizers may not expect some function calls from death callback. -/// Let's try to disable instrumentation to avoid possible issues. -/// However, this callback may call other functions that are still instrumented. -/// We can try [[clang::always_inline]] attribute for statements in future (available in clang-15) -/// See https://github.com/google/sanitizers/issues/1543 and https://github.com/google/sanitizers/issues/1549. -static DISABLE_SANITIZER_INSTRUMENTATION void sanitizerDeathCallback() +static void sanitizerDeathCallback() { DENY_ALLOCATIONS_IN_SCOPE; /// Also need to send data via pipe. Otherwise it may lead to deadlocks or failures in printing diagnostic info. @@ -608,7 +615,9 @@ void debugIncreaseOOMScore() LOG_INFO(&Poco::Logger::root(), "Set OOM score adjustment to {}", new_score); } #else -void debugIncreaseOOMScore() {} +void debugIncreaseOOMScore() +{ +} #endif } @@ -731,14 +740,12 @@ void BaseDaemon::initialize(Application & self) if (!log_path.empty()) { std::string path = createDirectory(log_path); - if (is_daemon - && chdir(path.c_str()) != 0) + if (is_daemon && chdir(path.c_str()) != 0) throw Poco::Exception("Cannot change directory to " + path); } else { - if (is_daemon - && chdir("/tmp") != 0) + if (is_daemon && chdir("/tmp") != 0) throw Poco::Exception("Cannot change directory to /tmp"); } @@ -885,50 +892,40 @@ void BaseDaemon::initializeTerminationAndSignalProcessing() void BaseDaemon::logRevision() const { - Poco::Logger::root().information("Starting " + std::string{VERSION_FULL} - + " with revision " + std::to_string(ClickHouseRevision::getVersionRevision()) - + ", " + build_id_info - + ", PID " + std::to_string(getpid())); + Poco::Logger::root().information( + "Starting " + std::string{VERSION_FULL} + " with revision " + std::to_string(ClickHouseRevision::getVersionRevision()) + ", " + + build_id_info + ", PID " + std::to_string(getpid())); } void BaseDaemon::defineOptions(Poco::Util::OptionSet & new_options) { - new_options.addOption( - Poco::Util::Option("config-file", "C", "load configuration from a given file") - .required(false) - .repeatable(false) - .argument("") - .binding("config-file")); + new_options.addOption(Poco::Util::Option("config-file", "C", "load configuration from a given file") + .required(false) + .repeatable(false) + .argument("") + .binding("config-file")); + + new_options.addOption(Poco::Util::Option("log-file", "L", "use given log file") + .required(false) + .repeatable(false) + .argument("") + .binding("logger.log")); + + new_options.addOption(Poco::Util::Option("errorlog-file", "E", "use given log file for errors only") + .required(false) + .repeatable(false) + .argument("") + .binding("logger.errorlog")); new_options.addOption( - Poco::Util::Option("log-file", "L", "use given log file") - .required(false) - .repeatable(false) - .argument("") - .binding("logger.log")); - - new_options.addOption( - Poco::Util::Option("errorlog-file", "E", "use given log file for errors only") - .required(false) - .repeatable(false) - .argument("") - .binding("logger.errorlog")); - - new_options.addOption( - Poco::Util::Option("pid-file", "P", "use given pidfile") - .required(false) - .repeatable(false) - .argument("") - .binding("pid")); + Poco::Util::Option("pid-file", "P", "use given pidfile").required(false).repeatable(false).argument("").binding("pid")); Poco::Util::ServerApplication::defineOptions(new_options); } void BaseDaemon::handleSignal(int signal_id) { - if (signal_id == SIGINT || - signal_id == SIGQUIT || - signal_id == SIGTERM) + if (signal_id == SIGINT || signal_id == SIGQUIT || signal_id == SIGTERM) { std::lock_guard lock(signal_handler_mutex); { @@ -962,7 +959,7 @@ void BaseDaemon::waitForTerminationRequest() { /// NOTE: as we already process signals via pipe, we don't have to block them with sigprocmask in threads std::unique_lock lock(signal_handler_mutex); - signal_event.wait(lock, [this](){ return terminate_signals_counter > 0; }); + signal_event.wait(lock, [this]() { return terminate_signals_counter > 0; }); } @@ -1001,7 +998,7 @@ void BaseDaemon::setupWatchdog() } /// Change short thread name and process name. - setThreadName("clckhouse-watch"); /// 15 characters + setThreadName("clckhouse-watch"); /// 15 characters if (argv0) { @@ -1013,9 +1010,18 @@ void BaseDaemon::setupWatchdog() /// If streaming compression of logs is used then we write watchdog logs to cerr if (config().getRawString("logger.stream_compress", "false") == "true") { - Poco::AutoPtr pf = new OwnPatternFormatter; - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr)); - logger().setChannel(log); + if (config().has("logger.json")) + { + Poco::AutoPtr pf = new OwnJSONPatternFormatter; + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr)); + logger().setChannel(log); + } + else + { + Poco::AutoPtr pf = new OwnPatternFormatter; + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr)); + logger().setChannel(log); + } } logger().information(fmt::format("Will watch for the process with pid {}", pid)); @@ -1073,9 +1079,11 @@ void BaseDaemon::setupWatchdog() if (sig == SIGKILL) { - logger().fatal(fmt::format("Child process was terminated by signal {} (KILL)." + logger().fatal(fmt::format( + "Child process was terminated by signal {} (KILL)." " If it is not done by 'forcestop' command or manually," - " the possible cause is OOM Killer (see 'dmesg' and look at the '/var/log/kern.log' for the details).", sig)); + " the possible cause is OOM Killer (see 'dmesg' and look at the '/var/log/kern.log' for the details).", + sig)); } else { diff --git a/src/Loggers/Loggers.cpp b/src/Loggers/Loggers.cpp index 70205998bb5..fdcd75f761c 100644 --- a/src/Loggers/Loggers.cpp +++ b/src/Loggers/Loggers.cpp @@ -1,17 +1,18 @@ #include "Loggers.h" #include -#include -#include -#include "OwnFormattingChannel.h" -#include "OwnPatternFormatter.h" -#include "OwnSplitChannel.h" #include #include #include +#include +#include +#include "OwnFormattingChannel.h" +#include "OwnJSONPatternFormatter.h" +#include "OwnPatternFormatter.h" +#include "OwnSplitChannel.h" #ifdef WITH_TEXT_LOG - #include +# include #endif #include @@ -20,10 +21,9 @@ namespace fs = std::filesystem; namespace DB { - class SensitiveDataMasker; +class SensitiveDataMasker; } - // TODO: move to libcommon static std::string createDirectory(const std::string & file) { @@ -49,7 +49,6 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log if (auto log = text_log.lock()) split->addTextLog(log, text_log_max_priority); #endif - auto current_logger = config.getString("logger", ""); if (config_logger == current_logger) //-V1051 return; @@ -97,11 +96,22 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log log_file->setProperty(Poco::FileChannel::PROP_ROTATEONOPEN, config.getRawString("logger.rotateOnOpen", "false")); log_file->open(); - Poco::AutoPtr pf = new OwnPatternFormatter; + if (config.has("logger.json")) + { + Poco::AutoPtr pf = new OwnJSONPatternFormatter; - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, log_file); - log->setLevel(log_level); - split->addChannel(log, "log"); + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, log_file); + log->setLevel(log_level); + split->addChannel(log, "log"); + } + else + { + Poco::AutoPtr pf = new OwnPatternFormatter; + + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, log_file); + log->setLevel(log_level); + split->addChannel(log, "log"); + } } const auto errorlog_path = config.getString("logger.errorlog", ""); @@ -133,12 +143,24 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log error_log_file->setProperty(Poco::FileChannel::PROP_FLUSH, config.getRawString("logger.flush", "true")); error_log_file->setProperty(Poco::FileChannel::PROP_ROTATEONOPEN, config.getRawString("logger.rotateOnOpen", "false")); - Poco::AutoPtr pf = new OwnPatternFormatter; + if (config.has("logger.json")) + { + Poco::AutoPtr pf = new OwnJSONPatternFormatter; - Poco::AutoPtr errorlog = new DB::OwnFormattingChannel(pf, error_log_file); - errorlog->setLevel(errorlog_level); - errorlog->open(); - split->addChannel(errorlog, "errorlog"); + Poco::AutoPtr errorlog = new DB::OwnFormattingChannel(pf, error_log_file); + errorlog->setLevel(errorlog_level); + errorlog->open(); + split->addChannel(errorlog, "errorlog"); + } + else + { + Poco::AutoPtr pf = new OwnPatternFormatter; + + Poco::AutoPtr errorlog = new DB::OwnFormattingChannel(pf, error_log_file); + errorlog->setLevel(errorlog_level); + errorlog->open(); + split->addChannel(errorlog, "errorlog"); + } } if (config.getBool("logger.use_syslog", false)) @@ -172,19 +194,29 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log } syslog_channel->open(); - Poco::AutoPtr pf = new OwnPatternFormatter; + if (config.has("logger.json")) + { + Poco::AutoPtr pf = new OwnJSONPatternFormatter; - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, syslog_channel); - log->setLevel(syslog_level); + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, syslog_channel); + log->setLevel(syslog_level); - split->addChannel(log, "syslog"); + split->addChannel(log, "syslog"); + } + else + { + Poco::AutoPtr pf = new OwnPatternFormatter; + + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, syslog_channel); + log->setLevel(syslog_level); + + split->addChannel(log, "syslog"); + } } bool should_log_to_console = isatty(STDIN_FILENO) || isatty(STDERR_FILENO); bool color_logs_by_default = isatty(STDERR_FILENO); - - if (config.getBool("logger.console", false) - || (!config.hasProperty("logger.console") && !is_daemon && should_log_to_console)) + if (config.getBool("logger.console", false) || (!config.hasProperty("logger.console") && !is_daemon && should_log_to_console)) { bool color_enabled = config.getBool("logger.color_terminal", color_logs_by_default); @@ -194,13 +226,23 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log { max_log_level = console_log_level; } - - Poco::AutoPtr pf = new OwnPatternFormatter(color_enabled); - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel); - log->setLevel(console_log_level); - split->addChannel(log, "console"); + if (config.has("logger.json")) + { + Poco::AutoPtr pf = new OwnJSONPatternFormatter(); + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel); + log->setLevel(console_log_level); + split->addChannel(log, "console"); + } + else + { + Poco::AutoPtr pf = new OwnPatternFormatter(color_enabled); + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel); + log->setLevel(console_log_level); + split->addChannel(log, "console"); + } } + split->open(); logger.close(); logger.setChannel(split); @@ -260,8 +302,7 @@ void Loggers::updateLevels(Poco::Util::AbstractConfiguration & config, Poco::Log // Set level to console bool is_daemon = config.getBool("application.runAsDaemon", false); bool should_log_to_console = isatty(STDIN_FILENO) || isatty(STDERR_FILENO); - if (config.getBool("logger.console", false) - || (!config.hasProperty("logger.console") && !is_daemon && should_log_to_console)) + if (config.getBool("logger.console", false) || (!config.hasProperty("logger.console") && !is_daemon && should_log_to_console)) split->setLevel("console", log_level); else split->setLevel("console", 0); diff --git a/src/Loggers/OwnFormattingChannel.cpp b/src/Loggers/OwnFormattingChannel.cpp index f03d155bde7..35f035d44ce 100644 --- a/src/Loggers/OwnFormattingChannel.cpp +++ b/src/Loggers/OwnFormattingChannel.cpp @@ -1,15 +1,19 @@ #include "OwnFormattingChannel.h" +#include "OwnJSONPatternFormatter.h" #include "OwnPatternFormatter.h" - - namespace DB { - void OwnFormattingChannel::logExtended(const ExtendedLogMessage & msg) { if (pChannel && priority >= msg.base.getPriority()) { - if (pFormatter) + if (pFormatterJSON) + { + std::string text; + pFormatterJSON->formatExtendedJSON(msg, text); + pChannel->log(Poco::Message(msg.base, text)); + } + else if (pFormatter) { std::string text; pFormatter->formatExtended(msg, text); diff --git a/src/Loggers/OwnFormattingChannel.h b/src/Loggers/OwnFormattingChannel.h index 0480d0d5061..12e8b24192d 100644 --- a/src/Loggers/OwnFormattingChannel.h +++ b/src/Loggers/OwnFormattingChannel.h @@ -4,6 +4,7 @@ #include #include #include "ExtendedLogChannel.h" +#include "OwnJSONPatternFormatter.h" #include "OwnPatternFormatter.h" @@ -19,6 +20,12 @@ public: { } + explicit OwnFormattingChannel( + Poco::AutoPtr pFormatterJSON_ = nullptr, Poco::AutoPtr pChannel_ = nullptr) + : pFormatterJSON(std::move(pFormatterJSON_)), pChannel(std::move(pChannel_)), priority(Poco::Message::PRIO_TRACE) + { + } + void setChannel(Poco::AutoPtr pChannel_) { pChannel = std::move(pChannel_); } void setLevel(Poco::Message::Priority priority_) { priority = priority_; } @@ -45,6 +52,7 @@ public: private: Poco::AutoPtr pFormatter; + Poco::AutoPtr pFormatterJSON; Poco::AutoPtr pChannel; std::atomic priority; }; diff --git a/src/Loggers/OwnJSONPatternFormatter.cpp b/src/Loggers/OwnJSONPatternFormatter.cpp new file mode 100644 index 00000000000..e9132ef0f0b --- /dev/null +++ b/src/Loggers/OwnJSONPatternFormatter.cpp @@ -0,0 +1,123 @@ +#include "OwnJSONPatternFormatter.h" + +#include +#include +#include +#include +#include +#include +#include + +OwnJSONPatternFormatter::OwnJSONPatternFormatter() : Poco::PatternFormatter("") +{ +} + + +void OwnJSONPatternFormatter::formatExtendedJSON(const DB::ExtendedLogMessage & msg_ext, std::string & text) const +{ + DB::WriteBufferFromString wb(text); + + DB::FormatSettings settings; + String key_name; + + const Poco::Message & msg = msg_ext.base; + DB::writeChar('{', wb); + + key_name = "date_time"; + writeJSONString(StringRef(key_name), wb, settings); + DB::writeChar(':', wb); + + DB::writeChar('\"', wb); + /// Change delimiters in date for compatibility with old logs. + writeDateTimeUnixTimestamp(msg_ext.time_seconds, 0, wb); + DB::writeChar('.', wb); + DB::writeChar('0' + ((msg_ext.time_microseconds / 100000) % 10), wb); + DB::writeChar('0' + ((msg_ext.time_microseconds / 10000) % 10), wb); + DB::writeChar('0' + ((msg_ext.time_microseconds / 1000) % 10), wb); + DB::writeChar('0' + ((msg_ext.time_microseconds / 100) % 10), wb); + DB::writeChar('0' + ((msg_ext.time_microseconds / 10) % 10), wb); + DB::writeChar('0' + ((msg_ext.time_microseconds / 1) % 10), wb); + DB::writeChar('\"', wb); + + DB::writeChar(',', wb); + + key_name = "thread_name"; + writeJSONString(StringRef(key_name), wb, settings); + DB::writeChar(':', wb); + writeJSONString(StringRef(msg.getThread()), wb, settings); + + DB::writeChar(',', wb); + + key_name = "thread_id"; + writeJSONString(StringRef(key_name), wb, settings); + DB::writeChar(':', wb); + DB::writeChar('\"', wb); + DB::writeIntText(msg_ext.thread_id, wb); + DB::writeChar('\"', wb); + + DB::writeChar(',', wb); + + key_name = "level"; + writeJSONString(StringRef(key_name), wb, settings); + DB::writeChar(':', wb); + int priority = static_cast(msg.getPriority()); + writeJSONString(StringRef(getPriorityName(priority)), wb, settings); + + DB::writeChar(',', wb); + + /// We write query_id even in case when it is empty (no query context) + /// just to be convenient for various log parsers. + + key_name = "query_id"; + writeJSONString(StringRef(key_name), wb, settings); + DB::writeChar(':', wb); + writeJSONString(msg_ext.query_id, wb, settings); + + DB::writeChar(',', wb); + + key_name = "logger_name"; + writeJSONString(StringRef(key_name), wb, settings); + DB::writeChar(':', wb); + + writeJSONString(StringRef(msg.getSource()), wb, settings); + + DB::writeChar(',', wb); + + key_name = "message"; + writeJSONString(StringRef(key_name), wb, settings); + DB::writeChar(':', wb); + String msg_text = msg.getText(); + writeJSONString(StringRef(msg_text), wb, settings); + + DB::writeChar(',', wb); + + key_name = "source_file"; + writeJSONString(StringRef(key_name), wb, settings); + DB::writeChar(':', wb); + const char * source_file = msg.getSourceFile(); + if (source_file != nullptr) + { + writeJSONString(StringRef(source_file), wb, settings); + } + + else + { + writeJSONString(StringRef(""), wb, settings); + } + + DB::writeChar(',', wb); + + key_name = "source_line"; + writeJSONString(StringRef(key_name), wb, settings); + DB::writeChar(':', wb); + DB::writeChar('\"', wb); + DB::writeIntText(msg.getSourceLine(), wb); + DB::writeChar('\"', wb); + + DB::writeChar('}', wb); +} + +void OwnJSONPatternFormatter::format(const Poco::Message & msg, std::string & text) +{ + formatExtendedJSON(DB::ExtendedLogMessage::getFrom(msg), text); +} diff --git a/src/Loggers/OwnJSONPatternFormatter.h b/src/Loggers/OwnJSONPatternFormatter.h new file mode 100644 index 00000000000..54e49a6391d --- /dev/null +++ b/src/Loggers/OwnJSONPatternFormatter.h @@ -0,0 +1,31 @@ +#pragma once + + +#include +#include "ExtendedLogChannel.h" + + +/** Format log messages own way in JSON. + * We can't obtain some details using Poco::PatternFormatter. + * + * Firstly, the thread number here is peaked not from Poco::Thread + * threads only, but from all threads with number assigned (see ThreadNumber.h) + * + * Secondly, the local date and time are correctly displayed. + * Poco::PatternFormatter does not work well with local time, + * when timestamps are close to DST timeshift moments. + * - see Poco sources and http://thread.gmane.org/gmane.comp.time.tz/8883 + * + * Also it's made a bit more efficient (unimportant). + */ + +class Loggers; + +class OwnJSONPatternFormatter : public Poco::PatternFormatter +{ +public: + OwnJSONPatternFormatter(); + + void format(const Poco::Message & msg, std::string & text) override; + void formatExtendedJSON(const DB::ExtendedLogMessage & msg_ext, std::string & text) const; +}; diff --git a/tests/integration/test_structured_logging_json/__init__.py b/tests/integration/test_structured_logging_json/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_structured_logging_json/test.py b/tests/integration/test_structured_logging_json/test.py new file mode 100644 index 00000000000..34507a605c6 --- /dev/null +++ b/tests/integration/test_structured_logging_json/test.py @@ -0,0 +1,57 @@ +import pytest +from helpers.cluster import ClickHouseCluster +import logging +import json +from xml.etree import ElementTree + +cluster = ClickHouseCluster(__file__) +node = cluster.add_instance("node", stay_alive=True) + + +@pytest.fixture(scope="module") +def start_cluster(): + try: + cluster.start() + yield cluster + finally: + cluster.shutdown() + + +def get_log_array(logs): + log_array = [] + temp_log = "" + for i in range(0, len(logs)): + temp_log += logs[i] + if logs[i] == "}": + log_array.append(temp_log) + temp_log = "" + return log_array + + +def is_json(log_json): + try: + json.loads(log_json) + except ValueError as e: + return False + return True + + +def test_structured_logging_json_format(start_cluster): + config = node.exec_in_container(["cat", "/etc/clickhouse-server/config.xml"]) + root = ElementTree.fromstring(config) + for logger in root.findall("logger"): + if logger.find("json") is None: + pytest.skip("JSON is not activated in config.xml") + + node.query("SELECT 1") + + logs = node.grep_in_log(" ") + log_array = get_log_array(logs) + result = True + for i in range(0, len(log_array)): + temporary_result = is_json(log_array[i]) + result &= temporary_result + # we will test maximum 5 logs + if i >= min(4, len(log_array) - 1): + break + assert result == True From 25bafe5585a3bebdaccf450eb6c3712ad4f72c3b Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Fri, 15 Jul 2022 21:06:04 +0200 Subject: [PATCH 038/672] Fix test 02325_dates_schema_inference --- tests/queries/0_stateless/02325_dates_schema_inference.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/02325_dates_schema_inference.sql b/tests/queries/0_stateless/02325_dates_schema_inference.sql index 4527d4d32f5..3534a0eb48f 100644 --- a/tests/queries/0_stateless/02325_dates_schema_inference.sql +++ b/tests/queries/0_stateless/02325_dates_schema_inference.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + set input_format_try_infer_dates=1; set input_format_try_infer_datetimes=1; From b5bbf45ba72b7a4770839693c9f9b3111ac29306 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Fri, 15 Jul 2022 21:06:43 +0200 Subject: [PATCH 039/672] Fix test 02326_numbers_from_json_strings_schema_inference --- .../02326_numbers_from_json_strings_schema_inference.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.sql b/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.sql index d94e9b2dc23..2012a53c09d 100644 --- a/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.sql +++ b/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + set input_format_json_try_infer_numbers_from_strings=1; desc format(JSONEachRow, '{"x" : "123"}'); From ddcb8aece8d8b505b73439fccee51d28653dbc9f Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Fri, 15 Jul 2022 21:07:10 +0200 Subject: [PATCH 040/672] Fix test 02327_try_infer_integers_schema_inference --- .../0_stateless/02327_try_infer_integers_schema_inference.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/02327_try_infer_integers_schema_inference.sql b/tests/queries/0_stateless/02327_try_infer_integers_schema_inference.sql index 6dc94a643a2..0ceed178865 100644 --- a/tests/queries/0_stateless/02327_try_infer_integers_schema_inference.sql +++ b/tests/queries/0_stateless/02327_try_infer_integers_schema_inference.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + set input_format_try_infer_integers=1; select 'JSONEachRow'; From af718f7e04013d108ebff72ec20c00f5e8b29174 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 15 Jul 2022 13:06:39 -0700 Subject: [PATCH 041/672] updated config.xml --- programs/server/config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/server/config.xml b/programs/server/config.xml index f8e7fa9d8cf..466301eef2b 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -68,7 +68,7 @@ To enable JSON logging support, just uncomment tag. Having the tag will make it work. For better understanding/visibility, you can add "true" or "1". --> - true + From e5b0c85f8c3827ee874c23c50d005d54402b663e Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Mon, 18 Jul 2022 09:30:43 +0000 Subject: [PATCH 042/672] improve test_rabbitmq_drop_mv: it was flaky and waited 900 second timeout --- .../integration/test_storage_rabbitmq/test.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_storage_rabbitmq/test.py b/tests/integration/test_storage_rabbitmq/test.py index 18b1e9d974b..f5d216a8b92 100644 --- a/tests/integration/test_storage_rabbitmq/test.py +++ b/tests/integration/test_storage_rabbitmq/test.py @@ -2590,9 +2590,17 @@ def test_rabbitmq_drop_mv(rabbitmq_cluster): rabbitmq_exchange_name = 'mv', rabbitmq_format = 'JSONEachRow', rabbitmq_queue_base = 'drop_mv'; + """ + ) + instance.query( + """ CREATE TABLE test.view (key UInt64, value UInt64) ENGINE = MergeTree() ORDER BY key; + """ + ) + instance.query( + """ CREATE MATERIALIZED VIEW test.consumer TO test.view AS SELECT * FROM test.rabbitmq; """ @@ -2611,6 +2619,14 @@ def test_rabbitmq_drop_mv(rabbitmq_cluster): exchange="mv", routing_key="", body=json.dumps({"key": i, "value": i}) ) + start = time.time() + while time.time() - start < 30: + res = instance.query("SELECT COUNT(*) FROM test.view") + if "20" == res: + break + else: + logging.debug(f"Number of rows in test.view: {res}") + instance.query("DROP VIEW test.consumer") for i in range(20, 40): channel.basic_publish( @@ -2643,7 +2659,8 @@ def test_rabbitmq_drop_mv(rabbitmq_cluster): connection.close() count = 0 - while True: + start = time.time() + while time.time() - start < 30: count = int(instance.query("SELECT count() FROM test.rabbitmq")) if count: break @@ -2685,7 +2702,7 @@ def test_rabbitmq_random_detach(rabbitmq_cluster): channel = connection.channel() messages = [] - for i in range(messages_num): + for j in range(messages_num): messages.append(json.dumps({"key": i[0], "value": i[0]})) i[0] += 1 mes_id = str(i) From d243b5c7854b05a80b310c7d113b3512456fd1e0 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Mon, 18 Jul 2022 09:32:19 +0000 Subject: [PATCH 043/672] remove unnecessary try-except --- tests/integration/helpers/cluster.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index e43fb0dd79d..c739583c587 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -586,10 +586,7 @@ class ClickHouseCluster: f"Trying to kill unstopped containers: {unstopped_containers}" ) for id in unstopped_containers: - try: - run_and_check(f"docker kill {id}", shell=True, nothrow=True) - except: - pass + run_and_check(f"docker kill {id}", shell=True, nothrow=True) run_and_check(f"docker rm {id}", shell=True, nothrow=True) unstopped_containers = self.get_running_containers() if unstopped_containers: From 517f821e944451b97101f636949a8070340b5a88 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Mon, 18 Jul 2022 15:02:01 +0200 Subject: [PATCH 044/672] Clean out our clickhouse-server.service from /etc --- packages/clickhouse-server.postinstall | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/clickhouse-server.postinstall b/packages/clickhouse-server.postinstall index 419c13e3daf..ff376b89bd4 100644 --- a/packages/clickhouse-server.postinstall +++ b/packages/clickhouse-server.postinstall @@ -26,7 +26,7 @@ if [ "$1" = configure ] || [ -n "$not_deb_os" ]; then ${CLICKHOUSE_GENERIC_PROGRAM} install --user "${CLICKHOUSE_USER}" --group "${CLICKHOUSE_GROUP}" --pid-path "${CLICKHOUSE_PIDDIR}" --config-path "${CLICKHOUSE_CONFDIR}" --binary-path "${CLICKHOUSE_BINDIR}" --log-path "${CLICKHOUSE_LOGDIR}" --data-path "${CLICKHOUSE_DATADIR}" - if [ -x "/bin/systemctl" ] && [ -f /etc/systemd/system/clickhouse-server.service ] && [ -d /run/systemd/system ]; then + if [ -x "/bin/systemctl" ] && [ -f /lib/systemd/system/clickhouse-server.service ] && [ -d /run/systemd/system ]; then # if old rc.d service present - remove it if [ -x "/etc/init.d/clickhouse-server" ] && [ -x "/usr/sbin/update-rc.d" ]; then /usr/sbin/update-rc.d clickhouse-server remove @@ -44,4 +44,16 @@ if [ "$1" = configure ] || [ -n "$not_deb_os" ]; then fi fi fi + + # /etc/systemd/system/clickhouse-server.service shouldn't be distributed by the package, but it was + # here we delete the service file if it was from our package + if [ -f /etc/systemd/system/clickhouse-server.service ]; then + SHA256=$(sha256sum /etc/systemd/system/clickhouse-server.service | cut -d' ' -f1) + for ref_sum in 7769a14773e811a56f67fd70f7960147217f5e68f746010aec96722e24d289bb 22890012047ea84fbfcebd6e291fe2ef2185cbfdd94a0294e13c8bf9959f58f8 b7790ae57156663c723f92e75ac2508453bf0a7b7e8313bb8081da99e5e88cd3 d1dcc1dbe92dab3ae17baa395f36abf1876b4513df272bf021484923e0111eef ac29ddd32a02eb31670bf5f0018c5d8a3cc006ca7ea572dcf717cb42310dcad7 c62d23052532a70115414833b500b266647d3924eb006a6f3eb673ff0d55f8fa b6b200ffb517afc2b9cf9e25ad8a4afdc0dad5a045bddbfb0174f84cc5a959ed; do + if [ "$SHA256" = "$ref_sum" ]; then + rm /etc/systemd/system/clickhouse-server.service + break + fi + done + fi fi From 7b28bd11c57308eedefdd502f1e1bc190272adfa Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 18 Jul 2022 15:39:10 +0200 Subject: [PATCH 045/672] Fix style --- src/DataTypes/transformTypesRecursively.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DataTypes/transformTypesRecursively.cpp b/src/DataTypes/transformTypesRecursively.cpp index 2f1b689a233..48e9dc60c19 100644 --- a/src/DataTypes/transformTypesRecursively.cpp +++ b/src/DataTypes/transformTypesRecursively.cpp @@ -25,7 +25,7 @@ void transformTypesRecursively(DataTypes & types, std::function Date: Mon, 18 Jul 2022 15:39:53 +0200 Subject: [PATCH 046/672] Fix style --- src/Formats/JSONUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Formats/JSONUtils.cpp b/src/Formats/JSONUtils.cpp index 63c06a8615d..ebf8ef7e783 100644 --- a/src/Formats/JSONUtils.cpp +++ b/src/Formats/JSONUtils.cpp @@ -362,7 +362,7 @@ namespace JSONUtils } }; - DataTypes readRowAndGetDataTypesForJSONCompactEachRow(ReadBuffer & in, const FormatSettings & settings, bool json_strings) + DataTypes readRowAndGetDataTypesForJSONCompactEachRow(ReadBuffer & in, const FormatSettings & settings, bool json_strings) { JSONCompactEachRowFieldsExtractor extractor; return determineColumnDataTypesFromJSONEachRowDataImpl(in, settings, json_strings, extractor); From 857290b586ab707c096651467bc1b4406ac07d78 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 18 Jul 2022 15:40:28 +0200 Subject: [PATCH 047/672] Fix style --- src/Formats/EscapingRuleUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Formats/EscapingRuleUtils.cpp b/src/Formats/EscapingRuleUtils.cpp index 3aceaeeff1b..69684b67071 100644 --- a/src/Formats/EscapingRuleUtils.cpp +++ b/src/Formats/EscapingRuleUtils.cpp @@ -431,7 +431,7 @@ void transformInferredTypesIfNeeded(DataTypePtr & first, DataTypePtr & second, c second = std::move(types[1]); } -void transformInferredJSONTypesIfNeeded(DataTypes & types, const FormatSettings & settings, const std::unordered_set * numbers_parsed_from_json_strings) +void transformInferredJSONTypesIfNeeded(DataTypes & types, const FormatSettings & settings, const std::unordered_set * numbers_parsed_from_json_strings) { transformInferredTypesIfNeededImpl(types, settings, true, numbers_parsed_from_json_strings); } From 81bbc3ef1a162a986766fa3aca3522ff062c0073 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 18 Jul 2022 16:10:25 +0200 Subject: [PATCH 048/672] Turn on new settings by default --- src/Core/Settings.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index b0e7f554717..f700707d1a8 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -686,10 +686,10 @@ static constexpr UInt64 operator""_GiB(unsigned long long value) M(Bool, input_format_arrow_skip_columns_with_unsupported_types_in_schema_inference, false, "Allow to skip columns with unsupported types while schema inference for format Arrow", 0) \ M(String, column_names_for_schema_inference, "", "The list of column names to use in schema inference for formats without column names. The format: 'column1,column2,column3,...'", 0) \ M(Bool, input_format_json_read_bools_as_numbers, true, "Allow to parse bools as numbers in JSON input formats", 0) \ - M(Bool, input_format_json_try_infer_numbers_from_strings, false, "Try to infer numbers from string fields while schema inference", 0) \ - M(Bool, input_format_try_infer_integers, false, "Try to infer numbers from string fields while schema inference in text formats", 0) \ - M(Bool, input_format_try_infer_dates, false, "Try to infer dates from string fields while schema inference in text formats", 0) \ - M(Bool, input_format_try_infer_datetimes, false, "Try to infer datetimes from string fields while schema inference in text formats", 0) \ + M(Bool, input_format_json_try_infer_numbers_from_strings, true, "Try to infer numbers from string fields while schema inference", 0) \ + M(Bool, input_format_try_infer_integers, true, "Try to infer numbers from string fields while schema inference in text formats", 0) \ + M(Bool, input_format_try_infer_dates, true, "Try to infer dates from string fields while schema inference in text formats", 0) \ + M(Bool, input_format_try_infer_datetimes, true, "Try to infer datetimes from string fields while schema inference in text formats", 0) \ M(Bool, input_format_protobuf_flatten_google_wrappers, false, "Enable Google wrappers for regular non-nested columns, e.g. google.protobuf.StringValue 'str' for String column 'str'. For Nullable columns empty wrappers are recognized as defaults, and missing as nulls", 0) \ M(Bool, output_format_protobuf_nullables_with_google_wrappers, false, "When serializing Nullable columns with Google wrappers, serialize default values as empty wrappers. If turned off, default and null values are not serialized", 0) \ M(UInt64, input_format_csv_skip_first_lines, 0, "Skip specified number of lines at the beginning of data in CSV format", 0) \ From 8eb628f7fac80c4731388a1d64e69d76d98e1eb4 Mon Sep 17 00:00:00 2001 From: kssenii Date: Mon, 18 Jul 2022 15:25:21 +0200 Subject: [PATCH 049/672] Update azure --- contrib/azure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/azure b/contrib/azure index ac4b763d4ca..9030dea3088 160000 --- a/contrib/azure +++ b/contrib/azure @@ -1 +1 @@ -Subproject commit ac4b763d4ca40122275f1497cbdc5451337461d9 +Subproject commit 9030dea30881e561b06dc80e0453b9b2a593cdbe From 28fd774df8c90e2e440e0cd8d6fe5aa653e121e1 Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Mon, 18 Jul 2022 16:44:14 +0000 Subject: [PATCH 050/672] Correct 01821_to_date_time_ubsan and 01921_datatype_date32 tests --- src/Common/DateLUTImpl.h | 6 +- .../01821_to_date_time_ubsan.reference | 4 +- .../01921_datatype_date32.reference | 334 +++++++++--------- .../0_stateless/01921_datatype_date32.sql | 11 +- 4 files changed, 177 insertions(+), 178 deletions(-) diff --git a/src/Common/DateLUTImpl.h b/src/Common/DateLUTImpl.h index 98df35b2dfa..a5a2b491a61 100644 --- a/src/Common/DateLUTImpl.h +++ b/src/Common/DateLUTImpl.h @@ -71,7 +71,7 @@ private: static inline LUTIndex normalizeLUTIndex(UInt32 index) { if (index >= DATE_LUT_SIZE) - LUTIndex(DATE_LUT_SIZE - 1); + return LUTIndex(DATE_LUT_SIZE - 1); return LUTIndex{index}; } @@ -80,7 +80,7 @@ private: if unlikely(index < 0 ) return LUTIndex(0); if (index >= DATE_LUT_SIZE) - LUTIndex(DATE_LUT_SIZE - 1); + return LUTIndex(DATE_LUT_SIZE - 1); return LUTIndex{index}; } @@ -93,7 +93,7 @@ private: template friend inline LUTIndex operator+(const T v, const LUTIndex & index) { - return normalizeLUTIndex(v + index.toUnderType()); + return normalizeLUTIndex(static_cast(v + index.toUnderType())); } friend inline LUTIndex operator+(const LUTIndex & index, const LUTIndex & v) diff --git a/tests/queries/0_stateless/01821_to_date_time_ubsan.reference b/tests/queries/0_stateless/01821_to_date_time_ubsan.reference index 0a762ec3b77..28c4987125c 100644 --- a/tests/queries/0_stateless/01821_to_date_time_ubsan.reference +++ b/tests/queries/0_stateless/01821_to_date_time_ubsan.reference @@ -1,2 +1,2 @@ -2283-11-11 23:48:05.4775806 -2283-11-11 23:52:48.54775806 +2299-12-31 23:48:05.4775806 +2299-12-31 23:52:48.54775806 diff --git a/tests/queries/0_stateless/01921_datatype_date32.reference b/tests/queries/0_stateless/01921_datatype_date32.reference index 70eebc76c01..acb0cc4ca59 100644 --- a/tests/queries/0_stateless/01921_datatype_date32.reference +++ b/tests/queries/0_stateless/01921_datatype_date32.reference @@ -1,19 +1,19 @@ -1925-01-01 -1925-01-01 -2282-12-31 -2283-11-11 +1900-01-01 +1900-01-01 +2299-12-15 +2299-12-31 2021-06-22 -------toYear--------- -1925 -1925 -2282 -2283 +1900 +1900 +2299 +2299 2021 -------toMonth--------- 1 1 12 -11 +12 6 -------toQuarter--------- 1 @@ -24,83 +24,83 @@ -------toDayOfMonth--------- 1 1 +15 31 -11 22 -------toDayOfWeek--------- -4 -4 -7 +1 +1 +5 7 2 -------toDayOfYear--------- 1 1 -365 -315 +349 +364 173 -------toHour--------- -------toMinute--------- -------toSecond--------- -------toStartOfDay--------- -2061-02-06 07:28:16 -2061-02-06 07:28:16 -2010-10-17 11:03:28 -2011-08-28 11:03:28 +2036-02-07 07:31:20 +2036-02-07 07:31:20 +2027-10-01 11:03:28 +2027-10-17 11:03:28 2021-06-22 00:00:00 -------toMonday--------- -2104-06-04 -2104-06-04 -2103-07-21 -2104-05-31 +2079-06-07 +2079-06-07 +2120-07-06 +2120-07-20 2021-06-21 -------toISOWeek--------- 1 1 +50 52 -45 25 -------toISOYear--------- -1925 -1925 -2282 -2283 +1900 +1900 +2299 +2299 2021 -------toWeek--------- 0 0 +50 53 -45 25 -------toYearWeek--------- -192452 -192452 -228253 -228345 +189953 +189953 +229950 +229953 202125 -------toStartOfWeek--------- -2104-06-03 -2104-06-03 -2103-07-27 -2104-06-06 +2079-06-06 +2079-06-06 +2120-07-05 +2120-07-26 2021-06-20 -------toStartOfMonth--------- -2104-06-07 -2104-06-07 -2103-06-27 -2104-05-27 +2079-06-07 +2079-06-07 +2120-06-26 +2120-06-26 2021-06-01 -------toStartOfQuarter--------- -2104-06-07 -2104-06-07 -2103-04-27 -2104-04-26 +2079-06-07 +2079-06-07 +2120-04-26 +2120-04-26 2021-04-01 -------toStartOfYear--------- -2104-06-07 -2104-06-07 -2102-07-28 -2103-07-28 +2079-06-07 +2079-06-07 +2119-07-28 +2119-07-28 2021-01-01 -------toStartOfSecond--------- -------toStartOfMinute--------- @@ -109,183 +109,183 @@ -------toStartOfFifteenMinutes--------- -------toStartOfHour--------- -------toStartOfISOYear--------- -2104-06-04 -2104-06-04 -2102-07-29 -2103-07-28 +2079-06-07 +2079-06-07 +2119-07-29 +2119-07-29 2021-01-04 -------toRelativeYearNum--------- -1925 -1925 -2282 -2283 +1900 +1900 +2299 +2299 2021 -------toRelativeQuarterNum--------- -7700 -7700 -9131 -9135 +7600 +7600 +9199 +9199 8085 -------toRelativeMonthNum--------- -23101 -23101 -27396 -27407 +22801 +22801 +27600 +27600 24258 -------toRelativeWeekNum--------- -63189 -63189 -16331 -63188 +61885 +61885 +17216 +17217 2686 -------toRelativeDayNum--------- -49100 -49100 -48784 -49099 +39969 +39969 +54977 +54993 18800 -------toRelativeHourNum--------- -4294572852 -4294572852 -2743677 -2751237 +4294353708 +4294353708 +2892309 +2892693 451197 -------toRelativeMinuteNum--------- -4271299336 -4271299336 -164620620 -165074220 +4258150699 +4258150699 +173538540 +173561580 27071820 -------toRelativeSecondNum--------- -2874889696 -2874889696 -1287302608 -1314518608 +2085971480 +2085971480 +1822377808 +1823760208 1624309200 -------toTime--------- -------toYYYYMM--------- -192501 -192501 -228212 -228311 +190001 +190001 +229912 +229912 202106 -------toYYYYMMDD--------- -19250101 -19250101 -22821231 -22831111 +19000101 +19000101 +22991215 +22991231 20210622 -------toYYYYMMDDhhmmss--------- -19250101000000 -19250101000000 -22821231000000 -22831111000000 +19000101000000 +19000101000000 +22991215000000 +22991231000000 20210622000000 -------addSeconds--------- -1925-01-01 01:00:00.000 -1925-01-01 01:00:00.000 -2282-12-31 01:00:00.000 -2283-11-11 01:00:00.000 +1900-01-01 01:00:00.000 +1900-01-01 01:00:00.000 +2299-12-15 01:00:00.000 +2299-12-31 01:00:00.000 2021-06-22 01:00:00.000 -------addMinutes--------- -1925-01-01 01:00:00.000 -1925-01-01 01:00:00.000 -2282-12-31 01:00:00.000 -2283-11-11 01:00:00.000 +1900-01-01 01:00:00.000 +1900-01-01 01:00:00.000 +2299-12-15 01:00:00.000 +2299-12-31 01:00:00.000 2021-06-22 01:00:00.000 -------addHours--------- -1925-01-01 01:00:00.000 -1925-01-01 01:00:00.000 -2282-12-31 01:00:00.000 -2283-11-11 01:00:00.000 +1900-01-01 01:00:00.000 +1900-01-01 01:00:00.000 +2299-12-15 01:00:00.000 +2299-12-31 01:00:00.000 2021-06-22 01:00:00.000 -------addDays--------- -1925-01-08 -1925-01-08 -2283-01-07 -1925-01-07 +1900-01-08 +1900-01-08 +2299-12-22 +2299-12-31 2021-06-29 -------addWeeks--------- -1925-01-08 -1925-01-08 -2283-01-07 -1925-01-07 +1900-01-08 +1900-01-08 +2299-12-22 +2299-12-31 2021-06-29 -------addMonths--------- -1925-02-01 -1925-02-01 -2283-01-31 -2283-11-11 +1900-02-01 +1900-02-01 +2299-12-31 +2299-12-31 2021-07-22 -------addQuarters--------- -1925-04-01 -1925-04-01 -2283-03-31 -2283-11-11 +1900-04-01 +1900-04-01 +2299-12-31 +2299-12-31 2021-09-22 -------addYears--------- -1926-01-01 -1926-01-01 -2283-11-11 -2283-11-11 +1901-01-01 +1901-01-01 +2299-12-31 +2299-12-31 2022-06-22 -------subtractSeconds--------- -1925-01-01 00:00:00.000 -1925-01-01 00:00:00.000 -2282-12-30 23:00:00.000 -2283-11-10 23:00:00.000 +1900-01-01 00:00:00.000 +1900-01-01 00:00:00.000 +2299-12-14 23:00:00.000 +2299-12-30 23:00:00.000 2021-06-21 23:00:00.000 -------subtractMinutes--------- -1925-01-01 00:00:00.000 -1925-01-01 00:00:00.000 -2282-12-30 23:00:00.000 -2283-11-10 23:00:00.000 +1900-01-01 00:00:00.000 +1900-01-01 00:00:00.000 +2299-12-14 23:00:00.000 +2299-12-30 23:00:00.000 2021-06-21 23:00:00.000 -------subtractHours--------- -1925-01-01 00:00:00.000 -1925-01-01 00:00:00.000 -2282-12-30 23:00:00.000 -2283-11-10 23:00:00.000 +1900-01-01 00:00:00.000 +1900-01-01 00:00:00.000 +2299-12-14 23:00:00.000 +2299-12-30 23:00:00.000 2021-06-21 23:00:00.000 -------subtractDays--------- -2283-11-05 -2283-11-05 -2282-12-24 -2283-11-04 +2299-12-31 +2299-12-31 +2299-12-08 +2299-12-24 2021-06-15 -------subtractWeeks--------- -2283-11-05 -2283-11-05 -2282-12-24 -2283-11-04 +2299-12-31 +2299-12-31 +2299-12-08 +2299-12-24 2021-06-15 -------subtractMonths--------- -1925-01-01 -1925-01-01 -2282-11-30 -2283-10-11 +1900-01-01 +1900-01-01 +2299-11-15 +2299-11-30 2021-05-22 -------subtractQuarters--------- -1925-01-01 -1925-01-01 -2282-09-30 -2283-08-11 +1900-01-01 +1900-01-01 +2299-09-15 +2299-09-30 2021-03-22 -------subtractYears--------- -1925-01-01 -1925-01-01 -2281-12-31 -2282-11-11 +1900-01-01 +1900-01-01 +2298-12-15 +2298-12-31 2020-06-22 -------toDate32--------- -1925-01-01 2000-01-01 -1925-01-01 1925-01-01 -1925-01-01 \N -1925-01-01 +1900-01-01 2000-01-01 +1900-01-01 1900-01-01 +1900-01-01 \N +1900-01-01 \N -1925-01-01 +1900-01-01 1969-12-31 1970-01-01 2149-06-06 2149-06-07 -2283-11-11 +2299-12-31 diff --git a/tests/queries/0_stateless/01921_datatype_date32.sql b/tests/queries/0_stateless/01921_datatype_date32.sql index ef6e3e5ee89..8b65f82825f 100644 --- a/tests/queries/0_stateless/01921_datatype_date32.sql +++ b/tests/queries/0_stateless/01921_datatype_date32.sql @@ -1,7 +1,7 @@ drop table if exists t1; create table t1(x1 Date32) engine Memory; -insert into t1 values ('1925-01-01'),('1924-01-01'),('2282-12-31'),('2283-12-31'),('2021-06-22'); +insert into t1 values ('1900-01-01'),('1899-01-01'),('2299-12-15'),('2300-12-31'),('2021-06-22'); select x1 from t1; select '-------toYear---------'; @@ -113,20 +113,19 @@ select subtractQuarters(x1, 1) from t1; select '-------subtractYears---------'; select subtractYears(x1, 1) from t1; select '-------toDate32---------'; -select toDate32('1925-01-01'), toDate32(toDate('2000-01-01')); -select toDate32OrZero('1924-01-01'), toDate32OrNull('1924-01-01'); +select toDate32('1900-01-01'), toDate32(toDate('2000-01-01')); +select toDate32OrZero('1899-01-01'), toDate32OrNull('1899-01-01'); select toDate32OrZero(''), toDate32OrNull(''); select (select toDate32OrZero('')); select (select toDate32OrNull('')); SELECT toString(T.d) dateStr FROM ( - SELECT '1925-01-01'::Date32 d + SELECT '1900-01-01'::Date32 d UNION ALL SELECT '1969-12-31'::Date32 UNION ALL SELECT '1970-01-01'::Date32 UNION ALL SELECT '2149-06-06'::Date32 UNION ALL SELECT '2149-06-07'::Date32 - UNION ALL SELECT '2283-11-11'::Date32 + UNION ALL SELECT '2299-12-31'::Date32 ) AS T ORDER BY T.d - From 77c66666b323d6aff0c24f3cf288d457f9654953 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 18 Jul 2022 20:21:21 +0000 Subject: [PATCH 051/672] Track mmap. --- .../glibc-compatibility/glibc-compatibility.c | 70 ++++++++++++++++++- src/Interpreters/AsynchronousMetrics.cpp | 3 + 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/base/glibc-compatibility/glibc-compatibility.c b/base/glibc-compatibility/glibc-compatibility.c index e3f62b7948a..6885051ca28 100644 --- a/base/glibc-compatibility/glibc-compatibility.c +++ b/base/glibc-compatibility/glibc-compatibility.c @@ -12,6 +12,8 @@ extern "C" { #include #include #include +#include +#include long int syscall(long int __sysno, ...) __THROW; @@ -100,7 +102,7 @@ int __dprintf_chk (int d, int unused, const char *format, ...) return ret; } -size_t fread(void *ptr, size_t size, size_t nmemb, void *stream); +//size_t fread(void *ptr, size_t size, size_t nmemb, void *stream); size_t __fread_chk(void *ptr, size_t unused, size_t size, size_t nmemb, void *stream) { @@ -133,6 +135,72 @@ int __open_2(const char *path, int oflag) return open(path, oflag); } +#include + +atomic_int_fast64_t mmap_allocated_bytes_total = 0; + +static size_t alignToPageSize(size_t size) +{ + /// We don't need to be precise here. + static size_t page_size_mask = 4096 - 1; + return (size + page_size_mask) & (~page_size_mask); +} + +void * __mmap(void * addr, size_t length, int prot, int flags, int fd, off_t offset); + +void * mmap(void * addr, size_t length, int prot, int flags, int fd, off_t offset) +{ + void * res = __mmap(addr, length, prot, flags, fd, offset); + //char is_executable_file = (flags & MAP_ANONYMOUS) == 0 && (flags & PROT_EXEC) != 0; + if (res != (void *) -1) + { + // size_t prev = atomic_load(&mmap_allocated_bytes_total) & 4095; + // fprintf( stderr, "++++++ %zu\n", prev); + atomic_fetch_add_explicit(&mmap_allocated_bytes_total, alignToPageSize(length), memory_order_relaxed); + } + return res; +} + +int __munmap(void * addr, size_t length); + +int munmap(void * addr, size_t length) +{ + int res = __munmap(addr, length); + if (res == 0) + { + + // size_t prev = atomic_load(&mmap_allocated_bytes_total) & 4095; + atomic_fetch_sub_explicit(&mmap_allocated_bytes_total, alignToPageSize(length), memory_order_relaxed); + // fprintf( stderr, "------ %zu\n", prev); + } + return res; +} + +void * mremap(void *old_addr, size_t old_len, size_t new_len, int flags, ...) +{ + va_list ap; + void *new_addr; + + va_start(ap, flags); + new_addr = va_arg(ap, void *); + va_end(ap); + + void * res = (void *)syscall(SYS_mremap, old_addr, old_len, new_len, flags, new_addr); + + if (res != (void *) -1) + { + // size_t prev = atomic_load(&mmap_allocated_bytes_total) & 4095; + // fprintf( stderr, "========= %zu\n", prev); + atomic_fetch_sub_explicit(&mmap_allocated_bytes_total, alignToPageSize(old_len), memory_order_relaxed); + atomic_fetch_add_explicit(&mmap_allocated_bytes_total, alignToPageSize(new_len), memory_order_relaxed); + } + return res; +} + +extern int_fast64_t getTotalMemoryMappedBytes() +{ + return atomic_load_explicit(&mmap_allocated_bytes_total, memory_order_relaxed); +} #include diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index 9275c1d6840..911e5271217 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -35,6 +35,7 @@ namespace CurrentMetrics extern const Metric MemoryTracking; } +extern "C" int_fast64_t getTotalMemoryMappedBytes(); namespace DB { @@ -651,6 +652,8 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti } } + new_values["MemoryMapped"] = getTotalMemoryMappedBytes(); + /// Process process memory usage according to OS #if defined(OS_LINUX) || defined(OS_FREEBSD) { From 7ca666381205de39c5a1e74b2e390ac76d207d3f Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 18 Jul 2022 20:21:37 +0000 Subject: [PATCH 052/672] Revert "Track mmap." This reverts commit 77c66666b323d6aff0c24f3cf288d457f9654953. --- .../glibc-compatibility/glibc-compatibility.c | 70 +------------------ src/Interpreters/AsynchronousMetrics.cpp | 3 - 2 files changed, 1 insertion(+), 72 deletions(-) diff --git a/base/glibc-compatibility/glibc-compatibility.c b/base/glibc-compatibility/glibc-compatibility.c index 6885051ca28..e3f62b7948a 100644 --- a/base/glibc-compatibility/glibc-compatibility.c +++ b/base/glibc-compatibility/glibc-compatibility.c @@ -12,8 +12,6 @@ extern "C" { #include #include #include -#include -#include long int syscall(long int __sysno, ...) __THROW; @@ -102,7 +100,7 @@ int __dprintf_chk (int d, int unused, const char *format, ...) return ret; } -//size_t fread(void *ptr, size_t size, size_t nmemb, void *stream); +size_t fread(void *ptr, size_t size, size_t nmemb, void *stream); size_t __fread_chk(void *ptr, size_t unused, size_t size, size_t nmemb, void *stream) { @@ -135,72 +133,6 @@ int __open_2(const char *path, int oflag) return open(path, oflag); } -#include - -atomic_int_fast64_t mmap_allocated_bytes_total = 0; - -static size_t alignToPageSize(size_t size) -{ - /// We don't need to be precise here. - static size_t page_size_mask = 4096 - 1; - return (size + page_size_mask) & (~page_size_mask); -} - -void * __mmap(void * addr, size_t length, int prot, int flags, int fd, off_t offset); - -void * mmap(void * addr, size_t length, int prot, int flags, int fd, off_t offset) -{ - void * res = __mmap(addr, length, prot, flags, fd, offset); - //char is_executable_file = (flags & MAP_ANONYMOUS) == 0 && (flags & PROT_EXEC) != 0; - if (res != (void *) -1) - { - // size_t prev = atomic_load(&mmap_allocated_bytes_total) & 4095; - // fprintf( stderr, "++++++ %zu\n", prev); - atomic_fetch_add_explicit(&mmap_allocated_bytes_total, alignToPageSize(length), memory_order_relaxed); - } - return res; -} - -int __munmap(void * addr, size_t length); - -int munmap(void * addr, size_t length) -{ - int res = __munmap(addr, length); - if (res == 0) - { - - // size_t prev = atomic_load(&mmap_allocated_bytes_total) & 4095; - atomic_fetch_sub_explicit(&mmap_allocated_bytes_total, alignToPageSize(length), memory_order_relaxed); - // fprintf( stderr, "------ %zu\n", prev); - } - return res; -} - -void * mremap(void *old_addr, size_t old_len, size_t new_len, int flags, ...) -{ - va_list ap; - void *new_addr; - - va_start(ap, flags); - new_addr = va_arg(ap, void *); - va_end(ap); - - void * res = (void *)syscall(SYS_mremap, old_addr, old_len, new_len, flags, new_addr); - - if (res != (void *) -1) - { - // size_t prev = atomic_load(&mmap_allocated_bytes_total) & 4095; - // fprintf( stderr, "========= %zu\n", prev); - atomic_fetch_sub_explicit(&mmap_allocated_bytes_total, alignToPageSize(old_len), memory_order_relaxed); - atomic_fetch_add_explicit(&mmap_allocated_bytes_total, alignToPageSize(new_len), memory_order_relaxed); - } - return res; -} - -extern int_fast64_t getTotalMemoryMappedBytes() -{ - return atomic_load_explicit(&mmap_allocated_bytes_total, memory_order_relaxed); -} #include diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index 911e5271217..9275c1d6840 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -35,7 +35,6 @@ namespace CurrentMetrics extern const Metric MemoryTracking; } -extern "C" int_fast64_t getTotalMemoryMappedBytes(); namespace DB { @@ -652,8 +651,6 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti } } - new_values["MemoryMapped"] = getTotalMemoryMappedBytes(); - /// Process process memory usage according to OS #if defined(OS_LINUX) || defined(OS_FREEBSD) { From 87e5b31598b44360fff26c2b900f996461426288 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 18 Jul 2022 21:16:16 +0000 Subject: [PATCH 053/672] Do not include memory buffered by allocator into drift. --- src/Common/MemoryTracker.cpp | 13 +++-- src/Interpreters/AsynchronousMetrics.cpp | 68 +++++++++++++----------- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/Common/MemoryTracker.cpp b/src/Common/MemoryTracker.cpp index ba097568477..23ae758ccdd 100644 --- a/src/Common/MemoryTracker.cpp +++ b/src/Common/MemoryTracker.cpp @@ -136,6 +136,7 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT { /// For global memory tracker always update memory usage. amount.fetch_add(size, std::memory_order_relaxed); + rss.fetch_add(size, std::memory_order_relaxed); auto metric_loaded = metric.load(std::memory_order_relaxed); if (metric_loaded != CurrentMetrics::end()) @@ -207,17 +208,21 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT allocation_traced = true; } + Int64 amount_to_check = will_be; bool used_rss_counter = false; + /// For Global memory tracker, additionally check RSS. + /// It is needed to avoid possible OOM. + /// We can't track all memory allocations from external libraries (yet). if (level == VariableContext::Global) { - if (Int64 current_rss = rss.load(std::memory_order_relaxed); unlikely(current_rss + size > will_be)) + if (Int64 current_rss = size + rss.fetch_add(size, std::memory_order_relaxed); unlikely(current_rss > will_be)) { used_rss_counter = true; - will_be = current_rss + size; + amount_to_check = current_rss; } } - if (unlikely(current_hard_limit && will_be > current_hard_limit) && memoryTrackerCanThrow(level, false) && throw_if_memory_exceeded) + if (unlikely(current_hard_limit && amount_to_check > current_hard_limit) && memoryTrackerCanThrow(level, false) && throw_if_memory_exceeded) { OvercommitResult overcommit_result = OvercommitResult::NONE; if (auto * overcommit_tracker_ptr = overcommit_tracker.load(std::memory_order_relaxed); overcommit_tracker_ptr != nullptr && query_tracker != nullptr) @@ -235,7 +240,7 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT description ? " " : "", description ? description : "", used_rss_counter ? "(RSS) " : "", - formatReadableSizeWithBinarySuffix(will_be), + formatReadableSizeWithBinarySuffix(amount_to_check), size, formatReadableSizeWithBinarySuffix(current_hard_limit), toDescription(overcommit_result)); diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index 9275c1d6840..2dda8f5b39c 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -387,7 +387,7 @@ uint64_t updateJemallocEpoch() } template -static void saveJemallocMetricImpl(AsynchronousMetricValues & values, +static Value saveJemallocMetricImpl(AsynchronousMetricValues & values, const std::string & jemalloc_full_name, const std::string & clickhouse_full_name) { @@ -395,22 +395,23 @@ static void saveJemallocMetricImpl(AsynchronousMetricValues & values, size_t size = sizeof(value); mallctl(jemalloc_full_name.c_str(), &value, &size, nullptr, 0); values[clickhouse_full_name] = value; + return value; } template -static void saveJemallocMetric(AsynchronousMetricValues & values, +static Value saveJemallocMetric(AsynchronousMetricValues & values, const std::string & metric_name) { - saveJemallocMetricImpl(values, + return saveJemallocMetricImpl(values, fmt::format("stats.{}", metric_name), fmt::format("jemalloc.{}", metric_name)); } template -static void saveAllArenasMetric(AsynchronousMetricValues & values, +static Value saveAllArenasMetric(AsynchronousMetricValues & values, const std::string & metric_name) { - saveJemallocMetricImpl(values, + return saveJemallocMetricImpl(values, fmt::format("stats.arenas.{}.{}", MALLCTL_ARENAS_ALL, metric_name), fmt::format("jemalloc.arenas.all.{}", metric_name)); } @@ -651,6 +652,31 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti } } +#if USE_JEMALLOC + // 'epoch' is a special mallctl -- it updates the statistics. Without it, all + // the following calls will return stale values. It increments and returns + // the current epoch number, which might be useful to log as a sanity check. + auto epoch = updateJemallocEpoch(); + new_values["jemalloc.epoch"] = epoch; + + // Collect the statistics themselves. + size_t je_malloc_allocated = saveJemallocMetric(new_values, "allocated"); + saveJemallocMetric(new_values, "active"); + saveJemallocMetric(new_values, "metadata"); + saveJemallocMetric(new_values, "metadata_thp"); + saveJemallocMetric(new_values, "resident"); + size_t je_malloc_mapped = saveJemallocMetric(new_values, "mapped"); + saveJemallocMetric(new_values, "retained"); + saveJemallocMetric(new_values, "background_thread.num_threads"); + saveJemallocMetric(new_values, "background_thread.num_runs"); + saveJemallocMetric(new_values, "background_thread.run_intervals"); + saveAllArenasMetric(new_values, "pactive"); + saveAllArenasMetric(new_values, "pdirty"); + saveAllArenasMetric(new_values, "pmuzzy"); + saveAllArenasMetric(new_values, "dirty_purged"); + saveAllArenasMetric(new_values, "muzzy_purged"); +#endif + /// Process process memory usage according to OS #if defined(OS_LINUX) || defined(OS_FREEBSD) { @@ -672,6 +698,13 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti Int64 peak = total_memory_tracker.getPeak(); Int64 rss = data.resident; +#if USE_JEMALLOC + /// This is a memory which is kept by allocator. + /// Remove it from RSS to decrease memory drift. + rss -= je_malloc_mapped - je_malloc_allocated; +#endif + /// In theory, the difference between RSS and tracked memory should be caused by + /// external libraries which allocation we can't track. Int64 rss_drift = rss - amount; Int64 difference = rss_drift - last_logged_rss_drift; @@ -1470,31 +1503,6 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti } } -#if USE_JEMALLOC - // 'epoch' is a special mallctl -- it updates the statistics. Without it, all - // the following calls will return stale values. It increments and returns - // the current epoch number, which might be useful to log as a sanity check. - auto epoch = updateJemallocEpoch(); - new_values["jemalloc.epoch"] = epoch; - - // Collect the statistics themselves. - saveJemallocMetric(new_values, "allocated"); - saveJemallocMetric(new_values, "active"); - saveJemallocMetric(new_values, "metadata"); - saveJemallocMetric(new_values, "metadata_thp"); - saveJemallocMetric(new_values, "resident"); - saveJemallocMetric(new_values, "mapped"); - saveJemallocMetric(new_values, "retained"); - saveJemallocMetric(new_values, "background_thread.num_threads"); - saveJemallocMetric(new_values, "background_thread.num_runs"); - saveJemallocMetric(new_values, "background_thread.run_intervals"); - saveAllArenasMetric(new_values, "pactive"); - saveAllArenasMetric(new_values, "pdirty"); - saveAllArenasMetric(new_values, "pmuzzy"); - saveAllArenasMetric(new_values, "dirty_purged"); - saveAllArenasMetric(new_values, "muzzy_purged"); -#endif - /// Add more metrics as you wish. new_values["AsynchronousMetricsCalculationTimeSpent"] = watch.elapsedSeconds(); From 5a3cb0771152897a5267f92bed155f9c1589e776 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 18 Jul 2022 21:24:41 +0000 Subject: [PATCH 054/672] Fix rss --- src/Common/MemoryTracker.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Common/MemoryTracker.cpp b/src/Common/MemoryTracker.cpp index 1e370d9d400..0d7d7003ae2 100644 --- a/src/Common/MemoryTracker.cpp +++ b/src/Common/MemoryTracker.cpp @@ -301,6 +301,7 @@ void MemoryTracker::free(Int64 size) { /// For global memory tracker always update memory usage. amount.fetch_sub(size, std::memory_order_relaxed); + rss.fetch_sub(size, std::memory_order_relaxed); auto metric_loaded = metric.load(std::memory_order_relaxed); if (metric_loaded != CurrentMetrics::end()) From fc6982f9e28ade1b7077be4381b5079f46244c19 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 19 Jul 2022 10:10:02 +0000 Subject: [PATCH 055/672] Fixing test. --- src/Common/MemoryTracker.cpp | 5 +++-- .../test.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Common/MemoryTracker.cpp b/src/Common/MemoryTracker.cpp index 0d7d7003ae2..ef5e0c45373 100644 --- a/src/Common/MemoryTracker.cpp +++ b/src/Common/MemoryTracker.cpp @@ -295,14 +295,15 @@ bool MemoryTracker::updatePeak(Int64 will_be, bool log_memory_usage) void MemoryTracker::free(Int64 size) { + if (level == VariableContext::Global) + rss.fetch_sub(size, std::memory_order_relaxed); + if (MemoryTrackerBlockerInThread::isBlocked(level)) { if (level == VariableContext::Global) { /// For global memory tracker always update memory usage. amount.fetch_sub(size, std::memory_order_relaxed); - rss.fetch_sub(size, std::memory_order_relaxed); - auto metric_loaded = metric.load(std::memory_order_relaxed); if (metric_loaded != CurrentMetrics::end()) CurrentMetrics::add(metric_loaded, size); diff --git a/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py b/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py index c95bbfda708..eba3aeff303 100644 --- a/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py +++ b/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py @@ -42,7 +42,8 @@ def test_memory_tracking_total(): "bash", "-c", "clickhouse local -q \"SELECT arrayStringConcat(arrayMap(x->toString(cityHash64(x)), range(1000)), ' ') from numbers(10000)\" > data.json", - ] + ], + user="root" ) for it in range(0, 20): From dd1349e2d4496a2771050d2c38f49f40fc5eb07d Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 19 Jul 2022 16:20:59 +0000 Subject: [PATCH 056/672] Fix test. --- .../test_dictionaries_ddl/configs/allow_remote_node.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/test_dictionaries_ddl/configs/allow_remote_node.xml b/tests/integration/test_dictionaries_ddl/configs/allow_remote_node.xml index 031e2257ba0..259ce83c03f 100644 --- a/tests/integration/test_dictionaries_ddl/configs/allow_remote_node.xml +++ b/tests/integration/test_dictionaries_ddl/configs/allow_remote_node.xml @@ -1,5 +1,6 @@ node1 + node4 From 5f9c293963de0a217c31abacdf143e4851a0149e Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Tue, 19 Jul 2022 17:29:08 +0000 Subject: [PATCH 057/672] Fix addDays() and addWeeks() in upper and lower limits of Date and Date32 --- src/Common/DateLUTImpl.h | 4 ---- .../FunctionDateOrDateTimeAddInterval.h | 18 ++++++++++++++---- .../01921_datatype_date32.reference | 8 ++++---- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Common/DateLUTImpl.h b/src/Common/DateLUTImpl.h index a5a2b491a61..d1b226e18b1 100644 --- a/src/Common/DateLUTImpl.h +++ b/src/Common/DateLUTImpl.h @@ -19,10 +19,7 @@ #define DATE_LUT_MAX (0xFFFFFFFFU - 86400) #define DATE_LUT_MAX_DAY_NUM 0xFFFF -#define DAYNUM_OFFSET_EPOCH 25567 - /// Max int value of Date32, DATE LUT cache size minus daynum_offset_epoch -// #define DATE_LUT_MAX_EXTEND_DAY_NUM (DATE_LUT_SIZE - (Time)DAYNUM_OFFSET_EPOCH) #define DATE_LUT_MAX_EXTEND_DAY_NUM (DATE_LUT_SIZE - 25567) /// A constant to add to time_t so every supported time point becomes non-negative and still has the same remainder of division by 3600. @@ -195,7 +192,6 @@ private: /// Offset to epoch in days (ExtendedDayNum) of the first day in LUT. /// "epoch" is the Unix Epoch (starts at unix timestamp zero) - // static constexpr UInt32 daynum_offset_epoch = DAYNUM_OFFSET_EPOCH; static constexpr UInt32 daynum_offset_epoch = 25567; static_assert(daynum_offset_epoch == (1970 - DATE_LUT_MIN_YEAR) * 365 + (1970 - DATE_LUT_MIN_YEAR / 4 * 4) / 4); diff --git a/src/Functions/FunctionDateOrDateTimeAddInterval.h b/src/Functions/FunctionDateOrDateTimeAddInterval.h index fbfc9e9bc1f..10408093240 100644 --- a/src/Functions/FunctionDateOrDateTimeAddInterval.h +++ b/src/Functions/FunctionDateOrDateTimeAddInterval.h @@ -288,12 +288,17 @@ struct AddDaysImpl static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &, UInt16 = 0) { - return d + delta; + Int64 r = d + delta; + if (r < 0) + return 0; + if (r > 65535) + return 65535; + return static_cast(r); } static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl &, UInt16 = 0) { - return d + delta; + return std::max(static_cast(d + delta), -static_cast(DateLUT::instance().getDayNumOffsetEpoch())); } }; @@ -322,12 +327,17 @@ struct AddWeeksImpl static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int32 delta, const DateLUTImpl &, UInt16 = 0) { - return d + delta * 7; + Int64 r = d + delta * 7; + if (r < 0) + return 0; + if (r > 65535) + return 65535; + return static_cast(r); } static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int32 delta, const DateLUTImpl &, UInt16 = 0) { - return d + delta * 7; + return std::max(static_cast(d + delta * 7), -static_cast(DateLUT::instance().getDayNumOffsetEpoch())); } }; diff --git a/tests/queries/0_stateless/01921_datatype_date32.reference b/tests/queries/0_stateless/01921_datatype_date32.reference index acb0cc4ca59..8cc9cc2886f 100644 --- a/tests/queries/0_stateless/01921_datatype_date32.reference +++ b/tests/queries/0_stateless/01921_datatype_date32.reference @@ -248,14 +248,14 @@ 2299-12-30 23:00:00.000 2021-06-21 23:00:00.000 -------subtractDays--------- -2299-12-31 -2299-12-31 +1900-01-01 +1900-01-01 2299-12-08 2299-12-24 2021-06-15 -------subtractWeeks--------- -2299-12-31 -2299-12-31 +1900-01-01 +1900-01-01 2299-12-08 2299-12-24 2021-06-15 From a00bcd389fb923491c5df37b18f4220ee3e2cd12 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 19 Jul 2022 19:33:14 +0200 Subject: [PATCH 058/672] Fixing build --- src/Storages/MergeTree/KeyCondition.cpp | 198 ++++++------------------ 1 file changed, 47 insertions(+), 151 deletions(-) diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index e62968453e4..e590d98fce2 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -241,6 +241,11 @@ public: /// Simple literal out_value = lit->value; out_type = block_with_constants.getByName(column_name).type; + + /// If constant is not Null, we can assume it's type is not Nullable as well. + if (!out_value.isNull()) + out_type = removeNullable(out_type); + return true; } else if (block_with_constants.has(column_name) && isColumnConst(*block_with_constants.getByName(column_name).column)) @@ -249,6 +254,10 @@ public: const auto & expr_info = block_with_constants.getByName(column_name); out_value = (*expr_info.column)[0]; out_type = expr_info.type; + + if (!out_value.isNull()) + out_type = removeNullable(out_type); + return true; } } @@ -258,6 +267,10 @@ public: { out_value = (*dag->column)[0]; out_type = dag->result_type; + + if (!out_value.isNull()) + out_type = removeNullable(out_type); + return true; } } @@ -1067,156 +1080,34 @@ void KeyCondition::traverseAST(const Tree & node, ContextPtr context, Block & bl rpn.emplace_back(std::move(element)); } -bool KeyCondition::canConstantBeWrappedByMonotonicFunctions( - const Tree & node, +/** The key functional expression constraint may be inferred from a plain column in the expression. + * For example, if the key contains `toStartOfHour(Timestamp)` and query contains `WHERE Timestamp >= now()`, + * it can be assumed that if `toStartOfHour()` is monotonic on [now(), inf), the `toStartOfHour(Timestamp) >= toStartOfHour(now())` + * condition also holds, so the index may be used to select only parts satisfying this condition. + * + * To check the assumption, we'd need to assert that the inverse function to this transformation is also monotonic, however the + * inversion isn't exported (or even viable for not strictly monotonic functions such as `toStartOfHour()`). + * Instead, we can qualify only functions that do not transform the range (for example rounding), + * which while not strictly monotonic, are monotonic everywhere on the input range. + */ +bool KeyCondition::transformConstantWithValidFunctions( + const String & expr_name, size_t & out_key_column_num, DataTypePtr & out_key_column_type, Field & out_value, - DataTypePtr & out_type) + DataTypePtr & out_type, + std::function always_monotonic) const { - String expr_name = node.getColumnName(); - - if (array_joined_columns.count(expr_name)) - return false; - - if (key_subexpr_names.count(expr_name) == 0) - return false; - - /// TODO Nullable index is not yet landed. - if (out_value.isNull()) - return false; - const auto & sample_block = key_expr->getSampleBlock(); - /** The key functional expression constraint may be inferred from a plain column in the expression. - * For example, if the key contains `toStartOfHour(Timestamp)` and query contains `WHERE Timestamp >= now()`, - * it can be assumed that if `toStartOfHour()` is monotonic on [now(), inf), the `toStartOfHour(Timestamp) >= toStartOfHour(now())` - * condition also holds, so the index may be used to select only parts satisfying this condition. - * - * To check the assumption, we'd need to assert that the inverse function to this transformation is also monotonic, however the - * inversion isn't exported (or even viable for not strictly monotonic functions such as `toStartOfHour()`). - * Instead, we can qualify only functions that do not transform the range (for example rounding), - * which while not strictly monotonic, are monotonic everywhere on the input range. - */ - for (const auto & dag_node : key_expr->getNodes()) + for (const auto & node : key_expr->getNodes()) { - auto it = key_columns.find(dag_node.result_name); + auto it = key_columns.find(node.result_name); if (it != key_columns.end()) { std::stack chain; - const auto * cur_node = &dag_node; - bool is_valid_chain = true; - - while (is_valid_chain) - { - if (cur_node->result_name == expr_name) - break; - - chain.push(cur_node); - - if (cur_node->type == ActionsDAG::ActionType::FUNCTION && cur_node->children.size() == 1) - { - const auto * next_node = cur_node->children.front(); - - if (!cur_node->function_base->hasInformationAboutMonotonicity()) - is_valid_chain = false; - else - { - /// Range is irrelevant in this case. - auto monotonicity = cur_node->function_base->getMonotonicityForRange( - *next_node->result_type, Field(), Field()); - if (!monotonicity.is_always_monotonic) - is_valid_chain = false; - } - - cur_node = next_node; - } - else if (cur_node->type == ActionsDAG::ActionType::ALIAS) - cur_node = cur_node->children.front(); - else - is_valid_chain = false; - } - - if (is_valid_chain && !chain.empty()) - { - /// Here we cast constant to the input type. - /// It is not clear, why this works in general. - /// I can imagine the case when expression like `column < const` is legal, - /// but `type(column)` and `type(const)` are of different types, - /// and const cannot be casted to column type. - /// (There could be `superType(type(column), type(const))` which is used for comparison). - /// - /// However, looks like this case newer happenes (I could not find such). - /// Let's assume that any two comparable types are castable to each other. - auto const_type = cur_node->result_type; - auto const_column = out_type->createColumnConst(1, out_value); - auto const_value = (*castColumn({const_column, out_type, ""}, const_type))[0]; - - while (!chain.empty()) - { - const auto * func = chain.top(); - chain.pop(); - - if (func->type != ActionsDAG::ActionType::FUNCTION) - continue; - - std::tie(const_value, const_type) = - applyFunctionForFieldOfUnknownType(func->function_base, const_type, const_value); - } - - out_key_column_num = it->second; - out_key_column_type = sample_block.getByName(it->first).type; - out_value = const_value; - out_type = const_type; - return true; - } - } - } - - return false; -} - -/// Looking for possible transformation of `column = constant` into `partition_expr = function(constant)` -bool KeyCondition::canConstantBeWrappedByFunctions( - const Tree & node, size_t & out_key_column_num, DataTypePtr & out_key_column_type, Field & out_value, DataTypePtr & out_type) -{ - String expr_name = node.getColumnName(); - - if (array_joined_columns.count(expr_name)) - return false; - - if (key_subexpr_names.count(expr_name) == 0) - { - /// Let's check another one case. - /// If our storage was created with moduloLegacy in partition key, - /// We can assume that `modulo(...) = const` is the same as `moduloLegacy(...) = const`. - /// Replace modulo to moduloLegacy in AST and check if we also have such a column. - /// - /// We do not check this in canConstantBeWrappedByMonotonicFunctions. - /// The case `f(modulo(...))` for totally monotonic `f ` is consedered to be rare. - /// - /// Note: for negative values, we can filter more partitions then needed. - expr_name = node.getColumnNameLegacy(); - - if (key_subexpr_names.count(expr_name) == 0) - return false; - } - - const auto & sample_block = key_expr->getSampleBlock(); - - /// TODO Nullable index is not yet landed. - if (out_value.isNull()) - return false; - - for (const auto & dag_node : key_expr->getNodes()) - { - auto it = key_columns.find(dag_node.result_name); - if (it != key_columns.end()) - { - std::stack chain; - - const auto * cur_node = &dag_node; + const auto * cur_node = &node; bool is_valid_chain = true; while (is_valid_chain) @@ -1255,12 +1146,18 @@ bool KeyCondition::canConstantBeWrappedByFunctions( if (is_valid_chain) { + /// Here we cast constant to the input type. + /// It is not clear, why this works in general. + /// I can imagine the case when expression like `column < const` is legal, + /// but `type(column)` and `type(const)` are of different types, + /// and const cannot be casted to column type. + /// (There could be `superType(type(column), type(const))` which is used for comparison). + /// + /// However, looks like this case newer happenes (I could not find such). + /// Let's assume that any two comparable types are castable to each other. auto const_type = cur_node->result_type; auto const_column = out_type->createColumnConst(1, out_value); - auto const_value = (*castColumnAccurateOrNull({const_column, out_type, ""}, const_type))[0]; - - if (const_value.isNull()) - return false; + auto const_value = (*castColumn({const_column, out_type, ""}, const_type))[0]; while (!chain.empty()) { @@ -1304,17 +1201,18 @@ bool KeyCondition::canConstantBeWrappedByFunctions( } } } + return false; } bool KeyCondition::canConstantBeWrappedByMonotonicFunctions( - const ASTPtr & node, + const Tree & node, size_t & out_key_column_num, DataTypePtr & out_key_column_type, Field & out_value, DataTypePtr & out_type) { - String expr_name = node->getColumnNameWithoutAlias(); + String expr_name = node.getColumnName(); if (array_joined_columns.contains(expr_name)) return false; @@ -1343,9 +1241,9 @@ bool KeyCondition::canConstantBeWrappedByMonotonicFunctions( /// Looking for possible transformation of `column = constant` into `partition_expr = function(constant)` bool KeyCondition::canConstantBeWrappedByFunctions( - const ASTPtr & ast, size_t & out_key_column_num, DataTypePtr & out_key_column_type, Field & out_value, DataTypePtr & out_type) + const Tree & node, size_t & out_key_column_num, DataTypePtr & out_key_column_type, Field & out_value, DataTypePtr & out_type) { - String expr_name = ast->getColumnNameWithoutAlias(); + String expr_name = node.getColumnName(); if (array_joined_columns.contains(expr_name)) return false; @@ -1358,12 +1256,10 @@ bool KeyCondition::canConstantBeWrappedByFunctions( /// Replace modulo to moduloLegacy in AST and check if we also have such a column. /// /// We do not check this in canConstantBeWrappedByMonotonicFunctions. - /// The case `f(modulo(...))` for totally monotonic `f ` is consedered to be rare. + /// The case `f(modulo(...))` for totally monotonic `f ` is considered to be rare. /// /// Note: for negative values, we can filter more partitions then needed. - auto adjusted_ast = ast->clone(); - KeyDescription::moduloToModuloLegacyRecursive(adjusted_ast); - expr_name = adjusted_ast->getColumnName(); + expr_name = node.getColumnNameLegacy(); if (!key_subexpr_names.contains(expr_name)) return false; From afab965d7bf8691bd0f79e96ae138376b310d0d4 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 19 Jul 2022 12:38:47 -0700 Subject: [PATCH 059/672] style check --- src/Daemon/BaseDaemon.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index f9b627aaf79..74521a5fd4a 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include From f570cde815d1fb55f83b3c1d5819b57b8baec3ee Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 19 Jul 2022 20:19:57 +0000 Subject: [PATCH 060/672] Fixing build. --- src/Functions/indexHint.h | 2 ++ src/Interpreters/ActionsVisitor.cpp | 3 ++- .../Optimizations/optimizePrimaryKeyCondition.cpp | 9 +-------- src/Processors/QueryPlan/ReadFromMergeTree.cpp | 6 ++++++ src/Processors/QueryPlan/ReadFromMergeTree.h | 3 +++ src/Storages/Hive/StorageHive.cpp | 4 ++-- src/Storages/MergeTree/MergeTreeData.cpp | 2 +- src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp | 3 +++ 8 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Functions/indexHint.h b/src/Functions/indexHint.h index 0dc44e613ef..3b71c7a5585 100644 --- a/src/Functions/indexHint.h +++ b/src/Functions/indexHint.h @@ -44,6 +44,8 @@ public: bool isSuitableForConstantFolding() const override { return false; } + bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; } + String getName() const override { return name; diff --git a/src/Interpreters/ActionsVisitor.cpp b/src/Interpreters/ActionsVisitor.cpp index b78c4a1e48f..e31fea977ae 100644 --- a/src/Interpreters/ActionsVisitor.cpp +++ b/src/Interpreters/ActionsVisitor.cpp @@ -951,7 +951,8 @@ void ActionsMatcher::visit(const ASTFunction & node, const ASTPtr & ast, Data & data.no_subqueries, data.no_makeset, data.only_consts, - /*create_source_for_in*/ false); + /*create_source_for_in*/ false, + data.aggregation_keys_info); NamesWithAliases args; diff --git a/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp b/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp index 5724e50f893..f9abe662006 100644 --- a/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp +++ b/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include @@ -35,13 +34,7 @@ void optimizePrimaryKeyCondition(QueryPlan::Node & root) if (auto * filter_step = typeid_cast(frame.node->step.get())) { auto * child = frame.node->children.at(0); - if (typeid_cast(child->step.get())) - { - auto * child_child = child->children.at(0); - if (auto * read_from_merge_tree = typeid_cast(child_child->step.get())) - read_from_merge_tree->addFilter(filter_step->getExpression(), filter_step->getFilterColumnName()); - } - else if (auto * read_from_merge_tree = typeid_cast(child->step.get())) + if (auto * read_from_merge_tree = typeid_cast(child->step.get())) read_from_merge_tree->addFilter(filter_step->getExpression(), filter_step->getFilterColumnName()); } diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index 36acc235dae..3306178ec11 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -831,6 +831,9 @@ MergeTreeDataSelectAnalysisResultPtr ReadFromMergeTree::selectRangesToRead(Merge { return selectRangesToRead( std::move(parts), + prewhere_info, + added_filter, + added_filter_column_name, storage_snapshot->metadata, storage_snapshot->getMetadataForQuery(), query_info, @@ -845,6 +848,9 @@ MergeTreeDataSelectAnalysisResultPtr ReadFromMergeTree::selectRangesToRead(Merge MergeTreeDataSelectAnalysisResultPtr ReadFromMergeTree::selectRangesToRead( MergeTreeData::DataPartsVector parts, + const PrewhereInfoPtr & prewhere_info, + const ActionsDAGPtr & added_filter, + const std::string & added_filter_column_name, const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.h b/src/Processors/QueryPlan/ReadFromMergeTree.h index 5231f2c0e05..9257bbdd119 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.h +++ b/src/Processors/QueryPlan/ReadFromMergeTree.h @@ -124,6 +124,9 @@ public: static MergeTreeDataSelectAnalysisResultPtr selectRangesToRead( MergeTreeData::DataPartsVector parts, + const PrewhereInfoPtr & prewhere_info, + const ActionsDAGPtr & added_filter, + const std::string & added_filter_column_name, const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, diff --git a/src/Storages/Hive/StorageHive.cpp b/src/Storages/Hive/StorageHive.cpp index ddd9f526091..2ddca5e9ba6 100644 --- a/src/Storages/Hive/StorageHive.cpp +++ b/src/Storages/Hive/StorageHive.cpp @@ -623,7 +623,7 @@ HiveFiles StorageHive::collectHiveFilesFromPartition( for (size_t i = 0; i < partition_names.size(); ++i) ranges.emplace_back(fields[i]); - const KeyCondition partition_key_condition(query_info, getContext(), partition_names, partition_minmax_idx_expr); + const KeyCondition partition_key_condition(query_info.query, query_info.syntax_analyzer_result, query_info.sets, getContext(), partition_names, partition_minmax_idx_expr); if (!partition_key_condition.checkInHyperrectangle(ranges, partition_types).can_be_true) return {}; } @@ -691,7 +691,7 @@ HiveFilePtr StorageHive::getHiveFileIfNeeded( if (prune_level >= PruneLevel::File) { - const KeyCondition hivefile_key_condition(query_info, getContext(), hivefile_name_types.getNames(), hivefile_minmax_idx_expr); + const KeyCondition hivefile_key_condition(query_info.query, query_info.syntax_analyzer_result, query_info.sets, getContext(), hivefile_name_types.getNames(), hivefile_minmax_idx_expr); if (hive_file->useFileMinMaxIndex()) { /// Load file level minmax index and apply diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 3b39100b3de..c2f29ed4c3c 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -5240,7 +5240,7 @@ Block MergeTreeData::getMinMaxCountProjectionBlock( minmax_columns_types = getMinMaxColumnsTypes(partition_key); minmax_idx_condition.emplace( - query_info, + query_info.query, query_info.syntax_analyzer_result, query_info.sets, query_context, minmax_columns_names, getMinMaxExpr(partition_key, ExpressionActionsSettings::fromContext(query_context))); diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 1a55844ad94..9381d6f5264 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -1267,6 +1267,9 @@ MergeTreeDataSelectAnalysisResultPtr MergeTreeDataSelectExecutor::estimateNumMar return ReadFromMergeTree::selectRangesToRead( std::move(parts), + query_info.prewhere_info, + nullptr, + "", metadata_snapshot_base, metadata_snapshot, query_info, From d67e06191584b312cc281138fa0c86f9dd3a2785 Mon Sep 17 00:00:00 2001 From: HeenaBansal2009 Date: Tue, 19 Jul 2022 21:38:36 -0700 Subject: [PATCH 061/672] Clickhouse-local fixes --- programs/local/LocalServer.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index 4f3b92bbcf0..376fa1c2bc5 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -325,7 +325,20 @@ void LocalServer::setupUsers() access_control.setPlaintextPasswordAllowed(config().getBool("allow_plaintext_password", true)); if (config().has("users_config") || config().has("config-file") || fs::exists("config.xml")) { - const auto users_config_path = config().getString("users_config", config().getString("config-file", "config.xml")); + String config_path = config().getString("config-file",""); + bool has_user_directories = config().has("user_directories"); + const auto config_dir = std::filesystem::path{config_path}.remove_filename().string(); + String users_config_path = config().getString("users_config",""); + if (users_config_path.empty()) + { + if (!has_user_directories) + users_config_path = config_path; + } + if (has_user_directories) + users_config_path = config().getString("user_directories.users_xml.path",""); + + if (std::filesystem::path{users_config_path}.is_relative() && std::filesystem::exists(config_dir + users_config_path)) + users_config_path = config_dir + users_config_path; ConfigProcessor config_processor(users_config_path); const auto loaded_config = config_processor.loadConfig(); users_config = loaded_config.configuration; @@ -575,7 +588,8 @@ void LocalServer::processConfig() * Otherwise, metadata of temporary File(format, EXPLICIT_PATH) tables will pollute metadata/ directory; * if such tables will not be dropped, clickhouse-server will not be able to load them due to security reasons. */ - std::string default_database = config().getString("default_database", "_local"); + std::string default_database = config().getString("default_database", ""); + default_database = default_database + "_local"; DatabaseCatalog::instance().attachDatabase(default_database, std::make_shared(default_database, global_context)); global_context->setCurrentDatabase(default_database); applyCmdOptions(global_context); From 877854b14332195fab91b778c667ee020fec19bd Mon Sep 17 00:00:00 2001 From: root Date: Wed, 20 Jul 2022 05:45:04 -0700 Subject: [PATCH 062/672] resolved build error saying - error: method 'formatExtendedJSON' can be made static --- src/Loggers/OwnJSONPatternFormatter.cpp | 2 +- src/Loggers/OwnJSONPatternFormatter.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Loggers/OwnJSONPatternFormatter.cpp b/src/Loggers/OwnJSONPatternFormatter.cpp index e9132ef0f0b..825cb1e9806 100644 --- a/src/Loggers/OwnJSONPatternFormatter.cpp +++ b/src/Loggers/OwnJSONPatternFormatter.cpp @@ -13,7 +13,7 @@ OwnJSONPatternFormatter::OwnJSONPatternFormatter() : Poco::PatternFormatter("") } -void OwnJSONPatternFormatter::formatExtendedJSON(const DB::ExtendedLogMessage & msg_ext, std::string & text) const +void OwnJSONPatternFormatter::formatExtendedJSON(const DB::ExtendedLogMessage & msg_ext, std::string & text) { DB::WriteBufferFromString wb(text); diff --git a/src/Loggers/OwnJSONPatternFormatter.h b/src/Loggers/OwnJSONPatternFormatter.h index 54e49a6391d..0523869aebb 100644 --- a/src/Loggers/OwnJSONPatternFormatter.h +++ b/src/Loggers/OwnJSONPatternFormatter.h @@ -27,5 +27,5 @@ public: OwnJSONPatternFormatter(); void format(const Poco::Message & msg, std::string & text) override; - void formatExtendedJSON(const DB::ExtendedLogMessage & msg_ext, std::string & text) const; + static void formatExtendedJSON(const DB::ExtendedLogMessage & msg_ext, std::string & text); }; From 9cbf1fd7fdd132849f6bd3c486f98e8d9da60fbb Mon Sep 17 00:00:00 2001 From: avogar Date: Wed, 20 Jul 2022 14:17:25 +0000 Subject: [PATCH 063/672] Fix tests --- ...01825_type_json_schema_inference.reference | 6 +- .../02149_schema_inference.reference | 90 +++++++++---------- .../02240_tskv_schema_inference_bug.reference | 4 +- .../02245_s3_schema_desc.reference | 24 ++--- ...csv_best_effort_schema_inference.reference | 14 +-- ...2305_schema_inference_with_globs.reference | 4 +- ...om_json_strings_schema_inference.reference | 16 ++-- 7 files changed, 79 insertions(+), 79 deletions(-) diff --git a/tests/queries/0_stateless/01825_type_json_schema_inference.reference b/tests/queries/0_stateless/01825_type_json_schema_inference.reference index a2089ea3366..22dfdee96a7 100644 --- a/tests/queries/0_stateless/01825_type_json_schema_inference.reference +++ b/tests/queries/0_stateless/01825_type_json_schema_inference.reference @@ -3,6 +3,6 @@ Tuple(k1 Nullable(Int8), k2 Tuple(k3 Nullable(String), k4 Nested(k5 Nullable(Int8), k6 Nullable(Int8))), some Nullable(Int8)) {"id":"1","obj":"aaa","s":"foo"} {"id":"2","obj":"bbb","s":"bar"} -{"map":{"k1":1,"k2":2},"obj":{"k1":1,"k2.k3":2},"map_type":"Map(String, Nullable(Float64))","obj_type":"Object(Nullable('json'))"} -{"obj":{"k1":1,"k2":2},"map":{"k1":"1","k2":"2"}} -Tuple(k1 Float64, k2 Float64) +{"map":{"k1":"1","k2":"2"},"obj":{"k1":1,"k2.k3":2},"map_type":"Map(String, Nullable(Int64))","obj_type":"Object(Nullable('json'))"} +{"obj":{"k1":"1","k2":"2"},"map":{"k1":"1","k2":"2"}} +Tuple(k1 Int64, k2 Int64) diff --git a/tests/queries/0_stateless/02149_schema_inference.reference b/tests/queries/0_stateless/02149_schema_inference.reference index 2d7dd5caca7..954e1813157 100644 --- a/tests/queries/0_stateless/02149_schema_inference.reference +++ b/tests/queries/0_stateless/02149_schema_inference.reference @@ -1,22 +1,22 @@ TSV -c1 Nullable(Float64) +c1 Nullable(Int64) c2 Nullable(String) -c3 Array(Nullable(Float64)) -c4 Tuple(Nullable(Float64), Nullable(Float64), Nullable(Float64)) +c3 Array(Nullable(Int64)) +c4 Tuple(Nullable(Int64), Nullable(Int64), Nullable(Int64)) 42 Some string [1,2,3,4] (1,2,3) 42 abcd [] (4,5,6) TSVWithNames -number Nullable(Float64) +number Nullable(Int64) string Nullable(String) -array Array(Nullable(Float64)) -tuple Tuple(Nullable(Float64), Nullable(Float64), Nullable(Float64)) +array Array(Nullable(Int64)) +tuple Tuple(Nullable(Int64), Nullable(Int64), Nullable(Int64)) 42 Some string [1,2,3,4] (1,2,3) 42 abcd [] (4,5,6) CSV -c1 Nullable(Float64) +c1 Nullable(Int64) c2 Nullable(String) c3 Array(Tuple(Array(Nullable(Float64)), Nullable(String))) -c4 Array(Nullable(Float64)) +c4 Array(Nullable(Int64)) \N Some string [([1,2.3],'String'),([],NULL)] [1,NULL,3] 42 \N [([1,2.3],'String'),([3],'abcd')] [4,5,6] c1 Nullable(String) @@ -28,54 +28,54 @@ c2 Nullable(String) \N [NULL, NULL] \N [] CSVWithNames -a Nullable(Float64) +a Nullable(Int64) b Nullable(String) c Array(Tuple(Array(Nullable(Float64)), Nullable(String))) -d Array(Nullable(Float64)) +d Array(Nullable(Int64)) \N Some string [([1,2.3],'String'),([],NULL)] [1,NULL,3] 42 \N [([1,2.3],'String'),([3],'abcd')] [4,5,6] JSONCompactEachRow c1 Nullable(Float64) -c2 Array(Tuple(Nullable(Float64), Nullable(String))) -c3 Map(String, Nullable(Float64)) +c2 Array(Tuple(Nullable(Int64), Nullable(String))) +c3 Map(String, Nullable(Int64)) c4 Nullable(Bool) 42.42 [(1,'String'),(2,'abcd')] {'key':42,'key2':24} true -c1 Nullable(Float64) -c2 Array(Tuple(Nullable(Float64), Nullable(String))) -c3 Map(String, Nullable(Float64)) +c1 Nullable(Int64) +c2 Array(Tuple(Nullable(Int64), Nullable(String))) +c3 Map(String, Nullable(Int64)) c4 Nullable(Bool) \N [(1,'String'),(2,NULL)] {'key':NULL,'key2':24} \N 32 [(2,'String 2'),(3,'hello')] {'key3':4242,'key4':2424} true JSONCompactEachRowWithNames a Nullable(Float64) -b Array(Tuple(Nullable(Float64), Nullable(String))) -c Map(String, Nullable(Float64)) +b Array(Tuple(Nullable(Int64), Nullable(String))) +c Map(String, Nullable(Int64)) d Nullable(Bool) 42.42 [(1,'String'),(2,'abcd')] {'key':42,'key2':24} true JSONEachRow a Nullable(Float64) -b Array(Tuple(Nullable(Float64), Nullable(String))) -c Map(String, Nullable(Float64)) +b Array(Tuple(Nullable(Int64), Nullable(String))) +c Map(String, Nullable(Int64)) d Nullable(Bool) 42.42 [(1,'String'),(2,'abcd')] {'key':42,'key2':24} true -a Nullable(Float64) -b Array(Tuple(Nullable(Float64), Nullable(String))) -c Map(String, Nullable(Float64)) +a Nullable(Int64) +b Array(Tuple(Nullable(Int64), Nullable(String))) +c Map(String, Nullable(Int64)) d Nullable(Bool) \N [(1,'String'),(2,NULL)] {'key':NULL,'key2':24} \N 32 [(2,'String 2'),(3,'hello')] {'key3':4242,'key4':2424} true -a Nullable(Float64) +a Nullable(Int64) b Nullable(String) -c Array(Nullable(Float64)) +c Array(Nullable(Int64)) 1 s1 [] 2 \N [2] \N \N [] \N \N [] \N \N [3] TSKV -a Nullable(Float64) +a Nullable(Int64) b Nullable(String) -c Array(Nullable(Float64)) +c Array(Nullable(Int64)) 1 s1 [] 2 } [2] \N \N [] @@ -84,77 +84,77 @@ c Array(Nullable(Float64)) Values c1 Nullable(Float64) c2 Nullable(String) -c3 Array(Nullable(Float64)) -c4 Tuple(Nullable(Float64), Nullable(String)) -c5 Tuple(Array(Nullable(Float64)), Array(Tuple(Nullable(Float64), Nullable(String)))) +c3 Array(Nullable(Int64)) +c4 Tuple(Nullable(Int64), Nullable(String)) +c5 Tuple(Array(Nullable(Int64)), Array(Tuple(Nullable(Int64), Nullable(String)))) 42.42 Some string [1,2,3] (1,'2') ([1,2],[(3,'4'),(5,'6')]) c1 Nullable(Float64) c2 Nullable(String) -c3 Array(Nullable(Float64)) -c4 Tuple(Nullable(Float64), Nullable(Float64)) -c5 Tuple(Array(Nullable(Float64)), Array(Tuple(Nullable(Float64), Nullable(String)))) +c3 Array(Nullable(Int64)) +c4 Tuple(Nullable(Int64), Nullable(Int64)) +c5 Tuple(Array(Nullable(Int64)), Array(Tuple(Nullable(Int64), Nullable(String)))) 42.42 \N [1,NULL,3] (1,NULL) ([1,2],[(3,'4'),(5,'6')]) \N Some string [10] (1,2) ([],[]) Regexp -c1 Nullable(Float64) +c1 Nullable(Int64) c2 Nullable(String) c3 Nullable(String) 42 Some string 1 [([1, 2, 3], String 1), ([], String 1)] 2 Some string 2 [([4, 5, 6], String 2), ([], String 2)] 312 Some string 3 [([1, 2, 3], String 2), ([], String 2)] -c1 Nullable(Float64) +c1 Nullable(Int64) c2 Nullable(String) -c3 Array(Tuple(Array(Nullable(Float64)), Nullable(String))) +c3 Array(Tuple(Array(Nullable(Int64)), Nullable(String))) 42 Some string 1 [([1,2,3],'String 1'),([],'String 1')] 3 Some string 2 [([3,5,1],'String 2'),([],'String 2')] 244 Some string 3 [([],'String 3'),([],'String 3')] -c1 Nullable(Float64) +c1 Nullable(Int64) c2 Nullable(String) -c3 Array(Tuple(Array(Nullable(Float64)), Nullable(String))) +c3 Array(Tuple(Array(Nullable(Int64)), Nullable(String))) 42 Some string 1 [([1,2,3],'String 1'),([],'String 1')] 2 Some string 2 [([],'String 2'),([],'String 2')] 43 Some string 3 [([1,5,3],'String 3'),([],'String 3')] -c1 Nullable(Float64) +c1 Nullable(Int64) c2 Nullable(String) -c3 Array(Tuple(Array(Nullable(Float64)), Nullable(String))) +c3 Array(Tuple(Array(Nullable(Int64)), Nullable(String))) 42 Some string 1 [([1,2,3],'String 1'),([1],'String 1')] 52 Some string 2 [([],'String 2'),([1],'String 2')] 24 Some string 3 [([1,2,3],'String 3'),([1],'String 3')] CustomSeparated c1 Nullable(Float64) c2 Nullable(String) -c3 Array(Tuple(Array(Nullable(Float64)), Nullable(String))) +c3 Array(Tuple(Array(Nullable(Int64)), Nullable(String))) 42.42 Some string 1 [([1,2,3],'String 1'),([1],'String 1')] 42 Some string 2 [([],'String 2'),([],'String 2')] \N Some string 3 [([1,2,3],'String 3'),([1],'String 3')] c1 Nullable(Float64) c2 Nullable(String) -c3 Array(Tuple(Array(Nullable(Float64)), Nullable(String))) +c3 Array(Tuple(Array(Nullable(Int64)), Nullable(String))) 42.42 Some string 1 [([1,2,3],'String 1'),([1],'String 1')] 42 Some string 2 [([],'String 2'),([],'String 2')] \N Some string 3 [([1,2,3],'String 3'),([1],'String 3')] c1 Nullable(Float64) c2 Nullable(String) -c3 Array(Tuple(Array(Nullable(Float64)), Nullable(String))) +c3 Array(Tuple(Array(Nullable(Int64)), Nullable(String))) 42.42 Some string 1 [([1,2,3],'String 1'),([1],'String 1')] 42 Some string 2 [([],'String 2'),([],'String 2')] \N Some string 3 [([1,2,3],'String 3'),([1],'String 3')] Template column_1 Nullable(Float64) column_2 Nullable(String) -column_3 Array(Tuple(Array(Nullable(Float64)), Nullable(String))) +column_3 Array(Tuple(Array(Nullable(Int64)), Nullable(String))) 42.42 Some string 1 [([1,2,3],'String 1'),([1],'String 1')] 42 Some string 2 [([],'String 2'),([],'String 2')] \N Some string 3 [([1,2,3],'String 3'),([1],'String 3')] column_1 Nullable(Float64) column_2 Nullable(String) -column_3 Array(Tuple(Array(Nullable(Float64)), Nullable(String))) +column_3 Array(Tuple(Array(Nullable(Int64)), Nullable(String))) 42.42 Some string 1 [([1,2,3],'String 1'),([1],'String 1')] 42 Some string 2 [([],'String 2'),([],'String 2')] \N Some string 3 [([1,2,3],'String 3'),([1],'String 3')] column_1 Nullable(Float64) column_2 Nullable(String) -column_3 Array(Tuple(Array(Nullable(Float64)), Nullable(String))) +column_3 Array(Tuple(Array(Nullable(Int64)), Nullable(String))) 42.42 Some string 1 [([1,2,3],'String 1'),([1],'String 1')] 42 Some string 2 [([],'String 2'),([],'String 2')] \N Some string 3 [([1,2,3],'String 3'),([1],'String 3')] diff --git a/tests/queries/0_stateless/02240_tskv_schema_inference_bug.reference b/tests/queries/0_stateless/02240_tskv_schema_inference_bug.reference index d0ced74f8f6..0f8ac77ff74 100644 --- a/tests/queries/0_stateless/02240_tskv_schema_inference_bug.reference +++ b/tests/queries/0_stateless/02240_tskv_schema_inference_bug.reference @@ -1,6 +1,6 @@ -a Nullable(Float64) +a Nullable(Int64) b Nullable(String) -c Array(Nullable(Float64)) +c Array(Nullable(Int64)) 1 s1 [] 2 } [2] \N \N [] diff --git a/tests/queries/0_stateless/02245_s3_schema_desc.reference b/tests/queries/0_stateless/02245_s3_schema_desc.reference index e039680d933..d840a365310 100644 --- a/tests/queries/0_stateless/02245_s3_schema_desc.reference +++ b/tests/queries/0_stateless/02245_s3_schema_desc.reference @@ -1,21 +1,21 @@ -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Nullable(Float64) -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Nullable(Int64) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Nullable(Int64) c1 UInt64 c2 UInt64 c3 UInt64 -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Nullable(Int64) c1 UInt64 c2 UInt64 c3 UInt64 -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Nullable(Int64) c1 UInt64 c2 UInt64 c3 UInt64 diff --git a/tests/queries/0_stateless/02246_tsv_csv_best_effort_schema_inference.reference b/tests/queries/0_stateless/02246_tsv_csv_best_effort_schema_inference.reference index c245f13fdbe..1c60e40942c 100644 --- a/tests/queries/0_stateless/02246_tsv_csv_best_effort_schema_inference.reference +++ b/tests/queries/0_stateless/02246_tsv_csv_best_effort_schema_inference.reference @@ -1,8 +1,8 @@ TSV -c1 Nullable(Float64) +c1 Nullable(Int64) c2 Nullable(String) -c3 Array(Nullable(Float64)) -c4 Tuple(Nullable(Float64), Nullable(Float64), Nullable(Float64)) +c3 Array(Nullable(Int64)) +c4 Tuple(Nullable(Int64), Nullable(Int64), Nullable(Int64)) 42 Some string [1,2,3,4] (1,2,3) 42 abcd [] (4,5,6) c1 Nullable(String) @@ -70,8 +70,8 @@ c1 Nullable(String) CSV c1 Nullable(String) c2 Nullable(String) -c3 Array(Nullable(Float64)) -c4 Array(Tuple(Nullable(Float64), Nullable(Float64), Nullable(Float64))) +c3 Array(Nullable(Int64)) +c4 Array(Tuple(Nullable(Int64), Nullable(Int64), Nullable(Int64))) 42 Some string [1,2,3,4] [(1,2,3)] 42\\ abcd [] [(4,5,6)] c1 Nullable(String) @@ -101,7 +101,7 @@ c1 Nullable(String) (1, 2, 3) c1 Nullable(String) 123.123 -c1 Array(Tuple(Nullable(Float64), Nullable(Float64), Nullable(Float64))) +c1 Array(Tuple(Nullable(Int64), Nullable(Int64), Nullable(Int64))) [(1,2,3)] -c1 Array(Tuple(Nullable(Float64), Nullable(Float64), Nullable(Float64))) +c1 Array(Tuple(Nullable(Int64), Nullable(Int64), Nullable(Int64))) [(1,2,3)] diff --git a/tests/queries/0_stateless/02305_schema_inference_with_globs.reference b/tests/queries/0_stateless/02305_schema_inference_with_globs.reference index 9df5d2a264c..defa2133823 100644 --- a/tests/queries/0_stateless/02305_schema_inference_with_globs.reference +++ b/tests/queries/0_stateless/02305_schema_inference_with_globs.reference @@ -1,5 +1,5 @@ 2 4 6 -x Nullable(String) -x Nullable(String) +x Nullable(Int64) +x Nullable(Int64) diff --git a/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.reference b/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.reference index 2972dd92756..6da939d7839 100644 --- a/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.reference +++ b/tests/queries/0_stateless/02326_numbers_from_json_strings_schema_inference.reference @@ -1,11 +1,11 @@ -x Nullable(Float64) +x Nullable(Int64) x Array(Nullable(Float64)) -x Map(String, Nullable(Float64)) -x Map(String, Array(Nullable(Float64))) -x Nullable(Float64) -x Array(Nullable(Float64)) -x Map(String, Nullable(Float64)) -x Map(String, Array(Nullable(Float64))) +x Map(String, Nullable(Int64)) +x Map(String, Array(Nullable(Int64))) +x Nullable(Int64) +x Array(Nullable(Int64)) +x Map(String, Nullable(Int64)) +x Map(String, Array(Nullable(Int64))) x Array(Nullable(String)) x Map(String, Nullable(String)) x Map(String, Array(Nullable(String))) @@ -13,5 +13,5 @@ x Nullable(String) x Array(Nullable(String)) x Map(String, Nullable(String)) x Map(String, Array(Nullable(String))) -x Tuple(Nullable(Float64), Nullable(String)) +x Tuple(Nullable(Int64), Nullable(String)) x Object(Nullable(\'json\')) From e3192cf753794a2520c4461cb7c0cf7d6b719aa8 Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Wed, 20 Jul 2022 15:19:02 +0000 Subject: [PATCH 064/672] Correct docs to reflect new range 1900..2299 for Date32 and DateTime64; Cleanup code --- docs/en/sql-reference/data-types/date32.md | 4 ++-- docs/en/sql-reference/data-types/datetime64.md | 2 +- .../functions/date-time-functions.md | 6 +++--- .../functions/type-conversion-functions.md | 16 ++++++++-------- docs/ru/sql-reference/data-types/date32.md | 4 ++-- docs/ru/sql-reference/data-types/datetime64.md | 2 +- .../functions/date-time-functions.md | 4 ++-- .../functions/type-conversion-functions.md | 18 +++++++++--------- docs/zh/sql-reference/data-types/datetime64.md | 2 +- .../functions/date-time-functions.md | 6 +++--- src/Common/DateLUTImpl.h | 14 +++++++++----- .../FunctionDateOrDateTimeAddInterval.h | 14 ++------------ src/Functions/FunctionsConversion.h | 1 - 13 files changed, 43 insertions(+), 50 deletions(-) diff --git a/docs/en/sql-reference/data-types/date32.md b/docs/en/sql-reference/data-types/date32.md index e1d6e2363e8..b5a82128e69 100644 --- a/docs/en/sql-reference/data-types/date32.md +++ b/docs/en/sql-reference/data-types/date32.md @@ -5,7 +5,7 @@ sidebar_label: Date32 # Date32 -A date. Supports the date range same with [Datetime64](../../sql-reference/data-types/datetime64.md). Stored in four bytes as the number of days since 1925-01-01. Allows storing values till 2283-11-11. +A date. Supports the date range same with [Datetime64](../../sql-reference/data-types/datetime64.md). Stored in four bytes as the number of days since 1900-01-01. Allows storing values till 2299-12-31. **Examples** @@ -36,5 +36,5 @@ SELECT * FROM new; - [toDate32](../../sql-reference/functions/type-conversion-functions.md#todate32) - [toDate32OrZero](../../sql-reference/functions/type-conversion-functions.md#todate32-or-zero) -- [toDate32OrNull](../../sql-reference/functions/type-conversion-functions.md#todate32-or-null) +- [toDate32OrNull](../../sql-reference/functions/type-conversion-functions.md#todate32-or-null) diff --git a/docs/en/sql-reference/data-types/datetime64.md b/docs/en/sql-reference/data-types/datetime64.md index ddc71e75e44..a5a520a978e 100644 --- a/docs/en/sql-reference/data-types/datetime64.md +++ b/docs/en/sql-reference/data-types/datetime64.md @@ -18,7 +18,7 @@ DateTime64(precision, [timezone]) Internally, stores data as a number of ‘ticks’ since epoch start (1970-01-01 00:00:00 UTC) as Int64. The tick resolution is determined by the precision parameter. Additionally, the `DateTime64` type can store time zone that is the same for the entire column, that affects how the values of the `DateTime64` type values are displayed in text format and how the values specified as strings are parsed (‘2020-01-01 05:00:01.000’). The time zone is not stored in the rows of the table (or in resultset), but is stored in the column metadata. See details in [DateTime](../../sql-reference/data-types/datetime.md). -Supported range of values: \[1925-01-01 00:00:00, 2283-11-11 23:59:59.99999999\] (Note: The precision of the maximum value is 8). +Supported range of values: \[1900-01-01 00:00:00, 2299-12-31 23:59:59.99999999\] (Note: The precision of the maximum value is 8). ## Examples diff --git a/docs/en/sql-reference/functions/date-time-functions.md b/docs/en/sql-reference/functions/date-time-functions.md index 621429fb02c..7a843e1c87b 100644 --- a/docs/en/sql-reference/functions/date-time-functions.md +++ b/docs/en/sql-reference/functions/date-time-functions.md @@ -266,8 +266,8 @@ Result: └────────────────┘ ``` -:::note -The return type `toStartOf*` functions described below is `Date` or `DateTime`. Though these functions can take `DateTime64` as an argument, passing them a `DateTime64` that is out of the normal range (years 1925 - 2283) will give an incorrect result. +:::note +The return type `toStartOf*` functions described below is `Date` or `DateTime`. Though these functions can take `DateTime64` as an argument, passing them a `DateTime64` that is out of the normal range (years 1900 - 2299) will give an incorrect result. ::: ## toStartOfYear @@ -291,7 +291,7 @@ Returns the date. Rounds down a date or date with time to the first day of the month. Returns the date. -:::note +:::note The behavior of parsing incorrect dates is implementation specific. ClickHouse may return zero date, throw an exception or do “natural” overflow. ::: diff --git a/docs/en/sql-reference/functions/type-conversion-functions.md b/docs/en/sql-reference/functions/type-conversion-functions.md index 3f4db831e3d..d0dc651958d 100644 --- a/docs/en/sql-reference/functions/type-conversion-functions.md +++ b/docs/en/sql-reference/functions/type-conversion-functions.md @@ -218,23 +218,23 @@ SELECT toDate32('1955-01-01') AS value, toTypeName(value); 2. The value is outside the range: ``` sql -SELECT toDate32('1924-01-01') AS value, toTypeName(value); +SELECT toDate32('1899-01-01') AS value, toTypeName(value); ``` ``` text -┌──────value─┬─toTypeName(toDate32('1925-01-01'))─┐ -│ 1925-01-01 │ Date32 │ +┌──────value─┬─toTypeName(toDate32('1899-01-01'))─┐ +│ 1900-01-01 │ Date32 │ └────────────┴────────────────────────────────────┘ ``` 3. With `Date`-type argument: ``` sql -SELECT toDate32(toDate('1924-01-01')) AS value, toTypeName(value); +SELECT toDate32(toDate('1899-01-01')) AS value, toTypeName(value); ``` ``` text -┌──────value─┬─toTypeName(toDate32(toDate('1924-01-01')))─┐ +┌──────value─┬─toTypeName(toDate32(toDate('1899-01-01')))─┐ │ 1970-01-01 │ Date32 │ └────────────┴────────────────────────────────────────────┘ ``` @@ -248,14 +248,14 @@ The same as [toDate32](#todate32) but returns the min value of [Date32](../../sq Query: ``` sql -SELECT toDate32OrZero('1924-01-01'), toDate32OrZero(''); +SELECT toDate32OrZero('1899-01-01'), toDate32OrZero(''); ``` Result: ``` text -┌─toDate32OrZero('1924-01-01')─┬─toDate32OrZero('')─┐ -│ 1925-01-01 │ 1925-01-01 │ +┌─toDate32OrZero('1899-01-01')─┬─toDate32OrZero('')─┐ +│ 1900-01-01 │ 1900-01-01 │ └──────────────────────────────┴────────────────────┘ ``` diff --git a/docs/ru/sql-reference/data-types/date32.md b/docs/ru/sql-reference/data-types/date32.md index 31b2258b70b..1fc5ff6e5e2 100644 --- a/docs/ru/sql-reference/data-types/date32.md +++ b/docs/ru/sql-reference/data-types/date32.md @@ -5,7 +5,7 @@ sidebar_label: Date32 # Date32 {#data_type-datetime32} -Дата. Поддерживается такой же диапазон дат, как для типа [Datetime64](../../sql-reference/data-types/datetime64.md). Значение хранится в четырех байтах и соответствует числу дней с 1925-01-01 по 2283-11-11. +Дата. Поддерживается такой же диапазон дат, как для типа [Datetime64](../../sql-reference/data-types/datetime64.md). Значение хранится в четырех байтах и соответствует числу дней с 1900-01-01 по 2299-12-31. **Пример** @@ -36,5 +36,5 @@ SELECT * FROM new; - [toDate32](../../sql-reference/functions/type-conversion-functions.md#todate32) - [toDate32OrZero](../../sql-reference/functions/type-conversion-functions.md#todate32-or-zero) -- [toDate32OrNull](../../sql-reference/functions/type-conversion-functions.md#todate32-or-null) +- [toDate32OrNull](../../sql-reference/functions/type-conversion-functions.md#todate32-or-null) diff --git a/docs/ru/sql-reference/data-types/datetime64.md b/docs/ru/sql-reference/data-types/datetime64.md index 8428c5b7309..0473d8256e9 100644 --- a/docs/ru/sql-reference/data-types/datetime64.md +++ b/docs/ru/sql-reference/data-types/datetime64.md @@ -18,7 +18,7 @@ DateTime64(precision, [timezone]) Данные хранятся в виде количества ‘тиков’, прошедших с момента начала эпохи (1970-01-01 00:00:00 UTC), в Int64. Размер тика определяется параметром precision. Дополнительно, тип `DateTime64` позволяет хранить часовой пояс, единый для всей колонки, который влияет на то, как будут отображаться значения типа `DateTime64` в текстовом виде и как будут парситься значения заданные в виде строк (‘2020-01-01 05:00:01.000’). Часовой пояс не хранится в строках таблицы (выборки), а хранится в метаданных колонки. Подробнее см. [DateTime](datetime.md). -Диапазон значений: \[1925-01-01 00:00:00, 2283-11-11 23:59:59.99999999\] (Примечание: Точность максимального значения составляет 8). +Диапазон значений: \[1900-01-01 00:00:00, 2299-12-31 23:59:59.99999999\] (Примечание: Точность максимального значения составляет 8). ## Примеры {#examples} diff --git a/docs/ru/sql-reference/functions/date-time-functions.md b/docs/ru/sql-reference/functions/date-time-functions.md index da48cd940a7..242861af0d9 100644 --- a/docs/ru/sql-reference/functions/date-time-functions.md +++ b/docs/ru/sql-reference/functions/date-time-functions.md @@ -57,7 +57,7 @@ toTimezone(value, timezone) **Аргументы** - `value` — время или дата с временем. [DateTime64](../../sql-reference/data-types/datetime64.md). -- `timezone` — часовой пояс для возвращаемого значения. [String](../../sql-reference/data-types/string.md). Этот аргумент является константой, потому что `toTimezone` изменяет часовой пояс столбца (часовой пояс является атрибутом типов `DateTime*`). +- `timezone` — часовой пояс для возвращаемого значения. [String](../../sql-reference/data-types/string.md). Этот аргумент является константой, потому что `toTimezone` изменяет часовой пояс столбца (часовой пояс является атрибутом типов `DateTime*`). **Возвращаемое значение** @@ -267,7 +267,7 @@ SELECT toUnixTimestamp('2017-11-05 08:07:47', 'Asia/Tokyo') AS unix_timestamp; ``` :::note "Attention" - `Date` или `DateTime` это возвращаемый тип функций `toStartOf*`, который описан ниже. Несмотря на то, что эти функции могут принимать `DateTime64` в качестве аргумента, если переданное значение типа `DateTime64` выходит за пределы нормального диапазона (с 1925 по 2283 год), то это даст неверный результат. + `Date` или `DateTime` это возвращаемый тип функций `toStartOf*`, который описан ниже. Несмотря на то, что эти функции могут принимать `DateTime64` в качестве аргумента, если переданное значение типа `DateTime64` выходит за пределы нормального диапазона (с 1900 по 2299 год), то это даст неверный результат. ::: ## toStartOfYear {#tostartofyear} diff --git a/docs/ru/sql-reference/functions/type-conversion-functions.md b/docs/ru/sql-reference/functions/type-conversion-functions.md index 946abddf3d0..71caeddea02 100644 --- a/docs/ru/sql-reference/functions/type-conversion-functions.md +++ b/docs/ru/sql-reference/functions/type-conversion-functions.md @@ -209,7 +209,7 @@ SELECT toDate32('1955-01-01') AS value, toTypeName(value); ``` ``` text -┌──────value─┬─toTypeName(toDate32('1925-01-01'))─┐ +┌──────value─┬─toTypeName(toDate32('1955-01-01'))─┐ │ 1955-01-01 │ Date32 │ └────────────┴────────────────────────────────────┘ ``` @@ -217,23 +217,23 @@ SELECT toDate32('1955-01-01') AS value, toTypeName(value); 2. Значение выходит за границы диапазона: ``` sql -SELECT toDate32('1924-01-01') AS value, toTypeName(value); +SELECT toDate32('1899-01-01') AS value, toTypeName(value); ``` ``` text -┌──────value─┬─toTypeName(toDate32('1925-01-01'))─┐ -│ 1925-01-01 │ Date32 │ +┌──────value─┬─toTypeName(toDate32('1899-01-01'))─┐ +│ 1900-01-01 │ Date32 │ └────────────┴────────────────────────────────────┘ ``` 3. С аргументом типа `Date`: ``` sql -SELECT toDate32(toDate('1924-01-01')) AS value, toTypeName(value); +SELECT toDate32(toDate('1899-01-01')) AS value, toTypeName(value); ``` ``` text -┌──────value─┬─toTypeName(toDate32(toDate('1924-01-01')))─┐ +┌──────value─┬─toTypeName(toDate32(toDate('1899-01-01')))─┐ │ 1970-01-01 │ Date32 │ └────────────┴────────────────────────────────────────────┘ ``` @@ -247,14 +247,14 @@ SELECT toDate32(toDate('1924-01-01')) AS value, toTypeName(value); Запрос: ``` sql -SELECT toDate32OrZero('1924-01-01'), toDate32OrZero(''); +SELECT toDate32OrZero('1899-01-01'), toDate32OrZero(''); ``` Результат: ``` text -┌─toDate32OrZero('1924-01-01')─┬─toDate32OrZero('')─┐ -│ 1925-01-01 │ 1925-01-01 │ +┌─toDate32OrZero('1899-01-01')─┬─toDate32OrZero('')─┐ +│ 1900-01-01 │ 1900-01-01 │ └──────────────────────────────┴────────────────────┘ ``` diff --git a/docs/zh/sql-reference/data-types/datetime64.md b/docs/zh/sql-reference/data-types/datetime64.md index 571bcffd66e..da637929180 100644 --- a/docs/zh/sql-reference/data-types/datetime64.md +++ b/docs/zh/sql-reference/data-types/datetime64.md @@ -19,7 +19,7 @@ DateTime64(precision, [timezone]) 在内部,此类型以Int64类型将数据存储为自Linux纪元开始(1970-01-01 00:00:00UTC)的时间刻度数(ticks)。时间刻度的分辨率由precision参数确定。此外,`DateTime64` 类型可以像存储其他数据列一样存储时区信息,时区会影响 `DateTime64` 类型的值如何以文本格式显示,以及如何解析以字符串形式指定的时间数据 (‘2020-01-01 05:00:01.000’)。时区不存储在表的行中(也不在resultset中),而是存储在列的元数据中。详细信息请参考 [DateTime](datetime.md) 数据类型. -值的范围: \[1925-01-01 00:00:00, 2283-11-11 23:59:59.99999999\] (注意: 最大值的精度是8)。 +值的范围: \[1900-01-01 00:00:00, 2299-12-31 23:59:59.99999999\] (注意: 最大值的精度是8)。 ## 示例 {#examples} diff --git a/docs/zh/sql-reference/functions/date-time-functions.md b/docs/zh/sql-reference/functions/date-time-functions.md index f268e9584ce..b9fdc4e21f2 100644 --- a/docs/zh/sql-reference/functions/date-time-functions.md +++ b/docs/zh/sql-reference/functions/date-time-functions.md @@ -263,8 +263,8 @@ SELECT toUnixTimestamp('2017-11-05 08:07:47', 'Asia/Tokyo') AS unix_timestamp └────────────────┘ ``` -:::注意 -下面描述的返回类型 `toStartOf` 函数是 `Date` 或 `DateTime`。尽管这些函数可以将 `DateTime64` 作为参数,但将超出正常范围(1925年-2283年)的 `DateTime64` 传递给它们会给出不正确的结果。 +:::注意 +下面描述的返回类型 `toStartOf` 函数是 `Date` 或 `DateTime`。尽管这些函数可以将 `DateTime64` 作为参数,但将超出正常范围(1900年-2299年)的 `DateTime64` 传递给它们会给出不正确的结果。 ::: ## toStartOfYear {#tostartofyear} @@ -1221,4 +1221,4 @@ SELECT fromModifiedJulianDayOrNull(58849); └────────────────────────────────────┘ ``` -[Original article](https://clickhouse.com/docs/en/query_language/functions/date_time_functions/) \ No newline at end of file +[Original article](https://clickhouse.com/docs/en/query_language/functions/date_time_functions/) diff --git a/src/Common/DateLUTImpl.h b/src/Common/DateLUTImpl.h index d1b226e18b1..f38c585dcdd 100644 --- a/src/Common/DateLUTImpl.h +++ b/src/Common/DateLUTImpl.h @@ -185,11 +185,6 @@ public: static_assert(sizeof(Values) == 16); private: - - /// Mask is all-ones to allow efficient protection against overflow. - // static constexpr UInt32 date_lut_mask = 0x1ffff; - // static_assert(date_lut_mask == DATE_LUT_SIZE - 1); - /// Offset to epoch in days (ExtendedDayNum) of the first day in LUT. /// "epoch" is the Unix Epoch (starts at unix timestamp zero) static constexpr UInt32 daynum_offset_epoch = 25567; @@ -343,6 +338,15 @@ public: return ExtendedDayNum{static_cast(toLUTIndex(v).toUnderType() - daynum_offset_epoch)}; } + static UInt16 normalizeDayNum(Int64 d) + { + if (d < 0) + return 0; + if (d > 65535) + return 65535; + return static_cast(d); + } + /// Round down to start of monday. template inline Time toFirstDayOfWeek(DateOrTime v) const diff --git a/src/Functions/FunctionDateOrDateTimeAddInterval.h b/src/Functions/FunctionDateOrDateTimeAddInterval.h index 10408093240..341a7cf504d 100644 --- a/src/Functions/FunctionDateOrDateTimeAddInterval.h +++ b/src/Functions/FunctionDateOrDateTimeAddInterval.h @@ -288,12 +288,7 @@ struct AddDaysImpl static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &, UInt16 = 0) { - Int64 r = d + delta; - if (r < 0) - return 0; - if (r > 65535) - return 65535; - return static_cast(r); + return DateLUT::instance().normalizeDayNum(d + delta); } static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl &, UInt16 = 0) @@ -327,12 +322,7 @@ struct AddWeeksImpl static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int32 delta, const DateLUTImpl &, UInt16 = 0) { - Int64 r = d + delta * 7; - if (r < 0) - return 0; - if (r > 65535) - return 65535; - return static_cast(r); + return DateLUT::instance().normalizeDayNum(d + delta * 7); } static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int32 delta, const DateLUTImpl &, UInt16 = 0) diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index 0b0a4e9f21b..af75e4f49ba 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -536,7 +536,6 @@ template struct ConvertImpl struct ConvertImpl : DateTimeTransformImpl> {}; - const time_t LUT_MIN_TIME = -2208988800l; // 1900-01-01 UTC const time_t LUT_MAX_TIME = 10382256000l; // 2299-12-31 UTC From 59501150d0a3fc04f7de40dea06da840137e5882 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 20 Jul 2022 17:28:46 +0200 Subject: [PATCH 065/672] Use github_helper in changelog, use a robot token in CI --- .github/workflows/tags_stable.yml | 2 +- utils/changelog/changelog.py | 150 +++++++++--------------------- utils/changelog/github_helper.py | 1 + 3 files changed, 46 insertions(+), 107 deletions(-) create mode 120000 utils/changelog/github_helper.py diff --git a/.github/workflows/tags_stable.yml b/.github/workflows/tags_stable.yml index 0e0eefb4a35..05ffc1df56f 100644 --- a/.github/workflows/tags_stable.yml +++ b/.github/workflows/tags_stable.yml @@ -29,7 +29,7 @@ jobs: fetch-depth: 0 - name: Generate versions env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.ROBOT_CLICKHOUSE_COMMIT_TOKEN }} run: | ./utils/list-versions/list-versions.sh > ./utils/list-versions/version_date.tsv GID=$(id -g "${UID}") diff --git a/utils/changelog/changelog.py b/utils/changelog/changelog.py index 9d1dabbabe1..939585afcaa 100755 --- a/utils/changelog/changelog.py +++ b/utils/changelog/changelog.py @@ -6,20 +6,14 @@ import logging import os.path as p import os import re -from datetime import date, datetime, timedelta -from queue import Empty, Queue +from datetime import date, timedelta from subprocess import CalledProcessError, DEVNULL -from threading import Thread -from time import sleep from typing import Dict, List, Optional, TextIO from fuzzywuzzy.fuzz import ratio # type: ignore -from github import Github +from github_helper import GitHub, PullRequest, PullRequests, Repository from github.GithubException import RateLimitExceededException, UnknownObjectException from github.NamedUser import NamedUser -from github.Issue import Issue -from github.PullRequest import PullRequest -from github.Repository import Repository from git_helper import is_shallow, git_runner as runner # This array gives the preferred category order, and is also used to @@ -39,7 +33,7 @@ categories_preferred_order = ( FROM_REF = "" TO_REF = "" SHA_IN_CHANGELOG = [] # type: List[str] -GitHub = Github() +gh = GitHub() CACHE_PATH = p.join(p.dirname(p.realpath(__file__)), "gh_cache") @@ -78,7 +72,7 @@ class Description: user_name = self.user.login break except RateLimitExceededException: - sleep_on_rate_limit() + gh.sleep_on_rate_limit() return ( f"* {entry} [#{self.number}]({self.html_url}) " f"([{user_name}]({self.user.html_url}))." @@ -94,85 +88,27 @@ class Description: return self.number < other.number -class Worker(Thread): - def __init__(self, request_queue: Queue, repo: Repository): - Thread.__init__(self) - self.queue = request_queue - self.repo = repo - self.response = [] # type: List[Description] - - def run(self): - while not self.queue.empty(): - try: - issue = self.queue.get() # type: Issue - except Empty: - break # possible race condition, just continue - api_pr = get_pull_cached(self.repo, issue.number, issue.updated_at) - in_changelog = False - merge_commit = api_pr.merge_commit_sha - try: - runner.run(f"git rev-parse '{merge_commit}'") - except CalledProcessError: - # It's possible that commit not in the repo, just continue - logging.info("PR %s does not belong to the repo", api_pr.number) - continue - - in_changelog = merge_commit in SHA_IN_CHANGELOG - if in_changelog: - desc = generate_description(api_pr, self.repo) - if desc is not None: - self.response.append(desc) - - self.queue.task_done() - - -def sleep_on_rate_limit(time: int = 20): - logging.warning("Faced rate limit, sleeping %s", time) - sleep(time) - - -def get_pull_cached( - repo: Repository, number: int, updated_at: Optional[datetime] = None -) -> PullRequest: - pr_cache_file = p.join(CACHE_PATH, f"{number}.pickle") - if updated_at is None: - updated_at = datetime.now() - timedelta(hours=-1) - - if p.isfile(pr_cache_file): - cache_updated = datetime.fromtimestamp(p.getmtime(pr_cache_file)) - if cache_updated > updated_at: - with open(pr_cache_file, "rb") as prfd: - return GitHub.load(prfd) # type: ignore - while True: - try: - pr = repo.get_pull(number) - break - except RateLimitExceededException: - sleep_on_rate_limit() - with open(pr_cache_file, "wb") as prfd: - GitHub.dump(pr, prfd) # type: ignore - return pr - - def get_descriptions( - repo: Repository, issues: List[Issue], jobs: int + repo: Repository, prs: PullRequests ) -> Dict[str, List[Description]]: - workers = [] # type: List[Worker] - queue = Queue() # type: Queue[Issue] - for issue in issues: - queue.put(issue) - for _ in range(jobs): - worker = Worker(queue, repo) - worker.start() - workers.append(worker) - descriptions = {} # type: Dict[str, List[Description]] - for worker in workers: - worker.join() - for desc in worker.response: - if desc.category not in descriptions: - descriptions[desc.category] = [] - descriptions[desc.category].append(desc) + for pr in prs: + in_changelog = False + merge_commit = pr.merge_commit_sha + try: + runner.run(f"git rev-parse '{merge_commit}'") + except CalledProcessError: + # It's possible that commit not in the repo, just continue + logging.info("PR %s does not belong to the repo", pr.number) + continue + + in_changelog = merge_commit in SHA_IN_CHANGELOG + if in_changelog: + desc = generate_description(pr, repo) + if desc is not None: + if desc.category not in descriptions: + descriptions[desc.category] = [] + descriptions[desc.category].append(desc) for descs in descriptions.values(): descs.sort() @@ -193,6 +129,11 @@ def parse_args() -> argparse.Namespace: default=0, help="set the script verbosity, could be used multiple", ) + parser.add_argument( + "--debug-helpers", + action="store_true", + help="add debug logging for git_helper and github_helper", + ) parser.add_argument( "--output", type=argparse.FileType("w"), @@ -246,7 +187,7 @@ def generate_description(item: PullRequest, repo: Repository) -> Optional[Descri branch_parts = item.head.ref.split("/") if len(branch_parts) == 3: try: - item = get_pull_cached(repo, int(branch_parts[-1])) + item = gh.get_pull_cached(repo, int(branch_parts[-1])) except Exception as e: logging.warning("unable to get backpoted PR, exception: %s", e) else: @@ -338,7 +279,8 @@ def generate_description(item: PullRequest, repo: Repository) -> Optional[Descri def write_changelog(fd: TextIO, descriptions: Dict[str, List[Description]]): year = date.today().year fd.write( - f"---\nsidebar_position: 1\nsidebar_label: {year}\n---\n\n# {year} Changelog\n\n" + f"---\nsidebar_position: 1\nsidebar_label: {year}\n---\n\n" + f"# {year} Changelog\n\n" f"### ClickHouse release {TO_REF} FIXME as compared to {FROM_REF}\n\n" ) @@ -397,6 +339,9 @@ def main(): format="%(asctime)s %(levelname)-8s [%(filename)s:%(lineno)d]:\n%(message)s", level=log_levels[min(args.verbose, 3)], ) + if args.debug_helpers: + logging.getLogger("github_helper").setLevel(logging.DEBUG) + logging.getLogger("git_helper").setLevel(logging.DEBUG) # Create a cache directory if not p.isdir(CACHE_PATH): os.mkdir(CACHE_PATH, 0o700) @@ -418,30 +363,23 @@ def main(): # `tag^{}` format gives commit ref when we have annotated tags # format %cs gives a committer date, works better for cherry-picked commits from_date = runner.run(f"git log -1 --format=format:%cs '{FROM_REF}^{{}}'") - from_date = (date.fromisoformat(from_date) - timedelta(1)).isoformat() to_date = runner.run(f"git log -1 --format=format:%cs '{TO_REF}^{{}}'") - to_date = (date.fromisoformat(to_date) + timedelta(1)).isoformat() + merged = ( + date.fromisoformat(from_date) - timedelta(1), + date.fromisoformat(to_date) + timedelta(1), + ) # Get all PRs for the given time frame - global GitHub - GitHub = Github( + global gh + gh = GitHub( args.gh_user_or_token, args.gh_password, per_page=100, pool_size=args.jobs ) - query = f"type:pr repo:{args.repo} is:merged merged:{from_date}..{to_date}" - repo = GitHub.get_repo(args.repo) - api_prs = GitHub.search_issues(query=query, sort="created") - logging.info("Found %s PRs for the query: '%s'", api_prs.totalCount, query) + gh.cache_path = CACHE_PATH + query = f"type:pr repo:{args.repo} is:merged" + repo = gh.get_repo(args.repo) + prs = gh.get_pulls_from_search(query=query, merged=merged, sort="created") - issues = [] # type: List[Issue] - while True: - try: - for issue in api_prs: - issues.append(issue) - break - except RateLimitExceededException: - sleep_on_rate_limit() - - descriptions = get_descriptions(repo, issues, args.jobs) + descriptions = get_descriptions(repo, prs) write_changelog(args.output, descriptions) diff --git a/utils/changelog/github_helper.py b/utils/changelog/github_helper.py new file mode 120000 index 00000000000..2d44dfe8000 --- /dev/null +++ b/utils/changelog/github_helper.py @@ -0,0 +1 @@ +../../tests/ci/github_helper.py \ No newline at end of file From d100fac7ab99d4f05be1166f1c60fe0287f6f065 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 20 Jul 2022 17:29:34 +0200 Subject: [PATCH 066/672] Use checkout -f preventing some potential issues --- tests/ci/release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/release.py b/tests/ci/release.py index b07deffa1fb..a7eebf6feb9 100755 --- a/tests/ci/release.py +++ b/tests/ci/release.py @@ -335,7 +335,7 @@ class Release: yield except (Exception, KeyboardInterrupt): logging.warning("Rolling back checked out %s for %s", ref, orig_ref) - self.run(f"git reset --hard; git checkout {orig_ref}") + self.run(f"git reset --hard; git checkout -f {orig_ref}") raise else: if with_checkout_back and need_rollback: From 6028f7909b2775733935643d6ede16cf72436118 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 20 Jul 2022 20:45:29 +0000 Subject: [PATCH 067/672] Fixing build. --- src/Interpreters/AsynchronousMetrics.cpp | 4 ++-- .../test.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index b5dc9533f56..32c4e421ac3 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -664,12 +664,12 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti new_values["jemalloc.epoch"] = epoch; // Collect the statistics themselves. - size_t je_malloc_allocated = saveJemallocMetric(new_values, "allocated"); + [[maybe_unused]] size_t je_malloc_allocated = saveJemallocMetric(new_values, "allocated"); saveJemallocMetric(new_values, "active"); saveJemallocMetric(new_values, "metadata"); saveJemallocMetric(new_values, "metadata_thp"); saveJemallocMetric(new_values, "resident"); - size_t je_malloc_mapped = saveJemallocMetric(new_values, "mapped"); + [[maybe_unused]] size_t je_malloc_mapped = saveJemallocMetric(new_values, "mapped"); saveJemallocMetric(new_values, "retained"); saveJemallocMetric(new_values, "background_thread.num_threads"); saveJemallocMetric(new_values, "background_thread.num_runs"); diff --git a/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py b/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py index eba3aeff303..35c29959a43 100644 --- a/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py +++ b/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py @@ -43,7 +43,7 @@ def test_memory_tracking_total(): "-c", "clickhouse local -q \"SELECT arrayStringConcat(arrayMap(x->toString(cityHash64(x)), range(1000)), ' ') from numbers(10000)\" > data.json", ], - user="root" + user="root", ) for it in range(0, 20): From 6e77155dc84368e13e0856fa57dc361fa45d0356 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 20 Jul 2022 22:54:43 +0200 Subject: [PATCH 068/672] fix --- src/Databases/DatabaseAtomic.cpp | 16 +++++------ src/Databases/DatabaseAtomic.h | 6 ++--- src/Databases/DatabaseLazy.cpp | 2 +- src/Databases/DatabaseLazy.h | 2 +- src/Databases/DatabaseOrdinary.cpp | 17 ++++++------ src/Databases/DatabaseOrdinary.h | 7 ++--- src/Databases/DatabaseReplicated.cpp | 21 ++++++++------- src/Databases/DatabaseReplicated.h | 8 +++--- src/Databases/DatabaseReplicatedWorker.cpp | 2 +- src/Databases/IDatabase.h | 21 +++++++-------- src/Databases/LoadingStrictnessLevel.cpp | 27 +++++++++++++++++++ src/Databases/LoadingStrictnessLevel.h | 16 +++++++++++ .../MySQL/DatabaseMaterializedMySQL.cpp | 6 ++--- .../MySQL/DatabaseMaterializedMySQL.h | 2 +- src/Databases/MySQL/DatabaseMySQL.cpp | 2 +- src/Databases/MySQL/DatabaseMySQL.h | 2 +- .../DatabaseMaterializedPostgreSQL.cpp | 4 +-- .../DatabaseMaterializedPostgreSQL.h | 2 +- .../PostgreSQL/DatabasePostgreSQL.cpp | 2 +- src/Databases/PostgreSQL/DatabasePostgreSQL.h | 2 +- src/Databases/TablesLoader.cpp | 16 +++++------ src/Databases/TablesLoader.h | 20 +++++++------- src/Interpreters/InterpreterCreateQuery.cpp | 4 ++- src/Interpreters/InterpreterSystemQuery.cpp | 4 +-- src/Interpreters/loadMetadata.cpp | 21 +++++++++------ 25 files changed, 143 insertions(+), 89 deletions(-) create mode 100644 src/Databases/LoadingStrictnessLevel.cpp create mode 100644 src/Databases/LoadingStrictnessLevel.h diff --git a/src/Databases/DatabaseAtomic.cpp b/src/Databases/DatabaseAtomic.cpp index 92dae025dae..8899f7ccaa2 100644 --- a/src/Databases/DatabaseAtomic.cpp +++ b/src/Databases/DatabaseAtomic.cpp @@ -416,9 +416,9 @@ UUID DatabaseAtomic::tryGetTableUUID(const String & table_name) const return UUIDHelpers::Nil; } -void DatabaseAtomic::beforeLoadingMetadata(ContextMutablePtr /*context*/, bool force_restore, bool /*force_attach*/) +void DatabaseAtomic::beforeLoadingMetadata(ContextMutablePtr /*context*/, LoadingStrictnessLevel mode) { - if (!force_restore) + if (mode < LoadingStrictnessLevel::FORCE_RESTORE) return; /// Recreate symlinks to table data dirs in case of force restore, because some of them may be broken @@ -435,17 +435,17 @@ void DatabaseAtomic::beforeLoadingMetadata(ContextMutablePtr /*context*/, bool f } void DatabaseAtomic::loadStoredObjects( - ContextMutablePtr local_context, bool force_restore, bool force_attach, bool skip_startup_tables) + ContextMutablePtr local_context, LoadingStrictnessLevel mode, bool skip_startup_tables) { - beforeLoadingMetadata(local_context, force_restore, force_attach); - DatabaseOrdinary::loadStoredObjects(local_context, force_restore, force_attach, skip_startup_tables); + beforeLoadingMetadata(local_context, mode); + DatabaseOrdinary::loadStoredObjects(local_context, mode, skip_startup_tables); } -void DatabaseAtomic::startupTables(ThreadPool & thread_pool, bool force_restore, bool force_attach) +void DatabaseAtomic::startupTables(ThreadPool & thread_pool, LoadingStrictnessLevel mode) { - DatabaseOrdinary::startupTables(thread_pool, force_restore, force_attach); + DatabaseOrdinary::startupTables(thread_pool, mode); - if (!force_restore) + if (mode < LoadingStrictnessLevel::FORCE_RESTORE) return; NameToPathMap table_names; diff --git a/src/Databases/DatabaseAtomic.h b/src/Databases/DatabaseAtomic.h index 6cb2226a7f8..1caf3b03a72 100644 --- a/src/Databases/DatabaseAtomic.h +++ b/src/Databases/DatabaseAtomic.h @@ -47,11 +47,11 @@ public: DatabaseTablesIteratorPtr getTablesIterator(ContextPtr context, const FilterByNameFunction & filter_by_table_name) const override; - void loadStoredObjects(ContextMutablePtr context, bool force_restore, bool force_attach, bool skip_startup_tables) override; + void loadStoredObjects(ContextMutablePtr context, LoadingStrictnessLevel mode, bool skip_startup_tables) override; - void beforeLoadingMetadata(ContextMutablePtr context, bool force_restore, bool force_attach) override; + void beforeLoadingMetadata(ContextMutablePtr context, LoadingStrictnessLevel mode) override; - void startupTables(ThreadPool & thread_pool, bool force_restore, bool force_attach) override; + void startupTables(ThreadPool & thread_pool, LoadingStrictnessLevel mode) override; /// Atomic database cannot be detached if there is detached table which still in use void assertCanBeDetached(bool cleanup) override; diff --git a/src/Databases/DatabaseLazy.cpp b/src/Databases/DatabaseLazy.cpp index 3a1b3009878..9aa65602835 100644 --- a/src/Databases/DatabaseLazy.cpp +++ b/src/Databases/DatabaseLazy.cpp @@ -38,7 +38,7 @@ DatabaseLazy::DatabaseLazy(const String & name_, const String & metadata_path_, void DatabaseLazy::loadStoredObjects( - ContextMutablePtr local_context, bool /* force_restore */, bool /*force_attach*/, bool /* skip_startup_tables */) + ContextMutablePtr local_context, LoadingStrictnessLevel /*mode*/, bool /* skip_startup_tables */) { iterateMetadataFiles(local_context, [this, &local_context](const String & file_name) { diff --git a/src/Databases/DatabaseLazy.h b/src/Databases/DatabaseLazy.h index d3c3ed2843b..b01038073ef 100644 --- a/src/Databases/DatabaseLazy.h +++ b/src/Databases/DatabaseLazy.h @@ -26,7 +26,7 @@ public: bool canContainDistributedTables() const override { return false; } - void loadStoredObjects(ContextMutablePtr context, bool force_restore, bool force_attach, bool skip_startup_tables) override; + void loadStoredObjects(ContextMutablePtr context, LoadingStrictnessLevel /*mode*/, bool skip_startup_tables) override; void createTable( ContextPtr context, diff --git a/src/Databases/DatabaseOrdinary.cpp b/src/Databases/DatabaseOrdinary.cpp index 18b70222382..c6b089ea2c6 100644 --- a/src/Databases/DatabaseOrdinary.cpp +++ b/src/Databases/DatabaseOrdinary.cpp @@ -81,7 +81,7 @@ DatabaseOrdinary::DatabaseOrdinary( } void DatabaseOrdinary::loadStoredObjects( - ContextMutablePtr local_context, bool force_restore, bool force_attach, bool skip_startup_tables) + ContextMutablePtr local_context, LoadingStrictnessLevel mode, bool skip_startup_tables) { /** Tables load faster if they are loaded in sorted (by name) order. * Otherwise (for the ext4 filesystem), `DirectoryIterator` iterates through them in some order, @@ -89,6 +89,7 @@ void DatabaseOrdinary::loadStoredObjects( */ ParsedTablesMetadata metadata; + bool force_attach = LoadingStrictnessLevel::FORCE_ATTACH <= mode; loadTablesMetadata(local_context, metadata, force_attach); size_t total_tables = metadata.parsed_tables.size() - metadata.total_dictionaries; @@ -118,7 +119,7 @@ void DatabaseOrdinary::loadStoredObjects( { pool.scheduleOrThrowOnError([&]() { - loadTableFromMetadata(local_context, path, name, ast, force_restore); + loadTableFromMetadata(local_context, path, name, ast, mode); /// Messages, so that it's not boring to wait for the server to load for a long time. logAboutProgress(log, ++dictionaries_processed, metadata.total_dictionaries, watch); @@ -140,7 +141,7 @@ void DatabaseOrdinary::loadStoredObjects( { pool.scheduleOrThrowOnError([&]() { - loadTableFromMetadata(local_context, path, name, ast, force_restore); + loadTableFromMetadata(local_context, path, name, ast, mode); /// Messages, so that it's not boring to wait for the server to load for a long time. logAboutProgress(log, ++tables_processed, total_tables, watch); @@ -153,7 +154,7 @@ void DatabaseOrdinary::loadStoredObjects( if (!skip_startup_tables) { /// After all tables was basically initialized, startup them. - startupTables(pool, force_restore, force_attach); + startupTables(pool, mode); } } @@ -238,7 +239,8 @@ void DatabaseOrdinary::loadTablesMetadata(ContextPtr local_context, ParsedTables TSA_SUPPRESS_WARNING_FOR_READ(database_name), tables_in_database, dictionaries_in_database); } -void DatabaseOrdinary::loadTableFromMetadata(ContextMutablePtr local_context, const String & file_path, const QualifiedTableName & name, const ASTPtr & ast, bool force_restore) +void DatabaseOrdinary::loadTableFromMetadata(ContextMutablePtr local_context, const String & file_path, const QualifiedTableName & name, const ASTPtr & ast, + LoadingStrictnessLevel mode) { assert(name.database == TSA_SUPPRESS_WARNING_FOR_READ(database_name)); const auto & create_query = ast->as(); @@ -248,11 +250,10 @@ void DatabaseOrdinary::loadTableFromMetadata(ContextMutablePtr local_context, co create_query, *this, name.database, - file_path, - force_restore); + file_path, LoadingStrictnessLevel::FORCE_RESTORE <= mode); } -void DatabaseOrdinary::startupTables(ThreadPool & thread_pool, bool /*force_restore*/, bool /*force_attach*/) +void DatabaseOrdinary::startupTables(ThreadPool & thread_pool, LoadingStrictnessLevel /*mode*/) { LOG_INFO(log, "Starting up tables."); diff --git a/src/Databases/DatabaseOrdinary.h b/src/Databases/DatabaseOrdinary.h index 6e524ae18b0..386d6613af3 100644 --- a/src/Databases/DatabaseOrdinary.h +++ b/src/Databases/DatabaseOrdinary.h @@ -21,15 +21,16 @@ public: String getEngineName() const override { return "Ordinary"; } - void loadStoredObjects(ContextMutablePtr context, bool force_restore, bool force_attach, bool skip_startup_tables) override; + void loadStoredObjects(ContextMutablePtr context, LoadingStrictnessLevel mode, bool skip_startup_tables) override; bool supportsLoadingInTopologicalOrder() const override { return true; } void loadTablesMetadata(ContextPtr context, ParsedTablesMetadata & metadata, bool is_startup) override; - void loadTableFromMetadata(ContextMutablePtr local_context, const String & file_path, const QualifiedTableName & name, const ASTPtr & ast, bool force_restore) override; + void loadTableFromMetadata(ContextMutablePtr local_context, const String & file_path, const QualifiedTableName & name, const ASTPtr & ast, + LoadingStrictnessLevel mode) override; - void startupTables(ThreadPool & thread_pool, bool force_restore, bool force_attach) override; + void startupTables(ThreadPool & thread_pool, LoadingStrictnessLevel mode) override; void alterTable( ContextPtr context, diff --git a/src/Databases/DatabaseReplicated.cpp b/src/Databases/DatabaseReplicated.cpp index 1b5de6071f0..1c8f4881895 100644 --- a/src/Databases/DatabaseReplicated.cpp +++ b/src/Databases/DatabaseReplicated.cpp @@ -232,7 +232,7 @@ void DatabaseReplicated::fillClusterAuthInfo(String collection_name, const Poco: cluster_auth_info.cluster_secure_connection = config_ref.getBool(config_prefix + ".cluster_secure_connection", false); } -void DatabaseReplicated::tryConnectToZooKeeperAndInitDatabase(bool force_attach, bool is_create_query) +void DatabaseReplicated::tryConnectToZooKeeperAndInitDatabase(LoadingStrictnessLevel mode) { try { @@ -250,6 +250,7 @@ void DatabaseReplicated::tryConnectToZooKeeperAndInitDatabase(bool force_attach, } replica_path = fs::path(zookeeper_path) / "replicas" / getFullReplicaName(); + bool is_create_query = mode == LoadingStrictnessLevel::CREATE; String replica_host_id; if (current_zookeeper->tryGet(replica_path, replica_host_id)) @@ -290,7 +291,7 @@ void DatabaseReplicated::tryConnectToZooKeeperAndInitDatabase(bool force_attach, } catch (...) { - if (!force_attach) + if (mode < LoadingStrictnessLevel::FORCE_ATTACH) throw; /// It's server startup, ignore error. @@ -339,6 +340,8 @@ void DatabaseReplicated::createEmptyLogEntry(const ZooKeeperPtr & current_zookee bool DatabaseReplicated::waitForReplicaToProcessAllEntries(UInt64 timeout_ms) { + if (!ddl_worker) + return false; return ddl_worker->waitForReplicaToProcessAllEntries(timeout_ms); } @@ -374,21 +377,21 @@ void DatabaseReplicated::createReplicaNodesInZooKeeper(const zkutil::ZooKeeperPt createEmptyLogEntry(current_zookeeper); } -void DatabaseReplicated::beforeLoadingMetadata(ContextMutablePtr /*context*/, bool /*force_restore*/, bool force_attach) +void DatabaseReplicated::beforeLoadingMetadata(ContextMutablePtr /*context*/, LoadingStrictnessLevel mode) { - tryConnectToZooKeeperAndInitDatabase(force_attach, /* is_create_query */ !force_attach); + tryConnectToZooKeeperAndInitDatabase(mode); } void DatabaseReplicated::loadStoredObjects( - ContextMutablePtr local_context, bool force_restore, bool force_attach, bool skip_startup_tables) + ContextMutablePtr local_context, LoadingStrictnessLevel mode, bool skip_startup_tables) { - beforeLoadingMetadata(local_context, force_restore, force_attach); - DatabaseAtomic::loadStoredObjects(local_context, force_restore, force_attach, skip_startup_tables); + beforeLoadingMetadata(local_context, mode); + DatabaseAtomic::loadStoredObjects(local_context, mode, skip_startup_tables); } -void DatabaseReplicated::startupTables(ThreadPool & thread_pool, bool force_restore, bool force_attach) +void DatabaseReplicated::startupTables(ThreadPool & thread_pool, LoadingStrictnessLevel mode) { - DatabaseAtomic::startupTables(thread_pool, force_restore, force_attach); + DatabaseAtomic::startupTables(thread_pool, mode); ddl_worker = std::make_unique(this, getContext()); if (is_probably_dropped) return; diff --git a/src/Databases/DatabaseReplicated.h b/src/Databases/DatabaseReplicated.h index ec4ad7a3c0d..25d4b9a5082 100644 --- a/src/Databases/DatabaseReplicated.h +++ b/src/Databases/DatabaseReplicated.h @@ -64,11 +64,11 @@ public: void drop(ContextPtr /*context*/) override; - void loadStoredObjects(ContextMutablePtr context, bool force_restore, bool force_attach, bool skip_startup_tables) override; + void loadStoredObjects(ContextMutablePtr context, LoadingStrictnessLevel mode, bool skip_startup_tables) override; - void beforeLoadingMetadata(ContextMutablePtr context, bool force_restore, bool force_attach) override; + void beforeLoadingMetadata(ContextMutablePtr context, LoadingStrictnessLevel mode) override; - void startupTables(ThreadPool & thread_pool, bool force_restore, bool force_attach) override; + void startupTables(ThreadPool & thread_pool, LoadingStrictnessLevel mode) override; void shutdown() override; @@ -78,7 +78,7 @@ public: friend struct DatabaseReplicatedTask; friend class DatabaseReplicatedDDLWorker; private: - void tryConnectToZooKeeperAndInitDatabase(bool force_attach, bool is_create_query); + void tryConnectToZooKeeperAndInitDatabase(LoadingStrictnessLevel mode); bool createDatabaseNodesInZooKeeper(const ZooKeeperPtr & current_zookeeper); void createReplicaNodesInZooKeeper(const ZooKeeperPtr & current_zookeeper); diff --git a/src/Databases/DatabaseReplicatedWorker.cpp b/src/Databases/DatabaseReplicatedWorker.cpp index d114c6a6028..a81bd86fd2b 100644 --- a/src/Databases/DatabaseReplicatedWorker.cpp +++ b/src/Databases/DatabaseReplicatedWorker.cpp @@ -35,7 +35,7 @@ bool DatabaseReplicatedDDLWorker::initializeMainThread() chassert(!database->is_probably_dropped); auto zookeeper = getAndSetZooKeeper(); if (database->is_readonly) - database->tryConnectToZooKeeperAndInitDatabase(/* force_attach */ false, /* is_create_query */ false); + database->tryConnectToZooKeeperAndInitDatabase(LoadingStrictnessLevel::ATTACH); initializeReplication(); initialized = true; return true; diff --git a/src/Databases/IDatabase.h b/src/Databases/IDatabase.h index 72155bc818c..338ee045c9d 100644 --- a/src/Databases/IDatabase.h +++ b/src/Databases/IDatabase.h @@ -1,12 +1,13 @@ #pragma once -#include +#include +#include +#include #include #include -#include +#include #include #include -#include #include #include @@ -132,18 +133,15 @@ public: /// You can call only once, right after the object is created. virtual void loadStoredObjects( /// NOLINT ContextMutablePtr /*context*/, - bool /*force_restore*/, - bool /*force_attach*/ = false, - bool /* skip_startup_tables */ = false) + LoadingStrictnessLevel /*mode*/, + bool /* skip_startup_tables */) { } virtual bool supportsLoadingInTopologicalOrder() const { return false; } virtual void beforeLoadingMetadata( - ContextMutablePtr /*context*/, - bool /*force_restore*/, - bool /*force_attach*/) + ContextMutablePtr /*context*/, LoadingStrictnessLevel /*mode*/) { } @@ -152,12 +150,13 @@ public: throw Exception(ErrorCodes::LOGICAL_ERROR, "Not implemented"); } - virtual void loadTableFromMetadata(ContextMutablePtr /*local_context*/, const String & /*file_path*/, const QualifiedTableName & /*name*/, const ASTPtr & /*ast*/, bool /*force_restore*/) + virtual void loadTableFromMetadata(ContextMutablePtr /*local_context*/, const String & /*file_path*/, const QualifiedTableName & /*name*/, const ASTPtr & /*ast*/, + LoadingStrictnessLevel /*mode*/) { throw Exception(ErrorCodes::LOGICAL_ERROR, "Not implemented"); } - virtual void startupTables(ThreadPool & /*thread_pool*/, bool /*force_restore*/, bool /*force_attach*/) {} + virtual void startupTables(ThreadPool & /*thread_pool*/, LoadingStrictnessLevel /*mode*/) {} /// Check the existence of the table in memory (attached). virtual bool isTableExist(const String & name, ContextPtr context) const = 0; diff --git a/src/Databases/LoadingStrictnessLevel.cpp b/src/Databases/LoadingStrictnessLevel.cpp new file mode 100644 index 00000000000..5120fc2182f --- /dev/null +++ b/src/Databases/LoadingStrictnessLevel.cpp @@ -0,0 +1,27 @@ +#include + +namespace DB +{ + +LoadingStrictnessLevel getLoadingStrictnessLevel(bool attach, bool force_attach, bool force_restore) +{ + if (force_restore) + { + assert(attach); + assert(force_attach); + return LoadingStrictnessLevel::FORCE_RESTORE; + } + + if (force_attach) + { + assert(attach); + return LoadingStrictnessLevel::FORCE_ATTACH; + } + + if (attach) + return LoadingStrictnessLevel::ATTACH; + + return LoadingStrictnessLevel::CREATE; +} + +} diff --git a/src/Databases/LoadingStrictnessLevel.h b/src/Databases/LoadingStrictnessLevel.h new file mode 100644 index 00000000000..4c566d3ac72 --- /dev/null +++ b/src/Databases/LoadingStrictnessLevel.h @@ -0,0 +1,16 @@ +#pragma once + +namespace DB +{ + +enum class LoadingStrictnessLevel +{ + CREATE = 0, + ATTACH = 1, + FORCE_ATTACH = 2, + FORCE_RESTORE = 3, +}; + +LoadingStrictnessLevel getLoadingStrictnessLevel(bool attach, bool force_attach, bool force_restore); + +} diff --git a/src/Databases/MySQL/DatabaseMaterializedMySQL.cpp b/src/Databases/MySQL/DatabaseMaterializedMySQL.cpp index 230a0b4d4a4..91dbadca409 100644 --- a/src/Databases/MySQL/DatabaseMaterializedMySQL.cpp +++ b/src/Databases/MySQL/DatabaseMaterializedMySQL.cpp @@ -63,11 +63,11 @@ void DatabaseMaterializedMySQL::setException(const std::exception_ptr & exceptio exception = exception_; } -void DatabaseMaterializedMySQL::startupTables(ThreadPool & thread_pool, bool force_restore, bool force_attach) +void DatabaseMaterializedMySQL::startupTables(ThreadPool & thread_pool, LoadingStrictnessLevel mode) { - DatabaseAtomic::startupTables(thread_pool, force_restore, force_attach); + DatabaseAtomic::startupTables(thread_pool, mode); - if (!force_attach) + if (mode < LoadingStrictnessLevel::FORCE_ATTACH) materialize_thread.assertMySQLAvailable(); materialize_thread.startSynchronization(); diff --git a/src/Databases/MySQL/DatabaseMaterializedMySQL.h b/src/Databases/MySQL/DatabaseMaterializedMySQL.h index a6810f29d87..27a7ddc8acf 100644 --- a/src/Databases/MySQL/DatabaseMaterializedMySQL.h +++ b/src/Databases/MySQL/DatabaseMaterializedMySQL.h @@ -48,7 +48,7 @@ protected: public: String getEngineName() const override { return "MaterializedMySQL"; } - void startupTables(ThreadPool & thread_pool, bool force_restore, bool force_attach) override; + void startupTables(ThreadPool & thread_pool, LoadingStrictnessLevel mode) override; void createTable(ContextPtr context_, const String & name, const StoragePtr & table, const ASTPtr & query) override; diff --git a/src/Databases/MySQL/DatabaseMySQL.cpp b/src/Databases/MySQL/DatabaseMySQL.cpp index 95098ba9cbd..01c342c1771 100644 --- a/src/Databases/MySQL/DatabaseMySQL.cpp +++ b/src/Databases/MySQL/DatabaseMySQL.cpp @@ -398,7 +398,7 @@ String DatabaseMySQL::getMetadataPath() const return metadata_path; } -void DatabaseMySQL::loadStoredObjects(ContextMutablePtr, bool, bool /*force_attach*/, bool /* skip_startup_tables */) +void DatabaseMySQL::loadStoredObjects(ContextMutablePtr, LoadingStrictnessLevel /*mode*/, bool /* skip_startup_tables */) { std::lock_guard lock{mutex}; diff --git a/src/Databases/MySQL/DatabaseMySQL.h b/src/Databases/MySQL/DatabaseMySQL.h index 542cd65c1f1..5d0a366e5e6 100644 --- a/src/Databases/MySQL/DatabaseMySQL.h +++ b/src/Databases/MySQL/DatabaseMySQL.h @@ -76,7 +76,7 @@ public: void createTable(ContextPtr, const String & table_name, const StoragePtr & storage, const ASTPtr & create_query) override; - void loadStoredObjects(ContextMutablePtr, bool, bool force_attach, bool skip_startup_tables) override; + void loadStoredObjects(ContextMutablePtr, LoadingStrictnessLevel /*mode*/, bool skip_startup_tables) override; StoragePtr detachTable(ContextPtr context, const String & table_name) override; diff --git a/src/Databases/PostgreSQL/DatabaseMaterializedPostgreSQL.cpp b/src/Databases/PostgreSQL/DatabaseMaterializedPostgreSQL.cpp index 08a0859e6db..523cc7041be 100644 --- a/src/Databases/PostgreSQL/DatabaseMaterializedPostgreSQL.cpp +++ b/src/Databases/PostgreSQL/DatabaseMaterializedPostgreSQL.cpp @@ -125,9 +125,9 @@ void DatabaseMaterializedPostgreSQL::startSynchronization() } -void DatabaseMaterializedPostgreSQL::startupTables(ThreadPool & thread_pool, bool force_restore, bool force_attach) +void DatabaseMaterializedPostgreSQL::startupTables(ThreadPool & thread_pool, LoadingStrictnessLevel mode) { - DatabaseAtomic::startupTables(thread_pool, force_restore, force_attach); + DatabaseAtomic::startupTables(thread_pool, mode); startup_task->activateAndSchedule(); } diff --git a/src/Databases/PostgreSQL/DatabaseMaterializedPostgreSQL.h b/src/Databases/PostgreSQL/DatabaseMaterializedPostgreSQL.h index ac2bcedca60..6363e8e07c4 100644 --- a/src/Databases/PostgreSQL/DatabaseMaterializedPostgreSQL.h +++ b/src/Databases/PostgreSQL/DatabaseMaterializedPostgreSQL.h @@ -40,7 +40,7 @@ public: String getMetadataPath() const override { return metadata_path; } - void startupTables(ThreadPool & thread_pool, bool force_restore, bool force_attach) override; + void startupTables(ThreadPool & thread_pool, LoadingStrictnessLevel mode) override; DatabaseTablesIteratorPtr getTablesIterator(ContextPtr context, const DatabaseOnDisk::FilterByNameFunction & filter_by_table_name) const override; diff --git a/src/Databases/PostgreSQL/DatabasePostgreSQL.cpp b/src/Databases/PostgreSQL/DatabasePostgreSQL.cpp index c4b815c0c9f..8e89765b635 100644 --- a/src/Databases/PostgreSQL/DatabasePostgreSQL.cpp +++ b/src/Databases/PostgreSQL/DatabasePostgreSQL.cpp @@ -290,7 +290,7 @@ void DatabasePostgreSQL::drop(ContextPtr /*context*/) } -void DatabasePostgreSQL::loadStoredObjects(ContextMutablePtr /* context */, bool, bool /*force_attach*/, bool /* skip_startup_tables */) +void DatabasePostgreSQL::loadStoredObjects(ContextMutablePtr /* context */, LoadingStrictnessLevel /*mode*/, bool /* skip_startup_tables */) { { std::lock_guard lock{mutex}; diff --git a/src/Databases/PostgreSQL/DatabasePostgreSQL.h b/src/Databases/PostgreSQL/DatabasePostgreSQL.h index fe4dff2ca93..d70e529e4a6 100644 --- a/src/Databases/PostgreSQL/DatabasePostgreSQL.h +++ b/src/Databases/PostgreSQL/DatabasePostgreSQL.h @@ -45,7 +45,7 @@ public: bool empty() const override; - void loadStoredObjects(ContextMutablePtr, bool, bool force_attach, bool skip_startup_tables) override; + void loadStoredObjects(ContextMutablePtr, LoadingStrictnessLevel /*mode*/, bool skip_startup_tables) override; DatabaseTablesIteratorPtr getTablesIterator(ContextPtr context, const FilterByNameFunction & filter_by_table_name) const override; diff --git a/src/Databases/TablesLoader.cpp b/src/Databases/TablesLoader.cpp index 7e9b83d423a..1114206d469 100644 --- a/src/Databases/TablesLoader.cpp +++ b/src/Databases/TablesLoader.cpp @@ -62,11 +62,10 @@ void logAboutProgress(Poco::Logger * log, size_t processed, size_t total, Atomic } } -TablesLoader::TablesLoader(ContextMutablePtr global_context_, Databases databases_, bool force_restore_, bool force_attach_) +TablesLoader::TablesLoader(ContextMutablePtr global_context_, Databases databases_, LoadingStrictnessLevel strictness_mode_) : global_context(global_context_) , databases(std::move(databases_)) -, force_restore(force_restore_) -, force_attach(force_attach_) +, strictness_mode(strictness_mode_) { metadata.default_database = global_context->getCurrentDatabase(); log = &Poco::Logger::get("TablesLoader"); @@ -83,7 +82,7 @@ void TablesLoader::loadTables() if (need_resolve_dependencies && database.second->supportsLoadingInTopologicalOrder()) databases_to_load.push_back(database.first); else - database.second->loadStoredObjects(global_context, force_restore, force_attach, true); + database.second->loadStoredObjects(global_context, strictness_mode, /* skip_startup_tables */ true); } if (databases_to_load.empty()) @@ -92,8 +91,9 @@ void TablesLoader::loadTables() /// Read and parse metadata from Ordinary, Atomic, Materialized*, Replicated, etc databases. Build dependency graph. for (auto & database_name : databases_to_load) { - databases[database_name]->beforeLoadingMetadata(global_context, force_restore, force_attach); - databases[database_name]->loadTablesMetadata(global_context, metadata, force_attach); + databases[database_name]->beforeLoadingMetadata(global_context, strictness_mode); + bool is_startup = LoadingStrictnessLevel::FORCE_ATTACH <= strictness_mode; + databases[database_name]->loadTablesMetadata(global_context, metadata, is_startup); } LOG_INFO(log, "Parsed metadata of {} tables in {} databases in {} sec", @@ -119,7 +119,7 @@ void TablesLoader::startupTables() { /// Startup tables after all tables are loaded. Background tasks (merges, mutations, etc) may slow down data parts loading. for (auto & database : databases) - database.second->startupTables(pool, force_restore, force_attach); + database.second->startupTables(pool, strictness_mode); } @@ -253,7 +253,7 @@ void TablesLoader::startLoadingIndependentTables(ThreadPool & pool, size_t level pool.scheduleOrThrowOnError([this, load_context, total_tables, &table_name]() { const auto & path_and_query = metadata.parsed_tables[table_name]; - databases[table_name.database]->loadTableFromMetadata(load_context, path_and_query.path, table_name, path_and_query.ast, force_restore); + databases[table_name.database]->loadTableFromMetadata(load_context, path_and_query.path, table_name, path_and_query.ast, strictness_mode); logAboutProgress(log, ++tables_processed, total_tables, stopwatch); }); } diff --git a/src/Databases/TablesLoader.h b/src/Databases/TablesLoader.h index 43e8bfdb92c..7a29d0e3958 100644 --- a/src/Databases/TablesLoader.h +++ b/src/Databases/TablesLoader.h @@ -1,14 +1,15 @@ #pragma once -#include -#include -#include -#include -#include -#include #include +#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include namespace Poco { @@ -78,7 +79,7 @@ class TablesLoader public: using Databases = std::map; - TablesLoader(ContextMutablePtr global_context_, Databases databases_, bool force_restore_ = false, bool force_attach_ = false); + TablesLoader(ContextMutablePtr global_context_, Databases databases_, LoadingStrictnessLevel strictness_mode_); TablesLoader() = delete; void loadTables(); @@ -87,8 +88,7 @@ public: private: ContextMutablePtr global_context; Databases databases; - bool force_restore; - bool force_attach; + LoadingStrictnessLevel strictness_mode; Strings databases_to_load; ParsedTablesMetadata metadata; diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index a8f8a3f3e27..1fb5e386a58 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -249,6 +249,7 @@ BlockIO InterpreterCreateQuery::createDatabase(ASTCreateQuery & create) bool need_write_metadata = !create.attach || !fs::exists(metadata_file_path); bool need_lock_uuid = internal || need_write_metadata; + auto mode = getLoadingStrictnessLevel(create.attach, force_attach, has_force_restore_data_flag); /// Lock uuid, so we will known it's already in use. /// We do it when attaching databases on server startup (internal) and on CREATE query (!create.attach); @@ -303,8 +304,9 @@ BlockIO InterpreterCreateQuery::createDatabase(ASTCreateQuery & create) if (!load_database_without_tables) { + /// We use global context here, because storages lifetime is bigger than query context lifetime - TablesLoader loader{getContext()->getGlobalContext(), {{database_name, database}}, has_force_restore_data_flag, create.attach && force_attach}; //-V560 + TablesLoader loader{getContext()->getGlobalContext(), {{database_name, database}}, mode}; //-V560 loader.loadTables(); loader.startupTables(); } diff --git a/src/Interpreters/InterpreterSystemQuery.cpp b/src/Interpreters/InterpreterSystemQuery.cpp index 695ea53e65e..a3f4b21407b 100644 --- a/src/Interpreters/InterpreterSystemQuery.cpp +++ b/src/Interpreters/InterpreterSystemQuery.cpp @@ -760,6 +760,7 @@ void InterpreterSystemQuery::syncReplica(ASTSystemQuery &) void InterpreterSystemQuery::syncReplicatedDatabase(ASTSystemQuery & query) { const auto database_name = query.getDatabase(); + auto guard = DatabaseCatalog::instance().getDDLGuard(database_name, ""); auto database = DatabaseCatalog::instance().getDatabase(database_name); if (auto * ptr = typeid_cast(database.get())) @@ -767,8 +768,7 @@ void InterpreterSystemQuery::syncReplicatedDatabase(ASTSystemQuery & query) LOG_TRACE(log, "Synchronizing entries in the database replica's (name: {}) queue with the log", database_name); if (!ptr->waitForReplicaToProcessAllEntries(getContext()->getSettingsRef().receive_timeout.totalMilliseconds())) { - LOG_ERROR(log, "SYNC DATABASE REPLICA {}: Timed out!", database_name); - throw Exception(ErrorCodes::TIMEOUT_EXCEEDED, "SYNC DATABASE REPLICA {}: command timed out. " \ + throw Exception(ErrorCodes::TIMEOUT_EXCEEDED, "SYNC DATABASE REPLICA {}: database is readonly or command timed out. " \ "See the 'receive_timeout' setting", database_name); } LOG_TRACE(log, "SYNC DATABASE REPLICA {}: OK", database_name); diff --git a/src/Interpreters/loadMetadata.cpp b/src/Interpreters/loadMetadata.cpp index 15d4f7929f8..9ac076b99c5 100644 --- a/src/Interpreters/loadMetadata.cpp +++ b/src/Interpreters/loadMetadata.cpp @@ -38,6 +38,7 @@ static void executeCreateQuery( ContextMutablePtr context, const String & database, const String & file_name, + bool create, bool has_force_restore_data_flag) { ParserCreateQuery parser; @@ -49,8 +50,11 @@ static void executeCreateQuery( InterpreterCreateQuery interpreter(ast, context); interpreter.setInternal(true); - interpreter.setForceAttach(true); - interpreter.setForceRestoreData(has_force_restore_data_flag); + if (!create) + { + interpreter.setForceAttach(true); + interpreter.setForceRestoreData(has_force_restore_data_flag); + } interpreter.setLoadDatabaseWithoutTables(true); interpreter.execute(); } @@ -86,7 +90,7 @@ static void loadDatabase( try { - executeCreateQuery(database_attach_query, context, database, database_metadata_file, force_restore_data); + executeCreateQuery(database_attach_query, context, database, database_metadata_file, /* create */ true, force_restore_data); } catch (Exception & e) { @@ -173,7 +177,8 @@ void loadMetadata(ContextMutablePtr context, const String & default_database_nam loaded_databases.insert({name, DatabaseCatalog::instance().getDatabase(name)}); } - TablesLoader loader{context, std::move(loaded_databases), has_force_restore_data_flag, /* force_attach */ true}; + auto mode = getLoadingStrictnessLevel(/* attach */ true, /* force_attach */ true, has_force_restore_data_flag); + TablesLoader loader{context, std::move(loaded_databases), mode}; loader.loadTables(); loader.startupTables(); @@ -207,7 +212,7 @@ static void loadSystemDatabaseImpl(ContextMutablePtr context, const String & dat database_create_query += database_name; database_create_query += " ENGINE="; database_create_query += default_engine; - executeCreateQuery(database_create_query, context, database_name, "", true); + executeCreateQuery(database_create_query, context, database_name, "", true, true); } } @@ -315,7 +320,7 @@ void maybeConvertOrdinaryDatabaseToAtomic(ContextMutablePtr context, const Datab { {DatabaseCatalog::SYSTEM_DATABASE, DatabaseCatalog::instance().getSystemDatabase()}, }; - TablesLoader loader{context, databases, /* force_restore */ true, /* force_attach */ true}; + TablesLoader loader{context, databases, LoadingStrictnessLevel::FORCE_RESTORE}; loader.loadTables(); /// Will startup tables usual way @@ -331,7 +336,7 @@ void maybeConvertOrdinaryDatabaseToAtomic(ContextMutablePtr context, const Datab void startupSystemTables() { ThreadPool pool; - DatabaseCatalog::instance().getSystemDatabase()->startupTables(pool, /* force_restore */ true, /* force_attach */ true); + DatabaseCatalog::instance().getSystemDatabase()->startupTables(pool, LoadingStrictnessLevel::FORCE_RESTORE); } void loadMetadataSystem(ContextMutablePtr context) @@ -346,7 +351,7 @@ void loadMetadataSystem(ContextMutablePtr context) {DatabaseCatalog::INFORMATION_SCHEMA, DatabaseCatalog::instance().getDatabase(DatabaseCatalog::INFORMATION_SCHEMA)}, {DatabaseCatalog::INFORMATION_SCHEMA_UPPERCASE, DatabaseCatalog::instance().getDatabase(DatabaseCatalog::INFORMATION_SCHEMA_UPPERCASE)}, }; - TablesLoader loader{context, databases, /* force_restore */ true, /* force_attach */ true}; + TablesLoader loader{context, databases, LoadingStrictnessLevel::FORCE_RESTORE}; loader.loadTables(); /// Will startup tables in system database after all databases are loaded. } From aef212d5529a188ba8472286541f02414bdbf9e8 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 21 Jul 2022 09:00:31 +0200 Subject: [PATCH 069/672] Add ref commits to the chengelog headers --- utils/changelog/changelog.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utils/changelog/changelog.py b/utils/changelog/changelog.py index 939585afcaa..6bd064dfe72 100755 --- a/utils/changelog/changelog.py +++ b/utils/changelog/changelog.py @@ -278,10 +278,13 @@ def generate_description(item: PullRequest, repo: Repository) -> Optional[Descri def write_changelog(fd: TextIO, descriptions: Dict[str, List[Description]]): year = date.today().year + to_commit = runner(f"git rev-parse {TO_REF}^{{}}")[:11] + from_commit = runner(f"git rev-parse {FROM_REF}^{{}}")[:11] fd.write( f"---\nsidebar_position: 1\nsidebar_label: {year}\n---\n\n" f"# {year} Changelog\n\n" - f"### ClickHouse release {TO_REF} FIXME as compared to {FROM_REF}\n\n" + f"### ClickHouse release {TO_REF} ({to_commit}) FIXME " + f"as compared to {FROM_REF} ({from_commit})\n\n" ) seen_categories = [] # type: List[str] From 6b568ffa32542e89073d58fdbc1d8c4f950fe10d Mon Sep 17 00:00:00 2001 From: avogar Date: Thu, 21 Jul 2022 09:03:53 +0000 Subject: [PATCH 070/672] Fix test --- .../02314_csv_tsv_skip_first_lines.reference | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/queries/0_stateless/02314_csv_tsv_skip_first_lines.reference b/tests/queries/0_stateless/02314_csv_tsv_skip_first_lines.reference index 7d8e0c662cd..4274f5769ee 100644 --- a/tests/queries/0_stateless/02314_csv_tsv_skip_first_lines.reference +++ b/tests/queries/0_stateless/02314_csv_tsv_skip_first_lines.reference @@ -1,14 +1,14 @@ -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Nullable(Int64) 0 1 2 1 2 3 2 3 4 3 4 5 4 5 6 -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Nullable(Int64) 0 1 2 1 2 3 2 3 4 From 7f4a1b8bb8b7dcc3f28caee0aa29558346549e64 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Thu, 21 Jul 2022 11:45:09 +0200 Subject: [PATCH 071/672] ShellCommand wait pid refactoring --- src/Common/waitForPid.cpp | 146 ++++++++++++++++++++------------------ src/Common/waitForPid.h | 4 +- 2 files changed, 77 insertions(+), 73 deletions(-) diff --git a/src/Common/waitForPid.cpp b/src/Common/waitForPid.cpp index 38f43ae2f6a..b28433714ed 100644 --- a/src/Common/waitForPid.cpp +++ b/src/Common/waitForPid.cpp @@ -17,6 +17,17 @@ eintr_wrapper_result; \ }) +namespace DB +{ + +enum PollPidResult +{ + RESTART, + FAILED +}; + +} + #if defined(OS_LINUX) #include @@ -43,16 +54,9 @@ namespace DB static int syscall_pidfd_open(pid_t pid) { - // pidfd_open cannot be interrupted, no EINTR handling return syscall(SYS_pidfd_open, pid, 0); } -static int dir_pidfd_open(pid_t pid) -{ - std::string path = "/proc/" + std::to_string(pid); - return HANDLE_EINTR(open(path.c_str(), O_DIRECTORY)); -} - static bool supportsPidFdOpen() { VersionNumber pidfd_open_minimal_version(5, 3, 0); @@ -60,36 +64,52 @@ static bool supportsPidFdOpen() return linux_version >= pidfd_open_minimal_version; } -static int pidFdOpen(pid_t pid) +static PollPidResult pollPid(pid_t pid, int timeout_in_ms) { - // use pidfd_open or just plain old /proc/[pid] open for Linux + int pid_fd = 0; + if (supportsPidFdOpen()) { - return syscall_pidfd_open(pid); + // pidfd_open cannot be interrupted, no EINTR handling + + pid_fd = syscall_pidfd_open(pid); + + if (pid_fd < 0) + { + if (errno == ESRCH) + return PollPidResult::RESTART; + + return PollPidResult::FAILED; + } } else { - return dir_pidfd_open(pid); - } -} + std::string path = "/proc/" + std::to_string(pid); + pid_fd = HANDLE_EINTR(open(path.c_str(), O_DIRECTORY)); + + if (pid_fd < 0) + { + if (errno == ENOENT) + return PollPidResult::RESTART; + + return PollPidResult::FAILED; + } + } -static int pollPid(pid_t pid, int timeout_in_ms) -{ struct pollfd pollfd; - - int pid_fd = pidFdOpen(pid); - if (pid_fd == -1) - { - return false; - } pollfd.fd = pid_fd; pollfd.events = POLLIN; - int ready = poll(&pollfd, 1, timeout_in_ms); - int save_errno = errno; + + int ready = HANDLE_EINTR(poll(&pollfd, 1, timeout_in_ms)); + + if (ready <= 0) + return PollPidResult::FAILED; + close(pid_fd); - errno = save_errno; - return ready; + + return PollPidResult::RESTART; } + #elif defined(OS_DARWIN) || defined(OS_FREEBSD) #include @@ -98,38 +118,32 @@ static int pollPid(pid_t pid, int timeout_in_ms) namespace DB { -static int pollPid(pid_t pid, int timeout_in_ms) +static PollPidResult pollPid(pid_t pid, int timeout_in_ms) { - int status = 0; - int kq = HANDLE_EINTR(kqueue()); + int kq = kqueue(); if (kq == -1) - { - return false; - } + return PollPidResult::FAILED; + struct kevent change = {.ident = NULL}; EV_SET(&change, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); - int result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); - if (result == -1) + + int event_add_result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); + if (event_add_result == -1) { - if (errno != ESRCH) - { - return false; - } - // check if pid already died while we called kevent() - if (waitpid(pid, &status, WNOHANG) == pid) - { - return true; - } - return false; + if (errno == ESRCH) + return PollPidResult::RESTART; + + return PollPidResult::FAILED; } struct kevent event = {.ident = NULL}; struct timespec remaining_timespec = {.tv_sec = timeout_in_ms / 1000, .tv_nsec = (timeout_in_ms % 1000) * 1000000}; - int ready = kevent(kq, nullptr, 0, &event, 1, &remaining_timespec); - int save_errno = errno; + int ready = HANDLE_EINTR(kevent(kq, nullptr, 0, &event, 1, &remaining_timespec)); + PollPidResult result = ready < 0 ? PollPidResult::FAILED : PollPidResult::RESTART; + close(kq); - errno = save_errno; - return ready; + + return result; } #else #error "Unsupported OS type" @@ -146,7 +160,7 @@ bool waitForPid(pid_t pid, size_t timeout_in_seconds) /// If there is no timeout before signal try to waitpid 1 time without block so we can avoid sending /// signal if process is already normally terminated. - int waitpid_res = waitpid(pid, &status, WNOHANG); + int waitpid_res = HANDLE_EINTR(waitpid(pid, &status, WNOHANG)); bool process_terminated_normally = (waitpid_res == pid); return process_terminated_normally; } @@ -157,34 +171,24 @@ bool waitForPid(pid_t pid, size_t timeout_in_seconds) int timeout_in_ms = timeout_in_seconds * 1000; while (timeout_in_ms > 0) { - int waitpid_res = waitpid(pid, &status, WNOHANG); + int waitpid_res = HANDLE_EINTR(waitpid(pid, &status, WNOHANG)); bool process_terminated_normally = (waitpid_res == pid); if (process_terminated_normally) - { return true; - } - else if (waitpid_res == 0) - { - watch.restart(); - int ready = pollPid(pid, timeout_in_ms); - if (ready <= 0) - { - if (errno == EINTR || errno == EAGAIN) - { - timeout_in_ms -= watch.elapsedMilliseconds(); - } - else - { - return false; - } - } - continue; - } - else if (waitpid_res == -1 && errno != EINTR) - { + + if (waitpid_res != 0) return false; - } + + watch.restart(); + + PollPidResult result = pollPid(pid, timeout_in_ms); + + if (result == PollPidResult::FAILED) + return false; + + timeout_in_ms -= watch.elapsedMilliseconds(); } + return false; } diff --git a/src/Common/waitForPid.h b/src/Common/waitForPid.h index 71c1a74712c..85a0d2c8dd9 100644 --- a/src/Common/waitForPid.h +++ b/src/Common/waitForPid.h @@ -4,8 +4,8 @@ namespace DB { /* - * Waits for a specific pid with timeout, using modern Linux and OSX facilities - * Returns `true` if process terminated successfully or `false` otherwise + * Waits for a specific pid with timeout + * Returns `true` if process terminated successfully in specified timeout or `false` otherwise */ bool waitForPid(pid_t pid, size_t timeout_in_seconds); From 0e44e34e698ed24b1126cb656553ba5a8b42adc7 Mon Sep 17 00:00:00 2001 From: Anton Kozlov Date: Mon, 11 Jul 2022 15:13:36 +0000 Subject: [PATCH 072/672] Do not optimize GROUP BY functions that shadow their arguments --- .../FunctionMaskingArgumentCheckVisitor.h | 43 +++++++++++++++++++ src/Interpreters/TreeOptimizer.cpp | 14 ++++++ .../02352_grouby_shadows_arg.reference | 6 +++ .../0_stateless/02352_grouby_shadows_arg.sql | 5 +++ 4 files changed, 68 insertions(+) create mode 100644 src/Interpreters/FunctionMaskingArgumentCheckVisitor.h create mode 100644 tests/queries/0_stateless/02352_grouby_shadows_arg.reference create mode 100644 tests/queries/0_stateless/02352_grouby_shadows_arg.sql diff --git a/src/Interpreters/FunctionMaskingArgumentCheckVisitor.h b/src/Interpreters/FunctionMaskingArgumentCheckVisitor.h new file mode 100644 index 00000000000..b455dd6ac5a --- /dev/null +++ b/src/Interpreters/FunctionMaskingArgumentCheckVisitor.h @@ -0,0 +1,43 @@ +#pragma once + + +#include +#include +#include +#include + +namespace DB +{ + +/// Checks from bottom to top if a function's alias shadows the name +/// of one of it's arguments +class FunctionMaskingArgumentCheckMatcher +{ +public: + struct Data + { + const String& alias; + bool is_rejected = false; + void reject() { is_rejected = true; } + }; + + static void visit(const ASTPtr & ast, Data & data) + { + if (data.is_rejected) + return; + if (const auto & identifier = ast->as()) + visit(*identifier, data); + } + + static void visit(const ASTIdentifier & ast, Data & data) + { + if (ast.getAliasOrColumnName() == data.alias) + data.reject(); + } + + static bool needChildVisit(const ASTPtr &, const ASTPtr &) { return true; } +}; + +using FunctionMaskingArgumentCheckVisitor = ConstInDepthNodeVisitor; + +} diff --git a/src/Interpreters/TreeOptimizer.cpp b/src/Interpreters/TreeOptimizer.cpp index cf79cd158e8..1d0b41e796a 100644 --- a/src/Interpreters/TreeOptimizer.cpp +++ b/src/Interpreters/TreeOptimizer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -153,6 +154,19 @@ void optimizeGroupBy(ASTSelectQuery * select_query, ContextPtr context) continue; } } + /// don't optimise functions that shadow any of it's arguments: + /// https://github.com/ClickHouse/ClickHouse/issues/37032 + else if (!function->alias.empty()) + { + FunctionMaskingArgumentCheckVisitor::Data data{.alias=function->alias}; + FunctionMaskingArgumentCheckVisitor(data).visit(function->arguments); + + if (data.is_rejected) + { + ++i; + continue; + } + } /// copy shared pointer to args in order to ensure lifetime auto args_ast = function->arguments; diff --git a/tests/queries/0_stateless/02352_grouby_shadows_arg.reference b/tests/queries/0_stateless/02352_grouby_shadows_arg.reference new file mode 100644 index 00000000000..c6ed459bf52 --- /dev/null +++ b/tests/queries/0_stateless/02352_grouby_shadows_arg.reference @@ -0,0 +1,6 @@ +0 +1 +4 +0 +1 +2 diff --git a/tests/queries/0_stateless/02352_grouby_shadows_arg.sql b/tests/queries/0_stateless/02352_grouby_shadows_arg.sql new file mode 100644 index 00000000000..c293b250f86 --- /dev/null +++ b/tests/queries/0_stateless/02352_grouby_shadows_arg.sql @@ -0,0 +1,5 @@ +SET prefer_localhost_replica=0; +SELECT toString(dummy) as dummy FROM remote('127.{1,1}', 'system.one') GROUP BY dummy; +SELECT toString(dummy+1) as dummy FROM remote('127.{1,1}', 'system.one') GROUP BY dummy; +SELECT toString((toInt8(dummy)+2) * (toInt8(dummy)+2)) as dummy FROM remote('127.{1,1}', system.one) GROUP BY dummy; +SELECT round(number % 3) AS number FROM remote('127.{1,1}', numbers(20)) GROUP BY number ORDER BY number ASC; \ No newline at end of file From 6b541aa98f141a92eeeb586f94a5dc79281b575f Mon Sep 17 00:00:00 2001 From: avogar Date: Thu, 21 Jul 2022 12:18:37 +0000 Subject: [PATCH 073/672] Fix WriteBuffer finalize when cancel insert into function --- src/Processors/Formats/IOutputFormat.cpp | 4 ++- src/Storages/HDFS/StorageHDFS.cpp | 28 +++++++++++++++--- src/Storages/StorageFile.cpp | 29 ++++++++++++++++--- src/Storages/StorageS3.cpp | 28 +++++++++++++++--- src/Storages/StorageURL.cpp | 24 +++++++++++++-- src/Storages/StorageURL.h | 4 +++ .../02366_cancel_write_into_file.reference | 0 .../02366_cancel_write_into_file.sh | 24 +++++++++++++++ .../02367_cancel_write_into_s3.reference | 0 .../0_stateless/02367_cancel_write_into_s3.sh | 24 +++++++++++++++ .../02368_cancel_write_into_hdfs.reference | 0 .../02368_cancel_write_into_hdfs.sh | 24 +++++++++++++++ .../0_stateless/data_minio/02366_data.jsonl | 0 13 files changed, 173 insertions(+), 16 deletions(-) create mode 100644 tests/queries/0_stateless/02366_cancel_write_into_file.reference create mode 100755 tests/queries/0_stateless/02366_cancel_write_into_file.sh create mode 100644 tests/queries/0_stateless/02367_cancel_write_into_s3.reference create mode 100755 tests/queries/0_stateless/02367_cancel_write_into_s3.sh create mode 100644 tests/queries/0_stateless/02368_cancel_write_into_hdfs.reference create mode 100755 tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh create mode 100644 tests/queries/0_stateless/data_minio/02366_data.jsonl diff --git a/src/Processors/Formats/IOutputFormat.cpp b/src/Processors/Formats/IOutputFormat.cpp index 3c4e6861151..47ebaa9c5f5 100644 --- a/src/Processors/Formats/IOutputFormat.cpp +++ b/src/Processors/Formats/IOutputFormat.cpp @@ -73,7 +73,6 @@ void IOutputFormat::work() setRowsBeforeLimit(rows_before_limit_counter->get()); finalize(); - finalized = true; return; } @@ -120,9 +119,12 @@ void IOutputFormat::write(const Block & block) void IOutputFormat::finalize() { + if (finalized) + return; writePrefixIfNot(); writeSuffixIfNot(); finalizeImpl(); + finalized = true; } } diff --git a/src/Storages/HDFS/StorageHDFS.cpp b/src/Storages/HDFS/StorageHDFS.cpp index 57e893e9683..04bd58c3a81 100644 --- a/src/Storages/HDFS/StorageHDFS.cpp +++ b/src/Storages/HDFS/StorageHDFS.cpp @@ -427,18 +427,37 @@ public: void consume(Chunk chunk) override { + std::lock_guard lock(cancel_mutex); + if (cancelled) + return; writer->write(getHeader().cloneWithColumns(chunk.detachColumns())); } + void onCancel() override + { + std::lock_guard lock(cancel_mutex); + finalize(); + cancelled = false; + } + void onException() override { - if (!writer) - return; - onFinish(); + std::lock_guard lock(cancel_mutex); + finalize(); } void onFinish() override { + std::lock_guard lock(cancel_mutex); + finalize(); + } + +private: + void finalize() + { + if (!writer) + return; + try { writer->finalize(); @@ -454,9 +473,10 @@ public: } } -private: std::unique_ptr write_buf; OutputFormatPtr writer; + std::mutex cancel_mutex; + bool cancelled = false; }; class PartitionedHDFSSink : public PartitionedSink diff --git a/src/Storages/StorageFile.cpp b/src/Storages/StorageFile.cpp index d138104018a..672727b1478 100644 --- a/src/Storages/StorageFile.cpp +++ b/src/Storages/StorageFile.cpp @@ -810,18 +810,37 @@ public: void consume(Chunk chunk) override { + std::lock_guard cancel_lock(cancel_mutex); + if (cancelled) + return; writer->write(getHeader().cloneWithColumns(chunk.detachColumns())); } + void onCancel() override + { + std::lock_guard cancel_lock(cancel_mutex); + finalize(); + cancelled = true; + } + void onException() override { - if (!writer) - return; - onFinish(); + std::lock_guard cancel_lock(cancel_mutex); + finalize(); } void onFinish() override { + std::lock_guard cancel_lock(cancel_mutex); + finalize(); + } + +private: + void finalize() + { + if (!writer) + return; + try { writer->finalize(); @@ -836,7 +855,6 @@ public: } } -private: StorageMetadataPtr metadata_snapshot; String table_name_for_log; @@ -854,6 +872,9 @@ private: ContextPtr context; int flags; std::unique_lock lock; + + std::mutex cancel_mutex; + bool cancelled = false; }; class PartitionedStorageFileSink : public PartitionedSink diff --git a/src/Storages/StorageS3.cpp b/src/Storages/StorageS3.cpp index 130bc75a65c..aed3f541d47 100644 --- a/src/Storages/StorageS3.cpp +++ b/src/Storages/StorageS3.cpp @@ -599,18 +599,37 @@ public: void consume(Chunk chunk) override { + std::lock_guard lock(cancel_mutex); + if (cancelled) + return; writer->write(getHeader().cloneWithColumns(chunk.detachColumns())); } + void onCancel() override + { + std::lock_guard lock(cancel_mutex); + finalize(); + cancelled = true; + } + void onException() override { - if (!writer) - return; - onFinish(); + std::lock_guard lock(cancel_mutex); + finalize(); } void onFinish() override { + std::lock_guard lock(cancel_mutex); + finalize(); + } + +private: + void finalize() + { + if (!writer) + return; + try { writer->finalize(); @@ -625,11 +644,12 @@ public: } } -private: Block sample_block; std::optional format_settings; std::unique_ptr write_buf; OutputFormatPtr writer; + bool cancelled = false; + std::mutex cancel_mutex; }; diff --git a/src/Storages/StorageURL.cpp b/src/Storages/StorageURL.cpp index 15ae23305f3..acf7444dca4 100644 --- a/src/Storages/StorageURL.cpp +++ b/src/Storages/StorageURL.cpp @@ -447,18 +447,36 @@ StorageURLSink::StorageURLSink( void StorageURLSink::consume(Chunk chunk) { + std::lock_guard lock(cancel_mutex); + if (cancelled) + return; writer->write(getHeader().cloneWithColumns(chunk.detachColumns())); } +void StorageURLSink::onCancel() +{ + std::lock_guard lock(cancel_mutex); + finalize(); + cancelled = true; +} + void StorageURLSink::onException() { - if (!writer) - return; - onFinish(); + std::lock_guard lock(cancel_mutex); + finalize(); } void StorageURLSink::onFinish() { + std::lock_guard lock(cancel_mutex); + finalize(); +} + +void StorageURLSink::finalize() +{ + if (!writer) + return; + try { writer->finalize(); diff --git a/src/Storages/StorageURL.h b/src/Storages/StorageURL.h index 79371242bb1..0198eda9e67 100644 --- a/src/Storages/StorageURL.h +++ b/src/Storages/StorageURL.h @@ -114,12 +114,16 @@ public: std::string getName() const override { return "StorageURLSink"; } void consume(Chunk chunk) override; + void onCancel() override; void onException() override; void onFinish() override; private: + void finalize(); std::unique_ptr write_buf; OutputFormatPtr writer; + std::mutex cancel_mutex; + bool cancelled = false; }; class StorageURL : public IStorageURLBase diff --git a/tests/queries/0_stateless/02366_cancel_write_into_file.reference b/tests/queries/0_stateless/02366_cancel_write_into_file.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02366_cancel_write_into_file.sh b/tests/queries/0_stateless/02366_cancel_write_into_file.sh new file mode 100755 index 00000000000..d9abc69a414 --- /dev/null +++ b/tests/queries/0_stateless/02366_cancel_write_into_file.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + + +for i in $(seq 1 10); +do + $CLICKHOUSE_CLIENT --query_id="02366_$i" -q "insert into function file('02366_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings output_format_parallel_formatting=1" 2> /dev/null & +done + +sleep 2 + +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" > /dev/null + +for i in $(seq 1 10); +do + $CLICKHOUSE_CLIENT --query_id="02366_$i" -q "insert into function file('02366_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings output_format_parallel_formatting=0" 2> /dev/null & +done + +sleep 2 + +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" > /dev/null diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.reference b/tests/queries/0_stateless/02367_cancel_write_into_s3.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh new file mode 100755 index 00000000000..39316bece5f --- /dev/null +++ b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + + +for i in $(seq 1 10); +do + $CLICKHOUSE_CLIENT --query_id="02366_$i" -q "insert into function s3('http://localhost:11111/test/02367_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings s3_truncate_on_insert=1, output_format_parallel_formatting=1" 2> /dev/null & +done + +sleep 2 + +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null + +for i in $(seq 1 10); +do + $CLICKHOUSE_CLIENT --query_id="02367_$i" -q "insert into function s3('http://localhost:11111/test/02366_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings s3_truncate_on_inser=1, output_format_parallel_formatting=0" 2> /dev/null & +done + +sleep 2 + +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null diff --git a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.reference b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh new file mode 100755 index 00000000000..70fe1a3e150 --- /dev/null +++ b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + + +for i in $(seq 1 10); +do + $CLICKHOUSE_CLIENT --query_id="02368_$i" -q "insert into function hdfs('hdfs://localhost:12222/02368_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings hdfs_truncate_on_inser=1, toutput_format_parallel_formatting=1" 2> /dev/null & +done + +sleep 2 + +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null + +for i in $(seq 1 10); +do + $CLICKHOUSE_CLIENT --query_id="02368_$i" -q "insert into function hdfs('hdfs://localhost:12222/02368_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings hdfs_truncate_on_insert=1, output_format_parallel_formatting=0" 2> /dev/null & +done + +sleep 2 + +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null diff --git a/tests/queries/0_stateless/data_minio/02366_data.jsonl b/tests/queries/0_stateless/data_minio/02366_data.jsonl new file mode 100644 index 00000000000..e69de29bb2d From 52b3a87ed1e8b5f6fefb4ce69a7a77ef9a85f5cf Mon Sep 17 00:00:00 2001 From: avogar Date: Thu, 21 Jul 2022 12:38:18 +0000 Subject: [PATCH 074/672] Fix typo --- src/Storages/HDFS/StorageHDFS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/HDFS/StorageHDFS.cpp b/src/Storages/HDFS/StorageHDFS.cpp index 04bd58c3a81..5098dfd3ef1 100644 --- a/src/Storages/HDFS/StorageHDFS.cpp +++ b/src/Storages/HDFS/StorageHDFS.cpp @@ -437,7 +437,7 @@ public: { std::lock_guard lock(cancel_mutex); finalize(); - cancelled = false; + cancelled = true; } void onException() override From 209a5047c0f265644c8da55cb974c8544f76f6fc Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 21 Jul 2022 15:45:38 +0200 Subject: [PATCH 075/672] Improve caching logic, add cached NamedUser --- tests/ci/github_helper.py | 67 ++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/tests/ci/github_helper.py b/tests/ci/github_helper.py index 46cf7d2b726..15fcf88aa40 100644 --- a/tests/ci/github_helper.py +++ b/tests/ci/github_helper.py @@ -5,11 +5,12 @@ from datetime import date, datetime, timedelta from pathlib import Path from os import path as p from time import sleep -from typing import List, Optional +from typing import List, Optional, Tuple import github from github.GithubException import RateLimitExceededException from github.Issue import Issue +from github.NamedUser import NamedUser from github.PullRequest import PullRequest from github.Repository import Repository @@ -120,21 +121,15 @@ class GitHub(github.Github): return def get_pull_cached( - self, repo: Repository, number: int, updated_at: Optional[datetime] = None + self, repo: Repository, number: int, obj_updated_at: Optional[datetime] = None ) -> PullRequest: - pr_cache_file = self.cache_path / f"{number}.pickle" - if updated_at is None: - updated_at = datetime.now() - timedelta(hours=-1) + cache_file = self.cache_path / f"pr-{number}.pickle" - def _get_pr(path: Path) -> PullRequest: - with open(path, "rb") as prfd: - return self.load(prfd) # type: ignore - - if pr_cache_file.is_file(): - cached_pr = _get_pr(pr_cache_file) - if updated_at <= cached_pr.updated_at: + if cache_file.is_file(): + is_updated, cached_pr = self._is_cache_updated(cache_file, obj_updated_at) + if is_updated: logger.debug("Getting PR #%s from cache", number) - return cached_pr + return cached_pr # type: ignore logger.debug("Getting PR #%s from API", number) for i in range(self.retries): try: @@ -144,11 +139,53 @@ class GitHub(github.Github): if i == self.retries - 1: raise self.sleep_on_rate_limit() - logger.debug("Caching PR #%s from API in %s", number, pr_cache_file) - with open(pr_cache_file, "wb") as prfd: + logger.debug("Caching PR #%s from API in %s", number, cache_file) + with open(cache_file, "wb") as prfd: self.dump(pr, prfd) # type: ignore return pr + def get_user_cached( + self, login: str, obj_updated_at: Optional[datetime] = None + ) -> NamedUser: + cache_file = self.cache_path / f"user-{login}.pickle" + + if cache_file.is_file(): + is_updated, cached_user = self._is_cache_updated(cache_file, obj_updated_at) + if is_updated: + logger.debug("Getting user %s from cache", login) + return cached_user # type: ignore + logger.debug("Getting PR #%s from API", login) + for i in range(self.retries): + try: + user = self.get_user(login) + break + except RateLimitExceededException: + if i == self.retries - 1: + raise + self.sleep_on_rate_limit() + logger.debug("Caching user %s from API in %s", login, cache_file) + with open(cache_file, "wb") as prfd: + self.dump(user, prfd) # type: ignore + return user + + def _get_cached(self, path: Path): + with open(path, "rb") as ob_fd: + return self.load(ob_fd) # type: ignore + + def _is_cache_updated( + self, cache_file: Path, obj_updated_at: Optional[datetime] + ) -> Tuple[bool, object]: + cached_obj = self._get_cached(cache_file) + cache_updated = cached_obj.updated_at + if obj_updated_at is None: + obj_updated_at = datetime.now() - timedelta(hours=1) + cache_updated = max( + datetime.fromtimestamp(cache_file.stat().st_mtime), cache_updated + ) + if obj_updated_at <= cache_updated: + return True, cached_obj + return False, cached_obj + @property def cache_path(self): return self._cache_path From dd2a0ed755af4d77d1558b923e2c0a6c7576153b Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 21 Jul 2022 15:46:39 +0200 Subject: [PATCH 076/672] Improve speed significantly by using cached users and reuse repos --- utils/changelog/changelog.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/utils/changelog/changelog.py b/utils/changelog/changelog.py index 6bd064dfe72..3981bce73a6 100755 --- a/utils/changelog/changelog.py +++ b/utils/changelog/changelog.py @@ -43,7 +43,7 @@ class Description: ): self.number = number self.html_url = html_url - self.user = user + self.user = gh.get_user_cached(user._rawData["login"]) # type: ignore self.entry = entry self.category = category @@ -88,11 +88,18 @@ class Description: return self.number < other.number -def get_descriptions( - repo: Repository, prs: PullRequests -) -> Dict[str, List[Description]]: +def get_descriptions(prs: PullRequests) -> Dict[str, List[Description]]: descriptions = {} # type: Dict[str, List[Description]] + repos = {} # type: Dict[str, Repository] for pr in prs: + # See https://github.com/PyGithub/PyGithub/issues/2202, + # obj._rawData doesn't spend additional API requests + # We'll save some requests + # pylint: disable=protected-access + repo_name = pr._rawData["base"]["repo"]["full_name"] # type: ignore + # pylint: enable=protected-access + if repo_name not in repos: + repos[repo_name] = pr.base.repo in_changelog = False merge_commit = pr.merge_commit_sha try: @@ -104,7 +111,7 @@ def get_descriptions( in_changelog = merge_commit in SHA_IN_CHANGELOG if in_changelog: - desc = generate_description(pr, repo) + desc = generate_description(pr, repos[repo_name]) if desc is not None: if desc.category not in descriptions: descriptions[desc.category] = [] @@ -336,11 +343,11 @@ def set_sha_in_changelog(): def main(): - log_levels = [logging.CRITICAL, logging.WARN, logging.INFO, logging.DEBUG] + log_levels = [logging.WARN, logging.INFO, logging.DEBUG] args = parse_args() logging.basicConfig( format="%(asctime)s %(levelname)-8s [%(filename)s:%(lineno)d]:\n%(message)s", - level=log_levels[min(args.verbose, 3)], + level=log_levels[min(args.verbose, 2)], ) if args.debug_helpers: logging.getLogger("github_helper").setLevel(logging.DEBUG) @@ -379,10 +386,9 @@ def main(): ) gh.cache_path = CACHE_PATH query = f"type:pr repo:{args.repo} is:merged" - repo = gh.get_repo(args.repo) prs = gh.get_pulls_from_search(query=query, merged=merged, sort="created") - descriptions = get_descriptions(repo, prs) + descriptions = get_descriptions(prs) write_changelog(args.output, descriptions) From e0d2c8fb37e2c89990c332739fa1266d3ff9ee84 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 21 Jul 2022 14:47:19 +0000 Subject: [PATCH 077/672] fix json type with sparse columns --- src/Columns/ColumnConst.h | 5 - src/Columns/ColumnTuple.cpp | 11 --- src/Columns/ColumnTuple.h | 1 - src/Columns/IColumn.cpp | 5 - src/Columns/IColumn.h | 5 - src/DataTypes/DataTypeFactory.cpp | 1 - src/DataTypes/DataTypeFactory.h | 2 +- src/DataTypes/DataTypeTuple.cpp | 24 ++++- src/DataTypes/DataTypeTuple.h | 2 + src/DataTypes/IDataType.cpp | 11 ++- src/DataTypes/IDataType.h | 4 +- .../Serializations/SerializationInfo.cpp | 26 ++++- .../Serializations/SerializationInfo.h | 5 + .../Serializations/SerializationInfoTuple.cpp | 41 +++++--- .../Serializations/SerializationInfoTuple.h | 12 ++- src/Formats/NativeWriter.cpp | 4 +- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 96 ++++++++++--------- src/Storages/MergeTree/IMergeTreeDataPart.h | 11 ++- src/Storages/MergeTree/IMergeTreeReader.cpp | 42 +++----- src/Storages/MergeTree/IMergeTreeReader.h | 12 +-- .../MergeTree/MergeTreeBlockReadUtils.cpp | 2 +- .../MergeTree/MergeTreeReaderCompact.cpp | 9 +- .../MergeTree/MergeTreeReaderInMemory.cpp | 4 +- .../MergeTree/MergeTreeReaderWide.cpp | 6 +- .../01825_type_json_sparse.reference | 13 +++ .../0_stateless/01825_type_json_sparse.sql | 59 ++++++++++++ 26 files changed, 264 insertions(+), 149 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json_sparse.reference create mode 100644 tests/queries/0_stateless/01825_type_json_sparse.sql diff --git a/src/Columns/ColumnConst.h b/src/Columns/ColumnConst.h index fdee16a1dbb..21cfaf7f64c 100644 --- a/src/Columns/ColumnConst.h +++ b/src/Columns/ColumnConst.h @@ -263,11 +263,6 @@ public: } } - SerializationInfoPtr getSerializationInfo() const override - { - return data->getSerializationInfo(); - } - bool isNullable() const override { return isColumnNullable(*data); } bool onlyNull() const override { return data->isNullAt(0); } bool isNumeric() const override { return data->isNumeric(); } diff --git a/src/Columns/ColumnTuple.cpp b/src/Columns/ColumnTuple.cpp index 7c8d73edd16..6763c11bb9a 100644 --- a/src/Columns/ColumnTuple.cpp +++ b/src/Columns/ColumnTuple.cpp @@ -561,15 +561,4 @@ void ColumnTuple::getIndicesOfNonDefaultRows(Offsets & indices, size_t from, siz return getIndicesOfNonDefaultRowsImpl(indices, from, limit); } -SerializationInfoPtr ColumnTuple::getSerializationInfo() const -{ - MutableSerializationInfos infos; - infos.reserve(columns.size()); - - for (const auto & column : columns) - infos.push_back(const_pointer_cast(column->getSerializationInfo())); - - return std::make_shared(std::move(infos), SerializationInfo::Settings{}); -} - } diff --git a/src/Columns/ColumnTuple.h b/src/Columns/ColumnTuple.h index e6797c2582c..b1de8df74a9 100644 --- a/src/Columns/ColumnTuple.h +++ b/src/Columns/ColumnTuple.h @@ -102,7 +102,6 @@ public: ColumnPtr compress() const override; double getRatioOfDefaultRows(double sample_ratio) const override; void getIndicesOfNonDefaultRows(Offsets & indices, size_t from, size_t limit) const override; - SerializationInfoPtr getSerializationInfo() const override; size_t tupleSize() const { return columns.size(); } diff --git a/src/Columns/IColumn.cpp b/src/Columns/IColumn.cpp index e7caee3b23d..2158adb86a8 100644 --- a/src/Columns/IColumn.cpp +++ b/src/Columns/IColumn.cpp @@ -64,11 +64,6 @@ ColumnPtr IColumn::createWithOffsets(const Offsets & offsets, const Field & defa return res; } -SerializationInfoPtr IColumn::getSerializationInfo() const -{ - return std::make_shared(ISerialization::getKind(*this), SerializationInfo::Settings{}); -} - bool isColumnNullable(const IColumn & column) { return checkColumn(column); diff --git a/src/Columns/IColumn.h b/src/Columns/IColumn.h index a99d4172e5b..8b693015674 100644 --- a/src/Columns/IColumn.h +++ b/src/Columns/IColumn.h @@ -35,9 +35,6 @@ class ColumnGathererStream; class Field; class WeakHash32; -class SerializationInfo; -using SerializationInfoPtr = std::shared_ptr; - /* * Represents a set of equal ranges in previous column to perform sorting in current column. * Used in sorting by tuples. @@ -445,8 +442,6 @@ public: /// Used to create full column from sparse. [[nodiscard]] virtual Ptr createWithOffsets(const Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) const; - [[nodiscard]] virtual SerializationInfoPtr getSerializationInfo() const; - /// Compress column in memory to some representation that allows to decompress it back. /// Return itself if compression is not applicable for this column type. [[nodiscard]] virtual Ptr compress() const diff --git a/src/DataTypes/DataTypeFactory.cpp b/src/DataTypes/DataTypeFactory.cpp index dd08656dccc..9f60210f8d6 100644 --- a/src/DataTypes/DataTypeFactory.cpp +++ b/src/DataTypes/DataTypeFactory.cpp @@ -119,7 +119,6 @@ void DataTypeFactory::registerDataType(const String & family_name, Value creator throw Exception("DataTypeFactory: the data type family name '" + family_name + "' is not unique", ErrorCodes::LOGICAL_ERROR); - if (case_sensitiveness == CaseInsensitive && !case_insensitive_data_types.emplace(family_name_lowercase, creator).second) throw Exception("DataTypeFactory: the case insensitive data type family name '" + family_name + "' is not unique", diff --git a/src/DataTypes/DataTypeFactory.h b/src/DataTypes/DataTypeFactory.h index 704d8926bf0..f452f6167c7 100644 --- a/src/DataTypes/DataTypeFactory.h +++ b/src/DataTypes/DataTypeFactory.h @@ -25,7 +25,7 @@ class DataTypeFactory final : private boost::noncopyable, public IFactoryWithAli private: using SimpleCreator = std::function; using DataTypesDictionary = std::unordered_map; - using CreatorWithCustom = std::function(const ASTPtr & parameters)>; + using CreatorWithCustom = std::function(const ASTPtr & parameters)>; using SimpleCreatorWithCustom = std::function()>; public: diff --git a/src/DataTypes/DataTypeTuple.cpp b/src/DataTypes/DataTypeTuple.cpp index 558b13927c1..768d88b57df 100644 --- a/src/DataTypes/DataTypeTuple.cpp +++ b/src/DataTypes/DataTypeTuple.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -257,6 +258,7 @@ size_t DataTypeTuple::getSizeOfValueInMemory() const SerializationPtr DataTypeTuple::doGetDefaultSerialization() const { SerializationTuple::ElementSerializations serializations(elems.size()); + for (size_t i = 0; i < elems.size(); ++i) { String elem_name = have_explicit_names ? names[i] : toString(i + 1); @@ -289,7 +291,27 @@ MutableSerializationInfoPtr DataTypeTuple::createSerializationInfo(const Seriali for (const auto & elem : elems) infos.push_back(elem->createSerializationInfo(settings)); - return std::make_shared(std::move(infos), settings); + return std::make_shared(std::move(infos), names, settings); +} + +SerializationInfoPtr DataTypeTuple::getSerializationInfo(const IColumn & column) const +{ + if (const auto * column_const = checkAndGetColumn(&column)) + return getSerializationInfo(column_const->getDataColumn()); + + MutableSerializationInfos infos; + infos.reserve(elems.size()); + + const auto & column_tuple = assert_cast(column); + assert(elems.size() == column_tuple.getColumns().size()); + + for (size_t i = 0; i < elems.size(); ++i) + { + auto element_info = elems[i]->getSerializationInfo(column_tuple.getColumn(i)); + infos.push_back(const_pointer_cast(element_info)); + } + + return std::make_shared(std::move(infos), names, SerializationInfo::Settings{}); } diff --git a/src/DataTypes/DataTypeTuple.h b/src/DataTypes/DataTypeTuple.h index 009a2284a0a..1ce6bd9e966 100644 --- a/src/DataTypes/DataTypeTuple.h +++ b/src/DataTypes/DataTypeTuple.h @@ -22,6 +22,7 @@ private: DataTypes elems; Strings names; bool have_explicit_names; + public: static constexpr bool is_parametric = true; @@ -54,6 +55,7 @@ public: SerializationPtr doGetDefaultSerialization() const override; SerializationPtr getSerialization(const SerializationInfo & info) const override; MutableSerializationInfoPtr createSerializationInfo(const SerializationInfo::Settings & settings) const override; + SerializationInfoPtr getSerializationInfo(const IColumn & column) const override; const DataTypePtr & getElement(size_t i) const { return elems[i]; } const DataTypes & getElements() const { return elems; } diff --git a/src/DataTypes/IDataType.cpp b/src/DataTypes/IDataType.cpp index f2bb878a533..cb3bab5c653 100644 --- a/src/DataTypes/IDataType.cpp +++ b/src/DataTypes/IDataType.cpp @@ -179,12 +179,19 @@ void IDataType::setCustomization(DataTypeCustomDescPtr custom_desc_) const custom_serialization = std::move(custom_desc_->serialization); } -MutableSerializationInfoPtr IDataType::createSerializationInfo( - const SerializationInfo::Settings & settings) const +MutableSerializationInfoPtr IDataType::createSerializationInfo(const SerializationInfo::Settings & settings) const { return std::make_shared(ISerialization::Kind::DEFAULT, settings); } +SerializationInfoPtr IDataType::getSerializationInfo(const IColumn & column) const +{ + if (const auto * column_const = checkAndGetColumn(&column)) + return getSerializationInfo(column_const->getDataColumn()); + + return std::make_shared(ISerialization::getKind(column), SerializationInfo::Settings{}); +} + SerializationPtr IDataType::getDefaultSerialization() const { if (custom_serialization) diff --git a/src/DataTypes/IDataType.h b/src/DataTypes/IDataType.h index fce8906abe5..fc6ba2a2fc3 100644 --- a/src/DataTypes/IDataType.h +++ b/src/DataTypes/IDataType.h @@ -101,8 +101,8 @@ public: Names getSubcolumnNames() const; - virtual MutableSerializationInfoPtr createSerializationInfo( - const SerializationInfo::Settings & settings) const; + virtual MutableSerializationInfoPtr createSerializationInfo(const SerializationInfo::Settings & settings) const; + virtual SerializationInfoPtr getSerializationInfo(const IColumn & column) const; /// TODO: support more types. virtual bool supportsSparseSerialization() const { return !haveSubtypes(); } diff --git a/src/DataTypes/Serializations/SerializationInfo.cpp b/src/DataTypes/Serializations/SerializationInfo.cpp index a0dc20b6479..543b4a75f0a 100644 --- a/src/DataTypes/Serializations/SerializationInfo.cpp +++ b/src/DataTypes/Serializations/SerializationInfo.cpp @@ -1,9 +1,9 @@ #include -#include #include #include #include #include +#include #include #include @@ -47,12 +47,25 @@ void SerializationInfo::Data::add(const Data & other) num_defaults += other.num_defaults; } +void SerializationInfo::Data::addDefaults(size_t length) +{ + num_rows += length; + num_defaults += length; +} + SerializationInfo::SerializationInfo(ISerialization::Kind kind_, const Settings & settings_) : settings(settings_) , kind(kind_) { } +SerializationInfo::SerializationInfo(ISerialization::Kind kind_, const Settings & settings_, const Data & data_) + : settings(settings_) + , kind(kind_) + , data(data_) +{ +} + void SerializationInfo::add(const IColumn & column) { data.add(column); @@ -67,6 +80,13 @@ void SerializationInfo::add(const SerializationInfo & other) kind = chooseKind(data, settings); } +void SerializationInfo::addDefaults(size_t length) +{ + data.addDefaults(length); + if (settings.choose_kind) + kind = chooseKind(data, settings); +} + void SerializationInfo::replaceData(const SerializationInfo & other) { data = other.data; @@ -74,9 +94,7 @@ void SerializationInfo::replaceData(const SerializationInfo & other) MutableSerializationInfoPtr SerializationInfo::clone() const { - auto res = std::make_shared(kind, settings); - res->data = data; - return res; + return std::make_shared(kind, settings, data); } void SerializationInfo::serialializeKindBinary(WriteBuffer & out) const diff --git a/src/DataTypes/Serializations/SerializationInfo.h b/src/DataTypes/Serializations/SerializationInfo.h index d83fc16f2f6..a4a5685253f 100644 --- a/src/DataTypes/Serializations/SerializationInfo.h +++ b/src/DataTypes/Serializations/SerializationInfo.h @@ -34,6 +34,7 @@ public: void add(const IColumn & column); void add(const Data & other); + void addDefaults(size_t length); }; struct Settings @@ -45,6 +46,7 @@ public: }; SerializationInfo(ISerialization::Kind kind_, const Settings & settings_); + SerializationInfo(ISerialization::Kind kind_, const Settings & settings_, const Data & data_); virtual ~SerializationInfo() = default; @@ -52,7 +54,9 @@ public: virtual void add(const IColumn & column); virtual void add(const SerializationInfo & other); + virtual void addDefaults(size_t length); virtual void replaceData(const SerializationInfo & other); + virtual std::shared_ptr clone() const; virtual void serialializeKindBinary(WriteBuffer & out) const; @@ -61,6 +65,7 @@ public: virtual Poco::JSON::Object toJSON() const; virtual void fromJSON(const Poco::JSON::Object & object); + void setKind(ISerialization::Kind kind_) { kind = kind_; } const Settings & getSettings() const { return settings; } const Data & getData() const { return data; } ISerialization::Kind getKind() const { return kind; } diff --git a/src/DataTypes/Serializations/SerializationInfoTuple.cpp b/src/DataTypes/Serializations/SerializationInfoTuple.cpp index 803302f9642..b4683c7d0a0 100644 --- a/src/DataTypes/Serializations/SerializationInfoTuple.cpp +++ b/src/DataTypes/Serializations/SerializationInfoTuple.cpp @@ -10,13 +10,18 @@ namespace ErrorCodes { extern const int CORRUPTED_DATA; extern const int THERE_IS_NO_COLUMN; + extern const int LOGICAL_ERROR; } SerializationInfoTuple::SerializationInfoTuple( - MutableSerializationInfos elems_, const Settings & settings_) + MutableSerializationInfos elems_, Names names_, const Settings & settings_) : SerializationInfo(ISerialization::Kind::DEFAULT, settings_) , elems(std::move(elems_)) + , names(std::move(names_)) { + assert(names.size() == elems.size()); + for (size_t i = 0; i < names.size(); ++i) + name_to_elem[names[i]] = elems[i]; } bool SerializationInfoTuple::hasCustomSerialization() const @@ -40,22 +45,34 @@ void SerializationInfoTuple::add(const SerializationInfo & other) { SerializationInfo::add(other); - const auto & info_tuple = assert_cast(other); - assert(elems.size() == info_tuple.elems.size()); + const auto & other_info = assert_cast(other); + for (const auto & [name, elem] : name_to_elem) + { + auto it = other_info.name_to_elem.find(name); + if (it != other_info.name_to_elem.end()) + elem->add(*it->second); + else + elem->addDefaults(other_info.getData().num_rows); + } +} - for (size_t i = 0; i < elems.size(); ++i) - elems[i]->add(*info_tuple.elems[i]); +void SerializationInfoTuple::addDefaults(size_t length) +{ + for (const auto & elem : elems) + elem->addDefaults(length); } void SerializationInfoTuple::replaceData(const SerializationInfo & other) { SerializationInfo::add(other); - const auto & info_tuple = assert_cast(other); - assert(elems.size() == info_tuple.elems.size()); - - for (size_t i = 0; i < elems.size(); ++i) - elems[i]->replaceData(*info_tuple.elems[i]); + const auto & other_info = assert_cast(other); + for (const auto & [name, elem] : name_to_elem) + { + auto it = other_info.name_to_elem.find(name); + if (it != other_info.name_to_elem.end()) + elem->replaceData(*it->second); + } } MutableSerializationInfoPtr SerializationInfoTuple::clone() const @@ -65,7 +82,7 @@ MutableSerializationInfoPtr SerializationInfoTuple::clone() const for (const auto & elem : elems) elems_cloned.push_back(elem->clone()); - return std::make_shared(std::move(elems_cloned), settings); + return std::make_shared(std::move(elems_cloned), names, settings); } void SerializationInfoTuple::serialializeKindBinary(WriteBuffer & out) const @@ -99,7 +116,7 @@ void SerializationInfoTuple::fromJSON(const Poco::JSON::Object & object) if (!object.has("subcolumns")) throw Exception(ErrorCodes::CORRUPTED_DATA, - "Missed field '{}' in SerializationInfo of columns SerializationInfoTuple"); + "Missed field 'subcolumns' in SerializationInfo of columns SerializationInfoTuple"); auto subcolumns = object.getArray("subcolumns"); if (elems.size() != subcolumns->size()) diff --git a/src/DataTypes/Serializations/SerializationInfoTuple.h b/src/DataTypes/Serializations/SerializationInfoTuple.h index d196f80393e..b01c629d2ff 100644 --- a/src/DataTypes/Serializations/SerializationInfoTuple.h +++ b/src/DataTypes/Serializations/SerializationInfoTuple.h @@ -1,4 +1,5 @@ #pragma once +#include #include namespace DB @@ -7,25 +8,32 @@ namespace DB class SerializationInfoTuple : public SerializationInfo { public: - SerializationInfoTuple(MutableSerializationInfos elems_, const Settings & settings_); + SerializationInfoTuple(MutableSerializationInfos elems_, Names names_, const Settings & settings_); bool hasCustomSerialization() const override; + void add(const IColumn & column) override; void add(const SerializationInfo & other) override; + void addDefaults(size_t length) override; void replaceData(const SerializationInfo & other) override; MutableSerializationInfoPtr clone() const override; + void serialializeKindBinary(WriteBuffer & out) const override; void deserializeFromKindsBinary(ReadBuffer & in) override; Poco::JSON::Object toJSON() const override; void fromJSON(const Poco::JSON::Object & object) override; - MutableSerializationInfoPtr getElementInfo(size_t i) const { return elems[i]; } + const MutableSerializationInfoPtr & getElementInfo(size_t i) const { return elems[i]; } ISerialization::Kind getElementKind(size_t i) const { return elems[i]->getKind(); } private: MutableSerializationInfos elems; + Names names; + + using NameToElem = std::unordered_map; + NameToElem name_to_elem; }; } diff --git a/src/Formats/NativeWriter.cpp b/src/Formats/NativeWriter.cpp index 77692eec6b6..9d4cfb68d56 100644 --- a/src/Formats/NativeWriter.cpp +++ b/src/Formats/NativeWriter.cpp @@ -103,7 +103,7 @@ void NativeWriter::write(const Block & block) mark.offset_in_decompressed_block = ostr_concrete->getRemainingBytes(); } - ColumnWithTypeAndName column = block.safeGetByPosition(i); + auto column = block.safeGetByPosition(i); /// Send data to old clients without low cardinality type. if (remove_low_cardinality || (client_revision && client_revision < DBMS_MIN_REVISION_WITH_LOW_CARDINALITY_TYPE)) @@ -145,7 +145,7 @@ void NativeWriter::write(const Block & block) SerializationPtr serialization; if (client_revision >= DBMS_MIN_REVISION_WITH_CUSTOM_SERIALIZATION) { - auto info = column.column->getSerializationInfo(); + auto info = column.type->getSerializationInfo(*column.column); serialization = column.type->getSerialization(*info); bool has_custom = info->hasCustomSerialization(); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 60941108f00..d4d3844de6c 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -445,6 +447,18 @@ void IMergeTreeDataPart::setColumns(const NamesAndTypesList & new_columns) for (const auto & column : columns) column_name_to_position.emplace(column.name, pos++); + + columns_description = ColumnsDescription(columns); +} + +NameAndTypePair IMergeTreeDataPart::getColumn(const String & column_name) const +{ + return columns_description.getColumnOrSubcolumn(GetColumnsOptions::AllPhysical, column_name); +} + +std::optional IMergeTreeDataPart::tryGetColumn(const String & column_name) const +{ + return columns_description.tryGetColumnOrSubcolumn(GetColumnsOptions::AllPhysical, column_name); } void IMergeTreeDataPart::setSerializationInfos(const SerializationInfoByName & new_infos) @@ -454,10 +468,15 @@ void IMergeTreeDataPart::setSerializationInfos(const SerializationInfoByName & n SerializationPtr IMergeTreeDataPart::getSerialization(const NameAndTypePair & column) const { - auto it = serialization_infos.find(column.getNameInStorage()); - return it == serialization_infos.end() - ? IDataType::getSerialization(column) - : IDataType::getSerialization(column, *it->second); + auto column_in_part = tryGetColumn(column.name); + if (!column_in_part) + return IDataType::getSerialization(column); + + auto it = serialization_infos.find(column_in_part->getNameInStorage()); + if (it == serialization_infos.end()) + return IDataType::getSerialization(*column_in_part); + + return IDataType::getSerialization(*column_in_part, *it->second); } void IMergeTreeDataPart::removeIfNeeded() @@ -564,37 +583,38 @@ size_t IMergeTreeDataPart::getFileSizeOrZero(const String & file_name) const return checksum->second.file_size; } -String IMergeTreeDataPart::getColumnNameWithMinimumCompressedSize( - const StorageSnapshotPtr & storage_snapshot, bool with_subcolumns) const +String IMergeTreeDataPart::getColumnNameWithMinimumCompressedSize(bool with_subcolumns) const { - auto options = GetColumnsOptions(GetColumnsOptions::AllPhysical).withExtendedObjects(); - if (with_subcolumns) - options.withSubcolumns(); + auto find_column_with_minimum_size = [&](const auto & columns_list) + { + std::optional minimum_size_column; + UInt64 minimum_size = std::numeric_limits::max(); - auto storage_columns = storage_snapshot->getColumns(options); - MergeTreeData::AlterConversions alter_conversions; - if (!parent_part) - alter_conversions = storage.getAlterConversionsForPart(shared_from_this()); + for (const auto & column : columns_list) + { + if (!hasColumnFiles(column)) + continue; + + const auto size = getColumnSize(column.name).data_compressed; + if (size < minimum_size) + { + minimum_size = size; + minimum_size_column = column.name; + } + } + + return minimum_size_column; + }; std::optional minimum_size_column; - UInt64 minimum_size = std::numeric_limits::max(); - - for (const auto & column : storage_columns) + if (with_subcolumns) { - auto column_name = column.name; - auto column_type = column.type; - if (alter_conversions.isColumnRenamed(column.name)) - column_name = alter_conversions.getColumnOldName(column.name); - - if (!hasColumnFiles(column)) - continue; - - const auto size = getColumnSize(column_name).data_compressed; - if (size < minimum_size) - { - minimum_size = size; - minimum_size_column = column_name; - } + auto options = GetColumnsOptions(GetColumnsOptions::AllPhysical).withSubcolumns(); + minimum_size_column = find_column_with_minimum_size(columns_description.get(options)); + } + else + { + minimum_size_column = find_column_with_minimum_size(columns); } if (!minimum_size_column) @@ -603,22 +623,6 @@ String IMergeTreeDataPart::getColumnNameWithMinimumCompressedSize( return *minimum_size_column; } -// String IMergeTreeDataPart::getFullPath() const -// { -// if (relative_path.empty()) -// throw Exception("Part relative_path cannot be empty. It's bug.", ErrorCodes::LOGICAL_ERROR); - -// return fs::path(storage.getFullPathOnDisk(volume->getDisk())) / (parent_part ? parent_part->relative_path : "") / relative_path / ""; -// } - -// String IMergeTreeDataPart::getRelativePath() const -// { -// if (relative_path.empty()) -// throw Exception("Part relative_path cannot be empty. It's bug.", ErrorCodes::LOGICAL_ERROR); - -// return fs::path(storage.relative_data_path) / (parent_part ? parent_part->relative_path : "") / relative_path / ""; -// } - void IMergeTreeDataPart::loadColumnsChecksumsIndexes(bool require_columns_checksums, bool check_consistency) { assertOnDisk(); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 7f3c41ce4c2..613fda59c84 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -136,6 +137,9 @@ public: const NamesAndTypesList & getColumns() const { return columns; } + NameAndTypePair getColumn(const String & name) const; + std::optional tryGetColumn(const String & column_name) const; + void setSerializationInfos(const SerializationInfoByName & new_infos); const SerializationInfoByName & getSerializationInfos() const { return serialization_infos; } @@ -167,8 +171,7 @@ public: /// Returns the name of a column with minimum compressed size (as returned by getColumnSize()). /// If no checksums are present returns the name of the first physically existing column. - String getColumnNameWithMinimumCompressedSize( - const StorageSnapshotPtr & storage_snapshot, bool with_subcolumns) const; + String getColumnNameWithMinimumCompressedSize(bool with_subcolumns) const; bool contains(const IMergeTreeDataPart & other) const { return info.contains(other.info); } @@ -521,6 +524,10 @@ private: /// Map from name of column to its serialization info. SerializationInfoByName serialization_infos; + /// Columns description for more convinient access + /// to columns by name and getting subcolumns. + ColumnsDescription columns_description; + /// Reads part unique identifier (if exists) from uuid.txt void loadUUID(); diff --git a/src/Storages/MergeTree/IMergeTreeReader.cpp b/src/Storages/MergeTree/IMergeTreeReader.cpp index b8aeb8e6a5a..08796636f89 100644 --- a/src/Storages/MergeTree/IMergeTreeReader.cpp +++ b/src/Storages/MergeTree/IMergeTreeReader.cpp @@ -18,6 +18,7 @@ namespace namespace ErrorCodes { extern const int LOGICAL_ERROR; + extern const int THERE_IS_NO_COLUMN; } @@ -33,7 +34,6 @@ IMergeTreeReader::IMergeTreeReader( : data_part(data_part_) , avg_value_size_hints(avg_value_size_hints_) , columns(columns_) - , part_columns(data_part->getColumns()) , uncompressed_cache(uncompressed_cache_) , mark_cache(mark_cache_) , settings(settings_) @@ -47,11 +47,7 @@ IMergeTreeReader::IMergeTreeReader( /// For wide parts convert plain arrays of Nested to subcolumns /// to allow to use shared offset column from cache. columns = Nested::convertToSubcolumns(columns); - part_columns = Nested::collect(part_columns); } - - for (const auto & column_from_part : part_columns) - columns_from_part[column_from_part.name] = &column_from_part.type; } IMergeTreeReader::~IMergeTreeReader() = default; @@ -124,37 +120,25 @@ void IMergeTreeReader::evaluateMissingDefaults(Block additional_columns, Columns } } -NameAndTypePair IMergeTreeReader::getColumnFromPart(const NameAndTypePair & required_column) const +String IMergeTreeReader::getColumnNameInPart(const NameAndTypePair & required_column) const { auto name_in_storage = required_column.getNameInStorage(); - - ColumnsFromPart::ConstLookupResult it; if (alter_conversions.isColumnRenamed(name_in_storage)) { - String old_name = alter_conversions.getColumnOldName(name_in_storage); - it = columns_from_part.find(old_name); - } - else - { - it = columns_from_part.find(name_in_storage); + name_in_storage = alter_conversions.getColumnOldName(name_in_storage); + return Nested::concatenateName(name_in_storage, required_column.getSubcolumnName()); } - if (it == columns_from_part.end()) - return required_column; + return required_column.name; +} - const DataTypePtr & type = *it->getMapped(); - if (required_column.isSubcolumn()) - { - auto subcolumn_name = required_column.getSubcolumnName(); - auto subcolumn_type = type->tryGetSubcolumnType(subcolumn_name); +NameAndTypePair IMergeTreeReader::getColumnInPart(const NameAndTypePair & required_column) const +{ + auto column_in_part = data_part->tryGetColumn(getColumnNameInPart(required_column)); + if (column_in_part) + return *column_in_part; - if (!subcolumn_type) - return required_column; - - return {String(it->getKey()), subcolumn_name, type, subcolumn_type}; - } - - return {String(it->getKey()), type}; + return required_column; } void IMergeTreeReader::performRequiredConversions(Columns & res_columns) const @@ -183,7 +167,7 @@ void IMergeTreeReader::performRequiredConversions(Columns & res_columns) const if (res_columns[pos] == nullptr) continue; - copy_block.insert({res_columns[pos], getColumnFromPart(*name_and_type).type, name_and_type->name}); + copy_block.insert({res_columns[pos], getColumnInPart(*name_and_type).type, name_and_type->name}); } DB::performRequiredConversions(copy_block, columns, storage.getContext()); diff --git a/src/Storages/MergeTree/IMergeTreeReader.h b/src/Storages/MergeTree/IMergeTreeReader.h index b13db9c3255..78cc96ccd27 100644 --- a/src/Storages/MergeTree/IMergeTreeReader.h +++ b/src/Storages/MergeTree/IMergeTreeReader.h @@ -63,8 +63,10 @@ public: MergeTreeData::DataPartPtr data_part; protected: - /// Returns actual column type in part, which can differ from table metadata. - NameAndTypePair getColumnFromPart(const NameAndTypePair & required_column) const; + /// Returns actual column name in part, which can differ from table metadata. + String getColumnNameInPart(const NameAndTypePair & required_column) const; + /// Returns actual column name and type in part, which can differ from table metadata. + NameAndTypePair getColumnInPart(const NameAndTypePair & required_column) const; void checkNumberOfColumns(size_t num_columns_to_read) const; @@ -75,7 +77,6 @@ protected: /// Columns that are read. NamesAndTypesList columns; - NamesAndTypesList part_columns; UncompressedCache * uncompressed_cache; MarkCache * mark_cache; @@ -92,11 +93,6 @@ protected: private: /// Alter conversions, which must be applied on fly if required MergeTreeData::AlterConversions alter_conversions; - - /// Actual data type of columns in part - - using ColumnsFromPart = HashMapWithSavedHash; - ColumnsFromPart columns_from_part; }; } diff --git a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp index 50f4c34f004..b71044b8373 100644 --- a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp +++ b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp @@ -122,7 +122,7 @@ NameSet injectRequiredColumns( */ if (!have_at_least_one_physical_column) { - const auto minimum_size_column_name = part->getColumnNameWithMinimumCompressedSize(storage_snapshot, with_subcolumns); + const auto minimum_size_column_name = part->getColumnNameWithMinimumCompressedSize(with_subcolumns); columns.push_back(minimum_size_column_name); /// correctly report added column injected_columns.insert(columns.back()); diff --git a/src/Storages/MergeTree/MergeTreeReaderCompact.cpp b/src/Storages/MergeTree/MergeTreeReaderCompact.cpp index 8e2e1d02836..5cb37b0515a 100644 --- a/src/Storages/MergeTree/MergeTreeReaderCompact.cpp +++ b/src/Storages/MergeTree/MergeTreeReaderCompact.cpp @@ -54,14 +54,14 @@ MergeTreeReaderCompact::MergeTreeReaderCompact( { if (name_and_type->isSubcolumn()) { - auto storage_column_from_part = getColumnFromPart( + auto storage_column_from_part = getColumnInPart( {name_and_type->getNameInStorage(), name_and_type->getTypeInStorage()}); if (!storage_column_from_part.type->tryGetSubcolumnType(name_and_type->getSubcolumnName())) continue; } - auto column_from_part = getColumnFromPart(*name_and_type); + auto column_from_part = getColumnInPart(*name_and_type); auto position = data_part->getColumnPosition(column_from_part.getNameInStorage()); if (!position && typeid_cast(column_from_part.type.get())) @@ -153,7 +153,7 @@ size_t MergeTreeReaderCompact::readRows( if (!column_positions[i]) continue; - auto column_from_part = getColumnFromPart(*column_it); + auto column_from_part = getColumnInPart(*column_it); if (res_columns[i] == nullptr) { auto serialization = data_part->getSerialization(column_from_part); @@ -168,10 +168,11 @@ size_t MergeTreeReaderCompact::readRows( auto name_and_type = columns.begin(); for (size_t pos = 0; pos < num_columns; ++pos, ++name_and_type) { - auto column_from_part = getColumnFromPart(*name_and_type); if (!res_columns[pos]) continue; + auto column_from_part = getColumnInPart(*name_and_type); + try { auto & column = res_columns[pos]; diff --git a/src/Storages/MergeTree/MergeTreeReaderInMemory.cpp b/src/Storages/MergeTree/MergeTreeReaderInMemory.cpp index c1b0067dbb0..d8e0b6e4d6c 100644 --- a/src/Storages/MergeTree/MergeTreeReaderInMemory.cpp +++ b/src/Storages/MergeTree/MergeTreeReaderInMemory.cpp @@ -34,7 +34,7 @@ MergeTreeReaderInMemory::MergeTreeReaderInMemory( { for (const auto & name_and_type : columns) { - auto [name, type] = getColumnFromPart(name_and_type); + auto [name, type] = getColumnInPart(name_and_type); /// If array of Nested column is missing in part, /// we have to read its offsets if they exist. @@ -67,7 +67,7 @@ size_t MergeTreeReaderInMemory::readRows( auto column_it = columns.begin(); for (size_t i = 0; i < num_columns; ++i, ++column_it) { - auto name_type = getColumnFromPart(*column_it); + auto name_type = getColumnInPart(*column_it); /// Copy offsets, if array of Nested column is missing in part. auto offsets_it = positions_for_offsets.find(name_type.name); diff --git a/src/Storages/MergeTree/MergeTreeReaderWide.cpp b/src/Storages/MergeTree/MergeTreeReaderWide.cpp index 07fe187332e..e098e76d4ee 100644 --- a/src/Storages/MergeTree/MergeTreeReaderWide.cpp +++ b/src/Storages/MergeTree/MergeTreeReaderWide.cpp @@ -49,7 +49,7 @@ MergeTreeReaderWide::MergeTreeReaderWide( { for (const NameAndTypePair & column : columns) { - auto column_from_part = getColumnFromPart(column); + auto column_from_part = getColumnInPart(column); addStreams(column_from_part, profile_callback_, clock_type_); } } @@ -83,7 +83,7 @@ size_t MergeTreeReaderWide::readRows( auto name_and_type = columns.begin(); for (size_t pos = 0; pos < num_columns; ++pos, ++name_and_type) { - auto column_from_part = getColumnFromPart(*name_and_type); + auto column_from_part = getColumnInPart(*name_and_type); try { auto & cache = caches[column_from_part.getNameInStorage()]; @@ -102,7 +102,7 @@ size_t MergeTreeReaderWide::readRows( for (size_t pos = 0; pos < num_columns; ++pos, ++name_and_type) { - auto column_from_part = getColumnFromPart(*name_and_type); + auto column_from_part = getColumnInPart(*name_and_type); const auto & [name, type] = column_from_part; /// The column is already present in the block so we will append the values to the end. diff --git a/tests/queries/0_stateless/01825_type_json_sparse.reference b/tests/queries/0_stateless/01825_type_json_sparse.reference new file mode 100644 index 00000000000..aaaa683d7d5 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_sparse.reference @@ -0,0 +1,13 @@ +k1 Default 2 +k2.k3 Default 1 +============= +k1 Default 1 +k2.k3 Sparse 1 +============= +k1 Default 1 +k2.k3 Sparse 1 +============= +k1 Default 1 +k2.k3 Sparse 1 +1 1 4 +2 400000 0 diff --git a/tests/queries/0_stateless/01825_type_json_sparse.sql b/tests/queries/0_stateless/01825_type_json_sparse.sql new file mode 100644 index 00000000000..2e7520f3e7a --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_sparse.sql @@ -0,0 +1,59 @@ +-- Tags: no-fasttest + +DROP TABLE IF EXISTS t_json_sparse; + +SET allow_experimental_object_type = 1; + +CREATE TABLE t_json_sparse (data JSON) +ENGINE = MergeTree ORDER BY tuple() +SETTINGS ratio_of_defaults_for_sparse_serialization = 0.1, +min_bytes_for_wide_part = 0; + +SYSTEM STOP MERGES t_json_sparse; + +INSERT INTO t_json_sparse VALUES ('{"k1": 1, "k2": {"k3": 4}}'); +INSERT INTO t_json_sparse SELECT '{"k1": 2}' FROM numbers(200000); + +SELECT subcolumns.names, subcolumns.serializations, count() FROM system.parts_columns +ARRAY JOIN subcolumns +WHERE database = currentDatabase() + AND table = 't_json_sparse' AND column = 'data' AND active +GROUP BY subcolumns.names, subcolumns.serializations; + +SELECT '============='; + +SYSTEM START MERGES t_json_sparse; +OPTIMIZE TABLE t_json_sparse FINAL; + +SELECT subcolumns.names, subcolumns.serializations, count() FROM system.parts_columns +ARRAY JOIN subcolumns +WHERE database = currentDatabase() + AND table = 't_json_sparse' AND column = 'data' AND active +GROUP BY subcolumns.names, subcolumns.serializations; + +SELECT '============='; + +DETACH TABLE t_json_sparse; +ATTACH TABLE t_json_sparse; + +SELECT subcolumns.names, subcolumns.serializations, count() FROM system.parts_columns +ARRAY JOIN subcolumns +WHERE database = currentDatabase() + AND table = 't_json_sparse' AND column = 'data' AND active +GROUP BY subcolumns.names, subcolumns.serializations; + +INSERT INTO t_json_sparse SELECT '{"k1": 2}' FROM numbers(200000); + +SELECT '============='; + +OPTIMIZE TABLE t_json_sparse FINAL; + +SELECT subcolumns.names, subcolumns.serializations, count() FROM system.parts_columns +ARRAY JOIN subcolumns +WHERE database = currentDatabase() + AND table = 't_json_sparse' AND column = 'data' AND active +GROUP BY subcolumns.names, subcolumns.serializations; + +SELECT data.k1, count(), sum(data.k2.k3) FROM t_json_sparse GROUP BY data.k1; + +DROP TABLE t_json_sparse; From 10719fd61d36b9d991764727869fbd994553aa51 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 21 Jul 2022 15:06:55 +0000 Subject: [PATCH 078/672] fix style check --- src/DataTypes/Serializations/SerializationInfoTuple.cpp | 1 - src/Storages/MergeTree/IMergeTreeDataPart.h | 2 +- src/Storages/MergeTree/IMergeTreeReader.cpp | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/DataTypes/Serializations/SerializationInfoTuple.cpp b/src/DataTypes/Serializations/SerializationInfoTuple.cpp index b4683c7d0a0..d0fa5572a48 100644 --- a/src/DataTypes/Serializations/SerializationInfoTuple.cpp +++ b/src/DataTypes/Serializations/SerializationInfoTuple.cpp @@ -10,7 +10,6 @@ namespace ErrorCodes { extern const int CORRUPTED_DATA; extern const int THERE_IS_NO_COLUMN; - extern const int LOGICAL_ERROR; } SerializationInfoTuple::SerializationInfoTuple( diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 613fda59c84..b6e8b75147d 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -524,7 +524,7 @@ private: /// Map from name of column to its serialization info. SerializationInfoByName serialization_infos; - /// Columns description for more convinient access + /// Columns description for more convenient access /// to columns by name and getting subcolumns. ColumnsDescription columns_description; diff --git a/src/Storages/MergeTree/IMergeTreeReader.cpp b/src/Storages/MergeTree/IMergeTreeReader.cpp index 08796636f89..896fc805aa5 100644 --- a/src/Storages/MergeTree/IMergeTreeReader.cpp +++ b/src/Storages/MergeTree/IMergeTreeReader.cpp @@ -18,7 +18,6 @@ namespace namespace ErrorCodes { extern const int LOGICAL_ERROR; - extern const int THERE_IS_NO_COLUMN; } From 5549b64c066f747e8dc4bea29508773d82133348 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Thu, 21 Jul 2022 23:52:33 +0800 Subject: [PATCH 079/672] Fix issues 39469 --- src/Storages/MergeTree/MergeTreeData.cpp | 2 +- src/Storages/SelectQueryInfo.h | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 727ebc9c3cc..f10bc84c1eb 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -5522,7 +5522,7 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg if (analysis_result.prewhere_info) { - candidate.prewhere_info = analysis_result.prewhere_info; + candidate.prewhere_info = analysis_result.prewhere_info->clone(); auto prewhere_actions = candidate.prewhere_info->prewhere_actions->clone(); auto prewhere_required_columns = required_columns; diff --git a/src/Storages/SelectQueryInfo.h b/src/Storages/SelectQueryInfo.h index 5046a0b6fe0..2f3f1829883 100644 --- a/src/Storages/SelectQueryInfo.h +++ b/src/Storages/SelectQueryInfo.h @@ -64,6 +64,24 @@ struct PrewhereInfo : prewhere_actions(std::move(prewhere_actions_)), prewhere_column_name(std::move(prewhere_column_name_)) {} std::string dump() const; + + PrewhereInfoPtr clone() const + { + PrewhereInfoPtr prewhere_info = std::make_shared(); + + if (row_level_filter) + prewhere_info->row_level_filter = row_level_filter->clone(); + + if (prewhere_actions) + prewhere_info->prewhere_actions = prewhere_actions->clone(); + + prewhere_info->row_level_column_name = row_level_column_name; + prewhere_info->prewhere_column_name = prewhere_column_name; + prewhere_info->remove_prewhere_column = remove_prewhere_column; + prewhere_info->need_filter = need_filter; + + return prewhere_info; + } }; /// Helper struct to store all the information about the filter expression. From 02c90fd94b30cc48c45393351f977fb9764ed0e0 Mon Sep 17 00:00:00 2001 From: avogar Date: Thu, 21 Jul 2022 16:54:42 +0000 Subject: [PATCH 080/672] Fix tests --- tests/integration/test_storage_hdfs/test.py | 2 +- tests/integration/test_storage_s3/test.py | 4 +- .../02188_table_function_format.reference | 8 +- ...2211_shcema_inference_from_stdin.reference | 8 +- ...e_table_without_columns_metadata.reference | 2 +- ...column_names_in_shcmea_inference.reference | 8 +- .../0_stateless/02244_hdfs_cluster.reference | 24 ++-- ...247_names_order_in_json_and_tskv.reference | 24 ++-- ...02247_read_bools_as_numbers_json.reference | 4 +- .../02268_json_maps_and_objects.reference | 2 +- .../02286_mysql_dump_input_format.reference | 70 +++++------ .../02293_formats_json_columns.reference | 18 +-- ...modifiers_with_non-default_types.reference | 113 ++++++++++++++++++ 13 files changed, 200 insertions(+), 87 deletions(-) create mode 100644 tests/queries/0_stateless/02313_group_by_modifiers_with_non-default_types.reference diff --git a/tests/integration/test_storage_hdfs/test.py b/tests/integration/test_storage_hdfs/test.py index 0490c0c1f0d..a9e6cbda67c 100644 --- a/tests/integration/test_storage_hdfs/test.py +++ b/tests/integration/test_storage_hdfs/test.py @@ -537,7 +537,7 @@ def test_schema_inference_with_globs(started_cluster): ) result = node1.query(f"desc hdfs('hdfs://hdfs1:9000/data*.jsoncompacteachrow')") - assert result.strip() == "c1\tNullable(Float64)" + assert result.strip() == "c1\tNullable(Int64)" result = node1.query( f"select * from hdfs('hdfs://hdfs1:9000/data*.jsoncompacteachrow')" diff --git a/tests/integration/test_storage_s3/test.py b/tests/integration/test_storage_s3/test.py index 5dd09ddd362..5b0600c0023 100644 --- a/tests/integration/test_storage_s3/test.py +++ b/tests/integration/test_storage_s3/test.py @@ -1306,7 +1306,7 @@ def test_schema_inference_from_globs(started_cluster): result = instance.query( f"desc url('http://{started_cluster.minio_host}:{started_cluster.minio_port}/{bucket}/{url_filename}')" ) - assert result.strip() == "c1\tNullable(Float64)" + assert result.strip() == "c1\tNullable(Int64)" result = instance.query( f"select * from url('http://{started_cluster.minio_host}:{started_cluster.minio_port}/{bucket}/{url_filename}')" @@ -1316,7 +1316,7 @@ def test_schema_inference_from_globs(started_cluster): result = instance.query( f"desc s3('http://{started_cluster.minio_host}:{started_cluster.minio_port}/{bucket}/test*.jsoncompacteachrow')" ) - assert result.strip() == "c1\tNullable(Float64)" + assert result.strip() == "c1\tNullable(Int64)" result = instance.query( f"select * from s3('http://{started_cluster.minio_host}:{started_cluster.minio_port}/{bucket}/test*.jsoncompacteachrow')" diff --git a/tests/queries/0_stateless/02188_table_function_format.reference b/tests/queries/0_stateless/02188_table_function_format.reference index 403a4044544..5d7febda187 100644 --- a/tests/queries/0_stateless/02188_table_function_format.reference +++ b/tests/queries/0_stateless/02188_table_function_format.reference @@ -39,9 +39,9 @@ World 123 Hello 111 World 123 1 2 [1,2,3] [['abc'],[],['d','e']] -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Array(Nullable(Float64)) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Array(Nullable(Int64)) c4 Array(Array(Nullable(String))) Hello 111 World 123 @@ -49,4 +49,4 @@ Hello 111 Hello 131 World 123 a Nullable(String) -b Nullable(Float64) +b Nullable(Int64) diff --git a/tests/queries/0_stateless/02211_shcema_inference_from_stdin.reference b/tests/queries/0_stateless/02211_shcema_inference_from_stdin.reference index 6920aa16198..4a4389f638e 100644 --- a/tests/queries/0_stateless/02211_shcema_inference_from_stdin.reference +++ b/tests/queries/0_stateless/02211_shcema_inference_from_stdin.reference @@ -1,4 +1,4 @@ -x Nullable(Float64) +x Nullable(Int64) 0 1 2 @@ -9,7 +9,7 @@ x Nullable(Float64) 7 8 9 -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Nullable(Int64) 1 2 3 diff --git a/tests/queries/0_stateless/02222_create_table_without_columns_metadata.reference b/tests/queries/0_stateless/02222_create_table_without_columns_metadata.reference index f32b0eb8a92..869c6b58dd4 100644 --- a/tests/queries/0_stateless/02222_create_table_without_columns_metadata.reference +++ b/tests/queries/0_stateless/02222_create_table_without_columns_metadata.reference @@ -1,3 +1,3 @@ -CREATE TABLE default.test\n(\n `x` Nullable(Float64),\n `y` Nullable(String)\n)\nENGINE = File(\'JSONEachRow\', \'data.jsonl\') +CREATE TABLE test.test\n(\n `x` Nullable(Int64),\n `y` Nullable(String)\n)\nENGINE = File(\'JSONEachRow\', \'data.jsonl\') OK OK diff --git a/tests/queries/0_stateless/02244_column_names_in_shcmea_inference.reference b/tests/queries/0_stateless/02244_column_names_in_shcmea_inference.reference index d237caf630f..7a124346b9f 100644 --- a/tests/queries/0_stateless/02244_column_names_in_shcmea_inference.reference +++ b/tests/queries/0_stateless/02244_column_names_in_shcmea_inference.reference @@ -1,8 +1,8 @@ x Nullable(String) -y Nullable(Float64) +y Nullable(Int64) x Nullable(String) -y Nullable(Float64) +y Nullable(Int64) x Nullable(String) -y Nullable(Float64) +y Nullable(Int64) x Nullable(String) -y Nullable(Float64) +y Nullable(Int64) diff --git a/tests/queries/0_stateless/02244_hdfs_cluster.reference b/tests/queries/0_stateless/02244_hdfs_cluster.reference index 4bf4799e904..32a9f24388c 100644 --- a/tests/queries/0_stateless/02244_hdfs_cluster.reference +++ b/tests/queries/0_stateless/02244_hdfs_cluster.reference @@ -22,24 +22,24 @@ 1 2 3 4 5 6 7 8 9 -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Nullable(Float64) -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Nullable(Int64) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Nullable(Int64) c1 UInt32 c2 UInt32 c3 UInt32 c1 UInt32 c2 UInt32 c3 UInt32 -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Nullable(Float64) -c1 Nullable(Float64) -c2 Nullable(Float64) -c3 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Nullable(Int64) +c1 Nullable(Int64) +c2 Nullable(Int64) +c3 Nullable(Int64) c1 UInt32 c2 UInt32 c3 UInt32 diff --git a/tests/queries/0_stateless/02247_names_order_in_json_and_tskv.reference b/tests/queries/0_stateless/02247_names_order_in_json_and_tskv.reference index 300846c17a0..c7774c8138b 100644 --- a/tests/queries/0_stateless/02247_names_order_in_json_and_tskv.reference +++ b/tests/queries/0_stateless/02247_names_order_in_json_and_tskv.reference @@ -1,32 +1,32 @@ -a Nullable(Float64) +a Nullable(Int64) b Nullable(String) -c Array(Nullable(Float64)) +c Array(Nullable(Int64)) 1 s1 [] 2 } [2] \N \N [] \N \N [] \N \N [3] -b Nullable(Float64) -a Nullable(Float64) -c Nullable(Float64) -e Nullable(Float64) +b Nullable(Int64) +a Nullable(Int64) +c Nullable(Int64) +e Nullable(Int64) 1 \N \N \N \N 2 3 \N \N \N \N \N \N \N \N 3 3 3 1 \N -a Nullable(Float64) +a Nullable(Int64) b Nullable(String) -c Array(Nullable(Float64)) +c Array(Nullable(Int64)) 1 s1 [] 2 \N [2] \N \N [] \N \N [] \N \N [3] -b Nullable(Float64) -a Nullable(Float64) -c Nullable(Float64) -e Nullable(Float64) +b Nullable(Int64) +a Nullable(Int64) +c Nullable(Int64) +e Nullable(Int64) 1 \N \N \N \N 2 3 \N \N \N \N \N diff --git a/tests/queries/0_stateless/02247_read_bools_as_numbers_json.reference b/tests/queries/0_stateless/02247_read_bools_as_numbers_json.reference index b6d10581b16..840e77cb122 100644 --- a/tests/queries/0_stateless/02247_read_bools_as_numbers_json.reference +++ b/tests/queries/0_stateless/02247_read_bools_as_numbers_json.reference @@ -10,7 +10,7 @@ x Nullable(Float64) x Array(Nullable(Float64)) [1,0] [0.42] -x Array(Array(Nullable(Float64))) +x Array(Array(Nullable(Int64))) [[1,2,3],[1,0],[1,1,0]] [[1,2,3]] c1 Nullable(Bool) @@ -25,6 +25,6 @@ c1 Nullable(Float64) c1 Array(Nullable(Float64)) [1,0] [0.42] -c1 Array(Array(Nullable(Float64))) +c1 Array(Array(Nullable(Int64))) [[1,2,3],[1,0],[1,1,0]] [[1,2,3]] diff --git a/tests/queries/0_stateless/02268_json_maps_and_objects.reference b/tests/queries/0_stateless/02268_json_maps_and_objects.reference index 73a8a8f43cf..87fb8949e01 100644 --- a/tests/queries/0_stateless/02268_json_maps_and_objects.reference +++ b/tests/queries/0_stateless/02268_json_maps_and_objects.reference @@ -2,4 +2,4 @@ x Object(Nullable(\'json\')) x Object(Nullable(\'json\')) x Array(Object(Nullable(\'json\'))) x Array(Object(Nullable(\'json\'))) -x Tuple(Map(String, Nullable(String)), Map(String, Array(Nullable(Float64))), Array(Nullable(Float64))) +x Tuple(Map(String, Nullable(String)), Map(String, Array(Nullable(Int64))), Array(Nullable(Int64))) diff --git a/tests/queries/0_stateless/02286_mysql_dump_input_format.reference b/tests/queries/0_stateless/02286_mysql_dump_input_format.reference index 25be4b727bc..a736358b9b7 100644 --- a/tests/queries/0_stateless/02286_mysql_dump_input_format.reference +++ b/tests/queries/0_stateless/02286_mysql_dump_input_format.reference @@ -130,8 +130,8 @@ x Nullable(Int32) x Nullable(Int32) 1 dump7 -c1 Nullable(Float64) -c2 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) 1 \N 2 \N 3 \N @@ -139,8 +139,8 @@ c2 Nullable(Float64) 4 \N 5 \N 6 7 -c1 Nullable(Float64) -c2 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) 1 \N 2 \N 3 \N @@ -148,15 +148,15 @@ c2 Nullable(Float64) 4 \N 5 \N 6 7 -c1 Nullable(Float64) +c1 Nullable(Int64) 1 2 3 -c1 Nullable(Float64) +c1 Nullable(Int64) 1 dump8 -c1 Nullable(Float64) -c2 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) 1 \N 2 \N 3 \N @@ -164,8 +164,8 @@ c2 Nullable(Float64) 4 \N 5 \N 6 7 -c1 Nullable(Float64) -c2 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) 1 \N 2 \N 3 \N @@ -174,8 +174,8 @@ c2 Nullable(Float64) 5 \N 6 7 dump9 -c1 Nullable(Float64) -c2 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) 1 \N 2 \N 3 \N @@ -183,8 +183,8 @@ c2 Nullable(Float64) 4 \N 5 \N 6 7 -c1 Nullable(Float64) -c2 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) 1 \N 2 \N 3 \N @@ -193,8 +193,8 @@ c2 Nullable(Float64) 5 \N 6 7 dump10 -c1 Nullable(Float64) -c2 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) 1 \N 2 \N 3 \N @@ -202,8 +202,8 @@ c2 Nullable(Float64) 4 \N 5 \N 6 7 -c1 Nullable(Float64) -c2 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) 1 \N 2 \N 3 \N @@ -212,8 +212,8 @@ c2 Nullable(Float64) 5 \N 6 7 dump11 -c1 Nullable(Float64) -c2 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) 1 \N 2 \N 3 \N @@ -221,8 +221,8 @@ c2 Nullable(Float64) 4 \N 5 \N 6 7 -c1 Nullable(Float64) -c2 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) 1 \N 2 \N 3 \N @@ -265,8 +265,8 @@ color Nullable(String) price Nullable(Int32) apple red 42 dump14 -x Nullable(Float64) -y Nullable(Float64) +x Nullable(Int64) +y Nullable(Int64) 1 \N 2 \N 3 \N @@ -274,8 +274,8 @@ y Nullable(Float64) 4 \N 5 \N 6 7 -x Nullable(Float64) -y Nullable(Float64) +x Nullable(Int64) +y Nullable(Int64) 1 \N 2 \N 3 \N @@ -283,15 +283,15 @@ y Nullable(Float64) 4 \N 5 \N 6 7 -x Nullable(Float64) +x Nullable(Int64) 1 2 3 -x Nullable(Float64) +x Nullable(Int64) 1 dump15 -x Nullable(Float64) -y Nullable(Float64) +x Nullable(Int64) +y Nullable(Int64) 1 \N 2 \N 3 \N @@ -299,8 +299,8 @@ y Nullable(Float64) 4 \N 5 \N 6 7 -x Nullable(Float64) -y Nullable(Float64) +x Nullable(Int64) +y Nullable(Int64) 1 \N 2 \N 3 \N @@ -308,10 +308,10 @@ y Nullable(Float64) 4 \N 5 \N 6 7 -x Nullable(Float64) +x Nullable(Int64) 1 2 3 -x Nullable(Float64) -y Nullable(Float64) +x Nullable(Int64) +y Nullable(Int64) 1 2 diff --git a/tests/queries/0_stateless/02293_formats_json_columns.reference b/tests/queries/0_stateless/02293_formats_json_columns.reference index da8d080ac05..f59f02ad42b 100644 --- a/tests/queries/0_stateless/02293_formats_json_columns.reference +++ b/tests/queries/0_stateless/02293_formats_json_columns.reference @@ -4,9 +4,9 @@ JSONColumns "b": ["String", "String", "String", "String", "String"], "c": [[[[],"String"],[[],"gnirtS"]], [[[0],"String"],[[0],"gnirtS"]], [[[0,1],"String"],[[0,1],"gnirtS"]], [[[],"String"],[[0,1,2],"gnirtS"]], [[[0],"String"],[[],"gnirtS"]]] } -a Nullable(Float64) +a Nullable(Int64) b Nullable(String) -c Array(Tuple(Array(Nullable(Float64)), Nullable(String))) +c Array(Tuple(Array(Nullable(Int64)), Nullable(String))) 0 String [([],'String'),([],'gnirtS')] 1 String [([0],'String'),([0],'gnirtS')] 2 String [([0,1],'String'),([0,1],'gnirtS')] @@ -18,9 +18,9 @@ JSONCompactColumns ["String", "String", "String", "String", "String"], [[[[],"String"],[[],"gnirtS"]], [[[0],"String"],[[0],"gnirtS"]], [[[0,1],"String"],[[0,1],"gnirtS"]], [[[],"String"],[[0,1,2],"gnirtS"]], [[[0],"String"],[[],"gnirtS"]]] ] -c1 Nullable(Float64) +c1 Nullable(Int64) c2 Nullable(String) -c3 Array(Tuple(Array(Nullable(Float64)), Nullable(String))) +c3 Array(Tuple(Array(Nullable(Int64)), Nullable(String))) 0 String [([],'String'),([],'gnirtS')] 1 String [([0],'String'),([0],'gnirtS')] 2 String [([0,1],'String'),([0,1],'gnirtS')] @@ -74,9 +74,9 @@ JSONColumnsWithMetadata "bytes_read": 20 } } -b Nullable(Float64) -a Nullable(Float64) -c Nullable(Float64) +b Nullable(Int64) +a Nullable(Int64) +c Nullable(Int64) d Nullable(String) 1 3 \N \N 2 2 \N \N @@ -89,8 +89,8 @@ OK 3 2 1 -c1 Nullable(Float64) -c2 Nullable(Float64) +c1 Nullable(Int64) +c2 Nullable(Int64) c3 Nullable(String) 1 1 \N 2 2 \N diff --git a/tests/queries/0_stateless/02313_group_by_modifiers_with_non-default_types.reference b/tests/queries/0_stateless/02313_group_by_modifiers_with_non-default_types.reference new file mode 100644 index 00000000000..183c63d1222 --- /dev/null +++ b/tests/queries/0_stateless/02313_group_by_modifiers_with_non-default_types.reference @@ -0,0 +1,113 @@ +-- { echoOn } +SELECT + count() as d, a, b, c +FROM test02313 +GROUP BY ROLLUP(a, b, c) +ORDER BY d, a, b, c; +1 one default 0 +1 one default 2 +1 one default 4 +1 one default 6 +1 one default 8 +1 two non-default 1 +1 two non-default 3 +1 two non-default 5 +1 two non-default 7 +1 two non-default 9 +5 one default 0 +5 one default 0 +5 two default 0 +5 two non-default 0 +10 one default 0 +SELECT + count() as d, a, b, c +FROM test02313 +GROUP BY CUBE(a, b, c) +ORDER BY d, a, b, c; +1 one default 0 +1 one default 0 +1 one default 0 +1 one default 0 +1 one default 1 +1 one default 2 +1 one default 2 +1 one default 2 +1 one default 2 +1 one default 3 +1 one default 4 +1 one default 4 +1 one default 4 +1 one default 4 +1 one default 5 +1 one default 6 +1 one default 6 +1 one default 6 +1 one default 6 +1 one default 7 +1 one default 8 +1 one default 8 +1 one default 8 +1 one default 8 +1 one default 9 +1 one non-default 1 +1 one non-default 3 +1 one non-default 5 +1 one non-default 7 +1 one non-default 9 +1 two default 1 +1 two default 3 +1 two default 5 +1 two default 7 +1 two default 9 +1 two non-default 1 +1 two non-default 3 +1 two non-default 5 +1 two non-default 7 +1 two non-default 9 +5 one default 0 +5 one default 0 +5 one default 0 +5 one non-default 0 +5 two default 0 +5 two non-default 0 +10 one default 0 +SELECT + count() as d, a, b, c +FROM test02313 +GROUP BY GROUPING SETS + ( + (c), + (a, c), + (b, c) + ) +ORDER BY d, a, b, c; +1 one default 0 +1 one default 0 +1 one default 0 +1 one default 1 +1 one default 2 +1 one default 2 +1 one default 2 +1 one default 3 +1 one default 4 +1 one default 4 +1 one default 4 +1 one default 5 +1 one default 6 +1 one default 6 +1 one default 6 +1 one default 7 +1 one default 8 +1 one default 8 +1 one default 8 +1 one default 9 +1 one non-default 1 +1 one non-default 3 +1 one non-default 5 +1 one non-default 7 +1 one non-default 9 +1 two default 1 +1 two default 3 +1 two default 5 +1 two default 7 +1 two default 9 From df39af23a10f9eabd9f5954521cd2b0ec735b954 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 21 Jul 2022 20:32:33 +0200 Subject: [PATCH 081/672] fix --- src/Databases/LoadingStrictnessLevel.cpp | 1 + tests/integration/test_distributed_ddl/test.py | 7 ++++--- .../test_distributed_ddl_on_cross_replication/test.py | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Databases/LoadingStrictnessLevel.cpp b/src/Databases/LoadingStrictnessLevel.cpp index 5120fc2182f..8d491ca5689 100644 --- a/src/Databases/LoadingStrictnessLevel.cpp +++ b/src/Databases/LoadingStrictnessLevel.cpp @@ -1,4 +1,5 @@ #include +#include namespace DB { diff --git a/tests/integration/test_distributed_ddl/test.py b/tests/integration/test_distributed_ddl/test.py index 9c66d4d344e..065795b55eb 100755 --- a/tests/integration/test_distributed_ddl/test.py +++ b/tests/integration/test_distributed_ddl/test.py @@ -49,7 +49,7 @@ def test_default_database(test_cluster): test_cluster.ddl_check_query( instance, "CREATE TABLE null ON CLUSTER 'cluster2' (s String DEFAULT 'escape\t\nme') ENGINE = Null", - settings={"distributed_ddl_entry_format_version": 2} + settings={"distributed_ddl_entry_format_version": 2}, ) contents = instance.query( @@ -58,8 +58,9 @@ def test_default_database(test_cluster): assert TSV(contents) == TSV("ch1\tdefault\nch2\ttest2\nch3\tdefault\nch4\ttest2\n") test_cluster.ddl_check_query( - instance, "DROP TABLE IF EXISTS null ON CLUSTER cluster2", - settings={"distributed_ddl_entry_format_version": 2} + instance, + "DROP TABLE IF EXISTS null ON CLUSTER cluster2", + settings={"distributed_ddl_entry_format_version": 2}, ) test_cluster.ddl_check_query( instance, "DROP DATABASE IF EXISTS test2 ON CLUSTER 'cluster'" diff --git a/tests/integration/test_distributed_ddl_on_cross_replication/test.py b/tests/integration/test_distributed_ddl_on_cross_replication/test.py index a58fb212e27..2b710b4a3e3 100644 --- a/tests/integration/test_distributed_ddl_on_cross_replication/test.py +++ b/tests/integration/test_distributed_ddl_on_cross_replication/test.py @@ -7,21 +7,21 @@ cluster = ClickHouseCluster(__file__) node1 = cluster.add_instance( "node1", main_configs=["configs/remote_servers.xml"], - user_configs=[ "configs/settings.xml"], + user_configs=["configs/settings.xml"], with_zookeeper=True, macros={"shard": 1, "replica": 1, "shard_bk": 3, "replica_bk": 2}, ) node2 = cluster.add_instance( "node2", main_configs=["configs/remote_servers.xml"], - user_configs=[ "configs/settings.xml"], + user_configs=["configs/settings.xml"], with_zookeeper=True, macros={"shard": 2, "replica": 1, "shard_bk": 1, "replica_bk": 2}, ) node3 = cluster.add_instance( "node3", main_configs=["configs/remote_servers.xml"], - user_configs=[ "configs/settings.xml"], + user_configs=["configs/settings.xml"], with_zookeeper=True, macros={"shard": 3, "replica": 1, "shard_bk": 2, "replica_bk": 2}, ) From 06e8b78efa20f4b6998d475b1582e41150033455 Mon Sep 17 00:00:00 2001 From: HeenaBansal2009 Date: Thu, 21 Jul 2022 19:46:36 -0700 Subject: [PATCH 082/672] Added FT testcase --- programs/local/LocalServer.cpp | 29 +++++----- ...0_clickhouse_local_config-option.reference | 1 + .../02360_clickhouse_local_config-option.sh | 54 +++++++++++++++++++ 3 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 tests/queries/0_stateless/02360_clickhouse_local_config-option.reference create mode 100755 tests/queries/0_stateless/02360_clickhouse_local_config-option.sh diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index 376fa1c2bc5..de1f08dd5f5 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -323,25 +323,30 @@ void LocalServer::setupUsers() auto & access_control = global_context->getAccessControl(); access_control.setNoPasswordAllowed(config().getBool("allow_no_password", true)); access_control.setPlaintextPasswordAllowed(config().getBool("allow_plaintext_password", true)); - if (config().has("users_config") || config().has("config-file") || fs::exists("config.xml")) + if (config().has("config-file") || fs::exists("config.xml")) { String config_path = config().getString("config-file",""); bool has_user_directories = config().has("user_directories"); const auto config_dir = std::filesystem::path{config_path}.remove_filename().string(); String users_config_path = config().getString("users_config",""); - if (users_config_path.empty()) + if (users_config_path.empty() && !has_user_directories) + users_config = getConfigurationFromXMLString(minimal_default_user_xml); + else { - if (!has_user_directories) - users_config_path = config_path; - } - if (has_user_directories) - users_config_path = config().getString("user_directories.users_xml.path",""); + if (users_config_path.empty()) + { + if (!has_user_directories) + users_config_path = config_path; + } + if (has_user_directories) + users_config_path = config().getString("user_directories.users_xml.path",""); - if (std::filesystem::path{users_config_path}.is_relative() && std::filesystem::exists(config_dir + users_config_path)) - users_config_path = config_dir + users_config_path; - ConfigProcessor config_processor(users_config_path); - const auto loaded_config = config_processor.loadConfig(); - users_config = loaded_config.configuration; + if (std::filesystem::path{users_config_path}.is_relative() && std::filesystem::exists(config_dir + users_config_path)) + users_config_path = config_dir + users_config_path; + ConfigProcessor config_processor(users_config_path); + const auto loaded_config = config_processor.loadConfig(); + users_config = loaded_config.configuration; + } } else users_config = getConfigurationFromXMLString(minimal_default_user_xml); diff --git a/tests/queries/0_stateless/02360_clickhouse_local_config-option.reference b/tests/queries/0_stateless/02360_clickhouse_local_config-option.reference new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/tests/queries/0_stateless/02360_clickhouse_local_config-option.reference @@ -0,0 +1 @@ +1 diff --git a/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh b/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh new file mode 100755 index 00000000000..8eaa87689c1 --- /dev/null +++ b/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -e + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +echo " + + + trace + true + + + 9000 + + ./ + + 0 + + + + users.xml + + +" > $CUR_DIR/config.xml + +echo " + + + + + + + + ::/0 + + default + default + + + + + + " > $CUR_DIR/users.xml + +local_opts=( + "--config-file=$CUR_DIR/config.xml" + "--send_logs_level=none") + +${CLICKHOUSE_LOCAL} "${local_opts[@]}" --query 'Select 1' | grep -v -e 'Processing configuration file' + +rm -rf $CUR_DIR/users.xml +rm -rf $CUR_DIR/config.xml From c82059cb9b0071d507f16ead96596a0efb21c542 Mon Sep 17 00:00:00 2001 From: HeenaBansal2009 Date: Thu, 21 Jul 2022 20:05:35 -0700 Subject: [PATCH 083/672] Fixed FT testcase --- .../0_stateless/02360_clickhouse_local_config-option.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh b/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh index 8eaa87689c1..cbce333a493 100755 --- a/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh +++ b/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh @@ -5,8 +5,7 @@ CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CUR_DIR"/../shell_config.sh -echo " - +echo " trace true From 4e0b840cdc322828e1495a58ff47c03c9e4bec74 Mon Sep 17 00:00:00 2001 From: HeenaBansal2009 Date: Thu, 21 Jul 2022 20:27:35 -0700 Subject: [PATCH 084/672] Fixed FT testcase --- .../queries/0_stateless/02360_clickhouse_local_config-option.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh b/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh index cbce333a493..ed92b66ec87 100755 --- a/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh +++ b/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh @@ -47,7 +47,7 @@ local_opts=( "--config-file=$CUR_DIR/config.xml" "--send_logs_level=none") -${CLICKHOUSE_LOCAL} "${local_opts[@]}" --query 'Select 1' | grep -v -e 'Processing configuration file' +${CLICKHOUSE_LOCAL} "${local_opts[@]}" --query 'Select 1' |& grep -v -e 'Processing configuration file' rm -rf $CUR_DIR/users.xml rm -rf $CUR_DIR/config.xml From 99e7a063ddb61a346082c6bad21f0ff878888e4a Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Fri, 22 Jul 2022 13:14:10 +0800 Subject: [PATCH 085/672] Add tests --- ...371_select_projection_normal_agg.reference | 3 + .../02371_select_projection_normal_agg.sql | 78 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 tests/queries/0_stateless/02371_select_projection_normal_agg.reference create mode 100644 tests/queries/0_stateless/02371_select_projection_normal_agg.sql diff --git a/tests/queries/0_stateless/02371_select_projection_normal_agg.reference b/tests/queries/0_stateless/02371_select_projection_normal_agg.reference new file mode 100644 index 00000000000..d15a861b0ec --- /dev/null +++ b/tests/queries/0_stateless/02371_select_projection_normal_agg.reference @@ -0,0 +1,3 @@ +0 +0 +2022-07-22 01:00:00 0 0 diff --git a/tests/queries/0_stateless/02371_select_projection_normal_agg.sql b/tests/queries/0_stateless/02371_select_projection_normal_agg.sql new file mode 100644 index 00000000000..ac0189f4f48 --- /dev/null +++ b/tests/queries/0_stateless/02371_select_projection_normal_agg.sql @@ -0,0 +1,78 @@ +DROP TABLE IF EXISTS video_log; + +CREATE TABLE video_log +( + `datetime` DateTime, + `user_id` UInt64, + `device_id` UInt64, + `domain` LowCardinality(String), + `bytes` UInt64, + `duration` UInt64 +) +ENGINE = MergeTree +PARTITION BY toDate(datetime) +ORDER BY (user_id, device_id); + +DROP TABLE IF EXISTS rng; + +CREATE TABLE rng +( + `user_id_raw` UInt64, + `device_id_raw` UInt64, + `domain_raw` UInt64, + `bytes_raw` UInt64, + `duration_raw` UInt64 +) +ENGINE = GenerateRandom(1024); + +INSERT INTO video_log SELECT + toUnixTimestamp('2022-07-22 01:00:00') + + (rowNumberInAllBlocks() / 20000), + user_id_raw % 100000000 AS user_id, + device_id_raw % 200000000 AS device_id, + domain_raw % 100, + (bytes_raw % 1024) + 128, + (duration_raw % 300) + 100 +FROM rng +LIMIT 17280000; + +ALTER TABLE video_log ADD PROJECTION p_norm +( + SELECT + datetime, + device_id, + bytes, + duration + ORDER BY device_id +); + +ALTER TABLE video_log MATERIALIZE PROJECTION p_norm; + +ALTER TABLE video_log ADD PROJECTION p_agg +( + SELECT + toStartOfHour(datetime) AS hour, + domain, + sum(bytes), + avg(duration) + GROUP BY + hour, + domain +); + +ALTER TABLE video_log MATERIALIZE PROJECTION p_agg; + +SELECT sleep(3); +SELECT sleep(3); + +SELECT + toStartOfHour(datetime) AS hour, + ignore(sum(bytes)), + ignore(avg(duration)) +FROM video_log +WHERE (toDate(hour) = '2022-07-22') AND (device_id = '100') +GROUP BY hour; + +DROP TABLE IF EXISTS video_log; + +DROP TABLE IF EXISTS rng; From f4f05d33b1a38bf75aab0b54cca3198e23aadcd7 Mon Sep 17 00:00:00 2001 From: HeenaBansal2009 Date: Thu, 21 Jul 2022 22:23:54 -0700 Subject: [PATCH 086/672] Fixed FT testcase --- .../queries/0_stateless/02360_clickhouse_local_config-option.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh b/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh index ed92b66ec87..df0bdf38b4d 100755 --- a/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh +++ b/tests/queries/0_stateless/02360_clickhouse_local_config-option.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Tags: no-parallel set -e CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) From 74d2bf8d95a901dbefda5425bb9cdef1ebaaf96c Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Fri, 22 Jul 2022 07:49:02 +0000 Subject: [PATCH 087/672] Fix gTest for DateLUTTest --- src/Common/tests/gtest_DateLUTImpl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Common/tests/gtest_DateLUTImpl.cpp b/src/Common/tests/gtest_DateLUTImpl.cpp index 95cad92efca..49013625ed3 100644 --- a/src/Common/tests/gtest_DateLUTImpl.cpp +++ b/src/Common/tests/gtest_DateLUTImpl.cpp @@ -84,8 +84,8 @@ TEST(DateLUTTest, makeDayNumTest) EXPECT_EQ(-25567, lut.makeDayNum(1900, 1, 1)); EXPECT_EQ(-16436, lut.makeDayNum(1925, 1, 1)); EXPECT_EQ(0, lut.makeDayNum(1970, 1, 1)); - EXPECT_EQ(120894, lut.makeDayNum(2300, 12, 31)); - EXPECT_EQ(120894, lut.makeDayNum(2500, 12, 25)); + EXPECT_EQ(120529, lut.makeDayNum(2300, 12, 31)); + EXPECT_EQ(120529, lut.makeDayNum(2500, 12, 25)); } From 0b102c6d1ffeeeae79c3f50a522288367a567a46 Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Fri, 22 Jul 2022 08:24:05 +0000 Subject: [PATCH 088/672] Fix code style --- src/Common/DateLUTImpl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/DateLUTImpl.h b/src/Common/DateLUTImpl.h index f38c585dcdd..56f2e87ccb5 100644 --- a/src/Common/DateLUTImpl.h +++ b/src/Common/DateLUTImpl.h @@ -74,7 +74,7 @@ private: static inline LUTIndex normalizeLUTIndex(Int64 index) { - if unlikely(index < 0 ) + if (unlikely(index < 0)) return LUTIndex(0); if (index >= DATE_LUT_SIZE) return LUTIndex(DATE_LUT_SIZE - 1); From 4e995bce8f99e568b2c31a75a9cd9115ddc22390 Mon Sep 17 00:00:00 2001 From: avogar Date: Fri, 22 Jul 2022 11:23:30 +0000 Subject: [PATCH 089/672] Update tests --- .../0_stateless/02366_cancel_write_into_file.reference | 2 ++ tests/queries/0_stateless/02366_cancel_write_into_file.sh | 4 ++-- .../0_stateless/02367_cancel_write_into_s3.reference | 2 ++ tests/queries/0_stateless/02367_cancel_write_into_s3.sh | 8 ++++---- .../0_stateless/02368_cancel_write_into_hdfs.reference | 2 ++ tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh | 6 +++--- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/queries/0_stateless/02366_cancel_write_into_file.reference b/tests/queries/0_stateless/02366_cancel_write_into_file.reference index e69de29bb2d..b2f7f08c170 100644 --- a/tests/queries/0_stateless/02366_cancel_write_into_file.reference +++ b/tests/queries/0_stateless/02366_cancel_write_into_file.reference @@ -0,0 +1,2 @@ +10 +10 diff --git a/tests/queries/0_stateless/02366_cancel_write_into_file.sh b/tests/queries/0_stateless/02366_cancel_write_into_file.sh index d9abc69a414..4a6844e43ff 100755 --- a/tests/queries/0_stateless/02366_cancel_write_into_file.sh +++ b/tests/queries/0_stateless/02366_cancel_write_into_file.sh @@ -12,7 +12,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" | grep "finished" -c for i in $(seq 1 10); do @@ -21,4 +21,4 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" | grep "finished" -c diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.reference b/tests/queries/0_stateless/02367_cancel_write_into_s3.reference index e69de29bb2d..b2f7f08c170 100644 --- a/tests/queries/0_stateless/02367_cancel_write_into_s3.reference +++ b/tests/queries/0_stateless/02367_cancel_write_into_s3.reference @@ -0,0 +1,2 @@ +10 +10 diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh index 39316bece5f..4ddaae203a2 100755 --- a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh +++ b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh @@ -7,18 +7,18 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) for i in $(seq 1 10); do - $CLICKHOUSE_CLIENT --query_id="02366_$i" -q "insert into function s3('http://localhost:11111/test/02367_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings s3_truncate_on_insert=1, output_format_parallel_formatting=1" 2> /dev/null & + $CLICKHOUSE_CLIENT --query_id="02367_$i" -q "insert into function s3('http://localhost:11111/test/02367_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings s3_truncate_on_insert=1, output_format_parallel_formatting=1" 2> /dev/null & done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" | grep "finished" -c for i in $(seq 1 10); do - $CLICKHOUSE_CLIENT --query_id="02367_$i" -q "insert into function s3('http://localhost:11111/test/02366_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings s3_truncate_on_inser=1, output_format_parallel_formatting=0" 2> /dev/null & + $CLICKHOUSE_CLIENT --query_id="02367_$i" -q "insert into function s3('http://localhost:11111/test/02367_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings s3_truncate_on_insert=1, output_format_parallel_formatting=0" 2> /dev/null & done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" | grep "finished" -c diff --git a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.reference b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.reference index e69de29bb2d..b2f7f08c170 100644 --- a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.reference +++ b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.reference @@ -0,0 +1,2 @@ +10 +10 diff --git a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh index 70fe1a3e150..70815d339d2 100755 --- a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh +++ b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh @@ -7,12 +7,12 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) for i in $(seq 1 10); do - $CLICKHOUSE_CLIENT --query_id="02368_$i" -q "insert into function hdfs('hdfs://localhost:12222/02368_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings hdfs_truncate_on_inser=1, toutput_format_parallel_formatting=1" 2> /dev/null & + $CLICKHOUSE_CLIENT --query_id="02368_$i" -q "insert into function hdfs('hdfs://localhost:12222/02368_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings hdfs_truncate_on_insert=1, output_format_parallel_formatting=1" 2> /dev/null & done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" | grep "finished" -c for i in $(seq 1 10); do @@ -21,4 +21,4 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" | grep "finished" -c From 1e5062c769cdefe3803b2b6b18038ea64439f0f2 Mon Sep 17 00:00:00 2001 From: avogar Date: Fri, 22 Jul 2022 12:14:54 +0000 Subject: [PATCH 090/672] Fix fasttest --- tests/queries/0_stateless/02366_cancel_write_into_file.sh | 1 + tests/queries/0_stateless/02367_cancel_write_into_s3.sh | 1 + tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh | 1 + 3 files changed, 3 insertions(+) diff --git a/tests/queries/0_stateless/02366_cancel_write_into_file.sh b/tests/queries/0_stateless/02366_cancel_write_into_file.sh index 4a6844e43ff..47fa3094948 100755 --- a/tests/queries/0_stateless/02366_cancel_write_into_file.sh +++ b/tests/queries/0_stateless/02366_cancel_write_into_file.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Tags: no-fasttest CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh index 4ddaae203a2..d24462bf584 100755 --- a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh +++ b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Tags: no-fasttest CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh index 70815d339d2..6fda8e9dcff 100755 --- a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh +++ b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Tags: no-fasttest CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From 5ca5a3697f072abc8b984cb45ce0afe7e3f0a477 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 22 Jul 2022 18:41:41 +0200 Subject: [PATCH 091/672] better exception message for incorrect zk path --- src/Databases/DatabaseReplicated.cpp | 39 ++++++++++++++++++- src/Databases/DatabaseReplicated.h | 1 + .../01111_create_drop_replicated_db_stress.sh | 2 +- ..._create_table_on_cluster_normalization.sql | 8 +++- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/Databases/DatabaseReplicated.cpp b/src/Databases/DatabaseReplicated.cpp index 1c8f4881895..c4cfe5ef340 100644 --- a/src/Databases/DatabaseReplicated.cpp +++ b/src/Databases/DatabaseReplicated.cpp @@ -48,6 +48,7 @@ namespace ErrorCodes extern const int CANNOT_RESTORE_TABLE; } +static constexpr const char * REPLICATED_DATABASE_MARK = "DatabaseReplicated"; static constexpr const char * DROPPED_MARK = "DROPPED"; static constexpr const char * BROKEN_TABLES_SUFFIX = "_broken_tables"; static constexpr const char * BROKEN_REPLICATED_TABLES_SUFFIX = "_broken_replicated_tables"; @@ -305,7 +306,7 @@ bool DatabaseReplicated::createDatabaseNodesInZooKeeper(const zkutil::ZooKeeperP current_zookeeper->createAncestors(zookeeper_path); Coordination::Requests ops; - ops.emplace_back(zkutil::makeCreateRequest(zookeeper_path, "", zkutil::CreateMode::Persistent)); + ops.emplace_back(zkutil::makeCreateRequest(zookeeper_path, REPLICATED_DATABASE_MARK, zkutil::CreateMode::Persistent)); ops.emplace_back(zkutil::makeCreateRequest(zookeeper_path + "/log", "", zkutil::CreateMode::Persistent)); ops.emplace_back(zkutil::makeCreateRequest(zookeeper_path + "/replicas", "", zkutil::CreateMode::Persistent)); ops.emplace_back(zkutil::makeCreateRequest(zookeeper_path + "/counter", "", zkutil::CreateMode::Persistent)); @@ -331,6 +332,38 @@ bool DatabaseReplicated::createDatabaseNodesInZooKeeper(const zkutil::ZooKeeperP __builtin_unreachable(); } +bool DatabaseReplicated::looksLikeReplicatedDatabasePath(const ZooKeeperPtr & current_zookeeper, const String & path) +{ + Coordination::Stat stat; + String maybe_database_mark; + if (!current_zookeeper->tryGet(path, maybe_database_mark, &stat)) + return false; + if (maybe_database_mark.starts_with(REPLICATED_DATABASE_MARK)) + return true; + if (maybe_database_mark.empty()) + return false; + + /// Old versions did not have REPLICATED_DATABASE_MARK. Check specific nodes exist and add mark. + Coordination::Requests ops; + ops.emplace_back(zkutil::makeCheckRequest(path + "/log", -1)); + ops.emplace_back(zkutil::makeCheckRequest(path + "/replicas", -1)); + ops.emplace_back(zkutil::makeCheckRequest(path + "/counter", -1)); + ops.emplace_back(zkutil::makeCheckRequest(path + "/metadata", -1)); + ops.emplace_back(zkutil::makeCheckRequest(path + "/max_log_ptr", -1)); + ops.emplace_back(zkutil::makeCheckRequest(path + "/logs_to_keep", -1)); + ops.emplace_back(zkutil::makeSetRequest(path, REPLICATED_DATABASE_MARK, stat.version)); + Coordination::Responses responses; + auto res = current_zookeeper->tryMulti(ops, responses); + if (res == Coordination::Error::ZOK) + return true; + + /// Recheck database mark (just in case of concurrent update). + if (!current_zookeeper->tryGet(path, maybe_database_mark, &stat)) + return false; + + return maybe_database_mark.starts_with(REPLICATED_DATABASE_MARK); +} + void DatabaseReplicated::createEmptyLogEntry(const ZooKeeperPtr & current_zookeeper) { /// On replica creation add empty entry to log. Can be used to trigger some actions on other replicas (e.g. update cluster info). @@ -347,6 +380,10 @@ bool DatabaseReplicated::waitForReplicaToProcessAllEntries(UInt64 timeout_ms) void DatabaseReplicated::createReplicaNodesInZooKeeper(const zkutil::ZooKeeperPtr & current_zookeeper) { + if (!looksLikeReplicatedDatabasePath(current_zookeeper, zookeeper_path)) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cannot add new database replica: provided path {} " + "already contains some data and it does not look like Replicated database path.", zookeeper_path); + /// Write host name to replica_path, it will protect from multiple replicas with the same name auto host_id = getHostID(getContext(), db_uuid); diff --git a/src/Databases/DatabaseReplicated.h b/src/Databases/DatabaseReplicated.h index 25d4b9a5082..1c977cab895 100644 --- a/src/Databases/DatabaseReplicated.h +++ b/src/Databases/DatabaseReplicated.h @@ -80,6 +80,7 @@ public: private: void tryConnectToZooKeeperAndInitDatabase(LoadingStrictnessLevel mode); bool createDatabaseNodesInZooKeeper(const ZooKeeperPtr & current_zookeeper); + static bool looksLikeReplicatedDatabasePath(const ZooKeeperPtr & current_zookeeper, const String & path); void createReplicaNodesInZooKeeper(const ZooKeeperPtr & current_zookeeper); struct diff --git a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh index 9a3f97e81c0..c3446035c30 100755 --- a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh +++ b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh @@ -103,5 +103,5 @@ wait readarray -t databases_arr < <(${CLICKHOUSE_CLIENT} -q "select name from system.databases where name like '${CLICKHOUSE_DATABASE}_%'") for db in "${databases_arr[@]}" do - $CLICKHOUSE_CLIENT -q "drop database $db" + $CLICKHOUSE_CLIENT -q "drop database if exists $db" done diff --git a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql index 2468c064dd2..85831a21b47 100644 --- a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql +++ b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql @@ -1,5 +1,6 @@ -- Tags: no-replicated-database -- Tag no-replicated-database: ON CLUSTER is not allowed +drop table if exists local_t_l5ydey; create table local_t_l5ydey on cluster test_shard_localhost ( c_qv5rv INTEGER , @@ -7,7 +8,7 @@ create table local_t_l5ydey on cluster test_shard_localhost ( c_wmj INTEGER , c_m3 TEXT NOT NULL, primary key(c_qv5rv) -) engine=ReplicatedMergeTree('/clickhouse/tables/test_{database}/{shard}/local_t_l5ydey', '{replica}'); +) engine=ReplicatedMergeTree('/clickhouse/tables/test_' || currentDatabase() || '/{shard}/local_t_l5ydey', '{replica}'); create table t_l5ydey on cluster test_shard_localhost as local_t_l5ydey engine=Distributed('test_shard_localhost', currentDatabase(),'local_t_l5ydey', rand()); @@ -18,3 +19,8 @@ system flush distributed t_l5ydey; select * from t_l5ydey order by c_qv5rv; show create t_l5ydey; + +-- Correct error code if creating database with the same path as table has +create database local_t_l5ydey engine=Replicated('/clickhouse/tables/test_' || currentDatabase() || '/{shard}/local_t_l5ydey', '1', '1'); -- { serverError BAD_ARGUMENTS } + +drop table local_t_l5ydey; From 31c5f35e305098ab2a5442571b73777cb4c83985 Mon Sep 17 00:00:00 2001 From: avogar Date: Fri, 22 Jul 2022 19:03:23 +0000 Subject: [PATCH 092/672] Update tests --- .../0_stateless/02366_cancel_write_into_file.reference | 2 -- tests/queries/0_stateless/02366_cancel_write_into_file.sh | 5 ++--- .../queries/0_stateless/02367_cancel_write_into_s3.reference | 2 -- tests/queries/0_stateless/02367_cancel_write_into_s3.sh | 4 ++-- .../0_stateless/02368_cancel_write_into_hdfs.reference | 2 -- tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh | 4 ++-- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/tests/queries/0_stateless/02366_cancel_write_into_file.reference b/tests/queries/0_stateless/02366_cancel_write_into_file.reference index b2f7f08c170..e69de29bb2d 100644 --- a/tests/queries/0_stateless/02366_cancel_write_into_file.reference +++ b/tests/queries/0_stateless/02366_cancel_write_into_file.reference @@ -1,2 +0,0 @@ -10 -10 diff --git a/tests/queries/0_stateless/02366_cancel_write_into_file.sh b/tests/queries/0_stateless/02366_cancel_write_into_file.sh index 47fa3094948..6f213c5bac4 100755 --- a/tests/queries/0_stateless/02366_cancel_write_into_file.sh +++ b/tests/queries/0_stateless/02366_cancel_write_into_file.sh @@ -13,8 +13,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" | grep "finished" -c - +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" > /dev/null for i in $(seq 1 10); do $CLICKHOUSE_CLIENT --query_id="02366_$i" -q "insert into function file('02366_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings output_format_parallel_formatting=0" 2> /dev/null & @@ -22,4 +21,4 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" | grep "finished" -c +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" > /dev/null diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.reference b/tests/queries/0_stateless/02367_cancel_write_into_s3.reference index b2f7f08c170..e69de29bb2d 100644 --- a/tests/queries/0_stateless/02367_cancel_write_into_s3.reference +++ b/tests/queries/0_stateless/02367_cancel_write_into_s3.reference @@ -1,2 +0,0 @@ -10 -10 diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh index d24462bf584..facbd63dda1 100755 --- a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh +++ b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh @@ -13,7 +13,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" | grep "finished" -c +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null for i in $(seq 1 10); do @@ -22,4 +22,4 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" | grep "finished" -c +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null diff --git a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.reference b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.reference index b2f7f08c170..e69de29bb2d 100644 --- a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.reference +++ b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.reference @@ -1,2 +0,0 @@ -10 -10 diff --git a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh index 6fda8e9dcff..9a56663a1b6 100755 --- a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh +++ b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh @@ -13,7 +13,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" | grep "finished" -c +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null for i in $(seq 1 10); do @@ -22,4 +22,4 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" | grep "finished" -c +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null From e98d8a23ca5896b917cad05ca1fa2f670dabe15a Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 20 Jul 2022 20:39:26 +0000 Subject: [PATCH 093/672] Fixing test. --- .../01834_alias_columns_laziness_filimonov.reference | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/01834_alias_columns_laziness_filimonov.reference b/tests/queries/0_stateless/01834_alias_columns_laziness_filimonov.reference index 5290ffc5dbe..91c37a277a1 100644 --- a/tests/queries/0_stateless/01834_alias_columns_laziness_filimonov.reference +++ b/tests/queries/0_stateless/01834_alias_columns_laziness_filimonov.reference @@ -1,2 +1,2 @@ -SleepFunctionCalls: 4 (increment) -SleepFunctionMicroseconds: 400000 (increment) +SleepFunctionCalls: 3 (increment) +SleepFunctionMicroseconds: 300000 (increment) From 53175e43f37612b13b26337eca60fc9575206edf Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Fri, 22 Jul 2022 18:56:01 +0000 Subject: [PATCH 094/672] Better support for projections. --- src/Common/mmap.cpp | 26 +++++++++++++++++++ src/Storages/MergeTree/MergeTreeData.cpp | 19 ++++++++++++++ .../MergeTree/MergeTreeDataSelectExecutor.cpp | 6 +++-- .../MergeTree/MergeTreeDataSelectExecutor.h | 2 ++ 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/Common/mmap.cpp diff --git a/src/Common/mmap.cpp b/src/Common/mmap.cpp new file mode 100644 index 00000000000..15c42ea6636 --- /dev/null +++ b/src/Common/mmap.cpp @@ -0,0 +1,26 @@ +#if defined(OS_LINUX) +#include + + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +extern "C" +{ + void * mmap(void * /*addr*/, size_t /*length*/, int /*prot*/, int /*flags*/, int /*fd*/, off_t /*offset*/); + int munmap(void * /*addr*/, size_t /*length*/); +} +#pragma GCC diagnostic pop + +template +inline void ignore(T x __attribute__((unused))) +{ +} + +static void dummyFunctionForInterposing() __attribute__((used)); +static void dummyFunctionForInterposing() +{ + /// Suppression for PVS-Studio and clang-tidy. + ignore(mmap(nullptr, 0, 0, 0, 0, 0)); // -V575 NOLINT + ignore(munmap(nullptr, 0)); // -V575 NOLINT +} +#endif diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index c5c3b82e775..04a9580df21 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -5095,6 +5095,8 @@ static void selectBestProjection( const MergeTreeDataSelectExecutor & reader, const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, + const ActionsDAGPtr & added_filter, + const std::string & added_filter_column_name, const Names & required_columns, ProjectionCandidate & candidate, ContextPtr query_context, @@ -5125,6 +5127,8 @@ static void selectBestProjection( storage_snapshot->metadata, candidate.desc->metadata, query_info, + added_filter, + added_filter_column_name, query_context, settings.max_threads, max_added_blocks); @@ -5147,6 +5151,8 @@ static void selectBestProjection( storage_snapshot->metadata, storage_snapshot->metadata, query_info, // TODO syntax_analysis_result set in index + added_filter, + added_filter_column_name, query_context, settings.max_threads, max_added_blocks); @@ -5470,6 +5476,9 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg query_info.sets = std::move(select.getQueryAnalyzer()->getPreparedSets()); query_info.subquery_for_sets = std::move(select.getQueryAnalyzer()->getSubqueriesForSets()); + query_info.prewhere_info = analysis_result.prewhere_info; + const auto & before_where = analysis_result.before_where; + const auto & where_column_name = analysis_result.where_column_name; bool can_use_aggregate_projection = true; /// If the first stage of the query pipeline is more complex than Aggregating - Expression - Filter - ReadFromStorage, @@ -5739,6 +5748,8 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg metadata_snapshot, metadata_snapshot, query_info, + before_where, + where_column_name, query_context, settings.max_threads, max_added_blocks); @@ -5770,6 +5781,8 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg metadata_snapshot, metadata_snapshot, query_info, + before_where, + where_column_name, query_context, settings.max_threads, max_added_blocks); @@ -5795,6 +5808,8 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg reader, storage_snapshot, query_info, + before_where, + where_column_name, analysis_result.required_columns, candidate, query_context, @@ -5815,6 +5830,8 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg reader, storage_snapshot, query_info, + before_where, + where_column_name, analysis_result.required_columns, candidate, query_context, @@ -5844,6 +5861,8 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg selected_candidate->aggregate_descriptions = select.getQueryAnalyzer()->aggregates(); } + /// Just in case, reset prewhere info calculated from projection. + query_info.prewhere_info.reset(); return *selected_candidate; } diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 909fb6af82f..553e9a4fab8 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -1249,6 +1249,8 @@ MergeTreeDataSelectAnalysisResultPtr MergeTreeDataSelectExecutor::estimateNumMar const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, + const ActionsDAGPtr & added_filter, + const std::string & added_filter_column_name, ContextPtr context, unsigned num_streams, std::shared_ptr max_block_numbers_to_read) const @@ -1269,8 +1271,8 @@ MergeTreeDataSelectAnalysisResultPtr MergeTreeDataSelectExecutor::estimateNumMar return ReadFromMergeTree::selectRangesToRead( std::move(parts), query_info.prewhere_info, - nullptr, - "", + added_filter, + added_filter_column_name, metadata_snapshot_base, metadata_snapshot, query_info, diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h index e01e624a9b7..899cf1f2862 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h @@ -60,6 +60,8 @@ public: const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, + const ActionsDAGPtr & added_filter, + const std::string & added_filter_column_name, ContextPtr context, unsigned num_streams, std::shared_ptr max_block_numbers_to_read = nullptr) const; From dd65a36c5949dc803fb5c661c5ee7eb57ba6e70b Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Fri, 22 Jul 2022 19:10:10 +0000 Subject: [PATCH 095/672] Fix another one test. --- src/Storages/MergeTree/KeyCondition.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index e590d98fce2..158165d9022 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -1146,18 +1146,12 @@ bool KeyCondition::transformConstantWithValidFunctions( if (is_valid_chain) { - /// Here we cast constant to the input type. - /// It is not clear, why this works in general. - /// I can imagine the case when expression like `column < const` is legal, - /// but `type(column)` and `type(const)` are of different types, - /// and const cannot be casted to column type. - /// (There could be `superType(type(column), type(const))` which is used for comparison). - /// - /// However, looks like this case newer happenes (I could not find such). - /// Let's assume that any two comparable types are castable to each other. auto const_type = cur_node->result_type; auto const_column = out_type->createColumnConst(1, out_value); - auto const_value = (*castColumn({const_column, out_type, ""}, const_type))[0]; + auto const_value = (*castColumnAccurateOrNull({const_column, out_type, ""}, const_type))[0]; + + if (const_value.isNull()) + return false; while (!chain.empty()) { From 3d03b2714b7f97942d18c069bba4c03828ea3983 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 22 Jul 2022 23:05:11 +0000 Subject: [PATCH 096/672] try to fix Nested --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 7 ++++++- src/Storages/MergeTree/IMergeTreeReader.cpp | 6 ------ src/Storages/MergeTree/MergeTreeDataPartWide.cpp | 6 ++++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index d4d3844de6c..45e5d9d92de 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -448,7 +448,12 @@ void IMergeTreeDataPart::setColumns(const NamesAndTypesList & new_columns) for (const auto & column : columns) column_name_to_position.emplace(column.name, pos++); - columns_description = ColumnsDescription(columns); + /// For wide parts convert plain arrays to Nested for + /// more convinient managing of shared offsets column. + if (part_type == Type::Wide) + columns_description = ColumnsDescription(Nested::collect(columns)); + else + columns_description = ColumnsDescription(columns); } NameAndTypePair IMergeTreeDataPart::getColumn(const String & column_name) const diff --git a/src/Storages/MergeTree/IMergeTreeReader.cpp b/src/Storages/MergeTree/IMergeTreeReader.cpp index 2ae087deffa..a5ef2fa6ac8 100644 --- a/src/Storages/MergeTree/IMergeTreeReader.cpp +++ b/src/Storages/MergeTree/IMergeTreeReader.cpp @@ -42,12 +42,6 @@ IMergeTreeReader::IMergeTreeReader( , all_mark_ranges(all_mark_ranges_) , alter_conversions(storage.getAlterConversionsForPart(data_part)) { - if (isWidePart(data_part)) - { - /// For wide parts convert plain arrays of Nested to subcolumns - /// to allow to use shared offset column from cache. - columns = Nested::convertToSubcolumns(columns); - } } IMergeTreeReader::~IMergeTreeReader() = default; diff --git a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp index 7fe68420310..3e5576b6bdf 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp @@ -49,7 +49,8 @@ IMergeTreeDataPart::MergeTreeReaderPtr MergeTreeDataPartWide::getReader( { auto ptr = std::static_pointer_cast(shared_from_this()); return std::make_unique( - ptr, columns_to_read, metadata_snapshot, uncompressed_cache, + ptr, Nested::convertToSubcolumns(columns_to_read), + metadata_snapshot, uncompressed_cache, mark_cache, mark_ranges, reader_settings, avg_value_size_hints, profile_callback); } @@ -64,7 +65,8 @@ IMergeTreeDataPart::MergeTreeWriterPtr MergeTreeDataPartWide::getWriter( const MergeTreeIndexGranularity & computed_index_granularity) const { return std::make_unique( - shared_from_this(), data_part_storage_builder, columns_list, metadata_snapshot, indices_to_recalc, + shared_from_this(), data_part_storage_builder, + Nested::convertToSubcolumns(columns_list), metadata_snapshot, indices_to_recalc, index_granularity_info.marks_file_extension, default_codec_, writer_settings, computed_index_granularity); } From eacc57b6a49f714bf12d160967c4098260747549 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Sun, 24 Jul 2022 14:26:25 +0000 Subject: [PATCH 097/672] Simplify DistinctSorted + use ordinary Distinct for deduplication in MergeTree when no sorting provided --- src/Storages/MergeTree/MergeTask.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Storages/MergeTree/MergeTask.cpp b/src/Storages/MergeTree/MergeTask.cpp index dc468174dfa..424d1a20a0c 100644 --- a/src/Storages/MergeTree/MergeTask.cpp +++ b/src/Storages/MergeTree/MergeTask.cpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace DB { @@ -895,8 +896,14 @@ void MergeTask::ExecuteAndFinalizeHorizontalPart::createMergedStream() res_pipe.addTransform(std::move(merged_transform)); if (global_ctx->deduplicate) - res_pipe.addTransform(std::make_shared( - res_pipe.getHeader(), sort_description, SizeLimits(), 0 /*limit_hint*/, global_ctx->deduplicate_by_columns)); + { + if (!sort_description.empty()) + res_pipe.addTransform(std::make_shared( + res_pipe.getHeader(), sort_description, SizeLimits(), 0 /*limit_hint*/, global_ctx->deduplicate_by_columns)); + else + res_pipe.addTransform(std::make_shared( + res_pipe.getHeader(), SizeLimits(), 0 /*limit_hint*/, global_ctx->deduplicate_by_columns)); + } if (ctx->need_remove_expired_values) res_pipe.addTransform(std::make_shared( From c5d9a6c37c1e6fa5be6bfff332943cabb13242cb Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Mon, 25 Jul 2022 14:52:52 +0800 Subject: [PATCH 098/672] Fix test --- .../02371_select_projection_normal_agg.sql | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/02371_select_projection_normal_agg.sql b/tests/queries/0_stateless/02371_select_projection_normal_agg.sql index ac0189f4f48..9743d486ef9 100644 --- a/tests/queries/0_stateless/02371_select_projection_normal_agg.sql +++ b/tests/queries/0_stateless/02371_select_projection_normal_agg.sql @@ -34,7 +34,18 @@ INSERT INTO video_log SELECT (bytes_raw % 1024) + 128, (duration_raw % 300) + 100 FROM rng -LIMIT 17280000; +LIMIT 1728000; + +INSERT INTO video_log SELECT + toUnixTimestamp('2022-07-22 01:00:00') + + (rowNumberInAllBlocks() / 20000), + user_id_raw % 100000000 AS user_id, + 100 AS device_id, + domain_raw % 100, + (bytes_raw % 1024) + 128, + (duration_raw % 300) + 100 +FROM rng +LIMIT 10; ALTER TABLE video_log ADD PROJECTION p_norm ( @@ -70,7 +81,7 @@ SELECT ignore(sum(bytes)), ignore(avg(duration)) FROM video_log -WHERE (toDate(hour) = '2022-07-22') AND (device_id = '100') +WHERE (toDate(hour) = '2022-07-22') AND (device_id = '100') --(device_id = '100') Make sure it's not good and doesn't go into prewhere. GROUP BY hour; DROP TABLE IF EXISTS video_log; From 5ff77e3b5f8fb36435d136404efbe316cd962dba Mon Sep 17 00:00:00 2001 From: zvonand Date: Mon, 25 Jul 2022 10:47:07 +0200 Subject: [PATCH 099/672] dumb fix --- src/Core/DecimalFunctions.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Core/DecimalFunctions.h b/src/Core/DecimalFunctions.h index 331df9aa637..9aaf1ed7b55 100644 --- a/src/Core/DecimalFunctions.h +++ b/src/Core/DecimalFunctions.h @@ -292,7 +292,11 @@ inline auto binaryOpResult(const DecimalType & tx, const DecimalType & ty) if constexpr (is_multiply) scale = tx.getScale() + ty.getScale(); else if constexpr (is_division) + { scale = tx.getScale(); + if (scale * 2 > max_precision) + throw Exception("Overflow during decimal division", ErrorCodes::DECIMAL_OVERFLOW); + } else scale = (tx.getScale() > ty.getScale() ? tx.getScale() : ty.getScale()); From 1ce8e714d3dd53ff801a249894a2f6ceaa2cd6c8 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 25 Jul 2022 12:30:01 +0000 Subject: [PATCH 100/672] Fixing build. --- src/Common/mmap.cpp | 26 ------------------------- src/Storages/MergeTree/KeyCondition.cpp | 4 ++-- 2 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 src/Common/mmap.cpp diff --git a/src/Common/mmap.cpp b/src/Common/mmap.cpp deleted file mode 100644 index 15c42ea6636..00000000000 --- a/src/Common/mmap.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#if defined(OS_LINUX) -#include - - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wredundant-decls" -extern "C" -{ - void * mmap(void * /*addr*/, size_t /*length*/, int /*prot*/, int /*flags*/, int /*fd*/, off_t /*offset*/); - int munmap(void * /*addr*/, size_t /*length*/); -} -#pragma GCC diagnostic pop - -template -inline void ignore(T x __attribute__((unused))) -{ -} - -static void dummyFunctionForInterposing() __attribute__((used)); -static void dummyFunctionForInterposing() -{ - /// Suppression for PVS-Studio and clang-tidy. - ignore(mmap(nullptr, 0, 0, 0, 0, 0)); // -V575 NOLINT - ignore(munmap(nullptr, 0)); // -V575 NOLINT -} -#endif diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 158165d9022..d79861e2916 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -925,8 +925,8 @@ KeyCondition::KeyCondition( { for (size_t i = 0, size = key_column_names.size(); i < size; ++i) { - std::string name = key_column_names[i]; - if (!key_columns.count(name)) + const auto & name = key_column_names[i]; + if (!key_columns.contains(name)) key_columns[name] = i; } From 633b628e158459437a2d85bf2ab166cfdc402689 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Mon, 25 Jul 2022 12:35:34 +0000 Subject: [PATCH 101/672] fix after conflict --- tests/integration/helpers/cluster.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 1e2c56995f7..0efc20e77a8 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -267,16 +267,6 @@ def extract_test_name(base_path): return name -def get_instances_dir(): - if ( - "INTEGRATION_TESTS_RUN_ID" in os.environ - and os.environ["INTEGRATION_TESTS_RUN_ID"] - ): - return "_instances_" + shlex.quote(os.environ["INTEGRATION_TESTS_RUN_ID"]) - else: - return "_instances" - - class ClickHouseCluster: """ClickHouse cluster with several instances and (possibly) ZooKeeper. From 06f3b31179c37ec0d3656dc26963b434d61cf5e5 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 25 Jul 2022 13:10:01 +0000 Subject: [PATCH 102/672] Roll back test for index hint. --- tests/queries/0_stateless/01739_index_hint.reference | 4 ++-- tests/queries/0_stateless/01739_index_hint.sql | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/queries/0_stateless/01739_index_hint.reference b/tests/queries/0_stateless/01739_index_hint.reference index af22b4a9676..71dfab29154 100644 --- a/tests/queries/0_stateless/01739_index_hint.reference +++ b/tests/queries/0_stateless/01739_index_hint.reference @@ -30,6 +30,6 @@ SELECT sum(t) FROM XXXX WHERE indexHint(t = 42); drop table if exists XXXX; create table XXXX (t Int64, f Float64) Engine=MergeTree order by t settings index_granularity=8192; insert into XXXX select number*60, 0 from numbers(100000); -SELECT count() >= 1 FROM XXXX WHERE indexHint(t = toDateTime(0)); -1 +SELECT count() FROM XXXX WHERE indexHint(t = toDateTime(0)); +100000 drop table XXXX; diff --git a/tests/queries/0_stateless/01739_index_hint.sql b/tests/queries/0_stateless/01739_index_hint.sql index 63e7011327c..30dfa43d334 100644 --- a/tests/queries/0_stateless/01739_index_hint.sql +++ b/tests/queries/0_stateless/01739_index_hint.sql @@ -30,6 +30,6 @@ create table XXXX (t Int64, f Float64) Engine=MergeTree order by t settings inde insert into XXXX select number*60, 0 from numbers(100000); -SELECT count() >= 1 FROM XXXX WHERE indexHint(t = toDateTime(0)); +SELECT count() FROM XXXX WHERE indexHint(t = toDateTime(0)); drop table XXXX; From 0bf0ac6ee9b1d0498bdc7219196dc09d4fed4769 Mon Sep 17 00:00:00 2001 From: avogar Date: Mon, 25 Jul 2022 13:33:57 +0000 Subject: [PATCH 103/672] Update tests --- tests/queries/0_stateless/02366_cancel_write_into_file.sh | 8 ++++++-- tests/queries/0_stateless/02367_cancel_write_into_s3.sh | 2 ++ tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/02366_cancel_write_into_file.sh b/tests/queries/0_stateless/02366_cancel_write_into_file.sh index 6f213c5bac4..9771c5a8744 100755 --- a/tests/queries/0_stateless/02366_cancel_write_into_file.sh +++ b/tests/queries/0_stateless/02366_cancel_write_into_file.sh @@ -13,7 +13,9 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02366_')" 2>&1 /dev/null + for i in $(seq 1 10); do $CLICKHOUSE_CLIENT --query_id="02366_$i" -q "insert into function file('02366_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings output_format_parallel_formatting=0" 2> /dev/null & @@ -21,4 +23,6 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02366_')" 2>&1 /dev/null + diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh index facbd63dda1..a7eddfe0df1 100755 --- a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh +++ b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh @@ -14,6 +14,7 @@ done sleep 2 $CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02367_')" 2>&1 /dev/null for i in $(seq 1 10); do @@ -23,3 +24,4 @@ done sleep 2 $CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02367_')" 2>&1 /dev/null diff --git a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh index 9a56663a1b6..9704e7e1451 100755 --- a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh +++ b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh @@ -14,6 +14,7 @@ done sleep 2 $CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02368_')" 2>&1 /dev/null for i in $(seq 1 10); do @@ -23,3 +24,4 @@ done sleep 2 $CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null +$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02368_')" 2>&1 /dev/null From c4b72277df00347392c1e7dccc141c31930bc26b Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Mon, 25 Jul 2022 13:53:27 +0000 Subject: [PATCH 104/672] Fix logs rotation issue --- src/Daemon/BaseDaemon.cpp | 8 ++++++++ src/Loggers/OwnFormattingChannel.h | 6 ++++++ src/Loggers/OwnSplitChannel.cpp | 10 ++++++++++ src/Loggers/OwnSplitChannel.h | 3 +++ 4 files changed, 27 insertions(+) diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index 1d6acc7eac3..b2ff203c18d 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -1018,6 +1018,14 @@ void BaseDaemon::setupWatchdog() logger().setChannel(log); } + /// Cuncurrent writing logs to the same file from two threads is questionable on its own, + /// but rotating them from two threads is disastrous. + if (auto * channel = dynamic_cast(logger().getChannel())) + { + channel->setChannelProperty("log", Poco::FileChannel::PROP_ROTATION, "never"); + channel->setChannelProperty("log", Poco::FileChannel::PROP_ROTATEONOPEN, "false"); + } + logger().information(fmt::format("Will watch for the process with pid {}", pid)); /// Forward signals to the child process. diff --git a/src/Loggers/OwnFormattingChannel.h b/src/Loggers/OwnFormattingChannel.h index 0480d0d5061..1120117fbb7 100644 --- a/src/Loggers/OwnFormattingChannel.h +++ b/src/Loggers/OwnFormattingChannel.h @@ -38,6 +38,12 @@ public: pChannel->close(); } + void setProperty(const std::string& name, const std::string& value) override + { + if (pChannel) + pChannel->setProperty(name, value); + } + void log(const Poco::Message & msg) override; void logExtended(const ExtendedLogMessage & msg) override; diff --git a/src/Loggers/OwnSplitChannel.cpp b/src/Loggers/OwnSplitChannel.cpp index 355b733b624..933fc09d3e4 100644 --- a/src/Loggers/OwnSplitChannel.cpp +++ b/src/Loggers/OwnSplitChannel.cpp @@ -169,4 +169,14 @@ void OwnSplitChannel::setLevel(const std::string & name, int level) } } +void OwnSplitChannel::setChannelProperty(const std::string& channel_name, const std::string& name, const std::string& value) +{ + auto it = channels.find(channel_name); + if (it != channels.end()) + { + if (auto * channel = dynamic_cast(it->second.first.get())) + channel->setProperty(name, value); + } +} + } diff --git a/src/Loggers/OwnSplitChannel.h b/src/Loggers/OwnSplitChannel.h index 72027f66afd..16231c105b7 100644 --- a/src/Loggers/OwnSplitChannel.h +++ b/src/Loggers/OwnSplitChannel.h @@ -24,6 +24,9 @@ class OwnSplitChannel : public Poco::Channel public: /// Makes an extended message from msg and passes it to the client logs queue and child (if possible) void log(const Poco::Message & msg) override; + + void setChannelProperty(const std::string& channel_name, const std::string& name, const std::string& value); + /// Adds a child channel void addChannel(Poco::AutoPtr channel, const std::string & name); From c6131e93365cfc698d1e60e8542d786d956219b0 Mon Sep 17 00:00:00 2001 From: avogar Date: Mon, 25 Jul 2022 15:05:12 +0000 Subject: [PATCH 105/672] Update tests --- tests/queries/0_stateless/02366_cancel_write_into_file.sh | 2 -- tests/queries/0_stateless/02367_cancel_write_into_s3.sh | 6 ++---- tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh | 8 +++----- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/queries/0_stateless/02366_cancel_write_into_file.sh b/tests/queries/0_stateless/02366_cancel_write_into_file.sh index 9771c5a8744..4f999c95690 100755 --- a/tests/queries/0_stateless/02366_cancel_write_into_file.sh +++ b/tests/queries/0_stateless/02366_cancel_write_into_file.sh @@ -14,7 +14,6 @@ done sleep 2 $CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" 2>&1 /dev/null -$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02366_')" 2>&1 /dev/null for i in $(seq 1 10); do @@ -24,5 +23,4 @@ done sleep 2 $CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" 2>&1 /dev/null -$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02366_')" 2>&1 /dev/null diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh index a7eddfe0df1..ab9f4eaaa69 100755 --- a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh +++ b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh @@ -13,8 +13,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null -$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02367_')" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" 2>&1 /dev/null for i in $(seq 1 10); do @@ -23,5 +22,4 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null -$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02367_')" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" 2>&1 /dev/null diff --git a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh index 9704e7e1451..ac0d6d35de5 100755 --- a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh +++ b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-fasttest +# Tags: no-fasttest, no-stress CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh @@ -13,8 +13,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null -$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02368_')" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" 2>&1 /dev/null for i in $(seq 1 10); do @@ -23,5 +22,4 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null -$CLICKHOUSE_CLIENT -q "select demangle(addressToSymbol(arrayJoin(trace))) from system.stack_trace where startsWith(query_id, '02368_')" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" 2>&1 /dev/null From 3c2449eb3dc25140e87e3c9e53c73a33faea0438 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 25 Jul 2022 17:31:21 +0200 Subject: [PATCH 106/672] Fix tests --- .../02222_create_table_without_columns_metadata.reference | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02222_create_table_without_columns_metadata.reference b/tests/queries/0_stateless/02222_create_table_without_columns_metadata.reference index 869c6b58dd4..effc3644b41 100644 --- a/tests/queries/0_stateless/02222_create_table_without_columns_metadata.reference +++ b/tests/queries/0_stateless/02222_create_table_without_columns_metadata.reference @@ -1,3 +1,3 @@ -CREATE TABLE test.test\n(\n `x` Nullable(Int64),\n `y` Nullable(String)\n)\nENGINE = File(\'JSONEachRow\', \'data.jsonl\') +CREATE TABLE default.test\n(\n `x` Nullable(Int64),\n `y` Nullable(String)\n)\nENGINE = File(\'JSONEachRow\', \'data.jsonl\') OK OK From b412ea5f6d85c68d1cb246df2d17b93e8c95720a Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Mon, 25 Jul 2022 17:06:11 +0000 Subject: [PATCH 107/672] Improve generateRandom() for Date32; fix tests 01087_table_function_generate, 01277_fromUnixTimestamp64, 01691_DateTime64_clamp and 01702_toDateTime_from_string_clamping --- src/Common/DateLUTImpl.h | 4 +++- src/Functions/FunctionsConversion.h | 2 +- src/Storages/StorageGenerateRandom.cpp | 5 ++++- .../01087_table_function_generate.reference | 20 +++++++++---------- .../01277_fromUnixTimestamp64.reference | 4 ++-- .../0_stateless/01277_fromUnixTimestamp64.sql | 18 ++++++++--------- .../01691_DateTime64_clamp.reference | 8 ++++---- ..._toDateTime_from_string_clamping.reference | 6 +++--- .../01702_toDateTime_from_string_clamping.sql | 4 ++-- 9 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/Common/DateLUTImpl.h b/src/Common/DateLUTImpl.h index 56f2e87ccb5..fbbd29d7a2b 100644 --- a/src/Common/DateLUTImpl.h +++ b/src/Common/DateLUTImpl.h @@ -19,8 +19,10 @@ #define DATE_LUT_MAX (0xFFFFFFFFU - 86400) #define DATE_LUT_MAX_DAY_NUM 0xFFFF +#define DAYNUM_OFFSET_EPOCH 25567 + /// Max int value of Date32, DATE LUT cache size minus daynum_offset_epoch -#define DATE_LUT_MAX_EXTEND_DAY_NUM (DATE_LUT_SIZE - 25567) +#define DATE_LUT_MAX_EXTEND_DAY_NUM (DATE_LUT_SIZE - DAYNUM_OFFSET_EPOCH) /// A constant to add to time_t so every supported time point becomes non-negative and still has the same remainder of division by 3600. /// If we treat "remainder of division" operation in the sense of modular arithmetic (not like in C++). diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index af75e4f49ba..62f95031e58 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -538,7 +538,7 @@ template struct ConvertImplgetData().resize(limit); - fillBufferWithRandomData(reinterpret_cast(column->getData().data()), limit * sizeof(Int32), rng); + + for (size_t i = 0; i < limit; ++i) + column->getData()[i] = (rng() % static_cast(DATE_LUT_SIZE)) - DAYNUM_OFFSET_EPOCH; + return column; } case TypeIndex::UInt32: [[fallthrough]]; diff --git a/tests/queries/0_stateless/01087_table_function_generate.reference b/tests/queries/0_stateless/01087_table_function_generate.reference index d62ff5618fc..53792bfb579 100644 --- a/tests/queries/0_stateless/01087_table_function_generate.reference +++ b/tests/queries/0_stateless/01087_table_function_generate.reference @@ -70,16 +70,16 @@ DateTime64(3, \'UTC\') DateTime64(6, \'UTC\') DateTime64(6, \'UTC\') 1992-12-28 09:26:04.030 1971-07-29 06:20:38.230976 1980-03-26 15:49:55.428516 2051-12-11 07:09:13.162 1982-01-12 00:25:45.754492 2010-05-17 07:01:28.452864 Date32 -1934-01-06 -2039-08-16 -2103-11-03 -2064-08-14 -2187-08-21 -2099-04-08 -1947-06-22 -2012-01-19 -2170-07-09 -2263-01-17 +2120-04-24 +1908-10-02 +2105-09-04 +2129-03-23 +1921-04-05 +2020-04-14 +2251-12-25 +2266-03-27 +2161-02-18 +2172-07-24 - Float32 Float64 -1.3551149e32 1.2262973812461839e235 diff --git a/tests/queries/0_stateless/01277_fromUnixTimestamp64.reference b/tests/queries/0_stateless/01277_fromUnixTimestamp64.reference index a9ffd259af0..28006c1d168 100644 --- a/tests/queries/0_stateless/01277_fromUnixTimestamp64.reference +++ b/tests/queries/0_stateless/01277_fromUnixTimestamp64.reference @@ -4,6 +4,6 @@ Asia/Makassar 1234567891011 2009-02-14 07:31:31.011 1970-01-15 14:56:07.891011 1 non-const column 1234567891011 2009-02-13 23:31:31.011 1970-01-15 06:56:07.891011 1970-01-01 00:20:34.567891011 upper range bound -9904447342 2283-11-10 19:22:22.123 2283-11-10 19:22:22.123456 1925-01-01 00:00:00.413905173 +10413688942 2299-12-30 19:22:22.123 2299-12-30 19:22:22.123456 1900-01-01 00:00:00.413905173 lower range bound --1420066799 1925-01-01 01:00:00.877 1925-01-01 01:00:00.876544 1925-01-01 01:00:00.876543211 +-2208985199 1900-01-01 01:00:00.877 1900-01-01 01:00:00.876544 1900-01-01 01:00:00.876543211 diff --git a/tests/queries/0_stateless/01277_fromUnixTimestamp64.sql b/tests/queries/0_stateless/01277_fromUnixTimestamp64.sql index e76a4db7a27..846ffa094a5 100644 --- a/tests/queries/0_stateless/01277_fromUnixTimestamp64.sql +++ b/tests/queries/0_stateless/01277_fromUnixTimestamp64.sql @@ -46,10 +46,10 @@ SELECT SELECT 'upper range bound'; WITH - 9904447342 AS timestamp, - CAST(9904447342123 AS Int64) AS milli, - CAST(9904447342123456 AS Int64) AS micro, - CAST(9904447342123456789 AS Int64) AS nano, + 10413688942 AS timestamp, + CAST(10413688942123 AS Int64) AS milli, + CAST(10413688942123456 AS Int64) AS micro, + CAST(10413688942123456789 AS Int64) AS nano, 'UTC' AS tz SELECT timestamp, @@ -59,13 +59,13 @@ SELECT SELECT 'lower range bound'; WITH - -1420066799 AS timestamp, - CAST(-1420066799123 AS Int64) AS milli, - CAST(-1420066799123456 AS Int64) AS micro, - CAST(-1420066799123456789 AS Int64) AS nano, + -2208985199 AS timestamp, + CAST(-2208985199123 AS Int64) AS milli, + CAST(-2208985199123456 AS Int64) AS micro, + CAST(-2208985199123456789 AS Int64) AS nano, 'UTC' AS tz SELECT timestamp, fromUnixTimestamp64Milli(milli, tz), fromUnixTimestamp64Micro(micro, tz), - fromUnixTimestamp64Nano(nano, tz); \ No newline at end of file + fromUnixTimestamp64Nano(nano, tz); diff --git a/tests/queries/0_stateless/01691_DateTime64_clamp.reference b/tests/queries/0_stateless/01691_DateTime64_clamp.reference index 6272103440c..75435aebd67 100644 --- a/tests/queries/0_stateless/01691_DateTime64_clamp.reference +++ b/tests/queries/0_stateless/01691_DateTime64_clamp.reference @@ -18,10 +18,10 @@ SELECT toDateTime64(toFloat32(bitShiftLeft(toUInt64(1),33)), 2, 'Asia/Istanbul') SELECT toDateTime64(toFloat64(bitShiftLeft(toUInt64(1),33)), 2, 'Asia/Istanbul') FORMAT Null; -- These are outsize of extended range and hence clamped SELECT toDateTime64(-1 * bitShiftLeft(toUInt64(1), 35), 2, 'Asia/Istanbul'); -1925-01-01 02:00:00.00 +1900-01-01 01:56:56.00 SELECT CAST(-1 * bitShiftLeft(toUInt64(1), 35) AS DateTime64(3, 'Asia/Istanbul')); -1925-01-01 02:00:00.000 +1900-01-01 01:56:56.000 SELECT CAST(bitShiftLeft(toUInt64(1), 35) AS DateTime64(3, 'Asia/Istanbul')); -2282-12-31 03:00:00.000 +2299-12-31 23:59:59.000 SELECT toDateTime64(bitShiftLeft(toUInt64(1), 35), 2, 'Asia/Istanbul'); -2282-12-31 03:00:00.00 +2299-12-31 23:59:59.00 diff --git a/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.reference b/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.reference index f27bf42c7a5..ecea0a9f69f 100644 --- a/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.reference +++ b/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.reference @@ -1,4 +1,4 @@ 1940-10-09 21:13:17.6 -2283-11-11 23:46:43.6 -2283-11-11 23:46:40.1 -1925-01-01 00:00:00.9 +2284-06-04 23:46:43.6 +2299-12-31 23:40:00.1 +1900-01-01 00:00:00.9 diff --git a/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.sql b/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.sql index b0dbd1dfc84..e84bb35b3a5 100644 --- a/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.sql +++ b/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.sql @@ -1,4 +1,4 @@ SELECT toString(toDateTime('-922337203.6854775808', 1, 'Asia/Istanbul')); SELECT toString(toDateTime('9922337203.6854775808', 1, 'Asia/Istanbul')); -SELECT toDateTime64(CAST('10000000000.1' AS Decimal64(1)), 1, 'Asia/Istanbul'); -SELECT toDateTime64(CAST('-10000000000.1' AS Decimal64(1)), 1, 'Asia/Istanbul'); +SELECT toDateTime64(CAST('10500000000.1' AS Decimal64(1)), 1, 'Asia/Istanbul'); +SELECT toDateTime64(CAST('-10500000000.1' AS Decimal64(1)), 1, 'Asia/Istanbul'); From fe79b08a7cb20c1af62bc7dbb11ad9e95ae7f4f2 Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Mon, 25 Jul 2022 18:38:48 +0000 Subject: [PATCH 108/672] Fix test 02346_non_negative_derivative --- .../02346_non_negative_derivative.reference | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/queries/0_stateless/02346_non_negative_derivative.reference b/tests/queries/0_stateless/02346_non_negative_derivative.reference index 51fe2359bd0..b81af45962e 100644 --- a/tests/queries/0_stateless/02346_non_negative_derivative.reference +++ b/tests/queries/0_stateless/02346_non_negative_derivative.reference @@ -1,63 +1,63 @@ 1 1979-12-12 21:21:21.127 3.7 0 -2283-11-11 23:37:36.788 1.1 0 -2283-11-11 23:37:36.789 2.34 0 +2299-12-31 23:37:36.788 1.1 0 +2299-12-31 23:37:36.789 2.34 0 1979-12-12 21:21:21.129 2.1 0 1979-12-12 21:21:22.000 1.3345 0 1979-12-12 21:21:23.000 1.54 0.20550000000000002 1979-12-12 21:21:23.000 1.54 0 1979-12-12 21:21:21.127 3.7 0 -2283-11-11 23:37:36.788 1.1 0 -2283-11-11 23:37:36.789 2.34 0 +2299-12-31 23:37:36.788 1.1 0 +2299-12-31 23:37:36.789 2.34 0 1979-12-12 21:21:21.129 2.1 0 1979-12-12 21:21:22.000 1.3345 0 1979-12-12 21:21:23.000 1.54 6.165000000000001e-10 1979-12-12 21:21:23.000 1.54 0 1979-12-12 21:21:21.127 3.7 0 -2283-11-11 23:37:36.788 1.1 0 -2283-11-11 23:37:36.789 2.34 0 +2299-12-31 23:37:36.788 1.1 0 +2299-12-31 23:37:36.789 2.34 0 1979-12-12 21:21:21.129 2.1 0 1979-12-12 21:21:22.000 1.3345 0 1979-12-12 21:21:23.000 1.54 8.22e-7 1979-12-12 21:21:23.000 1.54 0 1979-12-12 21:21:21.127 3.7 0 -2283-11-11 23:37:36.788 1.1 0 -2283-11-11 23:37:36.789 2.34 0 +2299-12-31 23:37:36.788 1.1 0 +2299-12-31 23:37:36.789 2.34 0 1979-12-12 21:21:21.129 2.1 0 1979-12-12 21:21:22.000 1.3345 0 1979-12-12 21:21:23.000 1.54 0.0010275000000000002 1979-12-12 21:21:23.000 1.54 0 1979-12-12 21:21:21.127 3.7 0 -2283-11-11 23:37:36.788 1.1 0 -2283-11-11 23:37:36.789 2.34 0 +2299-12-31 23:37:36.788 1.1 0 +2299-12-31 23:37:36.789 2.34 0 1979-12-12 21:21:21.129 2.1 0 1979-12-12 21:21:22.000 1.3345 0 1979-12-12 21:21:23.000 1.54 1.233 1979-12-12 21:21:23.000 1.54 0 1979-12-12 21:21:21.127 3.7 0 -2283-11-11 23:37:36.788 1.1 0 -2283-11-11 23:37:36.789 2.34 0 +2299-12-31 23:37:36.788 1.1 0 +2299-12-31 23:37:36.789 2.34 0 1979-12-12 21:21:21.129 2.1 0 1979-12-12 21:21:22.000 1.3345 0 1979-12-12 21:21:23.000 1.54 86.31 1979-12-12 21:21:23.000 1.54 0 1979-12-12 21:21:21.127 3.7 0 -2283-11-11 23:37:36.788 1.1 0 -2283-11-11 23:37:36.789 2.34 0 +2299-12-31 23:37:36.788 1.1 0 +2299-12-31 23:37:36.789 2.34 0 1979-12-12 21:21:21.129 2.1 0 1979-12-12 21:21:22.000 1.3345 0 1979-12-12 21:21:23.000 1.54 5918.400000000001 1979-12-12 21:21:23.000 1.54 0 1979-12-12 21:21:21.127 3.7 0 -2283-11-11 23:37:36.788 1.1 0 -2283-11-11 23:37:36.789 2.34 0 +2299-12-31 23:37:36.788 1.1 0 +2299-12-31 23:37:36.789 2.34 0 1979-12-12 21:21:21.129 2.1 0 1979-12-12 21:21:22.000 1.3345 0 1979-12-12 21:21:23.000 1.54 159796.80000000002 1979-12-12 21:21:23.000 1.54 0 1979-12-12 21:21:21.127 3.7 0 -2283-11-11 23:37:36.788 1.1 0 -2283-11-11 23:37:36.789 2.34 0 +2299-12-31 23:37:36.788 1.1 0 +2299-12-31 23:37:36.789 2.34 0 1979-12-12 21:21:21.129 2.1 0 1979-12-12 21:21:22.000 1.3345 0 1979-12-12 21:21:23.000 1.54 1242864 From 9e62e683661d3dc2c71aa40bfcce32de3f799bb6 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 25 Jul 2022 20:47:16 +0200 Subject: [PATCH 109/672] fix --- .../0_stateless/02400_create_table_on_cluster_normalization.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql index 85831a21b47..54e4ccf6762 100644 --- a/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql +++ b/tests/queries/0_stateless/02400_create_table_on_cluster_normalization.sql @@ -21,6 +21,7 @@ select * from t_l5ydey order by c_qv5rv; show create t_l5ydey; -- Correct error code if creating database with the same path as table has +set allow_experimental_database_replicated=1; create database local_t_l5ydey engine=Replicated('/clickhouse/tables/test_' || currentDatabase() || '/{shard}/local_t_l5ydey', '1', '1'); -- { serverError BAD_ARGUMENTS } drop table local_t_l5ydey; From be9c7ed52c5c2f7eba85cc3d37287d2808a0d214 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 25 Jul 2022 19:41:43 +0000 Subject: [PATCH 110/672] Add ReadFromMerge step. --- src/QueryPipeline/QueryPipelineBuilder.cpp | 7 + src/QueryPipeline/QueryPipelineBuilder.h | 3 + src/Storages/StorageMerge.cpp | 162 +++++++++------------ src/Storages/StorageMerge.h | 43 ++++-- 4 files changed, 115 insertions(+), 100 deletions(-) diff --git a/src/QueryPipeline/QueryPipelineBuilder.cpp b/src/QueryPipeline/QueryPipelineBuilder.cpp index 340b85efae9..82907d883bc 100644 --- a/src/QueryPipeline/QueryPipelineBuilder.cpp +++ b/src/QueryPipeline/QueryPipelineBuilder.cpp @@ -23,6 +23,7 @@ #include #include #include "Core/SortDescription.h" +#include #include #include #include @@ -195,6 +196,12 @@ void QueryPipelineBuilder::resize(size_t num_streams, bool force, bool strict) pipe.resize(num_streams, force, strict); } +void QueryPipelineBuilder::narrow(size_t size) +{ + checkInitializedAndNotCompleted(); + narrowPipe(pipe, size); +} + void QueryPipelineBuilder::addTotalsHavingTransform(ProcessorPtr transform) { checkInitializedAndNotCompleted(); diff --git a/src/QueryPipeline/QueryPipelineBuilder.h b/src/QueryPipeline/QueryPipelineBuilder.h index 2d9b8028627..a48828be8b9 100644 --- a/src/QueryPipeline/QueryPipelineBuilder.h +++ b/src/QueryPipeline/QueryPipelineBuilder.h @@ -94,6 +94,9 @@ public: /// Changes the number of output ports if needed. Adds ResizeTransform. void resize(size_t num_streams, bool force = false, bool strict = false); + /// Concat some ports to have no more then size outputs. + void narrow(size_t size); + /// Unite several pipelines together. Result pipeline would have common_header structure. /// If collector is used, it will collect only newly-added processors, but not processors from pipelines. static QueryPipelineBuilder unitePipelines( diff --git a/src/Storages/StorageMerge.cpp b/src/Storages/StorageMerge.cpp index 0afc7a0df7e..500e35d062a 100644 --- a/src/Storages/StorageMerge.cpp +++ b/src/Storages/StorageMerge.cpp @@ -216,7 +216,7 @@ QueryProcessingStage::Enum StorageMerge::getQueryProcessingStage( } -SelectQueryInfo StorageMerge::getModifiedQueryInfo( +SelectQueryInfo getModifiedQueryInfo( const SelectQueryInfo & query_info, ContextPtr modified_context, const StorageID & current_storage_id, bool is_merge_engine) { SelectQueryInfo modified_query_info = query_info; @@ -248,7 +248,18 @@ void StorageMerge::read( const size_t max_block_size, unsigned num_streams) { - Pipes pipes; + /** Just in case, turn off optimization "transfer to PREWHERE", + * since there is no certainty that it works when one of table is MergeTree and other is not. + */ + auto modified_context = Context::createCopy(context); + modified_context->setSetting("optimize_move_to_prewhere", false); + + query_plan.addInterpreterContext(modified_context); +} + +void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const BuildQueryPipelineSettings &) +{ + std::vector> pipelines; bool has_database_virtual_column = false; bool has_table_virtual_column = false; @@ -257,60 +268,39 @@ void StorageMerge::read( for (const auto & column_name : column_names) { - if (column_name == "_database" && isVirtualColumn(column_name, storage_snapshot->metadata)) + if (column_name == "_database" && storage_merge->isVirtualColumn(column_name, merge_storage_snapshot->metadata)) has_database_virtual_column = true; - else if (column_name == "_table" && isVirtualColumn(column_name, storage_snapshot->metadata)) + else if (column_name == "_table" && storage_merge->isVirtualColumn(column_name, merge_storage_snapshot->metadata)) has_table_virtual_column = true; else real_column_names.push_back(column_name); } - /** Just in case, turn off optimization "transfer to PREWHERE", - * since there is no certainty that it works when one of table is MergeTree and other is not. - */ - auto modified_context = Context::createCopy(local_context); - modified_context->setSetting("optimize_move_to_prewhere", false); - /// What will be result structure depending on query processed stage in source tables? - Block header = getHeaderForProcessingStage(column_names, storage_snapshot, query_info, local_context, processed_stage); + Block header = getHeaderForProcessingStage(column_names, merge_storage_snapshot, query_info, context, common_processed_stage); /** First we make list of selected tables to find out its size. * This is necessary to correctly pass the recommended number of threads to each table. */ StorageListWithLocks selected_tables - = getSelectedTables(local_context, query_info.query, has_database_virtual_column, has_table_virtual_column); - - query_plan.addInterpreterContext(modified_context); - - QueryPlanResourceHolder resources; + = storage_merge->getSelectedTables(context, query_info.query, has_database_virtual_column, has_table_virtual_column); if (selected_tables.empty()) { - auto modified_query_info = getModifiedQueryInfo(query_info, modified_context, getStorageID(), false); - /// FIXME: do we support sampling in this case? - auto pipe = createSources( - resources, - {}, - modified_query_info, - processed_stage, - max_block_size, - header, - {}, - {}, - real_column_names, - modified_context, - 0, - has_database_virtual_column, - has_table_virtual_column); + pipeline = InterpreterSelectQuery( + query_info.query, context, + Pipe(std::make_shared(header)), + SelectQueryOptions(common_processed_stage).analyze()).buildQueryPipeline(); - IStorage::readFromPipe(query_plan, std::move(pipe), column_names, storage_snapshot, query_info, local_context, getName()); return; } + QueryPlanResourceHolder resources; + size_t tables_count = selected_tables.size(); Float64 num_streams_multiplier - = std::min(static_cast(tables_count), std::max(1U, static_cast(local_context->getSettingsRef().max_streams_multiplier_for_merge_tables))); - num_streams *= num_streams_multiplier; + = std::min(static_cast(tables_count), std::max(1U, static_cast(context->getSettingsRef().max_streams_multiplier_for_merge_tables))); + size_t num_streams = requested_num_streams * num_streams_multiplier; size_t remaining_streams = num_streams; InputOrderInfoPtr input_sorting_info; @@ -320,7 +310,7 @@ void StorageMerge::read( { auto storage_ptr = std::get<1>(*it); auto storage_metadata_snapshot = storage_ptr->getInMemoryMetadataPtr(); - auto current_info = query_info.order_optimizer->getInputOrder(storage_metadata_snapshot, local_context); + auto current_info = query_info.order_optimizer->getInputOrder(storage_metadata_snapshot, context); if (it == selected_tables.begin()) input_sorting_info = current_info; else if (!current_info || (input_sorting_info && *current_info != *input_sorting_info)) @@ -333,7 +323,7 @@ void StorageMerge::read( query_info.input_order_info = input_sorting_info; } - auto sample_block = getInMemoryMetadataPtr()->getSampleBlock(); + auto sample_block = merge_metadata_for_reading->getSampleBlock(); for (const auto & table : selected_tables) { @@ -351,14 +341,14 @@ void StorageMerge::read( Aliases aliases; auto storage_metadata_snapshot = storage->getInMemoryMetadataPtr(); auto storage_columns = storage_metadata_snapshot->getColumns(); - auto nested_storage_snaphsot = storage->getStorageSnapshot(storage_metadata_snapshot, local_context); + auto nested_storage_snaphsot = storage->getStorageSnapshot(storage_metadata_snapshot, context); - auto modified_query_info = getModifiedQueryInfo(query_info, modified_context, storage->getStorageID(), storage->as()); - auto syntax_result = TreeRewriter(local_context).analyzeSelect( + auto modified_query_info = getModifiedQueryInfo(query_info, context, storage->getStorageID(), storage->as()); + auto syntax_result = TreeRewriter(context).analyzeSelect( modified_query_info.query, TreeRewriterResult({}, storage, nested_storage_snaphsot)); Names column_names_as_aliases; - bool with_aliases = processed_stage == QueryProcessingStage::FetchColumns && !storage_columns.getAliases().empty(); + bool with_aliases = common_processed_stage == QueryProcessingStage::FetchColumns && !storage_columns.getAliases().empty(); if (with_aliases) { ASTPtr required_columns_expr_list = std::make_shared(); @@ -373,11 +363,11 @@ void StorageMerge::read( { column_expr = column_default->expression->clone(); replaceAliasColumnsInQuery(column_expr, storage_metadata_snapshot->getColumns(), - syntax_result->array_join_result_to_source, local_context); + syntax_result->array_join_result_to_source, context); auto column_description = storage_columns.get(column); column_expr = addTypeConversionToAST(std::move(column_expr), column_description.type->getName(), - storage_metadata_snapshot->getColumns().getAll(), local_context); + storage_metadata_snapshot->getColumns().getAll(), context); column_expr = setAlias(column_expr, column); auto type = sample_block.getByName(column).type; @@ -389,54 +379,51 @@ void StorageMerge::read( required_columns_expr_list->children.emplace_back(std::move(column_expr)); } - syntax_result = TreeRewriter(local_context).analyze( - required_columns_expr_list, storage_columns.getAllPhysical(), storage, storage->getStorageSnapshot(storage_metadata_snapshot, local_context)); + syntax_result = TreeRewriter(context).analyze( + required_columns_expr_list, storage_columns.getAllPhysical(), storage, storage->getStorageSnapshot(storage_metadata_snapshot, context)); - auto alias_actions = ExpressionAnalyzer(required_columns_expr_list, syntax_result, local_context).getActionsDAG(true); + auto alias_actions = ExpressionAnalyzer(required_columns_expr_list, syntax_result, context).getActionsDAG(true); column_names_as_aliases = alias_actions->getRequiredColumns().getNames(); if (column_names_as_aliases.empty()) column_names_as_aliases.push_back(ExpressionActions::getSmallestColumn(storage_metadata_snapshot->getColumns().getAllPhysical())); } - auto source_pipe = createSources( - resources, + auto source_pipeline = createSources( nested_storage_snaphsot, modified_query_info, - processed_stage, - max_block_size, + common_processed_stage, + required_max_block_size, header, aliases, table, column_names_as_aliases.empty() ? real_column_names : column_names_as_aliases, - modified_context, + context, current_streams, has_database_virtual_column, has_table_virtual_column); - if (!source_pipe.empty()) + if (!source_pipeline->initialized()) { - query_plan.addStorageHolder(std::get<1>(table)); - query_plan.addTableLock(std::get<2>(table)); + resources.storage_holders.push_back(std::get<1>(table)); + resources.table_locks.push_back(std::get<2>(table)); - pipes.emplace_back(std::move(source_pipe)); + pipelines.emplace_back(std::move(source_pipeline)); } } - auto pipe = Pipe::unitePipes(std::move(pipes)); + pipeline = QueryPipelineBuilder::unitePipelines(std::move(pipelines)); - if (!pipe.empty() && !query_info.input_order_info) + if (!query_info.input_order_info) // It's possible to have many tables read from merge, resize(num_streams) might open too many files at the same time. // Using narrowPipe instead. But in case of reading in order of primary key, we cannot do it, // because narrowPipe doesn't preserve order. - narrowPipe(pipe, num_streams); + pipeline.narrow(num_streams); - IStorage::readFromPipe(query_plan, std::move(pipe), column_names, storage_snapshot, query_info, local_context, getName()); - query_plan.addResources(std::move(resources)); + pipeline.addResources(std::move(resources)); } -Pipe StorageMerge::createSources( - QueryPlanResourceHolder & resources, +QueryPipelineBuilderPtr ReadFromMerge::createSources( const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & modified_query_info, const QueryProcessingStage::Enum & processed_stage, @@ -454,18 +441,14 @@ Pipe StorageMerge::createSources( const auto & [database_name, storage, _, table_name] = storage_with_lock; auto & modified_select = modified_query_info.query->as(); - Pipe pipe; + QueryPipelineBuilderPtr builder; if (!storage) { - auto builder = InterpreterSelectQuery( + return std::make_unique(InterpreterSelectQuery( modified_query_info.query, modified_context, Pipe(std::make_shared(header)), - SelectQueryOptions(processed_stage).analyze()).buildQueryPipeline(); - - pipe = QueryPipelineBuilder::getPipe(std::move(builder), resources); - - return pipe; + SelectQueryOptions(processed_stage).analyze()).buildQueryPipeline()); } if (!modified_select.final() && storage->needRewriteQueryWithFinal(real_column_names)) @@ -497,11 +480,9 @@ Pipe StorageMerge::createSources( if (!plan.isInitialized()) return {}; - auto builder = plan.buildQueryPipeline( + return plan.buildQueryPipeline( QueryPlanOptimizationSettings::fromContext(modified_context), BuildQueryPipelineSettings::fromContext(modified_context)); - - pipe = QueryPipelineBuilder::getPipe(std::move(*builder), resources); } else if (processed_stage > storage_stage) { @@ -515,28 +496,27 @@ Pipe StorageMerge::createSources( InterpreterSelectQuery interpreter{ modified_query_info.query, modified_context, SelectQueryOptions(processed_stage).ignoreProjections()}; - - pipe = QueryPipelineBuilder::getPipe(interpreter.buildQueryPipeline(), resources); + builder = std::make_unique(interpreter.buildQueryPipeline()); /** Materialization is needed, since from distributed storage the constants come materialized. * If you do not do this, different types (Const and non-Const) columns will be produced in different threads, * And this is not allowed, since all code is based on the assumption that in the block stream all types are the same. */ - pipe.addSimpleTransform([](const Block & stream_header) { return std::make_shared(stream_header); }); + builder->addSimpleTransform([](const Block & stream_header) { return std::make_shared(stream_header); }); } - if (!pipe.empty()) + if (builder->initialized()) { - if (concat_streams && pipe.numOutputPorts() > 1) + if (concat_streams && builder->getNumStreams() > 1) { // It's possible to have many tables read from merge, resize(1) might open too many files at the same time. // Using concat instead. - pipe.addTransform(std::make_shared(pipe.getHeader(), pipe.numOutputPorts())); + builder->addTransform(std::make_shared(builder->getHeader(), builder->getNumStreams())); } /// Add virtual columns if we don't already have them. - Block pipe_header = pipe.getHeader(); + Block pipe_header = builder->getHeader(); if (has_database_virtual_column && !pipe_header.has("_database")) { @@ -550,7 +530,7 @@ Pipe StorageMerge::createSources( std::move(adding_column_dag), ExpressionActionsSettings::fromContext(modified_context, CompileExpressions::yes)); - pipe.addSimpleTransform([&](const Block & stream_header) + builder->addSimpleTransform([&](const Block & stream_header) { return std::make_shared(stream_header, adding_column_actions); }); @@ -568,7 +548,7 @@ Pipe StorageMerge::createSources( std::move(adding_column_dag), ExpressionActionsSettings::fromContext(modified_context, CompileExpressions::yes)); - pipe.addSimpleTransform([&](const Block & stream_header) + builder->addSimpleTransform([&](const Block & stream_header) { return std::make_shared(stream_header, adding_column_actions); }); @@ -576,10 +556,10 @@ Pipe StorageMerge::createSources( /// Subordinary tables could have different but convertible types, like numeric types of different width. /// We must return streams with structure equals to structure of Merge table. - convertingSourceStream(header, storage_snapshot->metadata, aliases, modified_context, modified_query_info.query, pipe, processed_stage); + convertingSourceStream(header, storage_snapshot->metadata, aliases, modified_context, modified_query_info.query, *builder, processed_stage); } - return pipe; + return builder; } StorageMerge::StorageListWithLocks StorageMerge::getSelectedTables( @@ -747,19 +727,19 @@ void StorageMerge::alter( setInMemoryMetadata(storage_metadata); } -void StorageMerge::convertingSourceStream( +void ReadFromMerge::convertingSourceStream( const Block & header, const StorageMetadataPtr & metadata_snapshot, const Aliases & aliases, ContextPtr local_context, ASTPtr & query, - Pipe & pipe, + QueryPipelineBuilder & builder, QueryProcessingStage::Enum processed_stage) { - Block before_block_header = pipe.getHeader(); + Block before_block_header = builder.getHeader(); auto storage_sample_block = metadata_snapshot->getSampleBlock(); - auto pipe_columns = pipe.getHeader().getNamesAndTypesList(); + auto pipe_columns = builder.getHeader().getNamesAndTypesList(); for (const auto & alias : aliases) { @@ -772,21 +752,21 @@ void StorageMerge::convertingSourceStream( auto actions_dag = expression_analyzer.getActionsDAG(true, false); auto actions = std::make_shared(actions_dag, ExpressionActionsSettings::fromContext(local_context, CompileExpressions::yes)); - pipe.addSimpleTransform([&](const Block & stream_header) + builder.addSimpleTransform([&](const Block & stream_header) { return std::make_shared(stream_header, actions); }); } { - auto convert_actions_dag = ActionsDAG::makeConvertingActions(pipe.getHeader().getColumnsWithTypeAndName(), + auto convert_actions_dag = ActionsDAG::makeConvertingActions(builder.getHeader().getColumnsWithTypeAndName(), header.getColumnsWithTypeAndName(), ActionsDAG::MatchColumnsMode::Name); auto actions = std::make_shared( convert_actions_dag, ExpressionActionsSettings::fromContext(local_context, CompileExpressions::yes)); - pipe.addSimpleTransform([&](const Block & stream_header) + builder.addSimpleTransform([&](const Block & stream_header) { return std::make_shared(stream_header, actions); }); @@ -809,7 +789,7 @@ void StorageMerge::convertingSourceStream( if (!header_column.type->equals(*before_column.type.get())) { NamesAndTypesList source_columns = metadata_snapshot->getSampleBlock().getNamesAndTypesList(); - auto virtual_column = *getVirtuals().tryGetByName("_table"); + auto virtual_column = *storage_merge->getVirtuals().tryGetByName("_table"); source_columns.emplace_back(NameAndTypePair{virtual_column.name, virtual_column.type}); auto syntax_result = TreeRewriter(local_context).analyze(where_expression, source_columns); ExpressionActionsPtr actions = ExpressionAnalyzer{where_expression, syntax_result, local_context}.getActions(false, false); diff --git a/src/Storages/StorageMerge.h b/src/Storages/StorageMerge.h index f6dae239e79..440aa01890d 100644 --- a/src/Storages/StorageMerge.h +++ b/src/Storages/StorageMerge.h @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include namespace DB @@ -105,7 +107,36 @@ private: NamesAndTypesList getVirtuals() const override; ColumnSizeByName getColumnSizes() const override; -protected: + ColumnsDescription getColumnsDescriptionFromSourceTables() const; + + friend class ReadFromMerge; +}; + +class ReadFromMerge final : public ISourceStep +{ +public: + static constexpr auto name = "ReadFromMerge"; + String getName() const override { return name; } + + void initializePipeline(QueryPipelineBuilder & pipeline, const BuildQueryPipelineSettings &) override; + + using StorageWithLockAndName = std::tuple; + using StorageListWithLocks = std::list; + using DatabaseTablesIterators = std::vector; + +private: + const size_t requested_num_streams; + const size_t required_max_block_size; + + Names column_names; + std::shared_ptr storage_merge; + QueryProcessingStage::Enum common_processed_stage; + + SelectQueryInfo query_info; + StorageSnapshotPtr merge_storage_snapshot; + StorageMetadataPtr merge_metadata_for_reading; + ContextMutablePtr context; + struct AliasData { String name; @@ -115,8 +146,7 @@ protected: using Aliases = std::vector; - Pipe createSources( - QueryPlanResourceHolder & resources, + QueryPipelineBuilderPtr createSources( const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, const QueryProcessingStage::Enum & processed_stage, @@ -134,12 +164,7 @@ protected: void convertingSourceStream( const Block & header, const StorageMetadataPtr & metadata_snapshot, const Aliases & aliases, ContextPtr context, ASTPtr & query, - Pipe & pipe, QueryProcessingStage::Enum processed_stage); - - static SelectQueryInfo getModifiedQueryInfo( - const SelectQueryInfo & query_info, ContextPtr modified_context, const StorageID & current_storage_id, bool is_merge_engine); - - ColumnsDescription getColumnsDescriptionFromSourceTables() const; + QueryPipelineBuilder & builder, QueryProcessingStage::Enum processed_stage); }; } From c150fe6f0d33b6157443a58411cbe827caab1f4d Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Mon, 25 Jul 2022 21:56:20 +0200 Subject: [PATCH 111/672] Fix possible wrong FROM_REF by using merge-base commit --- utils/changelog/changelog.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/changelog/changelog.py b/utils/changelog/changelog.py index 3981bce73a6..305899fe7e8 100755 --- a/utils/changelog/changelog.py +++ b/utils/changelog/changelog.py @@ -368,11 +368,13 @@ def main(): logging.info("Using %s..%s as changelog interval", FROM_REF, TO_REF) + # use merge-base commit as a starting point, if used ref in another branch + base_commit = runner.run(f"git merge-base '{FROM_REF}^{{}}' '{TO_REF}^{{}}'") # Get starting and ending dates for gathering PRs # Add one day after and before to mitigate TZ possible issues # `tag^{}` format gives commit ref when we have annotated tags # format %cs gives a committer date, works better for cherry-picked commits - from_date = runner.run(f"git log -1 --format=format:%cs '{FROM_REF}^{{}}'") + from_date = runner.run(f"git log -1 --format=format:%cs '{base_commit}'") to_date = runner.run(f"git log -1 --format=format:%cs '{TO_REF}^{{}}'") merged = ( date.fromisoformat(from_date) - timedelta(1), From d6fb682f711c9e60d3346f8525fc66772e7dff03 Mon Sep 17 00:00:00 2001 From: Wangyang Guo Date: Mon, 25 Jul 2022 15:55:39 +0800 Subject: [PATCH 112/672] Columns: remove static declaration to zero registers Static declaration will enforce an extra mem load. But zeroing register does not need it (like: vpxor zmm, zmm, zmm). --- src/Columns/ColumnsCommon.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Columns/ColumnsCommon.h b/src/Columns/ColumnsCommon.h index 1e5849e2b88..20ec5872b93 100644 --- a/src/Columns/ColumnsCommon.h +++ b/src/Columns/ColumnsCommon.h @@ -27,17 +27,17 @@ namespace ErrorCodes inline UInt64 bytes64MaskToBits64Mask(const UInt8 * bytes64) { #if defined(__AVX512F__) && defined(__AVX512BW__) - static const __m512i zero64 = _mm512_setzero_epi32(); + const __m512i zero64 = _mm512_setzero_epi32(); UInt64 res = _mm512_cmp_epi8_mask(_mm512_loadu_si512(reinterpret_cast(bytes64)), zero64, _MM_CMPINT_EQ); #elif defined(__AVX__) && defined(__AVX2__) - static const __m256i zero32 = _mm256_setzero_si256(); + const __m256i zero32 = _mm256_setzero_si256(); UInt64 res = (static_cast(_mm256_movemask_epi8(_mm256_cmpeq_epi8( _mm256_loadu_si256(reinterpret_cast(bytes64)), zero32))) & 0xffffffff) | (static_cast(_mm256_movemask_epi8(_mm256_cmpeq_epi8( _mm256_loadu_si256(reinterpret_cast(bytes64+32)), zero32))) << 32); #elif defined(__SSE2__) && defined(__POPCNT__) - static const __m128i zero16 = _mm_setzero_si128(); + const __m128i zero16 = _mm_setzero_si128(); UInt64 res = (static_cast(_mm_movemask_epi8(_mm_cmpeq_epi8( _mm_loadu_si128(reinterpret_cast(bytes64)), zero16))) & 0xffff) From be54d0b954cdc974b43ab4e284647cfc9046566f Mon Sep 17 00:00:00 2001 From: Wangyang Guo Date: Mon, 25 Jul 2022 16:12:16 +0800 Subject: [PATCH 113/672] Columns: use AVX512BW vptestnmb to get mask --- src/Columns/ColumnsCommon.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Columns/ColumnsCommon.h b/src/Columns/ColumnsCommon.h index 20ec5872b93..8a1baa1430a 100644 --- a/src/Columns/ColumnsCommon.h +++ b/src/Columns/ColumnsCommon.h @@ -27,8 +27,8 @@ namespace ErrorCodes inline UInt64 bytes64MaskToBits64Mask(const UInt8 * bytes64) { #if defined(__AVX512F__) && defined(__AVX512BW__) - const __m512i zero64 = _mm512_setzero_epi32(); - UInt64 res = _mm512_cmp_epi8_mask(_mm512_loadu_si512(reinterpret_cast(bytes64)), zero64, _MM_CMPINT_EQ); + const __m512i vbytes = _mm512_loadu_si512(reinterpret_cast(bytes64)); + UInt64 res = _mm512_testn_epi8_mask(vbytes, vbytes); #elif defined(__AVX__) && defined(__AVX2__) const __m256i zero32 = _mm256_setzero_si256(); UInt64 res = From f135a82141b559a8ceb67584743886005211f3d7 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 25 Jul 2022 19:26:22 -0700 Subject: [PATCH 114/672] Addressed changes requested by @evillique (Restructured OwnJSONPatternFormatter, used different version of writeJSONString() etc... --- src/Daemon/BaseDaemon.cpp | 52 ++++++++-------- src/Loggers/Loggers.cpp | 62 ++++++------------ src/Loggers/OwnFormattingChannel.cpp | 13 ++-- src/Loggers/OwnJSONPatternFormatter.cpp | 83 ++++++++++++++----------- src/Loggers/OwnJSONPatternFormatter.h | 5 +- src/Loggers/OwnPatternFormatter.cpp | 10 +-- src/Loggers/OwnPatternFormatter.h | 1 + 7 files changed, 110 insertions(+), 116 deletions(-) diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index 95967bb2b82..81317aa3b6a 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -5,17 +5,17 @@ #include #include +#include #include #include #include #include -#include #if defined(OS_LINUX) - #include +# include #endif #include -#include #include +#include #include #include @@ -71,11 +71,11 @@ namespace fs = std::filesystem; namespace DB { - namespace ErrorCodes - { - extern const int CANNOT_SET_SIGNAL_HANDLER; - extern const int CANNOT_SEND_SIGNAL; - } +namespace ErrorCodes +{ + extern const int CANNOT_SET_SIGNAL_HANDLER; + extern const int CANNOT_SEND_SIGNAL; +} } DB::PipeFDs signal_pipe; @@ -367,8 +367,11 @@ private: String calculated_binary_hash = getHashOfLoadedBinaryHex(); if (daemon.stored_binary_hash.empty()) { - LOG_FATAL(log, "Integrity check of the executable skipped because the reference checksum could not be read." - " (calculated checksum: {})", calculated_binary_hash); + LOG_FATAL( + log, + "Integrity check of the executable skipped because the reference checksum could not be read." + " (calculated checksum: {})", + calculated_binary_hash); } else if (calculated_binary_hash == daemon.stored_binary_hash) { @@ -376,15 +379,18 @@ private: } else { - LOG_FATAL(log, "Calculated checksum of the executable ({0}) does not correspond" + LOG_FATAL( + log, + "Calculated checksum of the executable ({0}) does not correspond" " to the reference checksum stored in the executable ({1})." " This may indicate one of the following:" " - the executable was changed just after startup;" " - the executable was corrupted on disk due to faulty hardware;" " - the loaded executable was corrupted in memory due to faulty hardware;" " - the file was intentionally modified;" - " - a logical error in the code." - , calculated_binary_hash, daemon.stored_binary_hash); + " - a logical error in the code.", + calculated_binary_hash, + daemon.stored_binary_hash); } #endif @@ -1009,18 +1015,15 @@ void BaseDaemon::setupWatchdog() /// If streaming compression of logs is used then we write watchdog logs to cerr if (config().getRawString("logger.stream_compress", "false") == "true") { + Poco::AutoPtr pf; + if (config().has("logger.json")) - { - Poco::AutoPtr pf = new OwnJSONPatternFormatter; - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr)); - logger().setChannel(log); - } + pf = new OwnJSONPatternFormatter; else - { - Poco::AutoPtr pf = new OwnPatternFormatter; - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr)); - logger().setChannel(log); - } + pf = new OwnPatternFormatter(true); + + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr)); + logger().setChannel(log); } logger().information(fmt::format("Will watch for the process with pid {}", pid)); @@ -1028,8 +1031,7 @@ void BaseDaemon::setupWatchdog() /// Forward signals to the child process. addSignalHandler( {SIGHUP, SIGINT, SIGQUIT, SIGTERM}, - [](int sig, siginfo_t *, void *) - { + [](int sig, siginfo_t *, void *) { /// Forward all signals except INT as it can be send by terminal to the process group when user press Ctrl+C, /// and we process double delivery of this signal as immediate termination. if (sig == SIGINT) diff --git a/src/Loggers/Loggers.cpp b/src/Loggers/Loggers.cpp index fdcd75f761c..c02363c6017 100644 --- a/src/Loggers/Loggers.cpp +++ b/src/Loggers/Loggers.cpp @@ -96,22 +96,16 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log log_file->setProperty(Poco::FileChannel::PROP_ROTATEONOPEN, config.getRawString("logger.rotateOnOpen", "false")); log_file->open(); + Poco::AutoPtr pf; + if (config.has("logger.json")) - { - Poco::AutoPtr pf = new OwnJSONPatternFormatter; - - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, log_file); - log->setLevel(log_level); - split->addChannel(log, "log"); - } + pf = new OwnJSONPatternFormatter; else - { - Poco::AutoPtr pf = new OwnPatternFormatter; + pf = new OwnPatternFormatter(true); - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, log_file); - log->setLevel(log_level); - split->addChannel(log, "log"); - } + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, log_file); + log->setLevel(log_level); + split->addChannel(log, "log"); } const auto errorlog_path = config.getString("logger.errorlog", ""); @@ -143,24 +137,16 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log error_log_file->setProperty(Poco::FileChannel::PROP_FLUSH, config.getRawString("logger.flush", "true")); error_log_file->setProperty(Poco::FileChannel::PROP_ROTATEONOPEN, config.getRawString("logger.rotateOnOpen", "false")); + Poco::AutoPtr pf; if (config.has("logger.json")) - { - Poco::AutoPtr pf = new OwnJSONPatternFormatter; - - Poco::AutoPtr errorlog = new DB::OwnFormattingChannel(pf, error_log_file); - errorlog->setLevel(errorlog_level); - errorlog->open(); - split->addChannel(errorlog, "errorlog"); - } + pf = new OwnJSONPatternFormatter; else - { - Poco::AutoPtr pf = new OwnPatternFormatter; + pf = new OwnPatternFormatter(true); - Poco::AutoPtr errorlog = new DB::OwnFormattingChannel(pf, error_log_file); - errorlog->setLevel(errorlog_level); - errorlog->open(); - split->addChannel(errorlog, "errorlog"); - } + Poco::AutoPtr errorlog = new DB::OwnFormattingChannel(pf, error_log_file); + errorlog->setLevel(errorlog_level); + errorlog->open(); + split->addChannel(errorlog, "errorlog"); } if (config.getBool("logger.use_syslog", false)) @@ -193,25 +179,17 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log syslog_channel->setProperty(Poco::SyslogChannel::PROP_FACILITY, config.getString("logger.syslog.facility", "LOG_DAEMON")); } syslog_channel->open(); + Poco::AutoPtr pf; if (config.has("logger.json")) - { - Poco::AutoPtr pf = new OwnJSONPatternFormatter; - - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, syslog_channel); - log->setLevel(syslog_level); - - split->addChannel(log, "syslog"); - } + pf = new OwnJSONPatternFormatter; else - { - Poco::AutoPtr pf = new OwnPatternFormatter; + pf = new OwnPatternFormatter(true); - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, syslog_channel); - log->setLevel(syslog_level); + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, syslog_channel); + log->setLevel(syslog_level); - split->addChannel(log, "syslog"); - } + split->addChannel(log, "syslog"); } bool should_log_to_console = isatty(STDIN_FILENO) || isatty(STDERR_FILENO); diff --git a/src/Loggers/OwnFormattingChannel.cpp b/src/Loggers/OwnFormattingChannel.cpp index 35f035d44ce..a023c28c5ae 100644 --- a/src/Loggers/OwnFormattingChannel.cpp +++ b/src/Loggers/OwnFormattingChannel.cpp @@ -7,16 +7,13 @@ void OwnFormattingChannel::logExtended(const ExtendedLogMessage & msg) { if (pChannel && priority >= msg.base.getPriority()) { - if (pFormatterJSON) + if (pFormatterJSON || pFormatter) { std::string text; - pFormatterJSON->formatExtendedJSON(msg, text); - pChannel->log(Poco::Message(msg.base, text)); - } - else if (pFormatter) - { - std::string text; - pFormatter->formatExtended(msg, text); + if (pFormatterJSON) + pFormatterJSON->formatExtended(msg, text); + else + pFormatter->formatExtended(msg, text); pChannel->log(Poco::Message(msg.base, text)); } else diff --git a/src/Loggers/OwnJSONPatternFormatter.cpp b/src/Loggers/OwnJSONPatternFormatter.cpp index 825cb1e9806..63f2c60f70e 100644 --- a/src/Loggers/OwnJSONPatternFormatter.cpp +++ b/src/Loggers/OwnJSONPatternFormatter.cpp @@ -8,23 +8,24 @@ #include #include -OwnJSONPatternFormatter::OwnJSONPatternFormatter() : Poco::PatternFormatter("") +OwnJSONPatternFormatter::OwnJSONPatternFormatter() : OwnPatternFormatter("") { } -void OwnJSONPatternFormatter::formatExtendedJSON(const DB::ExtendedLogMessage & msg_ext, std::string & text) +void OwnJSONPatternFormatter::formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text) { DB::WriteBufferFromString wb(text); DB::FormatSettings settings; - String key_name; + char key_name[] = "a placeholder for key names in structured logging"; + char empty_string[] = ""; const Poco::Message & msg = msg_ext.base; DB::writeChar('{', wb); - key_name = "date_time"; - writeJSONString(StringRef(key_name), wb, settings); + strcpy(key_name, "date_time"); + writeJSONString(key_name, key_name + strlen(key_name), wb, settings); DB::writeChar(':', wb); DB::writeChar('\"', wb); @@ -41,15 +42,20 @@ void OwnJSONPatternFormatter::formatExtendedJSON(const DB::ExtendedLogMessage & DB::writeChar(',', wb); - key_name = "thread_name"; - writeJSONString(StringRef(key_name), wb, settings); + strcpy(key_name, "thread_name"); + writeJSONString(key_name, key_name + strlen(key_name), wb, settings); DB::writeChar(':', wb); - writeJSONString(StringRef(msg.getThread()), wb, settings); + + const char * thread_name = msg.getThread().c_str(); + if (thread_name != nullptr) + writeJSONString(thread_name, thread_name + strlen(thread_name), wb, settings); + else + writeJSONString(empty_string, empty_string + strlen(empty_string), wb, settings); DB::writeChar(',', wb); - key_name = "thread_id"; - writeJSONString(StringRef(key_name), wb, settings); + strcpy(key_name, "thread_id"); + writeJSONString(key_name, key_name + strlen(key_name), wb, settings); DB::writeChar(':', wb); DB::writeChar('\"', wb); DB::writeIntText(msg_ext.thread_id, wb); @@ -57,58 +63,65 @@ void OwnJSONPatternFormatter::formatExtendedJSON(const DB::ExtendedLogMessage & DB::writeChar(',', wb); - key_name = "level"; - writeJSONString(StringRef(key_name), wb, settings); + strcpy(key_name, "level"); + writeJSONString(key_name, key_name + strlen(key_name), wb, settings); DB::writeChar(':', wb); - int priority = static_cast(msg.getPriority()); - writeJSONString(StringRef(getPriorityName(priority)), wb, settings); + int priority_int = static_cast(msg.getPriority()); + String priority_str = std::to_string(priority_int); + const char * priority = priority_str.c_str(); + if (priority != nullptr) + writeJSONString(priority, priority + strlen(priority), wb, settings); + else + writeJSONString(empty_string, empty_string + strlen(empty_string), wb, settings); DB::writeChar(',', wb); /// We write query_id even in case when it is empty (no query context) /// just to be convenient for various log parsers. - key_name = "query_id"; - writeJSONString(StringRef(key_name), wb, settings); + strcpy(key_name, "query_id"); + writeJSONString(key_name, key_name + strlen(key_name), wb, settings); DB::writeChar(':', wb); writeJSONString(msg_ext.query_id, wb, settings); DB::writeChar(',', wb); - key_name = "logger_name"; - writeJSONString(StringRef(key_name), wb, settings); + strcpy(key_name, "logger_name"); + writeJSONString(key_name, key_name + strlen(key_name), wb, settings); DB::writeChar(':', wb); - writeJSONString(StringRef(msg.getSource()), wb, settings); + const char * logger_name = msg.getSource().c_str(); + if (logger_name != nullptr) + writeJSONString(logger_name, logger_name + strlen(logger_name), wb, settings); + else + writeJSONString(empty_string, empty_string + strlen(empty_string), wb, settings); DB::writeChar(',', wb); - key_name = "message"; - writeJSONString(StringRef(key_name), wb, settings); + strcpy(key_name, "message"); + writeJSONString(key_name, key_name + strlen(key_name), wb, settings); DB::writeChar(':', wb); - String msg_text = msg.getText(); - writeJSONString(StringRef(msg_text), wb, settings); + const char * msg_text = msg.getText().c_str(); + if (msg_text != nullptr) + writeJSONString(msg_text, msg_text + strlen(msg_text), wb, settings); + else + writeJSONString(empty_string, empty_string + strlen(empty_string), wb, settings); DB::writeChar(',', wb); - key_name = "source_file"; - writeJSONString(StringRef(key_name), wb, settings); + strcpy(key_name, "source_file"); + writeJSONString(key_name, key_name + strlen(key_name), wb, settings); DB::writeChar(':', wb); const char * source_file = msg.getSourceFile(); if (source_file != nullptr) - { - writeJSONString(StringRef(source_file), wb, settings); - } - + writeJSONString(source_file, source_file + strlen(source_file), wb, settings); else - { - writeJSONString(StringRef(""), wb, settings); - } + writeJSONString(empty_string, empty_string + strlen(empty_string), wb, settings); DB::writeChar(',', wb); - key_name = "source_line"; - writeJSONString(StringRef(key_name), wb, settings); + strcpy(key_name, "source_line"); + writeJSONString(key_name, key_name + strlen(key_name), wb, settings); DB::writeChar(':', wb); DB::writeChar('\"', wb); DB::writeIntText(msg.getSourceLine(), wb); @@ -119,5 +132,5 @@ void OwnJSONPatternFormatter::formatExtendedJSON(const DB::ExtendedLogMessage & void OwnJSONPatternFormatter::format(const Poco::Message & msg, std::string & text) { - formatExtendedJSON(DB::ExtendedLogMessage::getFrom(msg), text); + formatExtended(DB::ExtendedLogMessage::getFrom(msg), text); } diff --git a/src/Loggers/OwnJSONPatternFormatter.h b/src/Loggers/OwnJSONPatternFormatter.h index 0523869aebb..76a0104317e 100644 --- a/src/Loggers/OwnJSONPatternFormatter.h +++ b/src/Loggers/OwnJSONPatternFormatter.h @@ -3,6 +3,7 @@ #include #include "ExtendedLogChannel.h" +#include "OwnPatternFormatter.h" /** Format log messages own way in JSON. @@ -21,11 +22,11 @@ class Loggers; -class OwnJSONPatternFormatter : public Poco::PatternFormatter +class OwnJSONPatternFormatter : public OwnPatternFormatter { public: OwnJSONPatternFormatter(); void format(const Poco::Message & msg, std::string & text) override; - static void formatExtendedJSON(const DB::ExtendedLogMessage & msg_ext, std::string & text); + static void formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text); }; diff --git a/src/Loggers/OwnPatternFormatter.cpp b/src/Loggers/OwnPatternFormatter.cpp index 02a2c2e510b..f5ee60a2113 100644 --- a/src/Loggers/OwnPatternFormatter.cpp +++ b/src/Loggers/OwnPatternFormatter.cpp @@ -3,17 +3,19 @@ #include #include #include -#include #include -#include #include +#include +#include -OwnPatternFormatter::OwnPatternFormatter(bool color_) - : Poco::PatternFormatter(""), color(color_) +OwnPatternFormatter::OwnPatternFormatter(bool color_) : Poco::PatternFormatter(""), color(color_) { } +OwnPatternFormatter::OwnPatternFormatter() : Poco::PatternFormatter("") +{ +} void OwnPatternFormatter::formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text) const { diff --git a/src/Loggers/OwnPatternFormatter.h b/src/Loggers/OwnPatternFormatter.h index fba4f0964cb..154068f75fe 100644 --- a/src/Loggers/OwnPatternFormatter.h +++ b/src/Loggers/OwnPatternFormatter.h @@ -25,6 +25,7 @@ class OwnPatternFormatter : public Poco::PatternFormatter { public: OwnPatternFormatter(bool color_ = false); + OwnPatternFormatter(); void format(const Poco::Message & msg, std::string & text) override; void formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text) const; From 116618106574ed71eb9642e1506375fe34de922b Mon Sep 17 00:00:00 2001 From: root Date: Mon, 25 Jul 2022 19:59:04 -0700 Subject: [PATCH 115/672] removed unwanted changes from BaseDaemon.cpp --- src/Daemon/BaseDaemon.cpp | 235 +++++++++++++++++++------------------- 1 file changed, 117 insertions(+), 118 deletions(-) diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index 81317aa3b6a..af68399c5f4 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -1,69 +1,68 @@ #ifdef HAS_RESERVED_IDENTIFIER -# pragma clang diagnostic ignored "-Wreserved-identifier" +#pragma clang diagnostic ignored "-Wreserved-identifier" #endif #include #include -#include #include -#include #include +#include #include +#include #if defined(OS_LINUX) -# include + #include #endif #include -#include #include +#include #include -#include -#include -#include #include +#include +#include +#include #include -#include -#include #include #include +#include +#include -#include -#include -#include -#include #include +#include +#include +#include +#include -#include -#include -#include #include #include +#include +#include #include -#include -#include -#include #include #include #include +#include +#include +#include #include #include #include -#include -#include +#include #include +#include +#include #include -#include #include #include #if defined(OS_DARWIN) -# pragma GCC diagnostic ignored "-Wunused-macros" +# pragma GCC diagnostic ignored "-Wunused-macros" // NOLINTNEXTLINE(bugprone-reserved-identifier) -# define _XOPEN_SOURCE 700 // ucontext is not available without _XOPEN_SOURCE +# define _XOPEN_SOURCE 700 // ucontext is not available without _XOPEN_SOURCE #endif #include @@ -71,11 +70,11 @@ namespace fs = std::filesystem; namespace DB { -namespace ErrorCodes -{ - extern const int CANNOT_SET_SIGNAL_HANDLER; - extern const int CANNOT_SEND_SIGNAL; -} + namespace ErrorCodes + { + extern const int CANNOT_SET_SIGNAL_HANDLER; + extern const int CANNOT_SEND_SIGNAL; + } } DB::PipeFDs signal_pipe; @@ -93,14 +92,19 @@ static void call_default_signal_handler(int sig) DB::throwFromErrno("Cannot send signal.", DB::ErrorCodes::CANNOT_SEND_SIGNAL); } -static const size_t signal_pipe_buf_size - = sizeof(int) + sizeof(siginfo_t) + sizeof(ucontext_t *) + sizeof(StackTrace) + sizeof(UInt32) + sizeof(void *); +static const size_t signal_pipe_buf_size = + sizeof(int) + + sizeof(siginfo_t) + + sizeof(ucontext_t*) + + sizeof(StackTrace) + + sizeof(UInt32) + + sizeof(void*); -using signal_function = void(int, siginfo_t *, void *); +using signal_function = void(int, siginfo_t*, void*); static void writeSignalIDtoSignalPipe(int sig) { - auto saved_errno = errno; /// We must restore previous value of errno in signal handler. + auto saved_errno = errno; /// We must restore previous value of errno in signal handler. char buf[signal_pipe_buf_size]; DB::WriteBufferFromFileDescriptor out(signal_pipe.fds_rw[1], signal_pipe_buf_size, buf); @@ -129,7 +133,7 @@ static void terminateRequestedSignalHandler(int sig, siginfo_t *, void *) static void signalHandler(int sig, siginfo_t * info, void * context) { DENY_ALLOCATIONS_IN_SCOPE; - auto saved_errno = errno; /// We must restore previous value of errno in signal handler. + auto saved_errno = errno; /// We must restore previous value of errno in signal handler. char buf[signal_pipe_buf_size]; DB::WriteBufferFromFileDescriptorDiscardOnFailure out(signal_pipe.fds_rw[1], signal_pipe_buf_size, buf); @@ -149,7 +153,7 @@ static void signalHandler(int sig, siginfo_t * info, void * context) if (sig != SIGTSTP) /// This signal is used for debugging. { /// The time that is usually enough for separate thread to print info into log. - sleepForSeconds(20); /// FIXME: use some feedback from threads that process stacktrace + sleepForSeconds(20); /// FIXME: use some feedback from threads that process stacktrace call_default_signal_handler(sig); } @@ -158,7 +162,8 @@ static void signalHandler(int sig, siginfo_t * info, void * context) /// Avoid link time dependency on DB/Interpreters - will use this function only when linked. -__attribute__((__weak__)) void collectCrashLog(Int32 signal, UInt64 thread_id, const String & query_id, const StackTrace & stack_trace); +__attribute__((__weak__)) void collectCrashLog( + Int32 signal, UInt64 thread_id, const String & query_id, const StackTrace & stack_trace); /** The thread that read info about signal or std::terminate from pipe. @@ -176,14 +181,16 @@ public: SanitizerTrap = -3, }; - explicit SignalListener(BaseDaemon & daemon_) : log(&Poco::Logger::get("BaseDaemon")), daemon(daemon_) { } + explicit SignalListener(BaseDaemon & daemon_) + : log(&Poco::Logger::get("BaseDaemon")) + , daemon(daemon_) + { + } void run() override { static_assert(PIPE_BUF >= 512); - static_assert( - signal_pipe_buf_size <= PIPE_BUF, - "Only write of PIPE_BUF to pipe is atomic and the minimal known PIPE_BUF across supported platforms is 512"); + static_assert(signal_pipe_buf_size <= PIPE_BUF, "Only write of PIPE_BUF to pipe is atomic and the minimal known PIPE_BUF across supported platforms is 512"); char buf[signal_pipe_buf_size]; DB::ReadBufferFromFileDescriptor in(signal_pipe.fds_rw[0], signal_pipe_buf_size, buf); @@ -218,7 +225,9 @@ public: onTerminate(message, thread_num); } - else if (sig == SIGINT || sig == SIGQUIT || sig == SIGTERM) + else if (sig == SIGINT || + sig == SIGQUIT || + sig == SIGTERM) { daemon.handleSignal(sig); } @@ -255,14 +264,8 @@ private: { size_t pos = message.find('\n'); - LOG_FATAL( - log, - "(version {}{}, {}) (from thread {}) {}", - VERSION_STRING, - VERSION_OFFICIAL, - daemon.build_id_info, - thread_num, - message.substr(0, pos)); + LOG_FATAL(log, "(version {}{}, {}) (from thread {}) {}", + VERSION_STRING, VERSION_OFFICIAL, daemon.build_id_info, thread_num, message.substr(0, pos)); /// Print trace from std::terminate exception line-by-line to make it easy for grep. while (pos != std::string_view::npos) @@ -310,29 +313,15 @@ private: if (query_id.empty()) { - LOG_FATAL( - log, - "(version {}{}, {}) (from thread {}) (no query) Received signal {} ({})", - VERSION_STRING, - VERSION_OFFICIAL, - daemon.build_id_info, - thread_num, - strsignal(sig), - sig); + LOG_FATAL(log, "(version {}{}, {}) (from thread {}) (no query) Received signal {} ({})", + VERSION_STRING, VERSION_OFFICIAL, daemon.build_id_info, + thread_num, strsignal(sig), sig); } else { - LOG_FATAL( - log, - "(version {}{}, {}) (from thread {}) (query_id: {}) (query: {}) Received signal {} ({})", - VERSION_STRING, - VERSION_OFFICIAL, - daemon.build_id_info, - thread_num, - query_id, - query, - strsignal(sig), - sig); + LOG_FATAL(log, "(version {}{}, {}) (from thread {}) (query_id: {}) (query: {}) Received signal {} ({})", + VERSION_STRING, VERSION_OFFICIAL, daemon.build_id_info, + thread_num, query_id, query, strsignal(sig), sig); } String error_message; @@ -367,11 +356,8 @@ private: String calculated_binary_hash = getHashOfLoadedBinaryHex(); if (daemon.stored_binary_hash.empty()) { - LOG_FATAL( - log, - "Integrity check of the executable skipped because the reference checksum could not be read." - " (calculated checksum: {})", - calculated_binary_hash); + LOG_FATAL(log, "Integrity check of the executable skipped because the reference checksum could not be read." + " (calculated checksum: {})", calculated_binary_hash); } else if (calculated_binary_hash == daemon.stored_binary_hash) { @@ -379,18 +365,15 @@ private: } else { - LOG_FATAL( - log, - "Calculated checksum of the executable ({0}) does not correspond" + LOG_FATAL(log, "Calculated checksum of the executable ({0}) does not correspond" " to the reference checksum stored in the executable ({1})." " This may indicate one of the following:" " - the executable was changed just after startup;" " - the executable was corrupted on disk due to faulty hardware;" " - the loaded executable was corrupted in memory due to faulty hardware;" " - the file was intentionally modified;" - " - a logical error in the code.", - calculated_binary_hash, - daemon.stored_binary_hash); + " - a logical error in the code." + , calculated_binary_hash, daemon.stored_binary_hash); } #endif @@ -412,7 +395,12 @@ private: #if defined(SANITIZER) extern "C" void __sanitizer_set_death_callback(void (*)()); -static void sanitizerDeathCallback() +/// Sanitizers may not expect some function calls from death callback. +/// Let's try to disable instrumentation to avoid possible issues. +/// However, this callback may call other functions that are still instrumented. +/// We can try [[clang::always_inline]] attribute for statements in future (available in clang-15) +/// See https://github.com/google/sanitizers/issues/1543 and https://github.com/google/sanitizers/issues/1549. +static DISABLE_SANITIZER_INSTRUMENTATION void sanitizerDeathCallback() { DENY_ALLOCATIONS_IN_SCOPE; /// Also need to send data via pipe. Otherwise it may lead to deadlocks or failures in printing diagnostic info. @@ -620,9 +608,7 @@ void debugIncreaseOOMScore() LOG_INFO(&Poco::Logger::root(), "Set OOM score adjustment to {}", new_score); } #else -void debugIncreaseOOMScore() -{ -} +void debugIncreaseOOMScore() {} #endif } @@ -745,12 +731,14 @@ void BaseDaemon::initialize(Application & self) if (!log_path.empty()) { std::string path = createDirectory(log_path); - if (is_daemon && chdir(path.c_str()) != 0) + if (is_daemon + && chdir(path.c_str()) != 0) throw Poco::Exception("Cannot change directory to " + path); } else { - if (is_daemon && chdir("/tmp") != 0) + if (is_daemon + && chdir("/tmp") != 0) throw Poco::Exception("Cannot change directory to /tmp"); } @@ -897,40 +885,50 @@ void BaseDaemon::initializeTerminationAndSignalProcessing() void BaseDaemon::logRevision() const { - Poco::Logger::root().information( - "Starting " + std::string{VERSION_FULL} + " with revision " + std::to_string(ClickHouseRevision::getVersionRevision()) + ", " - + build_id_info + ", PID " + std::to_string(getpid())); + Poco::Logger::root().information("Starting " + std::string{VERSION_FULL} + + " with revision " + std::to_string(ClickHouseRevision::getVersionRevision()) + + ", " + build_id_info + + ", PID " + std::to_string(getpid())); } void BaseDaemon::defineOptions(Poco::Util::OptionSet & new_options) { - new_options.addOption(Poco::Util::Option("config-file", "C", "load configuration from a given file") - .required(false) - .repeatable(false) - .argument("") - .binding("config-file")); - - new_options.addOption(Poco::Util::Option("log-file", "L", "use given log file") - .required(false) - .repeatable(false) - .argument("") - .binding("logger.log")); - - new_options.addOption(Poco::Util::Option("errorlog-file", "E", "use given log file for errors only") - .required(false) - .repeatable(false) - .argument("") - .binding("logger.errorlog")); + new_options.addOption( + Poco::Util::Option("config-file", "C", "load configuration from a given file") + .required(false) + .repeatable(false) + .argument("") + .binding("config-file")); new_options.addOption( - Poco::Util::Option("pid-file", "P", "use given pidfile").required(false).repeatable(false).argument("").binding("pid")); + Poco::Util::Option("log-file", "L", "use given log file") + .required(false) + .repeatable(false) + .argument("") + .binding("logger.log")); + + new_options.addOption( + Poco::Util::Option("errorlog-file", "E", "use given log file for errors only") + .required(false) + .repeatable(false) + .argument("") + .binding("logger.errorlog")); + + new_options.addOption( + Poco::Util::Option("pid-file", "P", "use given pidfile") + .required(false) + .repeatable(false) + .argument("") + .binding("pid")); Poco::Util::ServerApplication::defineOptions(new_options); } void BaseDaemon::handleSignal(int signal_id) { - if (signal_id == SIGINT || signal_id == SIGQUIT || signal_id == SIGTERM) + if (signal_id == SIGINT || + signal_id == SIGQUIT || + signal_id == SIGTERM) { std::lock_guard lock(signal_handler_mutex); { @@ -964,7 +962,7 @@ void BaseDaemon::waitForTerminationRequest() { /// NOTE: as we already process signals via pipe, we don't have to block them with sigprocmask in threads std::unique_lock lock(signal_handler_mutex); - signal_event.wait(lock, [this]() { return terminate_signals_counter > 0; }); + signal_event.wait(lock, [this](){ return terminate_signals_counter > 0; }); } @@ -1003,7 +1001,7 @@ void BaseDaemon::setupWatchdog() } /// Change short thread name and process name. - setThreadName("clckhouse-watch"); /// 15 characters + setThreadName("clckhouse-watch"); /// 15 characters if (argv0) { @@ -1016,12 +1014,14 @@ void BaseDaemon::setupWatchdog() if (config().getRawString("logger.stream_compress", "false") == "true") { Poco::AutoPtr pf; - if (config().has("logger.json")) + { pf = new OwnJSONPatternFormatter; + } else + { pf = new OwnPatternFormatter(true); - + } Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr)); logger().setChannel(log); } @@ -1031,7 +1031,8 @@ void BaseDaemon::setupWatchdog() /// Forward signals to the child process. addSignalHandler( {SIGHUP, SIGINT, SIGQUIT, SIGTERM}, - [](int sig, siginfo_t *, void *) { + [](int sig, siginfo_t *, void *) + { /// Forward all signals except INT as it can be send by terminal to the process group when user press Ctrl+C, /// and we process double delivery of this signal as immediate termination. if (sig == SIGINT) @@ -1080,11 +1081,9 @@ void BaseDaemon::setupWatchdog() if (sig == SIGKILL) { - logger().fatal(fmt::format( - "Child process was terminated by signal {} (KILL)." + logger().fatal(fmt::format("Child process was terminated by signal {} (KILL)." " If it is not done by 'forcestop' command or manually," - " the possible cause is OOM Killer (see 'dmesg' and look at the '/var/log/kern.log' for the details).", - sig)); + " the possible cause is OOM Killer (see 'dmesg' and look at the '/var/log/kern.log' for the details).", sig)); } else { From 943affe2da8f4ad55c4de926c699a74b6a42353c Mon Sep 17 00:00:00 2001 From: Mallik Hassan Date: Tue, 26 Jul 2022 03:43:04 -0300 Subject: [PATCH 116/672] Update src/Daemon/BaseDaemon.cpp Co-authored-by: Nikolay Degterinsky <43110995+evillique@users.noreply.github.com> --- src/Daemon/BaseDaemon.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index af68399c5f4..fc6539a739f 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -1015,13 +1015,10 @@ void BaseDaemon::setupWatchdog() { Poco::AutoPtr pf; if (config().has("logger.json")) - { pf = new OwnJSONPatternFormatter; - } else - { pf = new OwnPatternFormatter(true); - } + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr)); logger().setChannel(log); } From 968e867a0d884df5f3ccd5b07846caae9b087a85 Mon Sep 17 00:00:00 2001 From: Mallik Hassan Date: Tue, 26 Jul 2022 03:44:16 -0300 Subject: [PATCH 117/672] Update src/Loggers/Loggers.cpp Co-authored-by: Nikolay Degterinsky <43110995+evillique@users.noreply.github.com> --- src/Loggers/Loggers.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Loggers/Loggers.cpp b/src/Loggers/Loggers.cpp index c02363c6017..fc34c978374 100644 --- a/src/Loggers/Loggers.cpp +++ b/src/Loggers/Loggers.cpp @@ -204,20 +204,16 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log { max_log_level = console_log_level; } + + Poco::AutoPtr pf = new OwnPatternFormatter; if (config.has("logger.json")) - { - Poco::AutoPtr pf = new OwnJSONPatternFormatter(); - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel); - log->setLevel(console_log_level); - split->addChannel(log, "console"); - } + pf = new OwnJSONPatternFormatter; else - { - Poco::AutoPtr pf = new OwnPatternFormatter(color_enabled); - Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel); - log->setLevel(console_log_level); - split->addChannel(log, "console"); - } + pf = new OwnPatternFormatter(true); + + Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel); + log->setLevel(console_log_level); + split->addChannel(log, "console"); } From 9607b21e731e45489b1674f90bb5edcb47c96d37 Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Tue, 26 Jul 2022 09:00:46 +0000 Subject: [PATCH 118/672] Fix integration test test_timezone_config --- tests/integration/test_timezone_config/test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_timezone_config/test.py b/tests/integration/test_timezone_config/test.py index e4a9f75abab..180026c5818 100644 --- a/tests/integration/test_timezone_config/test.py +++ b/tests/integration/test_timezone_config/test.py @@ -26,9 +26,9 @@ def test_overflow_toDate(start_cluster): def test_overflow_toDate32(start_cluster): - assert node.query("SELECT toDate32('2999-12-31','UTC')") == "2283-11-11\n" + assert node.query("SELECT toDate32('2999-12-31','UTC')") == "2299-12-31\n" assert node.query("SELECT toDate32('2021-12-21','UTC')") == "2021-12-21\n" - assert node.query("SELECT toDate32('1000-12-31','UTC')") == "1925-01-01\n" + assert node.query("SELECT toDate32('1000-12-31','UTC')") == "1900-01-01\n" def test_overflow_toDateTime(start_cluster): From ac2a42cfb0f68252d0fb2fcf8b17ec70949c5566 Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Tue, 26 Jul 2022 10:44:58 +0000 Subject: [PATCH 119/672] Fix test 00189_time_zones_long and integration test test_materialized_mysql_database --- .../materialize_with_ddl.py | 6 +++--- tests/queries/0_stateless/00189_time_zones_long.reference | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_materialized_mysql_database/materialize_with_ddl.py b/tests/integration/test_materialized_mysql_database/materialize_with_ddl.py index d7ea0c13a93..22d4633685e 100644 --- a/tests/integration/test_materialized_mysql_database/materialize_with_ddl.py +++ b/tests/integration/test_materialized_mysql_database/materialize_with_ddl.py @@ -2126,9 +2126,9 @@ def materialized_database_mysql_date_type_to_date32( mysql_node.query( "CREATE TABLE test_database.a (a INT(11) NOT NULL PRIMARY KEY, b date DEFAULT NULL)" ) - # can't support date that less than 1925 year for now - mysql_node.query("INSERT INTO test_database.a VALUES(1, '1900-04-16')") - # test date that is older than 1925 + # can't support date that less than 1900 year for now + mysql_node.query("INSERT INTO test_database.a VALUES(1, '1899-04-16')") + # test date that is older than 1900 mysql_node.query("INSERT INTO test_database.a VALUES(3, '1971-02-16')") mysql_node.query("INSERT INTO test_database.a VALUES(4, '2101-05-16')") diff --git a/tests/queries/0_stateless/00189_time_zones_long.reference b/tests/queries/0_stateless/00189_time_zones_long.reference index e53ec7ca815..8717a662771 100644 --- a/tests/queries/0_stateless/00189_time_zones_long.reference +++ b/tests/queries/0_stateless/00189_time_zones_long.reference @@ -137,7 +137,7 @@ toStartOfInterval 2015-01-01 2019-01-01 2019-01-01 -2018-10-01 +2018-07-01 2019-02-01 2019-01-01 2018-10-01 @@ -164,7 +164,7 @@ toStartOfInterval 2015-01-01 2019-01-01 2019-01-01 -2018-10-01 +2018-07-01 2019-02-01 2019-01-01 2018-10-01 From c05c32e8235ea483f053d61d2d18911a5a1fa781 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy <99031427+yakov-olkhovskiy@users.noreply.github.com> Date: Tue, 26 Jul 2022 08:41:36 -0400 Subject: [PATCH 120/672] copy self-extracting to output --- docker/packager/binary/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/packager/binary/build.sh b/docker/packager/binary/build.sh index 87f98df2ad8..c28eaaa5b54 100755 --- a/docker/packager/binary/build.sh +++ b/docker/packager/binary/build.sh @@ -104,6 +104,7 @@ if [ -n "$MAKE_DEB" ]; then fi mv ./programs/clickhouse* /output +mkdir /output/self-extracting && mv ./programs/self-extracting/clickhouse /output/self-extracting ||: mv ./src/unit_tests_dbms /output ||: # may not exist for some binary builds find . -name '*.so' -print -exec mv '{}' /output \; find . -name '*.so.*' -print -exec mv '{}' /output \; From 0720936c2d461ae75f6189cbf68be7170eeaac74 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy <99031427+yakov-olkhovskiy@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:16:31 -0400 Subject: [PATCH 121/672] rewrite original clickhouse with compressed one --- docker/packager/binary/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/packager/binary/build.sh b/docker/packager/binary/build.sh index c28eaaa5b54..e9d91fb9733 100755 --- a/docker/packager/binary/build.sh +++ b/docker/packager/binary/build.sh @@ -104,7 +104,7 @@ if [ -n "$MAKE_DEB" ]; then fi mv ./programs/clickhouse* /output -mkdir /output/self-extracting && mv ./programs/self-extracting/clickhouse /output/self-extracting ||: +mv ./programs/self-extracting/clickhouse /output ||: mv ./src/unit_tests_dbms /output ||: # may not exist for some binary builds find . -name '*.so' -print -exec mv '{}' /output \; find . -name '*.so.*' -print -exec mv '{}' /output \; From f7dcf23404dd777e166da58685b53232c98ed713 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 26 Jul 2022 06:41:52 -0700 Subject: [PATCH 122/672] removed pFormattterJSON pointer from OwnFormattingChannel and reformatted Loggers and BaseDaemon to look nicer --- src/Daemon/BaseDaemon.cpp | 3 +++ src/Loggers/Loggers.cpp | 28 ++++++++++++++++------------ src/Loggers/OwnFormattingChannel.cpp | 14 ++++++++------ src/Loggers/OwnFormattingChannel.h | 7 ------- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index fc6539a739f..2a3b24b6999 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -1018,7 +1018,10 @@ void BaseDaemon::setupWatchdog() pf = new OwnJSONPatternFormatter; else pf = new OwnPatternFormatter(true); +<<<<<<< HEAD +======= +>>>>>>> removed pFormattterJSON pointer from OwnFormattingChannel and reformatted Loggers and BaseDaemon to look nicer Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr)); logger().setChannel(log); } diff --git a/src/Loggers/Loggers.cpp b/src/Loggers/Loggers.cpp index fc34c978374..6f8c88a7e87 100644 --- a/src/Loggers/Loggers.cpp +++ b/src/Loggers/Loggers.cpp @@ -1,18 +1,17 @@ #include "Loggers.h" #include -#include -#include -#include #include #include #include "OwnFormattingChannel.h" -#include "OwnJSONPatternFormatter.h" #include "OwnPatternFormatter.h" #include "OwnSplitChannel.h" +#include +#include +#include #ifdef WITH_TEXT_LOG -# include + #include #endif #include @@ -21,9 +20,10 @@ namespace fs = std::filesystem; namespace DB { -class SensitiveDataMasker; + class SensitiveDataMasker; } + // TODO: move to libcommon static std::string createDirectory(const std::string & file) { @@ -49,6 +49,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log if (auto log = text_log.lock()) split->addTextLog(log, text_log_max_priority); #endif + auto current_logger = config.getString("logger", ""); if (config_logger == current_logger) //-V1051 return; @@ -138,6 +139,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log error_log_file->setProperty(Poco::FileChannel::PROP_ROTATEONOPEN, config.getRawString("logger.rotateOnOpen", "false")); Poco::AutoPtr pf; + if (config.has("logger.json")) pf = new OwnJSONPatternFormatter; else @@ -179,6 +181,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log syslog_channel->setProperty(Poco::SyslogChannel::PROP_FACILITY, config.getString("logger.syslog.facility", "LOG_DAEMON")); } syslog_channel->open(); + Poco::AutoPtr pf; if (config.has("logger.json")) @@ -194,7 +197,9 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log bool should_log_to_console = isatty(STDIN_FILENO) || isatty(STDERR_FILENO); bool color_logs_by_default = isatty(STDERR_FILENO); - if (config.getBool("logger.console", false) || (!config.hasProperty("logger.console") && !is_daemon && should_log_to_console)) + + if (config.getBool("logger.console", false) + || (!config.hasProperty("logger.console") && !is_daemon && should_log_to_console)) { bool color_enabled = config.getBool("logger.color_terminal", color_logs_by_default); @@ -205,18 +210,16 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log max_log_level = console_log_level; } - Poco::AutoPtr pf = new OwnPatternFormatter; + Poco::AutoPtr pf; if (config.has("logger.json")) pf = new OwnJSONPatternFormatter; else - pf = new OwnPatternFormatter(true); - + pf = new OwnPatternFormatter(color_enabled); Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel); log->setLevel(console_log_level); split->addChannel(log, "console"); } - split->open(); logger.close(); logger.setChannel(split); @@ -276,7 +279,8 @@ void Loggers::updateLevels(Poco::Util::AbstractConfiguration & config, Poco::Log // Set level to console bool is_daemon = config.getBool("application.runAsDaemon", false); bool should_log_to_console = isatty(STDIN_FILENO) || isatty(STDERR_FILENO); - if (config.getBool("logger.console", false) || (!config.hasProperty("logger.console") && !is_daemon && should_log_to_console)) + if (config.getBool("logger.console", false) + || (!config.hasProperty("logger.console") && !is_daemon && should_log_to_console)) split->setLevel("console", log_level); else split->setLevel("console", 0); diff --git a/src/Loggers/OwnFormattingChannel.cpp b/src/Loggers/OwnFormattingChannel.cpp index a023c28c5ae..d101f0fcbd7 100644 --- a/src/Loggers/OwnFormattingChannel.cpp +++ b/src/Loggers/OwnFormattingChannel.cpp @@ -7,13 +7,15 @@ void OwnFormattingChannel::logExtended(const ExtendedLogMessage & msg) { if (pChannel && priority >= msg.base.getPriority()) { - if (pFormatterJSON || pFormatter) + std::string text; + if (auto * formatter = dynamic_cast(pFormatter.get())) { - std::string text; - if (pFormatterJSON) - pFormatterJSON->formatExtended(msg, text); - else - pFormatter->formatExtended(msg, text); + formatter->formatExtended(msg, text); + pChannel->log(Poco::Message(msg.base, text)); + } + else if(pFormatter) + { + pFormatter->formatExtended(msg, text); pChannel->log(Poco::Message(msg.base, text)); } else diff --git a/src/Loggers/OwnFormattingChannel.h b/src/Loggers/OwnFormattingChannel.h index 12e8b24192d..f8e482f51ba 100644 --- a/src/Loggers/OwnFormattingChannel.h +++ b/src/Loggers/OwnFormattingChannel.h @@ -20,12 +20,6 @@ public: { } - explicit OwnFormattingChannel( - Poco::AutoPtr pFormatterJSON_ = nullptr, Poco::AutoPtr pChannel_ = nullptr) - : pFormatterJSON(std::move(pFormatterJSON_)), pChannel(std::move(pChannel_)), priority(Poco::Message::PRIO_TRACE) - { - } - void setChannel(Poco::AutoPtr pChannel_) { pChannel = std::move(pChannel_); } void setLevel(Poco::Message::Priority priority_) { priority = priority_; } @@ -52,7 +46,6 @@ public: private: Poco::AutoPtr pFormatter; - Poco::AutoPtr pFormatterJSON; Poco::AutoPtr pChannel; std::atomic priority; }; From 0e68ac4e4db680101b8b4f6e03cfd92b7cb3b45b Mon Sep 17 00:00:00 2001 From: Mallik Hassan Date: Tue, 26 Jul 2022 11:34:17 -0300 Subject: [PATCH 123/672] Update BaseDaemon.cpp removed conflict markers from BaseDaemon.cpp --- src/Daemon/BaseDaemon.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index 2a3b24b6999..e162360ddaa 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -1018,10 +1018,6 @@ void BaseDaemon::setupWatchdog() pf = new OwnJSONPatternFormatter; else pf = new OwnPatternFormatter(true); -<<<<<<< HEAD - -======= ->>>>>>> removed pFormattterJSON pointer from OwnFormattingChannel and reformatted Loggers and BaseDaemon to look nicer Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr)); logger().setChannel(log); } From c5110bb1640ea22bae24c481d721c70c07f92ee8 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 26 Jul 2022 14:43:05 +0000 Subject: [PATCH 124/672] Add a query plan step for StorageMerge --- src/Storages/StorageMerge.cpp | 69 ++++++++++++++++++++++++++++------- src/Storages/StorageMerge.h | 21 ++++++++--- 2 files changed, 71 insertions(+), 19 deletions(-) diff --git a/src/Storages/StorageMerge.cpp b/src/Storages/StorageMerge.cpp index 500e35d062a..286499a4e78 100644 --- a/src/Storages/StorageMerge.cpp +++ b/src/Storages/StorageMerge.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,8 @@ #include +using std::operator""sv; + namespace DB { @@ -251,10 +254,49 @@ void StorageMerge::read( /** Just in case, turn off optimization "transfer to PREWHERE", * since there is no certainty that it works when one of table is MergeTree and other is not. */ - auto modified_context = Context::createCopy(context); + auto modified_context = Context::createCopy(local_context); modified_context->setSetting("optimize_move_to_prewhere", false); query_plan.addInterpreterContext(modified_context); + + /// What will be result structure depending on query processed stage in source tables? + Block common_header = getHeaderForProcessingStage(column_names, storage_snapshot, query_info, local_context, processed_stage); + + auto step = std::make_unique( + common_header, + column_names, + max_block_size, + num_streams, + shared_from_this(), + storage_snapshot, + query_info, + std::move(modified_context), + processed_stage); + + query_plan.addStep(std::move(step)); +} + +ReadFromMerge::ReadFromMerge( + Block common_header_, + Names column_names_, + size_t max_block_size, + size_t num_streams, + StoragePtr storage, + StorageSnapshotPtr storage_snapshot, + const SelectQueryInfo & query_info_, + ContextMutablePtr context_, + QueryProcessingStage::Enum processed_stage) + : ISourceStep(DataStream{.header = common_header_ /*addVirtualColumns(common_header_, column_names_, *storage, *storage_snapshot)*/}) + , required_max_block_size(max_block_size) + , requested_num_streams(num_streams) + , common_header(std::move(common_header_)) + , column_names(std::move(column_names_)) + , storage_merge(std::move(storage)) + , merge_storage_snapshot(std::move(storage_snapshot)) + , query_info(query_info_) + , context(std::move(context_)) + , common_processed_stage(processed_stage) +{ } void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const BuildQueryPipelineSettings &) @@ -276,22 +318,15 @@ void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const Bu real_column_names.push_back(column_name); } - /// What will be result structure depending on query processed stage in source tables? - Block header = getHeaderForProcessingStage(column_names, merge_storage_snapshot, query_info, context, common_processed_stage); - /** First we make list of selected tables to find out its size. * This is necessary to correctly pass the recommended number of threads to each table. */ StorageListWithLocks selected_tables - = storage_merge->getSelectedTables(context, query_info.query, has_database_virtual_column, has_table_virtual_column); + = storage_merge->as().getSelectedTables(context, query_info.query, has_database_virtual_column, has_table_virtual_column); if (selected_tables.empty()) { - pipeline = InterpreterSelectQuery( - query_info.query, context, - Pipe(std::make_shared(header)), - SelectQueryOptions(common_processed_stage).analyze()).buildQueryPipeline(); - + pipeline.init(Pipe(std::make_shared(output_stream->header))); return; } @@ -323,7 +358,7 @@ void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const Bu query_info.input_order_info = input_sorting_info; } - auto sample_block = merge_metadata_for_reading->getSampleBlock(); + auto sample_block = merge_storage_snapshot->getMetadataForQuery()->getSampleBlock(); for (const auto & table : selected_tables) { @@ -394,7 +429,7 @@ void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const Bu modified_query_info, common_processed_stage, required_max_block_size, - header, + common_header, aliases, table, column_names_as_aliases.empty() ? real_column_names : column_names_as_aliases, @@ -403,7 +438,7 @@ void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const Bu has_database_virtual_column, has_table_virtual_column); - if (!source_pipeline->initialized()) + if (source_pipeline->initialized()) { resources.storage_holders.push_back(std::get<1>(table)); resources.table_locks.push_back(std::get<2>(table)); @@ -412,6 +447,12 @@ void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const Bu } } + if (pipelines.empty()) + { + pipeline.init(Pipe(std::make_shared(output_stream->header))); + return; + } + pipeline = QueryPipelineBuilder::unitePipelines(std::move(pipelines)); if (!query_info.input_order_info) @@ -480,7 +521,7 @@ QueryPipelineBuilderPtr ReadFromMerge::createSources( if (!plan.isInitialized()) return {}; - return plan.buildQueryPipeline( + builder = plan.buildQueryPipeline( QueryPlanOptimizationSettings::fromContext(modified_context), BuildQueryPipelineSettings::fromContext(modified_context)); } diff --git a/src/Storages/StorageMerge.h b/src/Storages/StorageMerge.h index 440aa01890d..243c20a0406 100644 --- a/src/Storages/StorageMerge.h +++ b/src/Storages/StorageMerge.h @@ -118,6 +118,17 @@ public: static constexpr auto name = "ReadFromMerge"; String getName() const override { return name; } + ReadFromMerge( + Block common_header_, + Names column_names_, + size_t max_block_size, + size_t num_streams, + StoragePtr storage, + StorageSnapshotPtr storage_snapshot, + const SelectQueryInfo & query_info_, + ContextMutablePtr context_, + QueryProcessingStage::Enum processed_stage); + void initializePipeline(QueryPipelineBuilder & pipeline, const BuildQueryPipelineSettings &) override; using StorageWithLockAndName = std::tuple; @@ -125,17 +136,17 @@ public: using DatabaseTablesIterators = std::vector; private: - const size_t requested_num_streams; const size_t required_max_block_size; + const size_t requested_num_streams; + const Block common_header; Names column_names; - std::shared_ptr storage_merge; - QueryProcessingStage::Enum common_processed_stage; + StoragePtr storage_merge; + StorageSnapshotPtr merge_storage_snapshot; SelectQueryInfo query_info; - StorageSnapshotPtr merge_storage_snapshot; - StorageMetadataPtr merge_metadata_for_reading; ContextMutablePtr context; + QueryProcessingStage::Enum common_processed_stage; struct AliasData { From d7cc3831ea94e84386bf115e21f6cd780b978569 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 26 Jul 2022 15:01:39 +0000 Subject: [PATCH 125/672] Push predicate over StorageMerge. --- .../Optimizations/optimizePrimaryKeyCondition.cpp | 3 +++ src/Processors/QueryPlan/QueryPlan.h | 2 ++ src/Storages/StorageMerge.cpp | 4 ++++ src/Storages/StorageMerge.h | 9 +++++++++ .../queries/0_stateless/00160_merge_and_index_in_in.sql | 1 - .../01783_merge_engine_join_key_condition.sql | 1 - 6 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp b/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp index f9abe662006..e559c23bbaf 100644 --- a/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp +++ b/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,8 @@ void optimizePrimaryKeyCondition(QueryPlan::Node & root) if (auto * read_from_merge_tree = typeid_cast(child->step.get())) read_from_merge_tree->addFilter(filter_step->getExpression(), filter_step->getFilterColumnName()); + if (auto * read_from_merge = typeid_cast(child->step.get())) + read_from_merge->addFilter(filter_step->getExpression(), filter_step->getFilterColumnName()); } stack.pop(); diff --git a/src/Processors/QueryPlan/QueryPlan.h b/src/Processors/QueryPlan/QueryPlan.h index ce12ce7beda..e9b08c20280 100644 --- a/src/Processors/QueryPlan/QueryPlan.h +++ b/src/Processors/QueryPlan/QueryPlan.h @@ -103,6 +103,8 @@ public: std::vector children = {}; }; + const Node * getRootNode() const { return root; } + using Nodes = std::list; private: diff --git a/src/Storages/StorageMerge.cpp b/src/Storages/StorageMerge.cpp index 286499a4e78..5f7cd776d11 100644 --- a/src/Storages/StorageMerge.cpp +++ b/src/Storages/StorageMerge.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -521,6 +522,9 @@ QueryPipelineBuilderPtr ReadFromMerge::createSources( if (!plan.isInitialized()) return {}; + if (auto * read_from_merge_tree = typeid_cast(plan.getRootNode()->step.get())) + read_from_merge_tree->addFilter(added_filter, added_filter_column_name); + builder = plan.buildQueryPipeline( QueryPlanOptimizationSettings::fromContext(modified_context), BuildQueryPipelineSettings::fromContext(modified_context)); diff --git a/src/Storages/StorageMerge.h b/src/Storages/StorageMerge.h index 243c20a0406..74f5628bb24 100644 --- a/src/Storages/StorageMerge.h +++ b/src/Storages/StorageMerge.h @@ -131,6 +131,12 @@ public: void initializePipeline(QueryPipelineBuilder & pipeline, const BuildQueryPipelineSettings &) override; + void addFilter(ActionsDAGPtr expression, std::string column_name) + { + added_filter = std::move(expression); + added_filter_column_name = std::move(column_name); + } + using StorageWithLockAndName = std::tuple; using StorageListWithLocks = std::list; using DatabaseTablesIterators = std::vector; @@ -148,6 +154,9 @@ private: ContextMutablePtr context; QueryProcessingStage::Enum common_processed_stage; + ActionsDAGPtr added_filter; + std::string added_filter_column_name; + struct AliasData { String name; diff --git a/tests/queries/0_stateless/00160_merge_and_index_in_in.sql b/tests/queries/0_stateless/00160_merge_and_index_in_in.sql index 3ed829c4d59..bdab3f7640d 100644 --- a/tests/queries/0_stateless/00160_merge_and_index_in_in.sql +++ b/tests/queries/0_stateless/00160_merge_and_index_in_in.sql @@ -9,7 +9,6 @@ SET max_block_size = 1000000; INSERT INTO mt_00160 (x) SELECT number AS x FROM system.numbers LIMIT 100000; SELECT *, b FROM mt_00160 WHERE x IN (12345, 67890) AND NOT ignore(blockSize() < 10 AS b) ORDER BY x; -SET query_plan_optimize_primary_key = 0; -- Need separate query plan step for merge SELECT *, b FROM merge_00160 WHERE x IN (12345, 67890) AND NOT ignore(blockSize() < 10 AS b) ORDER BY x; DROP TABLE merge_00160; diff --git a/tests/queries/0_stateless/01783_merge_engine_join_key_condition.sql b/tests/queries/0_stateless/01783_merge_engine_join_key_condition.sql index 606597850ab..372c1bd3572 100644 --- a/tests/queries/0_stateless/01783_merge_engine_join_key_condition.sql +++ b/tests/queries/0_stateless/01783_merge_engine_join_key_condition.sql @@ -11,7 +11,6 @@ CREATE TABLE t2 (Id Int32, Val Int32, X Int32) Engine=Memory; INSERT INTO t2 values (4, 3, 4); SET force_primary_key = 1, force_index_by_date=1; -SET query_plan_optimize_primary_key = 0; SELECT * FROM foo_merge WHERE Val = 3 AND Id = 3; SELECT count(), X FROM foo_merge JOIN t2 USING Val WHERE Val = 3 AND Id = 3 AND t2.X == 4 GROUP BY X; From 6406bd998a77eec6f6412f4a52eb84e09a3d733c Mon Sep 17 00:00:00 2001 From: root Date: Tue, 26 Jul 2022 08:01:59 -0700 Subject: [PATCH 126/672] style check correction in OwnFormattingChannel --- src/Loggers/OwnFormattingChannel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Loggers/OwnFormattingChannel.cpp b/src/Loggers/OwnFormattingChannel.cpp index d101f0fcbd7..1487c5ed03b 100644 --- a/src/Loggers/OwnFormattingChannel.cpp +++ b/src/Loggers/OwnFormattingChannel.cpp @@ -13,7 +13,7 @@ void OwnFormattingChannel::logExtended(const ExtendedLogMessage & msg) formatter->formatExtended(msg, text); pChannel->log(Poco::Message(msg.base, text)); } - else if(pFormatter) + else if (pFormatter) { pFormatter->formatExtended(msg, text); pChannel->log(Poco::Message(msg.base, text)); From 9321ca34cf317193cc872403e04c9ebfc9dc3e32 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 26 Jul 2022 17:31:56 +0000 Subject: [PATCH 127/672] try to fix Nested --- .../Serializations/ISerialization.cpp | 6 +++++- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 7 +------ src/Storages/MergeTree/IMergeTreeDataPart.h | 1 + src/Storages/MergeTree/IMergeTreeReader.h | 6 ++---- .../MergeTree/MergeTreeDataPartWide.cpp | 4 ++-- src/Storages/MergeTree/MergeTreeReaderWide.cpp | 18 ++++++++++++++++-- src/Storages/MergeTree/MergeTreeReaderWide.h | 2 ++ 7 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/DataTypes/Serializations/ISerialization.cpp b/src/DataTypes/Serializations/ISerialization.cpp index 7df4a956c1a..d4acb877e87 100644 --- a/src/DataTypes/Serializations/ISerialization.cpp +++ b/src/DataTypes/Serializations/ISerialization.cpp @@ -224,7 +224,11 @@ String ISerialization::getSubcolumnNameForStream(const SubstreamPath & path, siz void ISerialization::addToSubstreamsCache(SubstreamsCache * cache, const SubstreamPath & path, ColumnPtr column) { - if (cache && !path.empty()) + if (!cache || path.empty()) + return; + + auto subcolumn_name = getSubcolumnNameForStream(path); + if (!subcolumn_name.empty()) cache->emplace(getSubcolumnNameForStream(path), column); } diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 45e5d9d92de..d4d3844de6c 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -448,12 +448,7 @@ void IMergeTreeDataPart::setColumns(const NamesAndTypesList & new_columns) for (const auto & column : columns) column_name_to_position.emplace(column.name, pos++); - /// For wide parts convert plain arrays to Nested for - /// more convinient managing of shared offsets column. - if (part_type == Type::Wide) - columns_description = ColumnsDescription(Nested::collect(columns)); - else - columns_description = ColumnsDescription(columns); + columns_description = ColumnsDescription(columns); } NameAndTypePair IMergeTreeDataPart::getColumn(const String & column_name) const diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index b6e8b75147d..bf47ced2a77 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -136,6 +136,7 @@ public: void setColumns(const NamesAndTypesList & new_columns); const NamesAndTypesList & getColumns() const { return columns; } + const ColumnsDescription & getColumnsDescription() const { return columns_description; } NameAndTypePair getColumn(const String & name) const; std::optional tryGetColumn(const String & column_name) const; diff --git a/src/Storages/MergeTree/IMergeTreeReader.h b/src/Storages/MergeTree/IMergeTreeReader.h index 78cc96ccd27..f844946fe22 100644 --- a/src/Storages/MergeTree/IMergeTreeReader.h +++ b/src/Storages/MergeTree/IMergeTreeReader.h @@ -55,16 +55,14 @@ public: const NamesAndTypesList & getColumns() const { return columns; } size_t numColumnsInResult() const { return columns.size(); } - size_t getFirstMarkToRead() const - { - return all_mark_ranges.front().begin; - } + size_t getFirstMarkToRead() const { return all_mark_ranges.front().begin; } MergeTreeData::DataPartPtr data_part; protected: /// Returns actual column name in part, which can differ from table metadata. String getColumnNameInPart(const NameAndTypePair & required_column) const; + /// Returns actual column name and type in part, which can differ from table metadata. NameAndTypePair getColumnInPart(const NameAndTypePair & required_column) const; diff --git a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp index 3e5576b6bdf..b70d67de3b4 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp @@ -49,7 +49,7 @@ IMergeTreeDataPart::MergeTreeReaderPtr MergeTreeDataPartWide::getReader( { auto ptr = std::static_pointer_cast(shared_from_this()); return std::make_unique( - ptr, Nested::convertToSubcolumns(columns_to_read), + ptr, columns_to_read, metadata_snapshot, uncompressed_cache, mark_cache, mark_ranges, reader_settings, avg_value_size_hints, profile_callback); @@ -66,7 +66,7 @@ IMergeTreeDataPart::MergeTreeWriterPtr MergeTreeDataPartWide::getWriter( { return std::make_unique( shared_from_this(), data_part_storage_builder, - Nested::convertToSubcolumns(columns_list), metadata_snapshot, indices_to_recalc, + columns_list, metadata_snapshot, indices_to_recalc, index_granularity_info.marks_file_extension, default_codec_, writer_settings, computed_index_granularity); } diff --git a/src/Storages/MergeTree/MergeTreeReaderWide.cpp b/src/Storages/MergeTree/MergeTreeReaderWide.cpp index e098e76d4ee..aed9a906a5c 100644 --- a/src/Storages/MergeTree/MergeTreeReaderWide.cpp +++ b/src/Storages/MergeTree/MergeTreeReaderWide.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,19 @@ MergeTreeReaderWide::MergeTreeReaderWide( } } +String MergeTreeReaderWide::getNameForSubstreamCache(const NameAndTypePair & column) const +{ + if (!column.isSubcolumn() && isArray(column.type)) + { + auto split = Nested::splitName(column.name); + const auto & part_columns = data_part->getColumnsDescription(); + + if (!split.second.empty() && part_columns.hasNested(split.first)) + return split.first; + } + + return column.getNameInStorage(); +} size_t MergeTreeReaderWide::readRows( size_t from_mark, size_t current_task_last_mark, bool continue_reading, size_t max_rows_to_read, Columns & res_columns) @@ -86,7 +100,7 @@ size_t MergeTreeReaderWide::readRows( auto column_from_part = getColumnInPart(*name_and_type); try { - auto & cache = caches[column_from_part.getNameInStorage()]; + auto & cache = caches[getNameForSubstreamCache(column_from_part)]; prefetch(column_from_part, from_mark, continue_reading, current_task_last_mark, cache, prefetched_streams); } catch (Exception & e) @@ -117,7 +131,7 @@ size_t MergeTreeReaderWide::readRows( try { size_t column_size_before_reading = column->size(); - auto & cache = caches[column_from_part.getNameInStorage()]; + auto & cache = caches[getNameForSubstreamCache(column_from_part)]; readData( column_from_part, column, from_mark, continue_reading, current_task_last_mark, diff --git a/src/Storages/MergeTree/MergeTreeReaderWide.h b/src/Storages/MergeTree/MergeTreeReaderWide.h index 7bb1ccfd173..e382f3f1dde 100644 --- a/src/Storages/MergeTree/MergeTreeReaderWide.h +++ b/src/Storages/MergeTree/MergeTreeReaderWide.h @@ -38,6 +38,8 @@ public: private: FileStreams streams; + String getNameForSubstreamCache(const NameAndTypePair & column) const; + void addStreams(const NameAndTypePair & name_and_type, const ReadBufferFromFileBase::ProfileCallback & profile_callback, clockid_t clock_type); From fa0707b4e788620cf175487a210f4454427efadc Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 26 Jul 2022 17:51:09 +0000 Subject: [PATCH 128/672] Review fixes. --- src/Common/MemoryTracker.cpp | 4 ++-- src/Interpreters/AsynchronousMetrics.cpp | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Common/MemoryTracker.cpp b/src/Common/MemoryTracker.cpp index ef5e0c45373..35df2f9e473 100644 --- a/src/Common/MemoryTracker.cpp +++ b/src/Common/MemoryTracker.cpp @@ -204,7 +204,7 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT /// We can't track all memory allocations from external libraries (yet). if (level == VariableContext::Global) { - if (Int64 current_rss = size + rss.fetch_add(size, std::memory_order_relaxed); unlikely(current_rss > will_be)) + if (Int64 current_rss = size + rss.fetch_add(size, std::memory_order_relaxed); current_rss > will_be) { used_rss_counter = true; amount_to_check = current_rss; @@ -306,7 +306,7 @@ void MemoryTracker::free(Int64 size) amount.fetch_sub(size, std::memory_order_relaxed); auto metric_loaded = metric.load(std::memory_order_relaxed); if (metric_loaded != CurrentMetrics::end()) - CurrentMetrics::add(metric_loaded, size); + CurrentMetrics::sub(metric_loaded, size); } /// Since the MemoryTrackerBlockerInThread should respect the level, we should go to the next parent. diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index 32c4e421ac3..81fdef3d8a6 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -726,7 +726,6 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti } total_memory_tracker.setRSS(rss); - // CurrentMetrics::set(CurrentMetrics::MemoryTracking, new_amount); } } #endif From 1e3fa2e01fa295ee6318ceab880f81f6fff339d1 Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 18 Jul 2022 15:53:30 +0000 Subject: [PATCH 129/672] Refactor PreparedSets/SubqueryForSet --- src/Interpreters/ActionsVisitor.cpp | 39 ++++---- src/Interpreters/ActionsVisitor.h | 11 +-- src/Interpreters/ConcurrentHashJoin.cpp | 1 - src/Interpreters/ExpressionAnalyzer.cpp | 27 +++--- src/Interpreters/ExpressionAnalyzer.h | 19 ++-- src/Interpreters/GlobalSubqueriesVisitor.h | 17 ++-- src/Interpreters/InterpreterSelectQuery.cpp | 41 +++------ src/Interpreters/InterpreterSelectQuery.h | 16 ++-- src/Interpreters/MutationsInterpreter.cpp | 7 +- src/Interpreters/PreparedSets.cpp | 87 ++++++++++++++++++ src/Interpreters/PreparedSets.h | 92 +++++++++++-------- src/Interpreters/SubqueryForSet.cpp | 13 --- src/Interpreters/SubqueryForSet.h | 37 -------- src/Processors/QueryPlan/CreatingSetsStep.cpp | 16 ++-- src/Processors/QueryPlan/CreatingSetsStep.h | 4 +- .../Transforms/CreatingSetsTransform.h | 3 +- src/QueryPipeline/QueryPipelineBuilder.h | 1 - src/Storages/MergeTree/KeyCondition.cpp | 15 +-- src/Storages/MergeTree/KeyCondition.h | 2 +- src/Storages/MergeTree/MergeTreeData.cpp | 6 +- .../MergeTreeIndexConditionBloomFilter.cpp | 9 +- .../MergeTree/MergeTreeIndexFullText.cpp | 5 +- .../MergeTree/MergeTreeIndexFullText.h | 3 +- .../RocksDB/StorageEmbeddedRocksDB.cpp | 24 ++--- src/Storages/SelectQueryInfo.h | 10 +- src/Storages/VirtualColumnUtils.cpp | 6 +- 26 files changed, 263 insertions(+), 248 deletions(-) create mode 100644 src/Interpreters/PreparedSets.cpp delete mode 100644 src/Interpreters/SubqueryForSet.cpp delete mode 100644 src/Interpreters/SubqueryForSet.h diff --git a/src/Interpreters/ActionsVisitor.cpp b/src/Interpreters/ActionsVisitor.cpp index b62690b7a3a..2e95a5f906f 100644 --- a/src/Interpreters/ActionsVisitor.cpp +++ b/src/Interpreters/ActionsVisitor.cpp @@ -401,8 +401,8 @@ SetPtr makeExplicitSet( element_type = low_cardinality_type->getDictionaryType(); auto set_key = PreparedSetKey::forLiteral(*right_arg, set_element_types); - if (auto it = prepared_sets.find(set_key); it != prepared_sets.end()) - return it->second; /// Already prepared. + if (auto set = prepared_sets.getSet(set_key)) + return set; /// Already prepared. Block block; const auto & right_arg_func = std::dynamic_pointer_cast(right_arg); @@ -417,7 +417,7 @@ SetPtr makeExplicitSet( set->insertFromBlock(block.getColumnsWithTypeAndName()); set->finishInsert(); - prepared_sets.emplace(set_key, set); + prepared_sets.setSet(set_key, set); return set; } @@ -484,8 +484,7 @@ ActionsMatcher::Data::Data( size_t subquery_depth_, std::reference_wrapper source_columns_, ActionsDAGPtr actions_dag, - PreparedSets & prepared_sets_, - SubqueriesForSets & subqueries_for_sets_, + PreparedSetsPtr prepared_sets_, bool no_subqueries_, bool no_makeset_, bool only_consts_, @@ -497,7 +496,6 @@ ActionsMatcher::Data::Data( , subquery_depth(subquery_depth_) , source_columns(source_columns_) , prepared_sets(prepared_sets_) - , subqueries_for_sets(subqueries_for_sets_) , no_subqueries(no_subqueries_) , no_makeset(no_makeset_) , only_consts(only_consts_) @@ -1272,6 +1270,9 @@ void ActionsMatcher::visit(const ASTLiteral & literal, const ASTPtr & /* ast */, SetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool no_subqueries) { + if (!data.prepared_sets) + return nullptr; + /** You need to convert the right argument to a set. * This can be a table name, a value, a value enumeration, or a subquery. * The enumeration of values is parsed as a function `tuple`. @@ -1287,8 +1288,8 @@ SetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool no_su if (no_subqueries) return {}; auto set_key = PreparedSetKey::forSubquery(*right_in_operand); - if (auto it = data.prepared_sets.find(set_key); it != data.prepared_sets.end()) - return it->second; + if (SetPtr set = data.prepared_sets->getSet(set_key)) + return set; /// A special case is if the name of the table is specified on the right side of the IN statement, /// and the table has the type Set (a previously prepared set). @@ -1302,8 +1303,9 @@ SetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool no_su StorageSet * storage_set = dynamic_cast(table.get()); if (storage_set) { - data.prepared_sets.emplace(set_key, storage_set->getSet()); - return storage_set->getSet(); + SetPtr set = storage_set->getSet(); + data.prepared_sets->setSet(set_key, set); + return set; } } } @@ -1311,16 +1313,10 @@ SetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool no_su /// We get the stream of blocks for the subquery. Create Set and put it in place of the subquery. String set_id = right_in_operand->getColumnName(); - SubqueryForSet & subquery_for_set = data.subqueries_for_sets[set_id]; + SubqueryForSet & subquery_for_set = data.prepared_sets->createOrGetSubquery(set_id, set_key); - /// If you already created a Set with the same subquery / table. if (subquery_for_set.set) - { - data.prepared_sets.emplace(set_key, subquery_for_set.set); return subquery_for_set.set; - } - - SetPtr set = std::make_shared(data.set_size_limit, false, data.getContext()->getSettingsRef().transform_null_in); /** The following happens for GLOBAL INs or INs: * - in the addExternalStorage function, the IN (SELECT ...) subquery is replaced with IN _data1, @@ -1337,17 +1333,16 @@ SetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool no_su interpreter->buildQueryPlan(*subquery_for_set.source); } - subquery_for_set.set = set; - data.prepared_sets.emplace(set_key, set); - return set; + subquery_for_set.set = std::make_shared(data.set_size_limit, false, data.getContext()->getSettingsRef().transform_null_in); + return subquery_for_set.set; } else { const auto & last_actions = data.actions_stack.getLastActions(); const auto & index = data.actions_stack.getLastActionsIndex(); - if (index.contains(left_in_operand->getColumnName())) + if (data.prepared_sets && index.contains(left_in_operand->getColumnName())) /// An explicit enumeration of values in parentheses. - return makeExplicitSet(&node, last_actions, false, data.getContext(), data.set_size_limit, data.prepared_sets); + return makeExplicitSet(&node, last_actions, false, data.getContext(), data.set_size_limit, *data.prepared_sets); else return {}; } diff --git a/src/Interpreters/ActionsVisitor.h b/src/Interpreters/ActionsVisitor.h index 5b5a3d31da2..a27745d2cfa 100644 --- a/src/Interpreters/ActionsVisitor.h +++ b/src/Interpreters/ActionsVisitor.h @@ -5,10 +5,9 @@ #include #include #include -#include #include #include - +#include namespace DB { @@ -115,7 +114,7 @@ struct AggregationKeysInfo GroupByKind group_by_kind; }; -/// Collect ExpressionAction from AST. Returns PreparedSets and SubqueriesForSets too. +/// Collect ExpressionAction from AST. Returns PreparedSets class ActionsMatcher { public: @@ -126,8 +125,7 @@ public: SizeLimits set_size_limit; size_t subquery_depth; const NamesAndTypesList & source_columns; - PreparedSets & prepared_sets; - SubqueriesForSets & subqueries_for_sets; + PreparedSetsPtr prepared_sets; bool no_subqueries; bool no_makeset; bool only_consts; @@ -150,8 +148,7 @@ public: size_t subquery_depth_, std::reference_wrapper source_columns_, ActionsDAGPtr actions_dag, - PreparedSets & prepared_sets_, - SubqueriesForSets & subqueries_for_sets_, + PreparedSetsPtr prepared_sets_, bool no_subqueries_, bool no_makeset_, bool only_consts_, diff --git a/src/Interpreters/ConcurrentHashJoin.cpp b/src/Interpreters/ConcurrentHashJoin.cpp index 5d6318a8df1..0d86e1ff8ca 100644 --- a/src/Interpreters/ConcurrentHashJoin.cpp +++ b/src/Interpreters/ConcurrentHashJoin.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index 8a14c09819a..f7c6dd46233 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -227,18 +227,16 @@ ExpressionAnalyzer::ExpressionAnalyzer( size_t subquery_depth_, bool do_global, bool is_explain, - SubqueriesForSets subqueries_for_sets_, - PreparedSets prepared_sets_) + PreparedSetsPtr prepared_sets_) : WithContext(context_) , query(query_), settings(getContext()->getSettings()) , subquery_depth(subquery_depth_) , syntax(syntax_analyzer_result_) { /// Cache prepared sets because we might run analysis multiple times - subqueries_for_sets = std::move(subqueries_for_sets_); - prepared_sets = std::move(prepared_sets_); + prepared_sets = prepared_sets_; - /// external_tables, subqueries_for_sets for global subqueries. + /// external_tables, sets for global subqueries. /// Replaces global subqueries with the generated names of temporary tables that will be sent to remote servers. initGlobalSubqueriesAndExternalTables(do_global, is_explain); @@ -502,7 +500,7 @@ void ExpressionAnalyzer::initGlobalSubqueriesAndExternalTables(bool do_global, b if (do_global) { GlobalSubqueriesVisitor::Data subqueries_data( - getContext(), subquery_depth, isRemoteStorage(), is_explain, external_tables, subqueries_for_sets, has_global_subqueries); + getContext(), subquery_depth, isRemoteStorage(), is_explain, external_tables, prepared_sets, has_global_subqueries); GlobalSubqueriesVisitor(subqueries_data).visit(query); } } @@ -510,14 +508,17 @@ void ExpressionAnalyzer::initGlobalSubqueriesAndExternalTables(bool do_global, b void ExpressionAnalyzer::tryMakeSetForIndexFromSubquery(const ASTPtr & subquery_or_table_name, const SelectQueryOptions & query_options) { + if (!prepared_sets) + return; + auto set_key = PreparedSetKey::forSubquery(*subquery_or_table_name); - if (prepared_sets.contains(set_key)) + if (prepared_sets->getSet(set_key)) return; /// Already prepared. if (auto set_ptr_from_storage_set = isPlainStorageSetInSubquery(subquery_or_table_name)) { - prepared_sets.insert({set_key, set_ptr_from_storage_set}); + prepared_sets->setSet(set_key, set_ptr_from_storage_set); return; } @@ -541,7 +542,7 @@ void ExpressionAnalyzer::tryMakeSetForIndexFromSubquery(const ASTPtr & subquery_ set->finishInsert(); - prepared_sets[set_key] = std::move(set); + prepared_sets->setSet(set_key, std::move(set)); } SetPtr ExpressionAnalyzer::isPlainStorageSetInSubquery(const ASTPtr & subquery_or_table_name) @@ -597,8 +598,8 @@ void SelectQueryExpressionAnalyzer::makeSetsForIndex(const ASTPtr & node) auto temp_actions = std::make_shared(columns_after_join); getRootActions(left_in_operand, true, temp_actions); - if (temp_actions->tryFindInIndex(left_in_operand->getColumnName())) - makeExplicitSet(func, *temp_actions, true, getContext(), settings.size_limits_for_set, prepared_sets); + if (prepared_sets && temp_actions->tryFindInIndex(left_in_operand->getColumnName())) + makeExplicitSet(func, *temp_actions, true, getContext(), settings.size_limits_for_set, *prepared_sets); } } } @@ -615,7 +616,6 @@ void ExpressionAnalyzer::getRootActions(const ASTPtr & ast, bool no_makeset_for_ sourceColumns(), std::move(actions), prepared_sets, - subqueries_for_sets, no_makeset_for_subqueries, false /* no_makeset */, only_consts, @@ -635,7 +635,6 @@ void ExpressionAnalyzer::getRootActionsNoMakeSet(const ASTPtr & ast, ActionsDAGP sourceColumns(), std::move(actions), prepared_sets, - subqueries_for_sets, true /* no_makeset_for_subqueries, no_makeset implies no_makeset_for_subqueries */, true /* no_makeset */, only_consts, @@ -657,7 +656,6 @@ void ExpressionAnalyzer::getRootActionsForHaving( sourceColumns(), std::move(actions), prepared_sets, - subqueries_for_sets, no_makeset_for_subqueries, false /* no_makeset */, only_consts, @@ -678,7 +676,6 @@ void ExpressionAnalyzer::getRootActionsForWindowFunctions(const ASTPtr & ast, bo sourceColumns(), std::move(actions), prepared_sets, - subqueries_for_sets, no_makeset_for_subqueries, false /* no_makeset */, false /*only_consts */, diff --git a/src/Interpreters/ExpressionAnalyzer.h b/src/Interpreters/ExpressionAnalyzer.h index da92bc10832..7bc0891bbd5 100644 --- a/src/Interpreters/ExpressionAnalyzer.h +++ b/src/Interpreters/ExpressionAnalyzer.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -50,8 +49,7 @@ struct ExpressionAnalyzerData { ~ExpressionAnalyzerData(); - SubqueriesForSets subqueries_for_sets; - PreparedSets prepared_sets; + PreparedSetsPtr prepared_sets; std::unique_ptr joined_plan; @@ -106,7 +104,7 @@ public: /// Ctor for non-select queries. Generally its usage is: /// auto actions = ExpressionAnalyzer(query, syntax, context).getActions(); ExpressionAnalyzer(const ASTPtr & query_, const TreeRewriterResultPtr & syntax_analyzer_result_, ContextPtr context_) - : ExpressionAnalyzer(query_, syntax_analyzer_result_, context_, 0, false, false, {}, {}) + : ExpressionAnalyzer(query_, syntax_analyzer_result_, context_, 0, false, false, {}) { } @@ -130,9 +128,7 @@ public: * That is, you need to call getSetsWithSubqueries after all calls of `append*` or `getActions` * and create all the returned sets before performing the actions. */ - SubqueriesForSets & getSubqueriesForSets() { return subqueries_for_sets; } - - PreparedSets & getPreparedSets() { return prepared_sets; } + PreparedSetsPtr getPreparedSets() { return prepared_sets; } /// Get intermediates for tests const ExpressionAnalyzerData & getAnalyzedData() const { return *this; } @@ -164,8 +160,7 @@ protected: size_t subquery_depth_, bool do_global_, bool is_explain_, - SubqueriesForSets subqueries_for_sets_, - PreparedSets prepared_sets_); + PreparedSetsPtr prepared_sets_); ASTPtr query; const ExtractedSettings settings; @@ -317,8 +312,7 @@ public: const NameSet & required_result_columns_ = {}, bool do_global_ = false, const SelectQueryOptions & options_ = {}, - SubqueriesForSets subqueries_for_sets_ = {}, - PreparedSets prepared_sets_ = {}) + PreparedSetsPtr prepared_sets_ = nullptr) : ExpressionAnalyzer( query_, syntax_analyzer_result_, @@ -326,8 +320,7 @@ public: options_.subquery_depth, do_global_, options_.is_explain, - std::move(subqueries_for_sets_), - std::move(prepared_sets_)) + prepared_sets_) , metadata_snapshot(metadata_snapshot_) , required_result_columns(required_result_columns_) , query_options(options_) diff --git a/src/Interpreters/GlobalSubqueriesVisitor.h b/src/Interpreters/GlobalSubqueriesVisitor.h index 7086b1e950d..829beedbafa 100644 --- a/src/Interpreters/GlobalSubqueriesVisitor.h +++ b/src/Interpreters/GlobalSubqueriesVisitor.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -41,7 +41,7 @@ public: bool is_remote; bool is_explain; TemporaryTablesMapping & external_tables; - SubqueriesForSets & subqueries_for_sets; + PreparedSetsPtr prepared_sets; bool & has_global_subqueries; Data( @@ -50,14 +50,14 @@ public: bool is_remote_, bool is_explain_, TemporaryTablesMapping & tables, - SubqueriesForSets & subqueries_for_sets_, + PreparedSetsPtr prepared_sets_, bool & has_global_subqueries_) : WithContext(context_) , subquery_depth(subquery_depth_) , is_remote(is_remote_) , is_explain(is_explain_) , external_tables(tables) - , subqueries_for_sets(subqueries_for_sets_) + , prepared_sets(prepared_sets_) , has_global_subqueries(has_global_subqueries_) { } @@ -178,9 +178,12 @@ public: } else { - subqueries_for_sets[external_table_name].source = std::make_unique(); - interpreter->buildQueryPlan(*subqueries_for_sets[external_table_name].source); - subqueries_for_sets[external_table_name].table = external_storage; + auto set_key = PreparedSetKey::forSubquery(*subquery_or_table_name); + auto & subquery_for_set = prepared_sets->createOrGetSubquery(external_table_name, set_key); + + subquery_for_set.source = std::make_unique(); + interpreter->buildQueryPlan(*subquery_for_set.source); + subquery_for_set.table = external_storage; } /** NOTE If it was written IN tmp_table - the existing temporary (but not external) table, diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index de01115abec..a33c46ee248 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -212,10 +212,9 @@ InterpreterSelectQuery::InterpreterSelectQuery( const ASTPtr & query_ptr_, const ContextPtr & context_, const SelectQueryOptions & options_, - SubqueriesForSets subquery_for_sets_, - PreparedSets prepared_sets_) + PreparedSetsPtr prepared_sets_) : InterpreterSelectQuery( - query_ptr_, context_, std::nullopt, nullptr, options_, {}, {}, std::move(subquery_for_sets_), std::move(prepared_sets_)) + query_ptr_, context_, std::nullopt, nullptr, options_, {}, {}, prepared_sets_) {} InterpreterSelectQuery::~InterpreterSelectQuery() = default; @@ -333,8 +332,7 @@ InterpreterSelectQuery::InterpreterSelectQuery( const SelectQueryOptions & options_, const Names & required_result_column_names, const StorageMetadataPtr & metadata_snapshot_, - SubqueriesForSets subquery_for_sets_, - PreparedSets prepared_sets_) + PreparedSetsPtr prepared_sets_) : InterpreterSelectQuery( query_ptr_, Context::createCopy(context_), @@ -343,8 +341,7 @@ InterpreterSelectQuery::InterpreterSelectQuery( options_, required_result_column_names, metadata_snapshot_, - std::move(subquery_for_sets_), - std::move(prepared_sets_)) + prepared_sets_) {} InterpreterSelectQuery::InterpreterSelectQuery( @@ -355,16 +352,14 @@ InterpreterSelectQuery::InterpreterSelectQuery( const SelectQueryOptions & options_, const Names & required_result_column_names, const StorageMetadataPtr & metadata_snapshot_, - SubqueriesForSets subquery_for_sets_, - PreparedSets prepared_sets_) + PreparedSetsPtr prepared_sets_) /// NOTE: the query almost always should be cloned because it will be modified during analysis. : IInterpreterUnionOrSelectQuery(options_.modify_inplace ? query_ptr_ : query_ptr_->clone(), context_, options_) , storage(storage_) , input_pipe(std::move(input_pipe_)) , log(&Poco::Logger::get("InterpreterSelectQuery")) , metadata_snapshot(metadata_snapshot_) - , subquery_for_sets(std::move(subquery_for_sets_)) - , prepared_sets(std::move(prepared_sets_)) + , prepared_sets(prepared_sets_) { checkStackSize(); @@ -566,8 +561,7 @@ InterpreterSelectQuery::InterpreterSelectQuery( NameSet(required_result_column_names.begin(), required_result_column_names.end()), !options.only_analyze, options, - std::move(subquery_for_sets), - std::move(prepared_sets)); + prepared_sets); if (!options.only_analyze) { @@ -658,8 +652,7 @@ InterpreterSelectQuery::InterpreterSelectQuery( LOG_TRACE(log, "Running 'analyze' second time"); /// Reuse already built sets for multiple passes of analysis - subquery_for_sets = std::move(query_analyzer->getSubqueriesForSets()); - prepared_sets = std::move(query_analyzer->getPreparedSets()); + prepared_sets = query_analyzer->getPreparedSets(); /// Do not try move conditions to PREWHERE for the second time. /// Otherwise, we won't be able to fallback from inefficient PREWHERE to WHERE later. @@ -755,14 +748,9 @@ Block InterpreterSelectQuery::getSampleBlockImpl() auto & query = getSelectQuery(); query_analyzer->makeSetsForIndex(query.where()); query_analyzer->makeSetsForIndex(query.prewhere()); - query_info.sets = std::move(query_analyzer->getPreparedSets()); - query_info.subquery_for_sets = std::move(query_analyzer->getSubqueriesForSets()); + query_info.prepared_sets = query_analyzer->getPreparedSets(); from_stage = storage->getQueryProcessingStage(context, options.to_stage, storage_snapshot, query_info); - - /// query_info.sets is used for further set index analysis. Use copy instead of move. - query_analyzer->getPreparedSets() = query_info.sets; - query_analyzer->getSubqueriesForSets() = std::move(query_info.subquery_for_sets); } /// Do I need to perform the first part of the pipeline? @@ -1174,7 +1162,6 @@ void InterpreterSelectQuery::executeImpl(QueryPlan & query_plan, std::optional

getSettingsRef(); auto & expressions = analysis_result; - auto & subqueries_for_sets = query_analyzer->getSubqueriesForSets(); bool intermediate_stage = false; bool to_aggregation_stage = false; bool from_aggregation_stage = false; @@ -1682,8 +1669,7 @@ void InterpreterSelectQuery::executeImpl(QueryPlan & query_plan, std::optional

empty()) + return; + const Settings & settings = context->getSettingsRef(); SizeLimits limits(settings.max_rows_to_transfer, settings.max_bytes_to_transfer, settings.transfer_overflow_mode); - addCreatingSetsStep(query_plan, std::move(subqueries_for_sets), limits, context); + addCreatingSetsStep(query_plan, *prepared_sets, limits, context); } diff --git a/src/Interpreters/InterpreterSelectQuery.h b/src/Interpreters/InterpreterSelectQuery.h index e70490f13ac..0a9ddb28acf 100644 --- a/src/Interpreters/InterpreterSelectQuery.h +++ b/src/Interpreters/InterpreterSelectQuery.h @@ -77,14 +77,13 @@ public: const StorageMetadataPtr & metadata_snapshot_ = nullptr, const SelectQueryOptions & = {}); - /// Reuse existing subqueries_for_sets and prepared_sets for another pass of analysis. It's used for projection. + /// Reuse existing prepared_sets for another pass of analysis. It's used for projection. /// TODO: Find a general way of sharing sets among different interpreters, such as subqueries. InterpreterSelectQuery( const ASTPtr & query_ptr_, const ContextPtr & context_, const SelectQueryOptions &, - SubqueriesForSets subquery_for_sets_, - PreparedSets prepared_sets_); + PreparedSetsPtr prepared_sets_); ~InterpreterSelectQuery() override; @@ -140,8 +139,7 @@ private: const SelectQueryOptions &, const Names & required_result_column_names = {}, const StorageMetadataPtr & metadata_snapshot_ = nullptr, - SubqueriesForSets subquery_for_sets_ = {}, - PreparedSets prepared_sets_ = {}); + PreparedSetsPtr prepared_sets_ = nullptr); InterpreterSelectQuery( const ASTPtr & query_ptr_, @@ -151,8 +149,7 @@ private: const SelectQueryOptions &, const Names & required_result_column_names = {}, const StorageMetadataPtr & metadata_snapshot_ = nullptr, - SubqueriesForSets subquery_for_sets_ = {}, - PreparedSets prepared_sets_ = {}); + PreparedSetsPtr prepared_sets_ = nullptr); ASTSelectQuery & getSelectQuery() { return query_ptr->as(); } @@ -185,7 +182,7 @@ private: static void executeProjection(QueryPlan & query_plan, const ActionsDAGPtr & expression); void executeDistinct(QueryPlan & query_plan, bool before_order, Names columns, bool pre_distinct); void executeExtremes(QueryPlan & query_plan); - void executeSubqueriesInSetsAndJoins(QueryPlan & query_plan, std::unordered_map & subqueries_for_sets); + void executeSubqueriesInSetsAndJoins(QueryPlan & query_plan); void executeMergeSorted(QueryPlan & query_plan, const SortDescription & sort_description, UInt64 limit, const std::string & description); @@ -244,8 +241,7 @@ private: StorageSnapshotPtr storage_snapshot; /// Reuse already built sets for multiple passes of analysis, possibly across interpreters. - SubqueriesForSets subquery_for_sets; - PreparedSets prepared_sets; + PreparedSetsPtr prepared_sets; }; } diff --git a/src/Interpreters/MutationsInterpreter.cpp b/src/Interpreters/MutationsInterpreter.cpp index 8c1d929e409..ed13ec0040d 100644 --- a/src/Interpreters/MutationsInterpreter.cpp +++ b/src/Interpreters/MutationsInterpreter.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace DB @@ -911,13 +912,13 @@ QueryPipelineBuilder MutationsInterpreter::addStreamsForLaterStages(const std::v } } - SubqueriesForSets & subqueries_for_sets = stage.analyzer->getSubqueriesForSets(); - if (!subqueries_for_sets.empty()) + PreparedSetsPtr prepared_sets = stage.analyzer->getPreparedSets(); + if (prepared_sets && !prepared_sets->empty()) { const Settings & settings = context->getSettingsRef(); SizeLimits network_transfer_limits( settings.max_rows_to_transfer, settings.max_bytes_to_transfer, settings.transfer_overflow_mode); - addCreatingSetsStep(plan, std::move(subqueries_for_sets), network_transfer_limits, context); + addCreatingSetsStep(plan, *prepared_sets, network_transfer_limits, context); } } diff --git a/src/Interpreters/PreparedSets.cpp b/src/Interpreters/PreparedSets.cpp new file mode 100644 index 00000000000..5bfbf2b0705 --- /dev/null +++ b/src/Interpreters/PreparedSets.cpp @@ -0,0 +1,87 @@ +#include +#include + +namespace DB +{ + +PreparedSetKey PreparedSetKey::forLiteral(const IAST & ast, DataTypes types_) +{ + /// Remove LowCardinality types from type list because Set doesn't support LowCardinality keys now, + /// just converts LowCardinality to ordinary types. + for (auto & type : types_) + type = recursiveRemoveLowCardinality(type); + + PreparedSetKey key; + key.ast_hash = ast.getTreeHash(); + key.types = std::move(types_); + return key; +} + +PreparedSetKey PreparedSetKey::forSubquery(const IAST & ast) +{ + PreparedSetKey key; + key.ast_hash = ast.getTreeHash(); + return key; +} + +bool PreparedSetKey::operator==(const PreparedSetKey & other) const +{ + if (ast_hash != other.ast_hash) + return false; + + if (types.size() != other.types.size()) + return false; + + for (size_t i = 0; i < types.size(); ++i) + { + if (!types[i]->equals(*other.types[i])) + return false; + } + + return true; +} + +SubqueryForSet & PreparedSets::createOrGetSubquery(const String & subquery_id, const PreparedSetKey & key) +{ + if (auto subqiery_it = subqueries.find(subquery_id); subqiery_it != subqueries.end()) + { + /// If you already created a Set with the same subquery / table for another ast + /// In that case several PreparedSetKey would share same subquery and set + /// Not sure if it's really possible case (maybe for distributed query when set was filled by external table?) + if (subqiery_it->second.set) + sets[key] = subqiery_it->second.set; + return subqiery_it->second; + } + + return subqueries.emplace(subquery_id, sets[key]).first->second; +} + +void PreparedSets::setSet(const PreparedSetKey & key, SetPtr set_) +{ + sets[key] = std::move(set_); +} + +SetPtr & PreparedSets::getSet(const PreparedSetKey & key) +{ + return sets[key]; +} + +PreparedSets::SubqueriesForSets PreparedSets::moveSubqueries() +{ + auto res = std::move(subqueries); + subqueries = SubqueriesForSets(); + return res; +} + +bool PreparedSets::empty() const +{ + return sets.empty(); +} + +SubqueryForSet::SubqueryForSet(SetPtr & set_) : set(set_) {} + +SubqueryForSet::~SubqueryForSet() = default; + +SubqueryForSet::SubqueryForSet(SubqueryForSet &&) noexcept = default; + +}; diff --git a/src/Interpreters/PreparedSets.h b/src/Interpreters/PreparedSets.h index f486752e192..4a209b7c798 100644 --- a/src/Interpreters/PreparedSets.h +++ b/src/Interpreters/PreparedSets.h @@ -5,57 +5,51 @@ #include #include #include - +#include namespace DB { +class QueryPlan; + +class Set; +using SetPtr = std::shared_ptr; + +/// Information on how to build set for the [GLOBAL] IN section. +struct SubqueryForSet +{ + explicit SubqueryForSet(SetPtr & set_); + ~SubqueryForSet(); + + SubqueryForSet(SubqueryForSet &&) noexcept; + SubqueryForSet & operator=(SubqueryForSet &&) noexcept; + + /// The source is obtained using the InterpreterSelectQuery subquery. + std::unique_ptr source; + + /// Build this set from the result of the subquery. + SetPtr & set; + + /// If set, put the result into the table. + /// This is a temporary table for transferring to remote servers for distributed query processing. + StoragePtr table; +}; + struct PreparedSetKey { /// Prepared sets for tuple literals are indexed by the hash of the tree contents and by the desired /// data types of set elements (two different Sets can be required for two tuples with the same contents /// if left hand sides of the IN operators have different types). - static PreparedSetKey forLiteral(const IAST & ast, DataTypes types_) - { - /// Remove LowCardinality types from type list because Set doesn't support LowCardinality keys now, - /// just converts LowCardinality to ordinary types. - for (auto & type : types_) - type = recursiveRemoveLowCardinality(type); - - PreparedSetKey key; - key.ast_hash = ast.getTreeHash(); - key.types = std::move(types_); - return key; - } + static PreparedSetKey forLiteral(const IAST & ast, DataTypes types_); /// Prepared sets for subqueries are indexed only by the AST contents because the type of the resulting /// set is fully determined by the subquery. - static PreparedSetKey forSubquery(const IAST & ast) - { - PreparedSetKey key; - key.ast_hash = ast.getTreeHash(); - return key; - } + static PreparedSetKey forSubquery(const IAST & ast); IAST::Hash ast_hash; DataTypes types; /// Empty for subqueries. - bool operator==(const PreparedSetKey & other) const - { - if (ast_hash != other.ast_hash) - return false; - - if (types.size() != other.types.size()) - return false; - - for (size_t i = 0; i < types.size(); ++i) - { - if (!types[i]->equals(*other.types[i])) - return false; - } - - return true; - } + bool operator==(const PreparedSetKey & other) const; struct Hash { @@ -63,9 +57,31 @@ struct PreparedSetKey }; }; -class Set; -using SetPtr = std::shared_ptr; +class PreparedSets +{ +public: + using SubqueriesForSets = std::unordered_map; -using PreparedSets = std::unordered_map; + SubqueryForSet & createOrGetSubquery(const String & subquery_id, const PreparedSetKey & key); + + void setSet(const PreparedSetKey & key, SetPtr set_); + SetPtr & getSet(const PreparedSetKey & key); + + /// Get subqueries and clear them + SubqueriesForSets moveSubqueries(); + + /// Used in KeyCondition and MergeTreeIndexConditionBloomFilter to make non exact match for types in PreparedSetKey + const std::unordered_map & getSetsMap() const { return sets; } + + bool empty() const; + +private: + std::unordered_map sets; + + /// This is the information required for building sets + SubqueriesForSets subqueries; +}; + +using PreparedSetsPtr = std::shared_ptr; } diff --git a/src/Interpreters/SubqueryForSet.cpp b/src/Interpreters/SubqueryForSet.cpp deleted file mode 100644 index d669e091131..00000000000 --- a/src/Interpreters/SubqueryForSet.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include -#include - -namespace DB -{ - -SubqueryForSet::SubqueryForSet() = default; -SubqueryForSet::~SubqueryForSet() = default; -SubqueryForSet::SubqueryForSet(SubqueryForSet &&) noexcept = default; -SubqueryForSet & SubqueryForSet::operator= (SubqueryForSet &&) noexcept = default; - -} diff --git a/src/Interpreters/SubqueryForSet.h b/src/Interpreters/SubqueryForSet.h deleted file mode 100644 index f737ec4582b..00000000000 --- a/src/Interpreters/SubqueryForSet.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include - - -namespace DB -{ - -class QueryPlan; - -class Set; -using SetPtr = std::shared_ptr; - -/// Information on what to do when executing a subquery in the [GLOBAL] IN/JOIN section. -struct SubqueryForSet -{ - SubqueryForSet(); - ~SubqueryForSet(); - SubqueryForSet(SubqueryForSet &&) noexcept; - SubqueryForSet & operator=(SubqueryForSet &&) noexcept; - - /// The source is obtained using the InterpreterSelectQuery subquery. - std::unique_ptr source; - - /// If set, build it from result. - SetPtr set; - - /// If set, put the result into the table. - /// This is a temporary table for transferring to remote servers for distributed query processing. - StoragePtr table; -}; - -/// ID of subquery -> what to do with it. -using SubqueriesForSets = std::unordered_map; - -} diff --git a/src/Processors/QueryPlan/CreatingSetsStep.cpp b/src/Processors/QueryPlan/CreatingSetsStep.cpp index 94d841ff095..958e34bfd03 100644 --- a/src/Processors/QueryPlan/CreatingSetsStep.cpp +++ b/src/Processors/QueryPlan/CreatingSetsStep.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace DB { @@ -61,8 +62,6 @@ void CreatingSetStep::describeActions(FormatSettings & settings) const settings.out << prefix; if (subquery_for_set.set) settings.out << "Set: "; - // else if (subquery_for_set.join) - // settings.out << "Join: "; settings.out << description << '\n'; } @@ -71,8 +70,6 @@ void CreatingSetStep::describeActions(JSONBuilder::JSONMap & map) const { if (subquery_for_set.set) map.add("Set", description); - // else if (subquery_for_set.join) - // map.add("Join", description); } @@ -125,7 +122,7 @@ void CreatingSetsStep::describePipeline(FormatSettings & settings) const } void addCreatingSetsStep( - QueryPlan & query_plan, SubqueriesForSets subqueries_for_sets, const SizeLimits & limits, ContextPtr context) + QueryPlan & query_plan, PreparedSets & prepared_sets, const SizeLimits & limits, ContextPtr context) { DataStreams input_streams; input_streams.emplace_back(query_plan.getCurrentDataStream()); @@ -134,17 +131,18 @@ void addCreatingSetsStep( plans.emplace_back(std::make_unique(std::move(query_plan))); query_plan = QueryPlan(); - for (auto & [description, set] : subqueries_for_sets) + for (auto & [description, subquery_for_set] : prepared_sets.moveSubqueries()) { - if (!set.source) + if (!subquery_for_set.source) continue; - auto plan = std::move(set.source); + auto plan = std::move(subquery_for_set.source); + subquery_for_set.source = nullptr; auto creating_set = std::make_unique( plan->getCurrentDataStream(), description, - std::move(set), + std::move(subquery_for_set), limits, context); creating_set->setStepDescription("Create set for subquery"); diff --git a/src/Processors/QueryPlan/CreatingSetsStep.h b/src/Processors/QueryPlan/CreatingSetsStep.h index 20cdd24c8a9..15bc33b5f56 100644 --- a/src/Processors/QueryPlan/CreatingSetsStep.h +++ b/src/Processors/QueryPlan/CreatingSetsStep.h @@ -2,8 +2,8 @@ #include #include -#include #include +#include namespace DB { @@ -51,7 +51,7 @@ private: void addCreatingSetsStep( QueryPlan & query_plan, - SubqueriesForSets subqueries_for_sets, + PreparedSets & prepared_sets, const SizeLimits & limits, ContextPtr context); diff --git a/src/Processors/Transforms/CreatingSetsTransform.h b/src/Processors/Transforms/CreatingSetsTransform.h index 48a32ea8663..ca59fb9e220 100644 --- a/src/Processors/Transforms/CreatingSetsTransform.h +++ b/src/Processors/Transforms/CreatingSetsTransform.h @@ -2,10 +2,10 @@ #include #include -#include #include #include #include +#include #include #include @@ -50,7 +50,6 @@ private: Stopwatch watch; bool done_with_set = true; - //bool done_with_join = true; bool done_with_table = true; SizeLimits network_transfer_limits; diff --git a/src/QueryPipeline/QueryPipelineBuilder.h b/src/QueryPipeline/QueryPipelineBuilder.h index 2d9b8028627..9147046cf12 100644 --- a/src/QueryPipeline/QueryPipelineBuilder.h +++ b/src/QueryPipeline/QueryPipelineBuilder.h @@ -21,7 +21,6 @@ class PipelineExecutor; using PipelineExecutorPtr = std::shared_ptr; struct SubqueryForSet; -using SubqueriesForSets = std::unordered_map; struct SizeLimits; diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index daf31698aad..31a3fead389 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -883,14 +883,16 @@ bool KeyCondition::tryPrepareSetIndex( const ASTPtr & right_arg = args[1]; + + if (!prepared_sets) + return false; + SetPtr prepared_set; if (right_arg->as() || right_arg->as()) { - auto set_it = prepared_sets.find(PreparedSetKey::forSubquery(*right_arg)); - if (set_it == prepared_sets.end()) + prepared_set = prepared_sets->getSet(PreparedSetKey::forSubquery(*right_arg)); + if (!prepared_sets) return false; - - prepared_set = set_it->second; } else { @@ -899,8 +901,9 @@ bool KeyCondition::tryPrepareSetIndex( /// and find the one for the right arg based on the AST structure (getTreeHash), after that we check /// that the types it was prepared with are compatible with the types of the primary key. auto set_ast_hash = right_arg->getTreeHash(); + const auto & sets_map = prepared_sets->getSetsMap(); auto set_it = std::find_if( - prepared_sets.begin(), prepared_sets.end(), + sets_map.begin(), sets_map.end(), [&](const auto & candidate_entry) { if (candidate_entry.first.ast_hash != set_ast_hash) @@ -912,7 +915,7 @@ bool KeyCondition::tryPrepareSetIndex( return true; }); - if (set_it == prepared_sets.end()) + if (set_it == sets_map.end()) return false; prepared_set = set_it->second; diff --git a/src/Storages/MergeTree/KeyCondition.h b/src/Storages/MergeTree/KeyCondition.h index af85a90dd62..b5257625f91 100644 --- a/src/Storages/MergeTree/KeyCondition.h +++ b/src/Storages/MergeTree/KeyCondition.h @@ -434,7 +434,7 @@ private: const NameSet key_subexpr_names; NameSet array_joined_columns; - PreparedSets prepared_sets; + PreparedSetsPtr prepared_sets; // If true, always allow key_expr to be wrapped by function bool single_point; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 727ebc9c3cc..3b72a1568c9 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -5464,13 +5464,9 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg query_ptr, query_context, query_options, - std::move(query_info.subquery_for_sets), - std::move(query_info.sets)); + query_info.prepared_sets); const auto & analysis_result = select.getAnalysisResult(); - query_info.sets = std::move(select.getQueryAnalyzer()->getPreparedSets()); - query_info.subquery_for_sets = std::move(select.getQueryAnalyzer()->getSubqueriesForSets()); - bool can_use_aggregate_projection = true; /// If the first stage of the query pipeline is more complex than Aggregating - Expression - Filter - ReadFromStorage, /// we cannot use aggregate projection. diff --git a/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp b/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp index 7b194de8103..44b01df099f 100644 --- a/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp @@ -691,14 +691,13 @@ SetPtr MergeTreeIndexConditionBloomFilter::getPreparedSet(const ASTPtr & node) if (header.has(node->getColumnName())) { const auto & column_and_type = header.getByName(node->getColumnName()); - const auto & prepared_set_it = query_info.sets.find(getPreparedSetKey(node, column_and_type.type)); - - if (prepared_set_it != query_info.sets.end() && prepared_set_it->second->hasExplicitSetElements()) - return prepared_set_it->second; + auto set_key = getPreparedSetKey(node, column_and_type.type); + if (auto prepared_set = query_info.sets->getSet(set_key)) + return prepared_set; } else { - for (const auto & prepared_set_it : query_info.sets) + for (const auto & prepared_set_it : query_info.sets->getSetsMap()) if (prepared_set_it.first.ast_hash == node->getTreeHash() && prepared_set_it.second->hasExplicitSetElements()) return prepared_set_it.second; } diff --git a/src/Storages/MergeTree/MergeTreeIndexFullText.cpp b/src/Storages/MergeTree/MergeTreeIndexFullText.cpp index b244bd489f1..8902c41876d 100644 --- a/src/Storages/MergeTree/MergeTreeIndexFullText.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexFullText.cpp @@ -609,11 +609,10 @@ bool MergeTreeConditionFullText::tryPrepareSetBloomFilter( else set_key = PreparedSetKey::forLiteral(*right_arg, data_types); - auto set_it = prepared_sets.find(set_key); - if (set_it == prepared_sets.end()) + auto prepared_set = prepared_sets->getSet(set_key); + if (!prepared_set) return false; - const SetPtr & prepared_set = set_it->second; if (!prepared_set->hasExplicitSetElements()) return false; diff --git a/src/Storages/MergeTree/MergeTreeIndexFullText.h b/src/Storages/MergeTree/MergeTreeIndexFullText.h index 5f5956553dc..bb4f52a463e 100644 --- a/src/Storages/MergeTree/MergeTreeIndexFullText.h +++ b/src/Storages/MergeTree/MergeTreeIndexFullText.h @@ -142,8 +142,9 @@ private: BloomFilterParameters params; TokenExtractorPtr token_extractor; RPN rpn; + /// Sets from syntax analyzer. - PreparedSets prepared_sets; + PreparedSetsPtr prepared_sets; }; class MergeTreeIndexFullText final : public IMergeTreeIndex diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp index fd943fbe1c5..da7d94d27a0 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp @@ -76,7 +76,7 @@ static RocksDBOptions getOptionsFromConfig(const Poco::Util::AbstractConfigurati // returns keys may be filter by condition static bool traverseASTFilter( - const String & primary_key, const DataTypePtr & primary_key_type, const ASTPtr & elem, const PreparedSets & sets, FieldVectorPtr & res) + const String & primary_key, const DataTypePtr & primary_key_type, const ASTPtr & elem, const PreparedSetsPtr & prepared_sets, FieldVectorPtr & res) { const auto * function = elem->as(); if (!function) @@ -86,7 +86,7 @@ static bool traverseASTFilter( { // one child has the key filter condition is ok for (const auto & child : function->arguments->children) - if (traverseASTFilter(primary_key, primary_key_type, child, sets, res)) + if (traverseASTFilter(primary_key, primary_key_type, child, prepared_sets, res)) return true; return false; } @@ -94,7 +94,7 @@ static bool traverseASTFilter( { // make sure every child has the key filter condition for (const auto & child : function->arguments->children) - if (!traverseASTFilter(primary_key, primary_key_type, child, sets, res)) + if (!traverseASTFilter(primary_key, primary_key_type, child, prepared_sets, res)) return false; return true; } @@ -109,6 +109,9 @@ static bool traverseASTFilter( if (function->name == "in") { + if (!prepared_sets) + return false; + ident = args.children.at(0)->as(); if (!ident) return false; @@ -123,16 +126,15 @@ static bool traverseASTFilter( else set_key = PreparedSetKey::forLiteral(*value, {primary_key_type}); - auto set_it = sets.find(set_key); - if (set_it == sets.end()) - return false; - SetPtr prepared_set = set_it->second; - - if (!prepared_set->hasExplicitSetElements()) + SetPtr set = prepared_sets->getSet(set_key); + if (!set) return false; - prepared_set->checkColumnsNumber(1); - const auto & set_column = *prepared_set->getSetElements()[0]; + if (!set->hasExplicitSetElements()) + return false; + + set->checkColumnsNumber(1); + const auto & set_column = *set->getSetElements()[0]; for (size_t row = 0; row < set_column.size(); ++row) res->push_back(set_column[row]); return true; diff --git a/src/Storages/SelectQueryInfo.h b/src/Storages/SelectQueryInfo.h index 5046a0b6fe0..8e6009c07bf 100644 --- a/src/Storages/SelectQueryInfo.h +++ b/src/Storages/SelectQueryInfo.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -44,9 +43,6 @@ using ClusterPtr = std::shared_ptr; struct MergeTreeDataSelectAnalysisResult; using MergeTreeDataSelectAnalysisResultPtr = std::shared_ptr; -struct SubqueryForSet; -using SubqueriesForSets = std::unordered_map; - struct PrewhereInfo { /// Actions for row level security filter. Applied separately before prewhere_actions. @@ -166,7 +162,7 @@ struct SelectQueryInfoBase /// Prepared sets are used for indices by storage engine. /// Example: x IN (1, 2, 3) - PreparedSets sets; + PreparedSetsPtr sets; /// Cached value of ExpressionAnalysisResult::has_window bool has_window = false; @@ -189,8 +185,8 @@ struct SelectQueryInfo : SelectQueryInfoBase SelectQueryInfo() = default; SelectQueryInfo(const SelectQueryInfo & other) : SelectQueryInfoBase(other) {} - /// Make subquery_for_sets reusable across different interpreters. - SubqueriesForSets subquery_for_sets; + /// Make sets reusable across different interpreters. + PreparedSetsPtr prepared_sets; }; } diff --git a/src/Storages/VirtualColumnUtils.cpp b/src/Storages/VirtualColumnUtils.cpp index 40cf650f690..43e31b8e4f4 100644 --- a/src/Storages/VirtualColumnUtils.cpp +++ b/src/Storages/VirtualColumnUtils.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -154,14 +155,13 @@ bool prepareFilterBlockWithQuery(const ASTPtr & query, ContextPtr context, Block std::function is_constant = [&block, &context](const ASTPtr & node) { auto actions = std::make_shared(block.getColumnsWithTypeAndName()); - PreparedSets prepared_sets; - SubqueriesForSets subqueries_for_sets; + PreparedSetsPtr prepared_sets = std::make_shared(); const NamesAndTypesList source_columns; const NamesAndTypesList aggregation_keys; const ColumnNumbersList grouping_set_keys; ActionsVisitor::Data visitor_data( - context, SizeLimits{}, 1, source_columns, std::move(actions), prepared_sets, subqueries_for_sets, true, true, true, false, + context, SizeLimits{}, 1, source_columns, std::move(actions), prepared_sets, true, true, true, false, { aggregation_keys, grouping_set_keys, GroupByKind::NONE }); ActionsVisitor(visitor_data).visit(node); actions = visitor_data.getActions(); From d9928ac93d2d811e97af6ca0a817df4f397c8450 Mon Sep 17 00:00:00 2001 From: vdimir Date: Tue, 19 Jul 2022 10:28:21 +0000 Subject: [PATCH 130/672] Add methods to SubqueryForSet, do not use refernce to SetPtr --- src/Interpreters/ActionsVisitor.cpp | 14 ++-- src/Interpreters/GlobalSubqueriesVisitor.h | 8 +- src/Interpreters/InterpreterSelectQuery.h | 2 +- src/Interpreters/PreparedSets.cpp | 73 ++++++++++++------- src/Interpreters/PreparedSets.h | 28 +++++-- src/Processors/QueryPlan/CreatingSetsStep.cpp | 5 +- src/QueryPipeline/QueryPipelineBuilder.h | 2 +- src/Storages/MergeTree/KeyCondition.cpp | 33 +++++---- .../MergeTreeIndexConditionBloomFilter.cpp | 6 +- 9 files changed, 100 insertions(+), 71 deletions(-) diff --git a/src/Interpreters/ActionsVisitor.cpp b/src/Interpreters/ActionsVisitor.cpp index 2e95a5f906f..f6299d3d23b 100644 --- a/src/Interpreters/ActionsVisitor.cpp +++ b/src/Interpreters/ActionsVisitor.cpp @@ -1312,11 +1312,9 @@ SetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool no_su /// We get the stream of blocks for the subquery. Create Set and put it in place of the subquery. String set_id = right_in_operand->getColumnName(); - - SubqueryForSet & subquery_for_set = data.prepared_sets->createOrGetSubquery(set_id, set_key); - - if (subquery_for_set.set) - return subquery_for_set.set; + bool transform_null_in = data.getContext()->getSettingsRef().transform_null_in; + SubqueryForSet & subquery_for_set = data.prepared_sets->createOrGetSubquery( + set_id, set_key, std::make_shared(data.set_size_limit, false, transform_null_in)); /** The following happens for GLOBAL INs or INs: * - in the addExternalStorage function, the IN (SELECT ...) subquery is replaced with IN _data1, @@ -1326,14 +1324,12 @@ SetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool no_su * In case that we have HAVING with IN subquery, we have to force creating set for it. * Also it doesn't make sense if it is GLOBAL IN or ordinary IN. */ - if (!subquery_for_set.source && data.create_source_for_in) + if (data.create_source_for_in && !subquery_for_set.hasSource()) { auto interpreter = interpretSubquery(right_in_operand, data.getContext(), data.subquery_depth, {}); - subquery_for_set.source = std::make_unique(); - interpreter->buildQueryPlan(*subquery_for_set.source); + subquery_for_set.setSource(*interpreter); } - subquery_for_set.set = std::make_shared(data.set_size_limit, false, data.getContext()->getSettingsRef().transform_null_in); return subquery_for_set.set; } else diff --git a/src/Interpreters/GlobalSubqueriesVisitor.h b/src/Interpreters/GlobalSubqueriesVisitor.h index 829beedbafa..8f5efe77d2f 100644 --- a/src/Interpreters/GlobalSubqueriesVisitor.h +++ b/src/Interpreters/GlobalSubqueriesVisitor.h @@ -178,12 +178,8 @@ public: } else { - auto set_key = PreparedSetKey::forSubquery(*subquery_or_table_name); - auto & subquery_for_set = prepared_sets->createOrGetSubquery(external_table_name, set_key); - - subquery_for_set.source = std::make_unique(); - interpreter->buildQueryPlan(*subquery_for_set.source); - subquery_for_set.table = external_storage; + auto & subquery_for_set = prepared_sets->getSubquery(external_table_name); + subquery_for_set.setSource(*interpreter, external_storage); } /** NOTE If it was written IN tmp_table - the existing temporary (but not external) table, diff --git a/src/Interpreters/InterpreterSelectQuery.h b/src/Interpreters/InterpreterSelectQuery.h index 0a9ddb28acf..f2cdcbba9ed 100644 --- a/src/Interpreters/InterpreterSelectQuery.h +++ b/src/Interpreters/InterpreterSelectQuery.h @@ -23,7 +23,7 @@ class Logger; namespace DB { -struct SubqueryForSet; +class SubqueryForSet; class InterpreterSelectWithUnionQuery; class Context; class QueryPlan; diff --git a/src/Interpreters/PreparedSets.cpp b/src/Interpreters/PreparedSets.cpp index 5bfbf2b0705..f5cb4aa68b4 100644 --- a/src/Interpreters/PreparedSets.cpp +++ b/src/Interpreters/PreparedSets.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace DB { @@ -41,29 +42,35 @@ bool PreparedSetKey::operator==(const PreparedSetKey & other) const return true; } -SubqueryForSet & PreparedSets::createOrGetSubquery(const String & subquery_id, const PreparedSetKey & key) +SubqueryForSet & PreparedSets::createOrGetSubquery(const String & subquery_id, const PreparedSetKey & key, SetPtr set_) { - if (auto subqiery_it = subqueries.find(subquery_id); subqiery_it != subqueries.end()) + SubqueryForSet & subquery = subqueries[subquery_id]; + + /// If you already created a Set with the same subquery / table for another ast + /// In that case several PreparedSetKey would share same subquery and set + /// Not sure if it's really possible case (maybe for distributed query when set was filled by external table?) + if (subquery.set) + sets[key] = subquery.set; + else + sets[key] = subquery.set = set_; + return subquery; +} + +SubqueryForSet & PreparedSets::getSubquery(const String & subquery_id) { return subqueries[subquery_id]; } + +void PreparedSets::setSet(const PreparedSetKey & key, SetPtr set_) { sets[key] = set_; } + +SetPtr & PreparedSets::getSet(const PreparedSetKey & key) { return sets[key]; } + +std::vector PreparedSets::getByTreeHash(IAST::Hash ast_hash) +{ + std::vector res; + for (const auto & it : this->sets) { - /// If you already created a Set with the same subquery / table for another ast - /// In that case several PreparedSetKey would share same subquery and set - /// Not sure if it's really possible case (maybe for distributed query when set was filled by external table?) - if (subqiery_it->second.set) - sets[key] = subqiery_it->second.set; - return subqiery_it->second; + if (it.first.ast_hash == ast_hash) + res.push_back(it.second); } - - return subqueries.emplace(subquery_id, sets[key]).first->second; -} - -void PreparedSets::setSet(const PreparedSetKey & key, SetPtr set_) -{ - sets[key] = std::move(set_); -} - -SetPtr & PreparedSets::getSet(const PreparedSetKey & key) -{ - return sets[key]; + return res; } PreparedSets::SubqueriesForSets PreparedSets::moveSubqueries() @@ -73,15 +80,31 @@ PreparedSets::SubqueriesForSets PreparedSets::moveSubqueries() return res; } -bool PreparedSets::empty() const -{ - return sets.empty(); -} +bool PreparedSets::empty() const { return sets.empty(); } -SubqueryForSet::SubqueryForSet(SetPtr & set_) : set(set_) {} +SubqueryForSet::SubqueryForSet() = default; SubqueryForSet::~SubqueryForSet() = default; SubqueryForSet::SubqueryForSet(SubqueryForSet &&) noexcept = default; +void SubqueryForSet::setSource(InterpreterSelectWithUnionQuery & interpreter, StoragePtr table_) +{ + source = std::make_unique(); + interpreter.buildQueryPlan(*source); + table = table_; +} + +bool SubqueryForSet::hasSource() const +{ + return source != nullptr; +} + +QueryPlanPtr SubqueryForSet::moveSource() +{ + auto res = std::move(source); + source = nullptr; + return res; +} + }; diff --git a/src/Interpreters/PreparedSets.h b/src/Interpreters/PreparedSets.h index 4a209b7c798..b8a059830a8 100644 --- a/src/Interpreters/PreparedSets.h +++ b/src/Interpreters/PreparedSets.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -14,25 +15,36 @@ class QueryPlan; class Set; using SetPtr = std::shared_ptr; +class InterpreterSelectWithUnionQuery; /// Information on how to build set for the [GLOBAL] IN section. -struct SubqueryForSet +class SubqueryForSet { - explicit SubqueryForSet(SetPtr & set_); +public: + SubqueryForSet(); ~SubqueryForSet(); SubqueryForSet(SubqueryForSet &&) noexcept; SubqueryForSet & operator=(SubqueryForSet &&) noexcept; - /// The source is obtained using the InterpreterSelectQuery subquery. - std::unique_ptr source; + void setSource(InterpreterSelectWithUnionQuery & interpreter, StoragePtr table_ = nullptr); + + bool hasSource() const; + + /// Returns query plan for the source of the set + /// It would be removed from SubqueryForSet + std::unique_ptr moveSource(); /// Build this set from the result of the subquery. - SetPtr & set; + SetPtr set; /// If set, put the result into the table. /// This is a temporary table for transferring to remote servers for distributed query processing. StoragePtr table; + +private: + /// The source is obtained using the InterpreterSelectQuery subquery. + std::unique_ptr source; }; struct PreparedSetKey @@ -62,7 +74,8 @@ class PreparedSets public: using SubqueriesForSets = std::unordered_map; - SubqueryForSet & createOrGetSubquery(const String & subquery_id, const PreparedSetKey & key); + SubqueryForSet & createOrGetSubquery(const String & subquery_id, const PreparedSetKey & key, SetPtr set); + SubqueryForSet & getSubquery(const String & subquery_id); void setSet(const PreparedSetKey & key, SetPtr set_); SetPtr & getSet(const PreparedSetKey & key); @@ -70,8 +83,9 @@ public: /// Get subqueries and clear them SubqueriesForSets moveSubqueries(); + /// Returns all sets that match the given ast hash not checking types /// Used in KeyCondition and MergeTreeIndexConditionBloomFilter to make non exact match for types in PreparedSetKey - const std::unordered_map & getSetsMap() const { return sets; } + std::vector getByTreeHash(IAST::Hash ast_hash); bool empty() const; diff --git a/src/Processors/QueryPlan/CreatingSetsStep.cpp b/src/Processors/QueryPlan/CreatingSetsStep.cpp index 958e34bfd03..97ec3393a7d 100644 --- a/src/Processors/QueryPlan/CreatingSetsStep.cpp +++ b/src/Processors/QueryPlan/CreatingSetsStep.cpp @@ -133,11 +133,10 @@ void addCreatingSetsStep( for (auto & [description, subquery_for_set] : prepared_sets.moveSubqueries()) { - if (!subquery_for_set.source) + if (!subquery_for_set.hasSource()) continue; - auto plan = std::move(subquery_for_set.source); - subquery_for_set.source = nullptr; + auto plan = subquery_for_set.moveSource(); auto creating_set = std::make_unique( plan->getCurrentDataStream(), diff --git a/src/QueryPipeline/QueryPipelineBuilder.h b/src/QueryPipeline/QueryPipelineBuilder.h index 9147046cf12..bd6246d41ce 100644 --- a/src/QueryPipeline/QueryPipelineBuilder.h +++ b/src/QueryPipeline/QueryPipelineBuilder.h @@ -20,7 +20,7 @@ class QueryPlan; class PipelineExecutor; using PipelineExecutorPtr = std::shared_ptr; -struct SubqueryForSet; +class SubqueryForSet; struct SizeLimits; diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 31a3fead389..80b5f4282c9 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -900,25 +900,26 @@ bool KeyCondition::tryPrepareSetIndex( /// about types in left argument of the IN operator. Instead, we manually iterate through all the sets /// and find the one for the right arg based on the AST structure (getTreeHash), after that we check /// that the types it was prepared with are compatible with the types of the primary key. - auto set_ast_hash = right_arg->getTreeHash(); - const auto & sets_map = prepared_sets->getSetsMap(); - auto set_it = std::find_if( - sets_map.begin(), sets_map.end(), - [&](const auto & candidate_entry) - { - if (candidate_entry.first.ast_hash != set_ast_hash) + + auto types_match = [&indexes_mapping, &data_types](const SetPtr & candidate_set) + { + assert(indexes_mapping.size() == data_types.size()); + + for (size_t i = 0; i < indexes_mapping.size(); ++i) + if (!candidate_set->areTypesEqual(indexes_mapping[i].tuple_index, data_types[i])) return false; - for (size_t i = 0; i < indexes_mapping.size(); ++i) - if (!candidate_entry.second->areTypesEqual(indexes_mapping[i].tuple_index, data_types[i])) - return false; + return true; + }; - return true; - }); - if (set_it == sets_map.end()) - return false; - - prepared_set = set_it->second; + for (const auto & set : prepared_sets->getByTreeHash(right_arg->getTreeHash())) + { + if (types_match(set)) + { + prepared_set = set; + break; + } + } } /// The index can be prepared if the elements of the set were saved in advance. diff --git a/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp b/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp index 44b01df099f..f86255dc510 100644 --- a/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp @@ -697,9 +697,9 @@ SetPtr MergeTreeIndexConditionBloomFilter::getPreparedSet(const ASTPtr & node) } else { - for (const auto & prepared_set_it : query_info.sets->getSetsMap()) - if (prepared_set_it.first.ast_hash == node->getTreeHash() && prepared_set_it.second->hasExplicitSetElements()) - return prepared_set_it.second; + for (const auto & set : query_info.sets->getByTreeHash(node->getTreeHash())) + if (set->hasExplicitSetElements()) + return set; } return DB::SetPtr(); From 11d37a8dc9d2412e67046330bbf728a6cda07e79 Mon Sep 17 00:00:00 2001 From: vdimir Date: Wed, 20 Jul 2022 14:44:26 +0000 Subject: [PATCH 131/672] Properly initialize prepared_sets --- src/Interpreters/ExpressionAnalyzer.cpp | 5 ++++- src/Interpreters/InterpreterSelectQuery.cpp | 3 +++ src/Interpreters/PreparedSets.cpp | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index f7c6dd46233..b6a8456003e 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -234,7 +234,10 @@ ExpressionAnalyzer::ExpressionAnalyzer( , syntax(syntax_analyzer_result_) { /// Cache prepared sets because we might run analysis multiple times - prepared_sets = prepared_sets_; + if (prepared_sets_) + prepared_sets = prepared_sets_; + else + prepared_sets = std::make_shared(); /// external_tables, sets for global subqueries. /// Replaces global subqueries with the generated names of temporary tables that will be sent to remote servers. diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index a33c46ee248..3e322a62549 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -363,6 +363,9 @@ InterpreterSelectQuery::InterpreterSelectQuery( { checkStackSize(); + if (!prepared_sets) + prepared_sets = std::make_shared(); + query_info.ignore_projections = options.ignore_projections; query_info.is_projection_query = options.is_projection_query; diff --git a/src/Interpreters/PreparedSets.cpp b/src/Interpreters/PreparedSets.cpp index f5cb4aa68b4..cb85ef15f0f 100644 --- a/src/Interpreters/PreparedSets.cpp +++ b/src/Interpreters/PreparedSets.cpp @@ -92,7 +92,8 @@ void SubqueryForSet::setSource(InterpreterSelectWithUnionQuery & interpreter, St { source = std::make_unique(); interpreter.buildQueryPlan(*source); - table = table_; + if (table_) + table = table_; } bool SubqueryForSet::hasSource() const From 5ce2960f03cbea91ffcb9320e5c35d4abdf667dd Mon Sep 17 00:00:00 2001 From: vdimir Date: Wed, 20 Jul 2022 14:45:07 +0000 Subject: [PATCH 132/672] Get rid of SelectQueryInfoBase -> SelectQueryInfo --- src/Interpreters/InterpreterSelectQuery.cpp | 2 +- src/Storages/MergeTree/KeyCondition.cpp | 2 +- .../MergeTreeIndexConditionBloomFilter.cpp | 4 ++-- .../MergeTree/MergeTreeIndexFullText.cpp | 2 +- .../RocksDB/StorageEmbeddedRocksDB.cpp | 2 +- src/Storages/SelectQueryInfo.h | 19 +++++++------------ 6 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 3e322a62549..1e54d75b5d5 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -2015,7 +2015,7 @@ void InterpreterSelectQuery::executeFetchColumns(QueryProcessingStage::Enum proc SelectQueryInfo temp_query_info; temp_query_info.query = query_ptr; temp_query_info.syntax_analyzer_result = syntax_analyzer_result; - temp_query_info.sets = query_analyzer->getPreparedSets(); + temp_query_info.prepared_sets = query_analyzer->getPreparedSets(); num_rows = storage->totalRowsByPartitionPredicate(temp_query_info, context); } diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 80b5f4282c9..83e7eee263e 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -442,7 +442,7 @@ KeyCondition::KeyCondition( bool strict_) : key_expr(key_expr_) , key_subexpr_names(getAllSubexpressionNames(*key_expr)) - , prepared_sets(query_info.sets) + , prepared_sets(query_info.prepared_sets) , single_point(single_point_) , strict(strict_) { diff --git a/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp b/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp index f86255dc510..54b88c623f5 100644 --- a/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp @@ -692,12 +692,12 @@ SetPtr MergeTreeIndexConditionBloomFilter::getPreparedSet(const ASTPtr & node) { const auto & column_and_type = header.getByName(node->getColumnName()); auto set_key = getPreparedSetKey(node, column_and_type.type); - if (auto prepared_set = query_info.sets->getSet(set_key)) + if (auto prepared_set = query_info.prepared_sets->getSet(set_key)) return prepared_set; } else { - for (const auto & set : query_info.sets->getByTreeHash(node->getTreeHash())) + for (const auto & set : query_info.prepared_sets->getByTreeHash(node->getTreeHash())) if (set->hasExplicitSetElements()) return set; } diff --git a/src/Storages/MergeTree/MergeTreeIndexFullText.cpp b/src/Storages/MergeTree/MergeTreeIndexFullText.cpp index 8902c41876d..8cf4b615e56 100644 --- a/src/Storages/MergeTree/MergeTreeIndexFullText.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexFullText.cpp @@ -146,7 +146,7 @@ MergeTreeConditionFullText::MergeTreeConditionFullText( , index_data_types(index_sample_block.getNamesAndTypesList().getTypes()) , params(params_) , token_extractor(token_extactor_) - , prepared_sets(query_info.sets) + , prepared_sets(query_info.prepared_sets) { rpn = std::move( RPNBuilder( diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp index da7d94d27a0..05496f817f3 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp @@ -175,7 +175,7 @@ static std::pair getFilterKeys( return {{}, true}; FieldVectorPtr res = std::make_shared(); - auto matched_keys = traverseASTFilter(primary_key, primary_key_type, select.where(), query_info.sets, res); + auto matched_keys = traverseASTFilter(primary_key, primary_key_type, select.where(), query_info.prepared_sets, res); return std::make_pair(res, !matched_keys); } diff --git a/src/Storages/SelectQueryInfo.h b/src/Storages/SelectQueryInfo.h index 8e6009c07bf..143d9a1b616 100644 --- a/src/Storages/SelectQueryInfo.h +++ b/src/Storages/SelectQueryInfo.h @@ -132,8 +132,13 @@ struct ProjectionCandidate * that can be used during query processing * inside storage engines. */ -struct SelectQueryInfoBase +struct SelectQueryInfo { + + SelectQueryInfo() + : prepared_sets(std::make_shared()) + {} + ASTPtr query; ASTPtr view_query; /// Optimized VIEW query ASTPtr original_query; /// Unmodified query for projection analysis @@ -162,7 +167,7 @@ struct SelectQueryInfoBase /// Prepared sets are used for indices by storage engine. /// Example: x IN (1, 2, 3) - PreparedSetsPtr sets; + PreparedSetsPtr prepared_sets; /// Cached value of ExpressionAnalysisResult::has_window bool has_window = false; @@ -179,14 +184,4 @@ struct SelectQueryInfoBase MergeTreeDataSelectAnalysisResultPtr merge_tree_select_result_ptr; }; -/// Contains non-copyable stuff -struct SelectQueryInfo : SelectQueryInfoBase -{ - SelectQueryInfo() = default; - SelectQueryInfo(const SelectQueryInfo & other) : SelectQueryInfoBase(other) {} - - /// Make sets reusable across different interpreters. - PreparedSetsPtr prepared_sets; -}; - } From 1adb021df6c45507a9fba68adbd2024d7eb9b63f Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 26 Jul 2022 18:42:27 +0000 Subject: [PATCH 133/672] Fixing tests. --- src/Storages/StorageMerge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/StorageMerge.cpp b/src/Storages/StorageMerge.cpp index 5f7cd776d11..69666e2de35 100644 --- a/src/Storages/StorageMerge.cpp +++ b/src/Storages/StorageMerge.cpp @@ -439,7 +439,7 @@ void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const Bu has_database_virtual_column, has_table_virtual_column); - if (source_pipeline->initialized()) + if (source_pipeline && source_pipeline->initialized()) { resources.storage_holders.push_back(std::get<1>(table)); resources.table_locks.push_back(std::get<2>(table)); From 0db9dda5f338afae0b0f9f29c7c7cb9b4750bc2f Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Tue, 26 Jul 2022 23:10:58 +0200 Subject: [PATCH 134/672] Attempt to fix wrong workflow_run data for rerun --- tests/ci/workflow_approve_rerun_lambda/app.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/ci/workflow_approve_rerun_lambda/app.py b/tests/ci/workflow_approve_rerun_lambda/app.py index 43f15059980..d1910f2ea3e 100644 --- a/tests/ci/workflow_approve_rerun_lambda/app.py +++ b/tests/ci/workflow_approve_rerun_lambda/app.py @@ -402,10 +402,27 @@ def main(event): workflow_description = get_workflow_description_from_event(event_data) print("Got workflow description", workflow_description) - if ( - workflow_description.action == "completed" - and workflow_description.conclusion == "failure" - ): + if workflow_description.action == "completed": + attempt = 0 + # Nice and reliable GH API sends from time to time such events, e.g: + # action='completed', conclusion=None, status='in_progress', + # So let's try receiving a real workflow data + while workflow_description.conclusion is None and attempt < MAX_RETRY: + progressive_sleep = 3 * sum(i + 1 for i in range(attempt)) + time.sleep(progressive_sleep) + event_data["workflow_run"] = _exec_get_with_retry( + workflow_description.api_url + ) + workflow_description = get_workflow_description_from_event(event_data) + attempt += 1 + + if workflow_description.conclusion != "failure": + print( + "Workflow finished with status " + f"{workflow_description.conclusion}, exiting" + ) + return + print( "Workflow", workflow_description.url, From 50fdbcdc123ecb877f58c01260dd6b90f46b01b1 Mon Sep 17 00:00:00 2001 From: Wangyang Guo Date: Mon, 18 Jul 2022 13:56:25 +0800 Subject: [PATCH 135/672] CpuId: add AVX512VBMI2 detection --- src/Common/CpuId.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Common/CpuId.h b/src/Common/CpuId.h index 167fa22faf6..1e54ccf62b3 100644 --- a/src/Common/CpuId.h +++ b/src/Common/CpuId.h @@ -82,6 +82,7 @@ inline bool cpuid(UInt32 op, UInt32 * res) noexcept /// NOLINT OP(AVX512BW) \ OP(AVX512VL) \ OP(AVX512VBMI) \ + OP(AVX512VBMI2) \ OP(PREFETCHWT1) \ OP(SHA) \ OP(ADX) \ @@ -302,6 +303,11 @@ bool haveAVX512VBMI() noexcept return haveAVX512F() && ((CpuInfo(0x7, 0).registers.ecx >> 1) & 1u); } +bool haveAVX512VBMI2() noexcept +{ + return haveAVX512F() && ((CpuInfo(0x7, 0).registers.ecx >> 6) & 1u); +} + bool haveRDRAND() noexcept { return CpuInfo(0x0).registers.eax >= 0x7 && ((CpuInfo(0x1).registers.ecx >> 30) & 1u); From e6752d687f1cf4c56d8b95f163400523f89503f3 Mon Sep 17 00:00:00 2001 From: Wangyang Guo Date: Mon, 18 Jul 2022 14:30:03 +0800 Subject: [PATCH 136/672] TargetSpecific: add AVX512VBMI2 support --- src/Common/TargetSpecific.cpp | 7 +++++-- src/Common/TargetSpecific.h | 31 +++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/Common/TargetSpecific.cpp b/src/Common/TargetSpecific.cpp index 70b03833775..a5fbe7de078 100644 --- a/src/Common/TargetSpecific.cpp +++ b/src/Common/TargetSpecific.cpp @@ -20,6 +20,8 @@ UInt32 getSupportedArchs() result |= static_cast(TargetArch::AVX512BW); if (Cpu::CpuFlagsCache::have_AVX512VBMI) result |= static_cast(TargetArch::AVX512VBMI); + if (Cpu::CpuFlagsCache::have_AVX512VBMI2) + result |= static_cast(TargetArch::AVX512VBMI2); return result; } @@ -38,8 +40,9 @@ String toString(TargetArch arch) case TargetArch::AVX: return "avx"; case TargetArch::AVX2: return "avx2"; case TargetArch::AVX512F: return "avx512f"; - case TargetArch::AVX512BW: return "avx512bw"; - case TargetArch::AVX512VBMI: return "avx512vbmi"; + case TargetArch::AVX512BW: return "avx512bw"; + case TargetArch::AVX512VBMI: return "avx512vbmi"; + case TargetArch::AVX512VBMI2: return "avx512vbmi"; } __builtin_unreachable(); diff --git a/src/Common/TargetSpecific.h b/src/Common/TargetSpecific.h index f078c0e3ffc..4cd47bb1d3a 100644 --- a/src/Common/TargetSpecific.h +++ b/src/Common/TargetSpecific.h @@ -80,8 +80,9 @@ enum class TargetArch : UInt32 AVX = (1 << 1), AVX2 = (1 << 2), AVX512F = (1 << 3), - AVX512BW = (1 << 4), - AVX512VBMI = (1 << 5), + AVX512BW = (1 << 4), + AVX512VBMI = (1 << 5), + AVX512VBMI2 = (1 << 6), }; /// Runtime detection. @@ -100,6 +101,7 @@ String toString(TargetArch arch); #if defined(__clang__) +#define AVX512VBMI2_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,avx512vbmi2"))) #define AVX512VBMI_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi"))) #define AVX512BW_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw"))) #define AVX512_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f"))) @@ -108,6 +110,8 @@ String toString(TargetArch arch); #define SSE42_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt"))) #define DEFAULT_FUNCTION_SPECIFIC_ATTRIBUTE +# define BEGIN_AVX512VBMI2_SPECIFIC_CODE \ + _Pragma("clang attribute push(__attribute__((target(\"sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,avx512vbmi2\"))),apply_to=function)") # define BEGIN_AVX512VBMI_SPECIFIC_CODE \ _Pragma("clang attribute push(__attribute__((target(\"sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi\"))),apply_to=function)") # define BEGIN_AVX512BW_SPECIFIC_CODE \ @@ -129,6 +133,7 @@ String toString(TargetArch arch); # define DUMMY_FUNCTION_DEFINITION [[maybe_unused]] void _dummy_function_definition(); #else +#define AVX512VBMI2_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,avx512vbmi2,tune=native"))) #define AVX512VBMI_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,tune=native"))) #define AVX512BW_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,tune=native"))) #define AVX512_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,tune=native"))) @@ -137,6 +142,9 @@ String toString(TargetArch arch); #define SSE42_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt",tune=native))) #define DEFAULT_FUNCTION_SPECIFIC_ATTRIBUTE +# define BEGIN_AVX512VBMI2_SPECIFIC_CODE \ + _Pragma("GCC push_options") \ + _Pragma("GCC target(\"sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,avx512vbmi2,tune=native\")") # define BEGIN_AVX512VBMI_SPECIFIC_CODE \ _Pragma("GCC push_options") \ _Pragma("GCC target(\"sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,tune=native\")") @@ -217,6 +225,16 @@ namespace TargetSpecific::AVX512VBMI { \ } \ END_TARGET_SPECIFIC_CODE +#define DECLARE_AVX512VBMI2_SPECIFIC_CODE(...) \ +BEGIN_AVX512VBMI2_SPECIFIC_CODE \ +namespace TargetSpecific::AVX512VBMI2 { \ + DUMMY_FUNCTION_DEFINITION \ + using namespace DB::TargetSpecific::AVX512VBMI2; \ + __VA_ARGS__ \ +} \ +END_TARGET_SPECIFIC_CODE + + #else #define USE_MULTITARGET_CODE 0 @@ -229,6 +247,7 @@ END_TARGET_SPECIFIC_CODE #define DECLARE_AVX512F_SPECIFIC_CODE(...) #define DECLARE_AVX512BW_SPECIFIC_CODE(...) #define DECLARE_AVX512VBMI_SPECIFIC_CODE(...) +#define DECLARE_AVX512VBMI2_SPECIFIC_CODE(...) #endif @@ -245,8 +264,9 @@ DECLARE_SSE42_SPECIFIC_CODE (__VA_ARGS__) \ DECLARE_AVX_SPECIFIC_CODE (__VA_ARGS__) \ DECLARE_AVX2_SPECIFIC_CODE (__VA_ARGS__) \ DECLARE_AVX512F_SPECIFIC_CODE(__VA_ARGS__) \ -DECLARE_AVX512BW_SPECIFIC_CODE(__VA_ARGS__) \ -DECLARE_AVX512VBMI_SPECIFIC_CODE(__VA_ARGS__) +DECLARE_AVX512BW_SPECIFIC_CODE (__VA_ARGS__) \ +DECLARE_AVX512VBMI_SPECIFIC_CODE (__VA_ARGS__) \ +DECLARE_AVX512VBMI2_SPECIFIC_CODE (__VA_ARGS__) DECLARE_DEFAULT_CODE( constexpr auto BuildArch = TargetArch::Default; /// NOLINT @@ -276,6 +296,9 @@ DECLARE_AVX512VBMI_SPECIFIC_CODE( constexpr auto BuildArch = TargetArch::AVX512VBMI; /// NOLINT ) // DECLARE_AVX512VBMI_SPECIFIC_CODE +DECLARE_AVX512VBMI2_SPECIFIC_CODE( + constexpr auto BuildArch = TargetArch::AVX512VBMI2; /// NOLINT +) // DECLARE_AVX512VBMI2_SPECIFIC_CODE /** Runtime Dispatch helpers for class members. * From 7820e8278d0248d66e28126b6766a82315aeb2f0 Mon Sep 17 00:00:00 2001 From: Wangyang Guo Date: Tue, 19 Jul 2022 16:48:02 +0800 Subject: [PATCH 137/672] ColumnVector: optimize filter with AVX512VBMI2 compress store --- src/Columns/ColumnVector.cpp | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/Columns/ColumnVector.cpp b/src/Columns/ColumnVector.cpp index 60423e2b0fe..d6847d99f7f 100644 --- a/src/Columns/ColumnVector.cpp +++ b/src/Columns/ColumnVector.cpp @@ -12,12 +12,14 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -25,6 +27,10 @@ # include #endif +#if USE_MULTITARGET_CODE +# include +#endif + #if USE_EMBEDDED_COMPILER #include #include @@ -471,6 +477,65 @@ void ColumnVector::insertRangeFrom(const IColumn & src, size_t start, size_t memcpy(data.data() + old_size, &src_vec.data[start], length * sizeof(data[0])); } + +DECLARE_AVX512VBMI2_SPECIFIC_CODE( +template +inline void compressStoreAVX512(const void *src, void *dst, const UInt64 mask) +{ + __m512i vsrc = _mm512_loadu_si512(src); + if (ELEMENT_SIZE == 1) + _mm512_mask_compressstoreu_epi8(dst, static_cast<__mmask64>(mask), vsrc); + else if (ELEMENT_SIZE == 2) + _mm512_mask_compressstoreu_epi16(dst, static_cast<__mmask32>(mask), vsrc); + else if (ELEMENT_SIZE == 4) + _mm512_mask_compressstoreu_epi32(dst, static_cast<__mmask16>(mask), vsrc); + else if (ELEMENT_SIZE == 8) + _mm512_mask_compressstoreu_epi64(dst, static_cast<__mmask8>(mask), vsrc); +} + +template +inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_aligned, const T *& data_pos, Container & res_data) +{ + static constexpr size_t VEC_LEN = 64; + static constexpr size_t ELEMENT_SIZE = sizeof(T); + static constexpr size_t ELEMENT_PER_VEC = VEC_LEN / ELEMENT_SIZE; + static constexpr UInt64 kmask = 0xffffffffffffffff >> (64 - ELEMENT_PER_VEC); + while (filt_pos < filt_end_aligned) + { + UInt64 mask = bytes64MaskToBits64Mask(filt_pos); + + if (0xffffffffffffffff == mask) + { + res_data.insert(data_pos, data_pos + SIMD_BYTES); + } + else + { + if (mask) + { + size_t current_offset = res_data.size(); + int count = std::popcount(mask); + /// reserve and resize for later writing + res_data.resize(current_offset + count); + for (int i = 0; i < 64; i += ELEMENT_PER_VEC) + { + compressStoreAVX512(reinterpret_cast(data_pos + i), + reinterpret_cast(&res_data[current_offset]), mask & kmask); + /// prepare for next iter, if ELEMENT_PER_VEC = 64, no next iter + if (ELEMENT_PER_VEC < 64) + { + mask >>= ELEMENT_PER_VEC; + current_offset += std::popcount(mask & kmask); + } + } + } + } + + filt_pos += SIMD_BYTES; + data_pos += SIMD_BYTES; + } +} +) + template ColumnPtr ColumnVector::filter(const IColumn::Filter & filt, ssize_t result_size_hint) const { @@ -496,6 +561,14 @@ ColumnPtr ColumnVector::filter(const IColumn::Filter & filt, ssize_t result_s static constexpr size_t SIMD_BYTES = 64; const UInt8 * filt_end_aligned = filt_pos + size / SIMD_BYTES * SIMD_BYTES; +#if USE_MULTITARGET_CODE + if (sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8) + { + if (isArchSupported(TargetArch::AVX512VBMI2)) + TargetSpecific::AVX512VBMI2::doFilterAligned(filt_pos, filt_end_aligned, data_pos, res_data); + } +#endif + while (filt_pos < filt_end_aligned) { UInt64 mask = bytes64MaskToBits64Mask(filt_pos); From d781ed577933f719bf5b1d3b2057a8e52283b506 Mon Sep 17 00:00:00 2001 From: Wangyang Guo Date: Wed, 20 Jul 2022 19:22:33 +0800 Subject: [PATCH 138/672] ColumnVector: bug fix for unit test failure --- src/Columns/ColumnVector.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Columns/ColumnVector.cpp b/src/Columns/ColumnVector.cpp index d6847d99f7f..98a72d7cde5 100644 --- a/src/Columns/ColumnVector.cpp +++ b/src/Columns/ColumnVector.cpp @@ -496,7 +496,7 @@ inline void compressStoreAVX512(const void *src, void *dst, const UInt64 mask) template inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_aligned, const T *& data_pos, Container & res_data) { - static constexpr size_t VEC_LEN = 64; + static constexpr size_t VEC_LEN = 64; /// AVX512 vector length - 64 bytes static constexpr size_t ELEMENT_SIZE = sizeof(T); static constexpr size_t ELEMENT_PER_VEC = VEC_LEN / ELEMENT_SIZE; static constexpr UInt64 kmask = 0xffffffffffffffff >> (64 - ELEMENT_PER_VEC); @@ -516,15 +516,15 @@ inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_ali int count = std::popcount(mask); /// reserve and resize for later writing res_data.resize(current_offset + count); - for (int i = 0; i < 64; i += ELEMENT_PER_VEC) + for (size_t i = 0; i < SIMD_BYTES; i += ELEMENT_PER_VEC) { compressStoreAVX512(reinterpret_cast(data_pos + i), reinterpret_cast(&res_data[current_offset]), mask & kmask); /// prepare for next iter, if ELEMENT_PER_VEC = 64, no next iter if (ELEMENT_PER_VEC < 64) { - mask >>= ELEMENT_PER_VEC; current_offset += std::popcount(mask & kmask); + mask >>= ELEMENT_PER_VEC; } } } From 6d7bfc3b2aec97c1c121a8c774d7bf84debe707f Mon Sep 17 00:00:00 2001 From: Wangyang Guo Date: Thu, 21 Jul 2022 10:49:52 +0800 Subject: [PATCH 139/672] ColumnVector: add unit test for filter --- src/Columns/tests/gtest_column_vector.cpp | 90 +++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/Columns/tests/gtest_column_vector.cpp diff --git a/src/Columns/tests/gtest_column_vector.cpp b/src/Columns/tests/gtest_column_vector.cpp new file mode 100644 index 00000000000..a7d8de3fc00 --- /dev/null +++ b/src/Columns/tests/gtest_column_vector.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include + + +using namespace DB; + +static pcg64 rng(randomSeed()); +static constexpr int error_code = 12345; +static constexpr size_t TEST_RUNS = 500; +static constexpr size_t MAX_ROWS = 10000; +static constexpr size_t filter_ratios[] = {1, 2, 5, 11, 32, 64, 100, 1000}; +static constexpr size_t K = sizeof(filter_ratios) / sizeof(filter_ratios[0]); + +template +static MutableColumnPtr createColumn(size_t n) +{ + auto column = ColumnVector::create(); + auto & values = column->getData(); + + for (size_t i = 0; i < n; ++i) + { + values.push_back(i); + } + + return column; +} + +bool checkFilter(const PaddedPODArray &flit, const IColumn & src, const IColumn & dst) +{ + size_t n = flit.size(); + size_t dst_size = dst.size(); + size_t j = 0; /// index of dest + for (size_t i = 0; i < n; ++i) + { + if (flit[i] != 0) + { + if ((dst_size <= j) || (src.compareAt(i, j, dst, 0) != 0)) + return false; + j++; + } + } + return dst_size == j; /// filtered size check +} + +template +static void testFilter() +{ + auto test_case = [&](size_t n, size_t m) + { + auto vector_column = createColumn(n); + PaddedPODArray flit(n); + for (size_t i = 0; i < n; ++i) + flit[i] = rng() % m == 0; + auto filt_column = vector_column->filter(flit, -1); + + if (!checkFilter(flit, *vector_column, *filt_column)) + throw Exception(error_code, "VectorColumn filter failure, type: {}", typeid(T).name()); + }; + + try + { + for (size_t i = 0; i < TEST_RUNS; ++i) + { + size_t n = rng() % MAX_ROWS + 1; + size_t m = filter_ratios[rng() % K]; + + test_case(n, m); + } + } + catch (const Exception & e) + { + FAIL() << e.displayText(); + } +} + + +TEST(ColumnVector, Filter) +{ + testFilter(); + testFilter(); + testFilter(); + testFilter(); + testFilter(); + testFilter(); + testFilter(); + testFilter(); + testFilter(); +} From b77214792e6a12bb23b5ad765730106c3209d683 Mon Sep 17 00:00:00 2001 From: Wangyang Guo Date: Mon, 25 Jul 2022 15:48:59 +0800 Subject: [PATCH 140/672] ColumnVector: avoid calling resize too frequently --- src/Columns/ColumnVector.cpp | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Columns/ColumnVector.cpp b/src/Columns/ColumnVector.cpp index 98a72d7cde5..77b736fe054 100644 --- a/src/Columns/ColumnVector.cpp +++ b/src/Columns/ColumnVector.cpp @@ -500,30 +500,42 @@ inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_ali static constexpr size_t ELEMENT_SIZE = sizeof(T); static constexpr size_t ELEMENT_PER_VEC = VEC_LEN / ELEMENT_SIZE; static constexpr UInt64 kmask = 0xffffffffffffffff >> (64 - ELEMENT_PER_VEC); + + size_t current_offset = res_data.size(); + size_t reserve_size = res_data.size(); + size_t alloc_size = SIMD_BYTES * 2; + while (filt_pos < filt_end_aligned) { + /// to avoid calling resize too frequently, resize to reserve buffer. + if (reserve_size - current_offset < SIMD_BYTES) + { + reserve_size += alloc_size; + res_data.resize(reserve_size); + alloc_size *= 2; + } + UInt64 mask = bytes64MaskToBits64Mask(filt_pos); if (0xffffffffffffffff == mask) { - res_data.insert(data_pos, data_pos + SIMD_BYTES); + for (size_t i = 0; i < SIMD_BYTES; i += ELEMENT_PER_VEC) + _mm512_storeu_si512(reinterpret_cast(&res_data[current_offset + i]), + _mm512_loadu_si512(reinterpret_cast(data_pos + i))); + current_offset += SIMD_BYTES; } else { if (mask) { - size_t current_offset = res_data.size(); - int count = std::popcount(mask); - /// reserve and resize for later writing - res_data.resize(current_offset + count); for (size_t i = 0; i < SIMD_BYTES; i += ELEMENT_PER_VEC) { compressStoreAVX512(reinterpret_cast(data_pos + i), reinterpret_cast(&res_data[current_offset]), mask & kmask); - /// prepare for next iter, if ELEMENT_PER_VEC = 64, no next iter + current_offset += std::popcount(mask & kmask); + /// prepare mask for next iter, if ELEMENT_PER_VEC = 64, no next iter if (ELEMENT_PER_VEC < 64) { - current_offset += std::popcount(mask & kmask); mask >>= ELEMENT_PER_VEC; } } @@ -533,6 +545,8 @@ inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_ali filt_pos += SIMD_BYTES; data_pos += SIMD_BYTES; } + /// resize to the real size. + res_data.resize(current_offset); } ) From 9553b8964ca5d7dfe5e18f19760e27c53a98600a Mon Sep 17 00:00:00 2001 From: avogar Date: Wed, 27 Jul 2022 09:39:57 +0000 Subject: [PATCH 141/672] Fix tests --- tests/queries/0_stateless/02366_cancel_write_into_file.sh | 4 ++-- tests/queries/0_stateless/02367_cancel_write_into_s3.sh | 4 ++-- tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh | 4 ++-- tests/queries/0_stateless/test_ugtxj2/tuples | 0 4 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 tests/queries/0_stateless/test_ugtxj2/tuples diff --git a/tests/queries/0_stateless/02366_cancel_write_into_file.sh b/tests/queries/0_stateless/02366_cancel_write_into_file.sh index 4f999c95690..f7aca608aab 100755 --- a/tests/queries/0_stateless/02366_cancel_write_into_file.sh +++ b/tests/queries/0_stateless/02366_cancel_write_into_file.sh @@ -13,7 +13,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" 2>&1 > /dev/null for i in $(seq 1 10); do @@ -22,5 +22,5 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" 2>&1 > /dev/null diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh index ab9f4eaaa69..bea990e32e3 100755 --- a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh +++ b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh @@ -13,7 +13,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" 2>&1 > /dev/null for i in $(seq 1 10); do @@ -22,4 +22,4 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" 2>&1 > /dev/null diff --git a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh index ac0d6d35de5..405f059b52e 100755 --- a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh +++ b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh @@ -13,7 +13,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" 2>&1 > /dev/null for i in $(seq 1 10); do @@ -22,4 +22,4 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" 2>&1 /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" 2>&1 > /dev/null diff --git a/tests/queries/0_stateless/test_ugtxj2/tuples b/tests/queries/0_stateless/test_ugtxj2/tuples new file mode 100644 index 00000000000..e69de29bb2d From 8eecb9ef8236d6b87f67628b5fd21f1f573320cc Mon Sep 17 00:00:00 2001 From: vdimir Date: Wed, 27 Jul 2022 11:22:16 +0000 Subject: [PATCH 142/672] upd PreparedSets: rename/change signature of methods, add comments --- src/Interpreters/ActionsVisitor.cpp | 13 ++++----- src/Interpreters/ExpressionAnalyzer.cpp | 6 ++-- src/Interpreters/GlobalSubqueriesVisitor.h | 2 +- src/Interpreters/InterpreterSelectQuery.cpp | 8 +----- src/Interpreters/MutationsInterpreter.cpp | 9 +----- src/Interpreters/PreparedSets.cpp | 24 ++++++++-------- src/Interpreters/PreparedSets.h | 28 +++++++++---------- src/Processors/QueryPlan/CreatingSetsStep.cpp | 14 ++++++---- src/Processors/QueryPlan/CreatingSetsStep.h | 6 +--- src/Storages/MergeTree/KeyCondition.cpp | 2 +- .../MergeTreeIndexConditionBloomFilter.cpp | 2 +- .../MergeTree/MergeTreeIndexFullText.cpp | 2 +- .../RocksDB/StorageEmbeddedRocksDB.cpp | 2 +- 13 files changed, 51 insertions(+), 67 deletions(-) diff --git a/src/Interpreters/ActionsVisitor.cpp b/src/Interpreters/ActionsVisitor.cpp index f6299d3d23b..e545347b3ae 100644 --- a/src/Interpreters/ActionsVisitor.cpp +++ b/src/Interpreters/ActionsVisitor.cpp @@ -401,7 +401,7 @@ SetPtr makeExplicitSet( element_type = low_cardinality_type->getDictionaryType(); auto set_key = PreparedSetKey::forLiteral(*right_arg, set_element_types); - if (auto set = prepared_sets.getSet(set_key)) + if (auto set = prepared_sets.get(set_key)) return set; /// Already prepared. Block block; @@ -417,7 +417,7 @@ SetPtr makeExplicitSet( set->insertFromBlock(block.getColumnsWithTypeAndName()); set->finishInsert(); - prepared_sets.setSet(set_key, set); + prepared_sets.set(set_key, set); return set; } @@ -1288,7 +1288,7 @@ SetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool no_su if (no_subqueries) return {}; auto set_key = PreparedSetKey::forSubquery(*right_in_operand); - if (SetPtr set = data.prepared_sets->getSet(set_key)) + if (SetPtr set = data.prepared_sets->get(set_key)) return set; /// A special case is if the name of the table is specified on the right side of the IN statement, @@ -1304,7 +1304,7 @@ SetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool no_su if (storage_set) { SetPtr set = storage_set->getSet(); - data.prepared_sets->setSet(set_key, set); + data.prepared_sets->set(set_key, set); return set; } } @@ -1313,8 +1313,7 @@ SetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool no_su /// We get the stream of blocks for the subquery. Create Set and put it in place of the subquery. String set_id = right_in_operand->getColumnName(); bool transform_null_in = data.getContext()->getSettingsRef().transform_null_in; - SubqueryForSet & subquery_for_set = data.prepared_sets->createOrGetSubquery( - set_id, set_key, std::make_shared(data.set_size_limit, false, transform_null_in)); + SubqueryForSet & subquery_for_set = data.prepared_sets->createOrGetSubquery(set_id, set_key, data.set_size_limit, transform_null_in); /** The following happens for GLOBAL INs or INs: * - in the addExternalStorage function, the IN (SELECT ...) subquery is replaced with IN _data1, @@ -1327,7 +1326,7 @@ SetPtr ActionsMatcher::makeSet(const ASTFunction & node, Data & data, bool no_su if (data.create_source_for_in && !subquery_for_set.hasSource()) { auto interpreter = interpretSubquery(right_in_operand, data.getContext(), data.subquery_depth, {}); - subquery_for_set.setSource(*interpreter); + subquery_for_set.createSource(*interpreter); } return subquery_for_set.set; diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index b6a8456003e..d4ca51ea107 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -516,12 +516,12 @@ void ExpressionAnalyzer::tryMakeSetForIndexFromSubquery(const ASTPtr & subquery_ auto set_key = PreparedSetKey::forSubquery(*subquery_or_table_name); - if (prepared_sets->getSet(set_key)) + if (prepared_sets->get(set_key)) return; /// Already prepared. if (auto set_ptr_from_storage_set = isPlainStorageSetInSubquery(subquery_or_table_name)) { - prepared_sets->setSet(set_key, set_ptr_from_storage_set); + prepared_sets->set(set_key, set_ptr_from_storage_set); return; } @@ -545,7 +545,7 @@ void ExpressionAnalyzer::tryMakeSetForIndexFromSubquery(const ASTPtr & subquery_ set->finishInsert(); - prepared_sets->setSet(set_key, std::move(set)); + prepared_sets->set(set_key, std::move(set)); } SetPtr ExpressionAnalyzer::isPlainStorageSetInSubquery(const ASTPtr & subquery_or_table_name) diff --git a/src/Interpreters/GlobalSubqueriesVisitor.h b/src/Interpreters/GlobalSubqueriesVisitor.h index 8f5efe77d2f..b62fe817b84 100644 --- a/src/Interpreters/GlobalSubqueriesVisitor.h +++ b/src/Interpreters/GlobalSubqueriesVisitor.h @@ -179,7 +179,7 @@ public: else { auto & subquery_for_set = prepared_sets->getSubquery(external_table_name); - subquery_for_set.setSource(*interpreter, external_storage); + subquery_for_set.createSource(*interpreter, external_storage); } /** NOTE If it was written IN tmp_table - the existing temporary (but not external) table, diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 1e54d75b5d5..25a7a5f89fc 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -2829,13 +2829,7 @@ void InterpreterSelectQuery::executeExtremes(QueryPlan & query_plan) void InterpreterSelectQuery::executeSubqueriesInSetsAndJoins(QueryPlan & query_plan) { - if (!prepared_sets || prepared_sets->empty()) - return; - - const Settings & settings = context->getSettingsRef(); - - SizeLimits limits(settings.max_rows_to_transfer, settings.max_bytes_to_transfer, settings.transfer_overflow_mode); - addCreatingSetsStep(query_plan, *prepared_sets, limits, context); + addCreatingSetsStep(query_plan, prepared_sets, context); } diff --git a/src/Interpreters/MutationsInterpreter.cpp b/src/Interpreters/MutationsInterpreter.cpp index ed13ec0040d..e66ac9a5838 100644 --- a/src/Interpreters/MutationsInterpreter.cpp +++ b/src/Interpreters/MutationsInterpreter.cpp @@ -912,14 +912,7 @@ QueryPipelineBuilder MutationsInterpreter::addStreamsForLaterStages(const std::v } } - PreparedSetsPtr prepared_sets = stage.analyzer->getPreparedSets(); - if (prepared_sets && !prepared_sets->empty()) - { - const Settings & settings = context->getSettingsRef(); - SizeLimits network_transfer_limits( - settings.max_rows_to_transfer, settings.max_bytes_to_transfer, settings.transfer_overflow_mode); - addCreatingSetsStep(plan, *prepared_sets, network_transfer_limits, context); - } + addCreatingSetsStep(plan, stage.analyzer->getPreparedSets(), context); } QueryPlanOptimizationSettings do_not_optimize_plan; diff --git a/src/Interpreters/PreparedSets.cpp b/src/Interpreters/PreparedSets.cpp index cb85ef15f0f..79cfb8b688a 100644 --- a/src/Interpreters/PreparedSets.cpp +++ b/src/Interpreters/PreparedSets.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace DB { @@ -42,7 +43,8 @@ bool PreparedSetKey::operator==(const PreparedSetKey & other) const return true; } -SubqueryForSet & PreparedSets::createOrGetSubquery(const String & subquery_id, const PreparedSetKey & key, SetPtr set_) +SubqueryForSet & PreparedSets::createOrGetSubquery(const String & subquery_id, const PreparedSetKey & key, + SizeLimits set_size_limit, bool transform_null_in) { SubqueryForSet & subquery = subqueries[subquery_id]; @@ -52,15 +54,17 @@ SubqueryForSet & PreparedSets::createOrGetSubquery(const String & subquery_id, c if (subquery.set) sets[key] = subquery.set; else - sets[key] = subquery.set = set_; + sets[key] = subquery.set = std::make_shared(set_size_limit, false, transform_null_in); return subquery; } +/// If the subquery is not associated with any set, create default-constructed SubqueryForSet. +/// It's aimed to fill external table passed to SubqueryForSet::createSource. SubqueryForSet & PreparedSets::getSubquery(const String & subquery_id) { return subqueries[subquery_id]; } -void PreparedSets::setSet(const PreparedSetKey & key, SetPtr set_) { sets[key] = set_; } +void PreparedSets::set(const PreparedSetKey & key, SetPtr set_) { sets[key] = set_; } -SetPtr & PreparedSets::getSet(const PreparedSetKey & key) { return sets[key]; } +SetPtr & PreparedSets::get(const PreparedSetKey & key) { return sets[key]; } std::vector PreparedSets::getByTreeHash(IAST::Hash ast_hash) { @@ -73,7 +77,7 @@ std::vector PreparedSets::getByTreeHash(IAST::Hash ast_hash) return res; } -PreparedSets::SubqueriesForSets PreparedSets::moveSubqueries() +PreparedSets::SubqueriesForSets PreparedSets::detachSubqueries() { auto res = std::move(subqueries); subqueries = SubqueriesForSets(); @@ -82,13 +86,7 @@ PreparedSets::SubqueriesForSets PreparedSets::moveSubqueries() bool PreparedSets::empty() const { return sets.empty(); } -SubqueryForSet::SubqueryForSet() = default; - -SubqueryForSet::~SubqueryForSet() = default; - -SubqueryForSet::SubqueryForSet(SubqueryForSet &&) noexcept = default; - -void SubqueryForSet::setSource(InterpreterSelectWithUnionQuery & interpreter, StoragePtr table_) +void SubqueryForSet::createSource(InterpreterSelectWithUnionQuery & interpreter, StoragePtr table_) { source = std::make_unique(); interpreter.buildQueryPlan(*source); @@ -101,7 +99,7 @@ bool SubqueryForSet::hasSource() const return source != nullptr; } -QueryPlanPtr SubqueryForSet::moveSource() +QueryPlanPtr SubqueryForSet::detachSource() { auto res = std::move(source); source = nullptr; diff --git a/src/Interpreters/PreparedSets.h b/src/Interpreters/PreparedSets.h index b8a059830a8..06600c49f13 100644 --- a/src/Interpreters/PreparedSets.h +++ b/src/Interpreters/PreparedSets.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include namespace DB { @@ -21,19 +23,14 @@ class InterpreterSelectWithUnionQuery; class SubqueryForSet { public: - SubqueryForSet(); - ~SubqueryForSet(); - SubqueryForSet(SubqueryForSet &&) noexcept; - SubqueryForSet & operator=(SubqueryForSet &&) noexcept; - - void setSource(InterpreterSelectWithUnionQuery & interpreter, StoragePtr table_ = nullptr); + void createSource(InterpreterSelectWithUnionQuery & interpreter, StoragePtr table_ = nullptr); bool hasSource() const; - /// Returns query plan for the source of the set - /// It would be removed from SubqueryForSet - std::unique_ptr moveSource(); + /// Returns query plan for the set's source + /// and removes it from SubqueryForSet because we need to build it only once. + std::unique_ptr detachSource(); /// Build this set from the result of the subquery. SetPtr set; @@ -74,14 +71,17 @@ class PreparedSets public: using SubqueriesForSets = std::unordered_map; - SubqueryForSet & createOrGetSubquery(const String & subquery_id, const PreparedSetKey & key, SetPtr set); + SubqueryForSet & createOrGetSubquery(const String & subquery_id, const PreparedSetKey & key, + SizeLimits set_size_limit, bool transform_null_in); SubqueryForSet & getSubquery(const String & subquery_id); - void setSet(const PreparedSetKey & key, SetPtr set_); - SetPtr & getSet(const PreparedSetKey & key); + void set(const PreparedSetKey & key, SetPtr set_); + SetPtr & get(const PreparedSetKey & key); - /// Get subqueries and clear them - SubqueriesForSets moveSubqueries(); + /// Get subqueries and clear them. + /// We need to build a plan for subqueries just once. That's why we can clear them after accessing them. + /// SetPtr would still be available for consumers of PreparedSets. + SubqueriesForSets detachSubqueries(); /// Returns all sets that match the given ast hash not checking types /// Used in KeyCondition and MergeTreeIndexConditionBloomFilter to make non exact match for types in PreparedSetKey diff --git a/src/Processors/QueryPlan/CreatingSetsStep.cpp b/src/Processors/QueryPlan/CreatingSetsStep.cpp index 97ec3393a7d..bd079c0b8a9 100644 --- a/src/Processors/QueryPlan/CreatingSetsStep.cpp +++ b/src/Processors/QueryPlan/CreatingSetsStep.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace DB { @@ -121,9 +122,11 @@ void CreatingSetsStep::describePipeline(FormatSettings & settings) const IQueryPlanStep::describePipeline(processors, settings); } -void addCreatingSetsStep( - QueryPlan & query_plan, PreparedSets & prepared_sets, const SizeLimits & limits, ContextPtr context) +void addCreatingSetsStep(QueryPlan & query_plan, PreparedSetsPtr prepared_sets, ContextPtr context) { + if (!prepared_sets || prepared_sets->empty()) + return; + DataStreams input_streams; input_streams.emplace_back(query_plan.getCurrentDataStream()); @@ -131,18 +134,19 @@ void addCreatingSetsStep( plans.emplace_back(std::make_unique(std::move(query_plan))); query_plan = QueryPlan(); - for (auto & [description, subquery_for_set] : prepared_sets.moveSubqueries()) + for (auto & [description, subquery_for_set] : prepared_sets->detachSubqueries()) { if (!subquery_for_set.hasSource()) continue; - auto plan = subquery_for_set.moveSource(); + auto plan = subquery_for_set.detachSource(); + const Settings & settings = context->getSettingsRef(); auto creating_set = std::make_unique( plan->getCurrentDataStream(), description, std::move(subquery_for_set), - limits, + SizeLimits(settings.max_rows_to_transfer, settings.max_bytes_to_transfer, settings.transfer_overflow_mode), context); creating_set->setStepDescription("Create set for subquery"); plan->addStep(std::move(creating_set)); diff --git a/src/Processors/QueryPlan/CreatingSetsStep.h b/src/Processors/QueryPlan/CreatingSetsStep.h index 15bc33b5f56..9c61eb2012c 100644 --- a/src/Processors/QueryPlan/CreatingSetsStep.h +++ b/src/Processors/QueryPlan/CreatingSetsStep.h @@ -49,10 +49,6 @@ private: Processors processors; }; -void addCreatingSetsStep( - QueryPlan & query_plan, - PreparedSets & prepared_sets, - const SizeLimits & limits, - ContextPtr context); +void addCreatingSetsStep(QueryPlan & query_plan, PreparedSetsPtr prepared_sets, ContextPtr context); } diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 83e7eee263e..7c71e395068 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -890,7 +890,7 @@ bool KeyCondition::tryPrepareSetIndex( SetPtr prepared_set; if (right_arg->as() || right_arg->as()) { - prepared_set = prepared_sets->getSet(PreparedSetKey::forSubquery(*right_arg)); + prepared_set = prepared_sets->get(PreparedSetKey::forSubquery(*right_arg)); if (!prepared_sets) return false; } diff --git a/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp b/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp index 54b88c623f5..a80f7093775 100644 --- a/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexConditionBloomFilter.cpp @@ -692,7 +692,7 @@ SetPtr MergeTreeIndexConditionBloomFilter::getPreparedSet(const ASTPtr & node) { const auto & column_and_type = header.getByName(node->getColumnName()); auto set_key = getPreparedSetKey(node, column_and_type.type); - if (auto prepared_set = query_info.prepared_sets->getSet(set_key)) + if (auto prepared_set = query_info.prepared_sets->get(set_key)) return prepared_set; } else diff --git a/src/Storages/MergeTree/MergeTreeIndexFullText.cpp b/src/Storages/MergeTree/MergeTreeIndexFullText.cpp index 8cf4b615e56..ff924290783 100644 --- a/src/Storages/MergeTree/MergeTreeIndexFullText.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexFullText.cpp @@ -609,7 +609,7 @@ bool MergeTreeConditionFullText::tryPrepareSetBloomFilter( else set_key = PreparedSetKey::forLiteral(*right_arg, data_types); - auto prepared_set = prepared_sets->getSet(set_key); + auto prepared_set = prepared_sets->get(set_key); if (!prepared_set) return false; diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp index 05496f817f3..617e58c525b 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp @@ -126,7 +126,7 @@ static bool traverseASTFilter( else set_key = PreparedSetKey::forLiteral(*value, {primary_key_type}); - SetPtr set = prepared_sets->getSet(set_key); + SetPtr set = prepared_sets->get(set_key); if (!set) return false; From f925046dc45e0fb4cb88eeb5d95cc1c4c559f512 Mon Sep 17 00:00:00 2001 From: avogar Date: Wed, 27 Jul 2022 11:37:02 +0000 Subject: [PATCH 143/672] Add more Pretty formats --- docs/en/interfaces/formats.md | 62 ++++++--- .../Formats/Impl/PrettyBlockOutputFormat.cpp | 72 ++++++----- .../Formats/Impl/PrettyBlockOutputFormat.h | 39 +++++- .../Impl/PrettyCompactBlockOutputFormat.cpp | 72 +---------- .../Impl/PrettyCompactBlockOutputFormat.h | 8 +- .../Impl/PrettySpaceBlockOutputFormat.cpp | 28 +---- .../Impl/PrettySpaceBlockOutputFormat.h | 6 +- .../02375_pretty_formats.reference | 118 ++++++++++++++++++ .../0_stateless/02375_pretty_formats.sh | 11 ++ 9 files changed, 261 insertions(+), 155 deletions(-) create mode 100644 tests/queries/0_stateless/02375_pretty_formats.reference create mode 100755 tests/queries/0_stateless/02375_pretty_formats.sh diff --git a/docs/en/interfaces/formats.md b/docs/en/interfaces/formats.md index 6678af9036b..10a311d3aec 100644 --- a/docs/en/interfaces/formats.md +++ b/docs/en/interfaces/formats.md @@ -49,10 +49,17 @@ The supported formats are: | [JSONCompactStringsEachRowWithNamesAndTypes](#jsoncompactstringseachrowwithnamesandtypes) | ✔ | ✔ | | [TSKV](#tskv) | ✔ | ✔ | | [Pretty](#pretty) | ✗ | ✔ | -| [PrettyCompact](#prettycompact) | ✗ | ✔ | -| [PrettyCompactMonoBlock](#prettycompactmonoblock) | ✗ | ✔ | | [PrettyNoEscapes](#prettynoescapes) | ✗ | ✔ | +| [PrettyMonoBlock](#prettymonoblock) | ✗ | ✔ | +| [PrettyNoEscapesMonoBlock](#prettynoescapesmonoblock) | ✗ | ✔ | +| [PrettyCompact](#prettycompact) | ✗ | ✔ | +| [PrettyCompactNoEscapes](#prettycompactnoescapes) | ✗ | ✔ | +| [PrettyCompactMonoBlock](#prettycompactmonoblock) | ✗ | ✔ | +| [PrettyCompactNoEscapesMonoBlock](#prettycompactnoescapesmonoblock) | ✗ | ✔ | | [PrettySpace](#prettyspace) | ✗ | ✔ | +| [PrettySpaceNoEscapes](#prettyspacenoescapes) | ✗ | ✔ | +| [PrettySpaceMonoBlock](#prettyspacemonoblock) | ✗ | ✔ | +| [PrettySpaceNoEscapesMonoBlock](#prettyspacenoescapesmonoblock) | ✗ | ✔ | | [Prometheus](#prometheus) | ✗ | ✔ | | [Protobuf](#protobuf) | ✔ | ✔ | | [ProtobufSingle](#protobufsingle) | ✔ | ✔ | @@ -1198,18 +1205,9 @@ Extremes: └────────────┴─────────┘ ``` -## PrettyCompact {#prettycompact} - -Differs from [Pretty](#pretty) in that the grid is drawn between rows and the result is more compact. -This format is used by default in the command-line client in interactive mode. - -## PrettyCompactMonoBlock {#prettycompactmonoblock} - -Differs from [PrettyCompact](#prettycompact) in that up to 10,000 rows are buffered, then output as a single table, not by blocks. - ## PrettyNoEscapes {#prettynoescapes} -Differs from Pretty in that ANSI-escape sequences aren’t used. This is necessary for displaying this format in a browser, as well as for using the ‘watch’ command-line utility. +Differs from [Pretty](#pretty) in that ANSI-escape sequences aren’t used. This is necessary for displaying this format in a browser, as well as for using the ‘watch’ command-line utility. Example: @@ -1219,19 +1217,49 @@ $ watch -n1 "clickhouse-client --query='SELECT event, value FROM system.events F You can use the HTTP interface for displaying in the browser. -### PrettyCompactNoEscapes {#prettycompactnoescapes} +## PrettyMonoBlock {#prettymonoblock} -The same as the previous setting. +Differs from [Pretty](#pretty) in that up to 10,000 rows are buffered, then output as a single table, not by blocks. -### PrettySpaceNoEscapes {#prettyspacenoescapes} +## PrettyNoEscapesMonoBlock {#prettynoescapesmonoblock} -The same as the previous setting. +Differs from [PrettyNoEscapes](#prettynoescapes) in that up to 10,000 rows are buffered, then output as a single table, not by blocks. + + +## PrettyCompact {#prettycompact} + +Differs from [Pretty](#pretty) in that the grid is drawn between rows and the result is more compact. +This format is used by default in the command-line client in interactive mode. + +## PrettyCompactNoEscapes {#prettynoescapes} + +Differs from [PrettyCompact](#prettycompact) in that ANSI-escape sequences aren’t used. This is necessary for displaying this format in a browser, as well as for using the ‘watch’ command-line utility. + +## PrettyCompactMonoBlock {#prettycompactmonoblock} + +Differs from [PrettyCompact](#prettycompact) in that up to 10,000 rows are buffered, then output as a single table, not by blocks. + +## PrettyCompactNoEscapesMonoBlock {#prettycompactnoescapesmonoblock} + +Differs from [PrettyCompactNoEscapes](#prettycompactnoescapes) in that up to 10,000 rows are buffered, then output as a single table, not by blocks. ## PrettySpace {#prettyspace} Differs from [PrettyCompact](#prettycompact) in that whitespace (space characters) is used instead of the grid. -### Pretty formats settings {#pretty-formats-settings} +## PrettySpaceNoEscapes {#prettyspacenoescapes} + +Differs from [PrettySpace](#prettyspace) in that ANSI-escape sequences aren’t used. This is necessary for displaying this format in a browser, as well as for using the ‘watch’ command-line utility. + +## PrettySpaceMonoBlock {#prettyspacemonoblock} + +Differs from [PrettySpace](#prettyspace) in that up to 10,000 rows are buffered, then output as a single table, not by blocks. + +## PrettySpaceNoEscapesMonoBlock {#prettyspacenoescapesmonoblock} + +Differs from [PrettySpaceNoEscapes](#prettyspacenoescapes) in that up to 10,000 rows are buffered, then output as a single table, not by blocks. + +## Pretty formats settings {#pretty-formats-settings} - [output_format_pretty_max_rows](../operations/settings/settings.md#output_format_pretty_max_rows) - rows limit for Pretty formats. Default value - `10000`. - [output_format_pretty_max_column_pad_width](../operations/settings/settings.md#output_format_pretty_max_column_pad_width) - maximum width to pad all values in a column in Pretty formats. Default value - `250`. diff --git a/src/Processors/Formats/Impl/PrettyBlockOutputFormat.cpp b/src/Processors/Formats/Impl/PrettyBlockOutputFormat.cpp index 8fbf0a14916..811a24c94d9 100644 --- a/src/Processors/Formats/Impl/PrettyBlockOutputFormat.cpp +++ b/src/Processors/Formats/Impl/PrettyBlockOutputFormat.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include namespace DB @@ -21,8 +20,8 @@ namespace ErrorCodes PrettyBlockOutputFormat::PrettyBlockOutputFormat( - WriteBuffer & out_, const Block & header_, const FormatSettings & format_settings_) - : IOutputFormat(header_, out_), format_settings(format_settings_), serializations(header_.getSerializations()) + WriteBuffer & out_, const Block & header_, const FormatSettings & format_settings_, bool mono_block_) + : IOutputFormat(header_, out_), format_settings(format_settings_), serializations(header_.getSerializations()), mono_block(mono_block_) { struct winsize w; if (0 == ioctl(STDOUT_FILENO, TIOCGWINSZ, &w)) @@ -142,17 +141,38 @@ GridSymbols ascii_grid_symbols { } - void PrettyBlockOutputFormat::write(Chunk chunk, PortKind port_kind) { - UInt64 max_rows = format_settings.pretty.max_rows; - - if (total_rows >= max_rows) + if (total_rows >= format_settings.pretty.max_rows) { total_rows += chunk.getNumRows(); return; } + if (mono_block) + { + if (port_kind == PortKind::Main) + { + if (!mono_chunk) + { + mono_chunk = std::move(chunk); + return; + } + mono_chunk.append(chunk); + return; + } + else + { + /// Should be written from writeSuffix() + assert(!mono_chunk); + } + } + + writeChunk(chunk, port_kind); +} + +void PrettyBlockOutputFormat::writeChunk(const Chunk & chunk, PortKind port_kind) +{ auto num_rows = chunk.getNumRows(); auto num_columns = chunk.getNumColumns(); const auto & columns = chunk.getColumns(); @@ -265,7 +285,7 @@ void PrettyBlockOutputFormat::write(Chunk chunk, PortKind port_kind) } writeString(middle_names_separator_s, out); - for (size_t i = 0; i < num_rows && total_rows + i < max_rows; ++i) + for (size_t i = 0; i < num_rows && total_rows + i < format_settings.pretty.max_rows; ++i) { if (i != 0) { @@ -385,8 +405,19 @@ void PrettyBlockOutputFormat::consumeExtremes(Chunk chunk) } +void PrettyBlockOutputFormat::writeMonoChunkIfNeeded() +{ + if (mono_chunk) + { + writeChunk(mono_chunk, PortKind::Main); + mono_chunk.clear(); + } +} + void PrettyBlockOutputFormat::writeSuffix() { + writeMonoChunkIfNeeded(); + if (total_rows >= format_settings.pretty.max_rows) { writeCString(" Showed first ", out); @@ -395,32 +426,9 @@ void PrettyBlockOutputFormat::writeSuffix() } } - void registerOutputFormatPretty(FormatFactory & factory) { - factory.registerOutputFormat("Pretty", []( - WriteBuffer & buf, - const Block & sample, - const RowOutputFormatParams &, - const FormatSettings & format_settings) - { - return std::make_shared(buf, sample, format_settings); - }); - - factory.markOutputFormatSupportsParallelFormatting("Pretty"); - - factory.registerOutputFormat("PrettyNoEscapes", []( - WriteBuffer & buf, - const Block & sample, - const RowOutputFormatParams &, - const FormatSettings & format_settings) - { - FormatSettings changed_settings = format_settings; - changed_settings.pretty.color = false; - return std::make_shared(buf, sample, changed_settings); - }); - - factory.markOutputFormatSupportsParallelFormatting("PrettyNoEscapes"); + registerPrettyFormatWithNoEscapesAndMonoBlock(factory, "Pretty"); } } diff --git a/src/Processors/Formats/Impl/PrettyBlockOutputFormat.h b/src/Processors/Formats/Impl/PrettyBlockOutputFormat.h index cfdd2213515..2f68c73929f 100644 --- a/src/Processors/Formats/Impl/PrettyBlockOutputFormat.h +++ b/src/Processors/Formats/Impl/PrettyBlockOutputFormat.h @@ -3,6 +3,7 @@ #include #include #include +#include namespace DB @@ -18,7 +19,7 @@ class PrettyBlockOutputFormat : public IOutputFormat { public: /// no_escapes - do not use ANSI escape sequences - to display in the browser, not in the console. - PrettyBlockOutputFormat(WriteBuffer & out_, const Block & header_, const FormatSettings & format_settings_); + PrettyBlockOutputFormat(WriteBuffer & out_, const Block & header_, const FormatSettings & format_settings_, bool mono_block_); String getName() const override { return "PrettyBlockOutputFormat"; } @@ -38,7 +39,9 @@ protected: using Widths = PODArray; using WidthsPerColumn = std::vector; - virtual void write(Chunk chunk, PortKind port_kind); + void write(Chunk chunk, PortKind port_kind); + virtual void writeChunk(const Chunk & chunk, PortKind port_kind); + void writeMonoChunkIfNeeded(); void writeSuffix() override; void onRowsReadBeforeUpdate() override { total_rows = getRowsReadBefore(); } @@ -50,6 +53,38 @@ protected: void writeValueWithPadding( const IColumn & column, const ISerialization & serialization, size_t row_num, size_t value_width, size_t pad_to_width, bool align_right); + +private: + bool mono_block; + /// For mono_block == true only + Chunk mono_chunk; }; +template +void registerPrettyFormatWithNoEscapesAndMonoBlock(FormatFactory & factory, const String & base_name) +{ + for (bool no_escapes : {true, false}) + { + String name = no_escapes ? base_name + "NoEscapes" : base_name; + for (bool mono_block : {true, false}) + { + factory.registerOutputFormat(mono_block ? name + "MonoBlock" : name, [no_escapes, mono_block]( + WriteBuffer & buf, + const Block & sample, + const RowOutputFormatParams &, + const FormatSettings & format_settings) + { + if (no_escapes) + { + FormatSettings changed_settings = format_settings; + changed_settings.pretty.color = false; + return std::make_shared(buf, sample, changed_settings, mono_block); + } + return std::make_shared(buf, sample, format_settings, mono_block); + }); + } + factory.markOutputFormatSupportsParallelFormatting(name); + } +} + } diff --git a/src/Processors/Formats/Impl/PrettyCompactBlockOutputFormat.cpp b/src/Processors/Formats/Impl/PrettyCompactBlockOutputFormat.cpp index 9ba358a76e1..cc26a064b54 100644 --- a/src/Processors/Formats/Impl/PrettyCompactBlockOutputFormat.cpp +++ b/src/Processors/Formats/Impl/PrettyCompactBlockOutputFormat.cpp @@ -49,22 +49,10 @@ GridSymbols ascii_grid_symbols { } PrettyCompactBlockOutputFormat::PrettyCompactBlockOutputFormat(WriteBuffer & out_, const Block & header, const FormatSettings & format_settings_, bool mono_block_) - : PrettyBlockOutputFormat(out_, header, format_settings_) - , mono_block(mono_block_) + : PrettyBlockOutputFormat(out_, header, format_settings_, mono_block_) { } -void PrettyCompactBlockOutputFormat::writeSuffix() -{ - if (mono_chunk) - { - writeChunk(mono_chunk, PortKind::Main); - mono_chunk.clear(); - } - - PrettyBlockOutputFormat::writeSuffix(); -} - void PrettyCompactBlockOutputFormat::writeHeader( const Block & block, const Widths & max_widths, @@ -186,38 +174,6 @@ void PrettyCompactBlockOutputFormat::writeRow( writeCString("\n", out); } -void PrettyCompactBlockOutputFormat::write(Chunk chunk, PortKind port_kind) -{ - UInt64 max_rows = format_settings.pretty.max_rows; - - if (total_rows >= max_rows) - { - total_rows += chunk.getNumRows(); - return; - } - if (mono_block) - { - if (port_kind == PortKind::Main) - { - if (!mono_chunk) - { - mono_chunk = std::move(chunk); - return; - } - - mono_chunk.append(chunk); - return; - } - else - { - /// Should be written from writeSuffix() - assert(!mono_chunk); - } - } - - writeChunk(chunk, port_kind); -} - void PrettyCompactBlockOutputFormat::writeChunk(const Chunk & chunk, PortKind port_kind) { UInt64 max_rows = format_settings.pretty.max_rows; @@ -244,31 +200,7 @@ void PrettyCompactBlockOutputFormat::writeChunk(const Chunk & chunk, PortKind po void registerOutputFormatPrettyCompact(FormatFactory & factory) { - for (const auto & [name, mono_block] : {std::make_pair("PrettyCompact", false), std::make_pair("PrettyCompactMonoBlock", true)}) - { - factory.registerOutputFormat(name, [mono_block = mono_block]( - WriteBuffer & buf, - const Block & sample, - const RowOutputFormatParams &, - const FormatSettings & format_settings) - { - return std::make_shared(buf, sample, format_settings, mono_block); - }); - } - - factory.markOutputFormatSupportsParallelFormatting("PrettyCompact"); - - factory.registerOutputFormat("PrettyCompactNoEscapes", []( - WriteBuffer & buf, - const Block & sample, - const RowOutputFormatParams &, - const FormatSettings & format_settings) - { - FormatSettings changed_settings = format_settings; - changed_settings.pretty.color = false; - return std::make_shared(buf, sample, changed_settings, false /* mono_block */); - }); - factory.markOutputFormatSupportsParallelFormatting("PrettyCompactNoEscapes"); + registerPrettyFormatWithNoEscapesAndMonoBlock(factory, "PrettyCompact"); } } diff --git a/src/Processors/Formats/Impl/PrettyCompactBlockOutputFormat.h b/src/Processors/Formats/Impl/PrettyCompactBlockOutputFormat.h index 5c39328051c..432ff0ee120 100644 --- a/src/Processors/Formats/Impl/PrettyCompactBlockOutputFormat.h +++ b/src/Processors/Formats/Impl/PrettyCompactBlockOutputFormat.h @@ -17,7 +17,6 @@ public: String getName() const override { return "PrettyCompactBlockOutputFormat"; } private: - void write(Chunk chunk, PortKind port_kind) override; void writeHeader(const Block & block, const Widths & max_widths, const Widths & name_widths); void writeBottom(const Widths & max_widths); void writeRow( @@ -27,12 +26,7 @@ private: const WidthsPerColumn & widths, const Widths & max_widths); - bool mono_block; - /// For mono_block == true only - Chunk mono_chunk; - - void writeChunk(const Chunk & chunk, PortKind port_kind); - void writeSuffix() override; + void writeChunk(const Chunk & chunk, PortKind port_kind) override; }; } diff --git a/src/Processors/Formats/Impl/PrettySpaceBlockOutputFormat.cpp b/src/Processors/Formats/Impl/PrettySpaceBlockOutputFormat.cpp index 730907ba45c..4afb380dec9 100644 --- a/src/Processors/Formats/Impl/PrettySpaceBlockOutputFormat.cpp +++ b/src/Processors/Formats/Impl/PrettySpaceBlockOutputFormat.cpp @@ -9,7 +9,7 @@ namespace DB { -void PrettySpaceBlockOutputFormat::write(Chunk chunk, PortKind port_kind) +void PrettySpaceBlockOutputFormat::writeChunk(const Chunk & chunk, PortKind port_kind) { UInt64 max_rows = format_settings.pretty.max_rows; @@ -100,6 +100,8 @@ void PrettySpaceBlockOutputFormat::write(Chunk chunk, PortKind port_kind) void PrettySpaceBlockOutputFormat::writeSuffix() { + writeMonoChunkIfNeeded(); + if (total_rows >= format_settings.pretty.max_rows) { writeCString("\nShowed first ", out); @@ -111,29 +113,7 @@ void PrettySpaceBlockOutputFormat::writeSuffix() void registerOutputFormatPrettySpace(FormatFactory & factory) { - factory.registerOutputFormat("PrettySpace", []( - WriteBuffer & buf, - const Block & sample, - const RowOutputFormatParams &, - const FormatSettings & format_settings) - { - return std::make_shared(buf, sample, format_settings); - }); - - factory.markOutputFormatSupportsParallelFormatting("PrettySpace"); - - factory.registerOutputFormat("PrettySpaceNoEscapes", []( - WriteBuffer & buf, - const Block & sample, - const RowOutputFormatParams &, - const FormatSettings & format_settings) - { - FormatSettings changed_settings = format_settings; - changed_settings.pretty.color = false; - return std::make_shared(buf, sample, changed_settings); - }); - - factory.markOutputFormatSupportsParallelFormatting("PrettySpaceNoEscapes"); + registerPrettyFormatWithNoEscapesAndMonoBlock(factory, "PrettySpace"); } } diff --git a/src/Processors/Formats/Impl/PrettySpaceBlockOutputFormat.h b/src/Processors/Formats/Impl/PrettySpaceBlockOutputFormat.h index 6a8cb4e799c..1cccd5fb476 100644 --- a/src/Processors/Formats/Impl/PrettySpaceBlockOutputFormat.h +++ b/src/Processors/Formats/Impl/PrettySpaceBlockOutputFormat.h @@ -11,13 +11,13 @@ namespace DB class PrettySpaceBlockOutputFormat : public PrettyBlockOutputFormat { public: - PrettySpaceBlockOutputFormat(WriteBuffer & out_, const Block & header, const FormatSettings & format_settings_) - : PrettyBlockOutputFormat(out_, header, format_settings_) {} + PrettySpaceBlockOutputFormat(WriteBuffer & out_, const Block & header, const FormatSettings & format_settings_, bool mono_block_) + : PrettyBlockOutputFormat(out_, header, format_settings_, mono_block_) {} String getName() const override { return "PrettySpaceBlockOutputFormat"; } private: - void write(Chunk chunk, PortKind port_kind) override; + void writeChunk(const Chunk & chunk, PortKind port_kind) override; void writeSuffix() override; }; diff --git a/tests/queries/0_stateless/02375_pretty_formats.reference b/tests/queries/0_stateless/02375_pretty_formats.reference new file mode 100644 index 00000000000..51b18f7eb09 --- /dev/null +++ b/tests/queries/0_stateless/02375_pretty_formats.reference @@ -0,0 +1,118 @@ +Pretty +┏━━━┳━━━┓ +┃ x ┃ y ┃ +┡━━━╇━━━┩ +│ 0 │ 1 │ +├───┼───┤ +│ 1 │ 2 │ +└───┴───┘ +┏━━━┳━━━┓ +┃ x ┃ y ┃ +┡━━━╇━━━┩ +│ 2 │ 3 │ +├───┼───┤ +│ 3 │ 4 │ +└───┴───┘ +PrettyNoEscapes +┏━━━┳━━━┓ +┃ x ┃ y ┃ +┡━━━╇━━━┩ +│ 0 │ 1 │ +├───┼───┤ +│ 1 │ 2 │ +└───┴───┘ +┏━━━┳━━━┓ +┃ x ┃ y ┃ +┡━━━╇━━━┩ +│ 2 │ 3 │ +├───┼───┤ +│ 3 │ 4 │ +└───┴───┘ +PrettyMonoBlock +┏━━━┳━━━┓ +┃ x ┃ y ┃ +┡━━━╇━━━┩ +│ 0 │ 1 │ +├───┼───┤ +│ 1 │ 2 │ +├───┼───┤ +│ 2 │ 3 │ +├───┼───┤ +│ 3 │ 4 │ +└───┴───┘ +PrettyNoEscapesMonoBlock +┏━━━┳━━━┓ +┃ x ┃ y ┃ +┡━━━╇━━━┩ +│ 0 │ 1 │ +├───┼───┤ +│ 1 │ 2 │ +├───┼───┤ +│ 2 │ 3 │ +├───┼───┤ +│ 3 │ 4 │ +└───┴───┘ +PrettyCompact +┌─x─┬─y─┐ +│ 0 │ 1 │ +│ 1 │ 2 │ +└───┴───┘ +┌─x─┬─y─┐ +│ 2 │ 3 │ +│ 3 │ 4 │ +└───┴───┘ +PrettyCompactNoEscapes +┌─x─┬─y─┐ +│ 0 │ 1 │ +│ 1 │ 2 │ +└───┴───┘ +┌─x─┬─y─┐ +│ 2 │ 3 │ +│ 3 │ 4 │ +└───┴───┘ +PrettyCompactMonoBlock +┌─x─┬─y─┐ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +│ 3 │ 4 │ +└───┴───┘ +PrettyCompactNoEscapesMonoBlock +┌─x─┬─y─┐ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +│ 3 │ 4 │ +└───┴───┘ +PrettySpace + x y + + 0 1 + 1 2 + x y + + 2 3 + 3 4 +PrettySpaceNoEscapes + x y + + 0 1 + 1 2 + x y + + 2 3 + 3 4 +PrettySpaceMonoBlock + x y + + 0 1 + 1 2 + 2 3 + 3 4 +PrettySpaceNoEscapesMonoBlock + x y + + 0 1 + 1 2 + 2 3 + 3 4 diff --git a/tests/queries/0_stateless/02375_pretty_formats.sh b/tests/queries/0_stateless/02375_pretty_formats.sh new file mode 100755 index 00000000000..9c150343fa7 --- /dev/null +++ b/tests/queries/0_stateless/02375_pretty_formats.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +for format in Pretty PrettyNoEscapes PrettyMonoBlock PrettyNoEscapesMonoBlock PrettyCompact PrettyCompactNoEscapes PrettyCompactMonoBlock PrettyCompactNoEscapesMonoBlock PrettySpace PrettySpaceNoEscapes PrettySpaceMonoBlock PrettySpaceNoEscapesMonoBlock +do + echo $format + $CLICKHOUSE_CLIENT -q "select number as x, number + 1 as y from numbers(4) settings max_block_size=2 format $format" +done From f9a8a4cd3291715026141a5fa157b95309f30360 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 27 Jul 2022 12:00:55 +0000 Subject: [PATCH 144/672] Fixing read-in-order for Merge --- src/Storages/StorageMerge.cpp | 89 ++++++++++++++++++++++------------- src/Storages/StorageMerge.h | 16 ++++--- 2 files changed, 65 insertions(+), 40 deletions(-) diff --git a/src/Storages/StorageMerge.cpp b/src/Storages/StorageMerge.cpp index 69666e2de35..3fc7ca36569 100644 --- a/src/Storages/StorageMerge.cpp +++ b/src/Storages/StorageMerge.cpp @@ -258,6 +258,44 @@ void StorageMerge::read( auto modified_context = Context::createCopy(local_context); modified_context->setSetting("optimize_move_to_prewhere", false); + bool has_database_virtual_column = false; + bool has_table_virtual_column = false; + Names real_column_names; + real_column_names.reserve(column_names.size()); + + for (const auto & column_name : column_names) + { + if (column_name == "_database" && isVirtualColumn(column_name, storage_snapshot->metadata)) + has_database_virtual_column = true; + else if (column_name == "_table" && isVirtualColumn(column_name, storage_snapshot->metadata)) + has_table_virtual_column = true; + else + real_column_names.push_back(column_name); + } + + StorageListWithLocks selected_tables + = getSelectedTables(modified_context, query_info.query, has_database_virtual_column, has_table_virtual_column); + + InputOrderInfoPtr input_sorting_info; + if (query_info.order_optimizer) + { + for (auto it = selected_tables.begin(); it != selected_tables.end(); ++it) + { + auto storage_ptr = std::get<1>(*it); + auto storage_metadata_snapshot = storage_ptr->getInMemoryMetadataPtr(); + auto current_info = query_info.order_optimizer->getInputOrder(storage_metadata_snapshot, modified_context); + if (it == selected_tables.begin()) + input_sorting_info = current_info; + else if (!current_info || (input_sorting_info && *current_info != *input_sorting_info)) + input_sorting_info.reset(); + + if (!input_sorting_info) + break; + } + + query_info.input_order_info = input_sorting_info; + } + query_plan.addInterpreterContext(modified_context); /// What will be result structure depending on query processed stage in source tables? @@ -265,7 +303,10 @@ void StorageMerge::read( auto step = std::make_unique( common_header, - column_names, + std::move(selected_tables), + real_column_names, + has_database_virtual_column, + has_table_virtual_column, max_block_size, num_streams, shared_from_this(), @@ -279,7 +320,10 @@ void StorageMerge::read( ReadFromMerge::ReadFromMerge( Block common_header_, + StorageListWithLocks selected_tables_, Names column_names_, + bool has_database_virtual_column_, + bool has_table_virtual_column_, size_t max_block_size, size_t num_streams, StoragePtr storage, @@ -287,11 +331,14 @@ ReadFromMerge::ReadFromMerge( const SelectQueryInfo & query_info_, ContextMutablePtr context_, QueryProcessingStage::Enum processed_stage) - : ISourceStep(DataStream{.header = common_header_ /*addVirtualColumns(common_header_, column_names_, *storage, *storage_snapshot)*/}) + : ISourceStep(DataStream{.header = common_header_}) , required_max_block_size(max_block_size) , requested_num_streams(num_streams) , common_header(std::move(common_header_)) + , selected_tables(std::move(selected_tables_)) , column_names(std::move(column_names_)) + , has_database_virtual_column(has_database_virtual_column_) + , has_table_virtual_column(has_table_virtual_column_) , storage_merge(std::move(storage)) , merge_storage_snapshot(std::move(storage_snapshot)) , query_info(query_info_) @@ -302,37 +349,12 @@ ReadFromMerge::ReadFromMerge( void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const BuildQueryPipelineSettings &) { - std::vector> pipelines; - - bool has_database_virtual_column = false; - bool has_table_virtual_column = false; - Names real_column_names; - real_column_names.reserve(column_names.size()); - - for (const auto & column_name : column_names) - { - if (column_name == "_database" && storage_merge->isVirtualColumn(column_name, merge_storage_snapshot->metadata)) - has_database_virtual_column = true; - else if (column_name == "_table" && storage_merge->isVirtualColumn(column_name, merge_storage_snapshot->metadata)) - has_table_virtual_column = true; - else - real_column_names.push_back(column_name); - } - - /** First we make list of selected tables to find out its size. - * This is necessary to correctly pass the recommended number of threads to each table. - */ - StorageListWithLocks selected_tables - = storage_merge->as().getSelectedTables(context, query_info.query, has_database_virtual_column, has_table_virtual_column); - if (selected_tables.empty()) { pipeline.init(Pipe(std::make_shared(output_stream->header))); return; } - QueryPlanResourceHolder resources; - size_t tables_count = selected_tables.size(); Float64 num_streams_multiplier = std::min(static_cast(tables_count), std::max(1U, static_cast(context->getSettingsRef().max_streams_multiplier_for_merge_tables))); @@ -361,6 +383,9 @@ void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const Bu auto sample_block = merge_storage_snapshot->getMetadataForQuery()->getSampleBlock(); + std::vector> pipelines; + QueryPlanResourceHolder resources; + for (const auto & table : selected_tables) { size_t current_need_streams = tables_count >= num_streams ? 1 : (num_streams / tables_count); @@ -390,7 +415,7 @@ void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const Bu ASTPtr required_columns_expr_list = std::make_shared(); ASTPtr column_expr; - for (const auto & column : real_column_names) + for (const auto & column : column_names) { const auto column_default = storage_columns.getDefault(column); bool is_alias = column_default && column_default->kind == ColumnDefaultKind::Alias; @@ -433,11 +458,9 @@ void ReadFromMerge::initializePipeline(QueryPipelineBuilder & pipeline, const Bu common_header, aliases, table, - column_names_as_aliases.empty() ? real_column_names : column_names_as_aliases, + column_names_as_aliases.empty() ? column_names : column_names_as_aliases, context, - current_streams, - has_database_virtual_column, - has_table_virtual_column); + current_streams); if (source_pipeline && source_pipeline->initialized()) { @@ -476,8 +499,6 @@ QueryPipelineBuilderPtr ReadFromMerge::createSources( Names & real_column_names, ContextMutablePtr modified_context, size_t streams_num, - bool has_database_virtual_column, - bool has_table_virtual_column, bool concat_streams) { const auto & [database_name, storage, _, table_name] = storage_with_lock; diff --git a/src/Storages/StorageMerge.h b/src/Storages/StorageMerge.h index 74f5628bb24..d2f94ac6b88 100644 --- a/src/Storages/StorageMerge.h +++ b/src/Storages/StorageMerge.h @@ -118,9 +118,16 @@ public: static constexpr auto name = "ReadFromMerge"; String getName() const override { return name; } + using StorageWithLockAndName = std::tuple; + using StorageListWithLocks = std::list; + using DatabaseTablesIterators = std::vector; + ReadFromMerge( Block common_header_, + StorageListWithLocks selected_tables_, Names column_names_, + bool has_database_virtual_column_, + bool has_table_virtual_column_, size_t max_block_size, size_t num_streams, StoragePtr storage, @@ -137,16 +144,15 @@ public: added_filter_column_name = std::move(column_name); } - using StorageWithLockAndName = std::tuple; - using StorageListWithLocks = std::list; - using DatabaseTablesIterators = std::vector; - private: const size_t required_max_block_size; const size_t requested_num_streams; const Block common_header; + StorageListWithLocks selected_tables; Names column_names; + bool has_database_virtual_column; + bool has_table_virtual_column; StoragePtr storage_merge; StorageSnapshotPtr merge_storage_snapshot; @@ -177,8 +183,6 @@ private: Names & real_column_names, ContextMutablePtr modified_context, size_t streams_num, - bool has_database_virtual_column, - bool has_table_virtual_column, bool concat_streams = false); void convertingSourceStream( From f91a0935cd576f70d24b98c1f04f0b05d008256f Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy <99031427+yakov-olkhovskiy@users.noreply.github.com> Date: Wed, 27 Jul 2022 09:00:05 -0400 Subject: [PATCH 145/672] check existence of compressed clickhouse --- docker/packager/binary/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/packager/binary/build.sh b/docker/packager/binary/build.sh index e9d91fb9733..844fcc35181 100755 --- a/docker/packager/binary/build.sh +++ b/docker/packager/binary/build.sh @@ -104,7 +104,7 @@ if [ -n "$MAKE_DEB" ]; then fi mv ./programs/clickhouse* /output -mv ./programs/self-extracting/clickhouse /output ||: +[ -x ./programs/self-extracting/clickhouse ] && mv ./programs/self-extracting/clickhouse /output mv ./src/unit_tests_dbms /output ||: # may not exist for some binary builds find . -name '*.so' -print -exec mv '{}' /output \; find . -name '*.so.*' -print -exec mv '{}' /output \; From 4cf03538921f002d4dcc137cbfda11bdba4d2bab Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 27 Jul 2022 14:05:16 +0000 Subject: [PATCH 146/672] try to fix Nested --- .../Serializations/ISerialization.cpp | 9 +- src/Storages/MergeTree/DataPartsExchange.cpp | 4 +- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 91 ++++++++++--------- src/Storages/MergeTree/IMergeTreeDataPart.h | 10 +- src/Storages/MergeTree/IMergeTreeReader.cpp | 55 +++++++---- src/Storages/MergeTree/IMergeTreeReader.h | 18 +++- .../MergeTree/IMergedBlockOutputStream.cpp | 14 +-- src/Storages/MergeTree/MergeTask.cpp | 3 +- .../MergeTree/MergeTreeDataPartInMemory.cpp | 4 +- .../MergeTree/MergeTreeDataPartWide.cpp | 18 ++-- .../MergeTreeDataPartWriterCompact.cpp | 4 +- .../MergeTree/MergeTreeDataPartWriterWide.cpp | 18 ++-- .../MergeTree/MergeTreeDataWriter.cpp | 6 +- .../MergeTree/MergeTreeReaderCompact.cpp | 51 ++++------- .../MergeTree/MergeTreeReaderInMemory.cpp | 21 ++--- .../MergeTree/MergeTreeReaderWide.cpp | 68 +++++--------- src/Storages/MergeTree/MergeTreeReaderWide.h | 12 ++- .../MergeTree/MergeTreeWriteAheadLog.cpp | 2 +- .../MergeTree/MergedBlockOutputStream.cpp | 3 +- .../MergedColumnOnlyOutputStream.cpp | 3 +- src/Storages/MergeTree/MutateTask.cpp | 30 +++--- src/Storages/StorageReplicatedMergeTree.cpp | 2 +- .../System/StorageSystemPartsColumns.cpp | 2 +- 23 files changed, 216 insertions(+), 232 deletions(-) diff --git a/src/DataTypes/Serializations/ISerialization.cpp b/src/DataTypes/Serializations/ISerialization.cpp index d4acb877e87..e6e6fdba5dc 100644 --- a/src/DataTypes/Serializations/ISerialization.cpp +++ b/src/DataTypes/Serializations/ISerialization.cpp @@ -227,9 +227,7 @@ void ISerialization::addToSubstreamsCache(SubstreamsCache * cache, const Substre if (!cache || path.empty()) return; - auto subcolumn_name = getSubcolumnNameForStream(path); - if (!subcolumn_name.empty()) - cache->emplace(getSubcolumnNameForStream(path), column); + cache->emplace(getSubcolumnNameForStream(path), column); } ColumnPtr ISerialization::getFromSubstreamsCache(SubstreamsCache * cache, const SubstreamPath & path) @@ -238,10 +236,7 @@ ColumnPtr ISerialization::getFromSubstreamsCache(SubstreamsCache * cache, const return nullptr; auto it = cache->find(getSubcolumnNameForStream(path)); - if (it == cache->end()) - return nullptr; - - return it->second; + return it == cache->end() ? nullptr : it->second; } bool ISerialization::isSpecialCompressionAllowed(const SubstreamPath & path) diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index 3609a65bc71..4bf44689ba2 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -646,7 +646,7 @@ MergeTreeData::MutableDataPartPtr Fetcher::downloadPartToMemory( std::make_shared(data, projection_name, new_part_info, projection_part_storage, new_data_part.get()); new_projection_part->is_temp = false; - new_projection_part->setColumns(block.getNamesAndTypesList()); + new_projection_part->setColumns(block.getNamesAndTypesList(), {}); MergeTreePartition partition{}; new_projection_part->partition = std::move(partition); new_projection_part->minmax_idx = std::make_shared(); @@ -676,7 +676,7 @@ MergeTreeData::MutableDataPartPtr Fetcher::downloadPartToMemory( new_data_part->uuid = part_uuid; new_data_part->is_temp = true; - new_data_part->setColumns(block.getNamesAndTypesList()); + new_data_part->setColumns(block.getNamesAndTypesList(), {}); new_data_part->minmax_idx->update(block, data.getMinMaxColumnsNames(metadata_snapshot->getPartitionKey())); new_data_part->partition.create(metadata_snapshot, block, 0, context); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index d4d3844de6c..30ec9bbcdd4 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -64,6 +64,7 @@ namespace ErrorCodes extern const int BAD_SIZE_OF_FILE_IN_DATA_PART; extern const int BAD_TTL_FILE; extern const int NOT_IMPLEMENTED; + extern const int NO_SUCH_COLUMN_IN_TABLE; } void IMergeTreeDataPart::MinMaxIndex::load(const MergeTreeData & data, const PartMetadataManagerPtr & manager) @@ -437,17 +438,33 @@ std::pair IMergeTreeDataPart::getMinMaxTime() const } -void IMergeTreeDataPart::setColumns(const NamesAndTypesList & new_columns) +void IMergeTreeDataPart::setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos) { columns = new_columns; + serialization_infos = new_infos; column_name_to_position.clear(); column_name_to_position.reserve(new_columns.size()); size_t pos = 0; - for (const auto & column : columns) column_name_to_position.emplace(column.name, pos++); + for (const auto & column : columns) + { + auto it = serialization_infos.find(column.name); + auto serialization = it == serialization_infos.end() + ? IDataType::getSerialization(column) + : IDataType::getSerialization(column, *it->second); + + serializations.emplace(column.name, serialization); + + IDataType::forEachSubcolumn([&](const auto &, const auto & subname, const auto & subdata) + { + auto full_name = Nested::concatenateName(column.name, subname); + serializations.emplace(full_name, subdata.serialization); + }, {serialization, nullptr, nullptr, nullptr}); + } + columns_description = ColumnsDescription(columns); } @@ -461,22 +478,20 @@ std::optional IMergeTreeDataPart::tryGetColumn(const String & c return columns_description.tryGetColumnOrSubcolumn(GetColumnsOptions::AllPhysical, column_name); } -void IMergeTreeDataPart::setSerializationInfos(const SerializationInfoByName & new_infos) +SerializationPtr IMergeTreeDataPart::getSerialization(const String & column_name) const { - serialization_infos = new_infos; + auto serialization = tryGetSerialization(column_name); + if (!serialization) + throw Exception(ErrorCodes::NO_SUCH_COLUMN_IN_TABLE, + "There is no column or subcolumn {} in part {}", column_name, name); + + return serialization; } -SerializationPtr IMergeTreeDataPart::getSerialization(const NameAndTypePair & column) const +SerializationPtr IMergeTreeDataPart::tryGetSerialization(const String & column_name) const { - auto column_in_part = tryGetColumn(column.name); - if (!column_in_part) - return IDataType::getSerialization(column); - - auto it = serialization_infos.find(column_in_part->getNameInStorage()); - if (it == serialization_infos.end()) - return IDataType::getSerialization(*column_in_part); - - return IDataType::getSerialization(*column_in_part, *it->second); + auto it = serializations.find(column_name); + return it == serializations.end() ? nullptr : it->second; } void IMergeTreeDataPart::removeIfNeeded() @@ -585,36 +600,23 @@ size_t IMergeTreeDataPart::getFileSizeOrZero(const String & file_name) const String IMergeTreeDataPart::getColumnNameWithMinimumCompressedSize(bool with_subcolumns) const { - auto find_column_with_minimum_size = [&](const auto & columns_list) - { - std::optional minimum_size_column; - UInt64 minimum_size = std::numeric_limits::max(); - - for (const auto & column : columns_list) - { - if (!hasColumnFiles(column)) - continue; - - const auto size = getColumnSize(column.name).data_compressed; - if (size < minimum_size) - { - minimum_size = size; - minimum_size_column = column.name; - } - } - - return minimum_size_column; - }; + auto options = GetColumnsOptions(GetColumnsOptions::AllPhysical).withSubcolumns(with_subcolumns); + auto columns_list = columns_description.get(options); std::optional minimum_size_column; - if (with_subcolumns) + UInt64 minimum_size = std::numeric_limits::max(); + + for (const auto & column : columns_list) { - auto options = GetColumnsOptions(GetColumnsOptions::AllPhysical).withSubcolumns(); - minimum_size_column = find_column_with_minimum_size(columns_description.get(options)); - } - else - { - minimum_size_column = find_column_with_minimum_size(columns); + if (!hasColumnFiles(column)) + continue; + + const auto size = getColumnSize(column.name).data_compressed; + if (size < minimum_size) + { + minimum_size = size; + minimum_size_column = column.name; + } } if (!minimum_size_column) @@ -868,7 +870,7 @@ CompressionCodecPtr IMergeTreeDataPart::detectDefaultCompressionCodec() const if (column_size.data_compressed != 0 && !storage_columns.hasCompressionCodec(part_column.name)) { String path_to_data_file; - getSerialization(part_column)->enumerateStreams([&](const ISerialization::SubstreamPath & substream_path) + getSerialization(part_column.name)->enumerateStreams([&](const ISerialization::SubstreamPath & substream_path) { if (path_to_data_file.empty()) { @@ -1066,7 +1068,7 @@ void IMergeTreeDataPart::loadRowsCount() for (const NameAndTypePair & column : columns) { - ColumnPtr column_col = column.type->createColumn(*getSerialization(column)); + ColumnPtr column_col = column.type->createColumn(*getSerialization(column.name)); if (!column_col->isFixedAndContiguous() || column_col->lowCardinality()) continue; @@ -1208,8 +1210,7 @@ void IMergeTreeDataPart::loadColumns(bool require) infos.readJSON(*in); } - setColumns(loaded_columns); - setSerializationInfos(infos); + setColumns(loaded_columns, infos); } void IMergeTreeDataPart::assertHasVersionMetadata(MergeTreeTransaction * txn) const diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index bf47ced2a77..cbef544f816 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -133,19 +133,17 @@ public: String getTypeName() const { return getType().toString(); } - void setColumns(const NamesAndTypesList & new_columns); + void setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos); const NamesAndTypesList & getColumns() const { return columns; } - const ColumnsDescription & getColumnsDescription() const { return columns_description; } NameAndTypePair getColumn(const String & name) const; std::optional tryGetColumn(const String & column_name) const; - void setSerializationInfos(const SerializationInfoByName & new_infos); - const SerializationInfoByName & getSerializationInfos() const { return serialization_infos; } - SerializationPtr getSerialization(const NameAndTypePair & column) const; + SerializationPtr getSerialization(const String & column_name) const; + SerializationPtr tryGetSerialization(const String & column_name) const; /// Throws an exception if part is not stored in on-disk format. void assertOnDisk() const; @@ -525,6 +523,8 @@ private: /// Map from name of column to its serialization info. SerializationInfoByName serialization_infos; + SerializationByName serializations; + /// Columns description for more convenient access /// to columns by name and getting subcolumns. ColumnsDescription columns_description; diff --git a/src/Storages/MergeTree/IMergeTreeReader.cpp b/src/Storages/MergeTree/IMergeTreeReader.cpp index a5ef2fa6ac8..30969939622 100644 --- a/src/Storages/MergeTree/IMergeTreeReader.cpp +++ b/src/Storages/MergeTree/IMergeTreeReader.cpp @@ -33,7 +33,6 @@ IMergeTreeReader::IMergeTreeReader( const ValueSizeMap & avg_value_size_hints_) : data_part(data_part_) , avg_value_size_hints(avg_value_size_hints_) - , columns(columns_) , uncompressed_cache(uncompressed_cache_) , mark_cache(mark_cache_) , settings(settings_) @@ -41,12 +40,19 @@ IMergeTreeReader::IMergeTreeReader( , metadata_snapshot(metadata_snapshot_) , all_mark_ranges(all_mark_ranges_) , alter_conversions(storage.getAlterConversionsForPart(data_part)) + , requested_columns(isWidePart(data_part) ? Nested::convertToSubcolumns(columns_) : columns_) + , part_columns(isWidePart(data_part) ? Nested::collect(data_part->getColumns()) : data_part->getColumns()) { + columns_to_read.reserve(requested_columns.size()); + serializations.reserve(requested_columns.size()); + + for (const auto & column : requested_columns) + { + columns_to_read.emplace_back(getColumnInPart(column)); + serializations.emplace_back(getSerializationInPart(column)); + } } -IMergeTreeReader::~IMergeTreeReader() = default; - - const IMergeTreeReader::ValueSizeMap & IMergeTreeReader::getAvgValueSizeHints() const { return avg_value_size_hints; @@ -56,7 +62,7 @@ void IMergeTreeReader::fillMissingColumns(Columns & res_columns, bool & should_e { try { - DB::fillMissingColumns(res_columns, num_rows, columns, metadata_snapshot); + DB::fillMissingColumns(res_columns, num_rows, requested_columns, metadata_snapshot); should_evaluate_missing_defaults = std::any_of( res_columns.begin(), res_columns.end(), [](const auto & column) { return column == nullptr; }); } @@ -72,7 +78,7 @@ void IMergeTreeReader::evaluateMissingDefaults(Block additional_columns, Columns { try { - size_t num_columns = columns.size(); + size_t num_columns = requested_columns.size(); if (res_columns.size() != num_columns) throw Exception("invalid number of columns passed to MergeTreeReader::fillMissingColumns. " @@ -81,7 +87,7 @@ void IMergeTreeReader::evaluateMissingDefaults(Block additional_columns, Columns /// Convert columns list to block. /// TODO: rewrite with columns interface. It will be possible after changes in ExpressionActions. - auto name_and_type = columns.begin(); + auto name_and_type = requested_columns.begin(); for (size_t pos = 0; pos < num_columns; ++pos, ++name_and_type) { if (res_columns[pos] == nullptr) @@ -91,7 +97,7 @@ void IMergeTreeReader::evaluateMissingDefaults(Block additional_columns, Columns } auto dag = DB::evaluateMissingDefaults( - additional_columns, columns, metadata_snapshot->getColumns(), storage.getContext()); + additional_columns, requested_columns, metadata_snapshot->getColumns(), storage.getContext()); if (dag) { dag->addMaterializingOutputActions(); @@ -102,7 +108,7 @@ void IMergeTreeReader::evaluateMissingDefaults(Block additional_columns, Columns } /// Move columns from block. - name_and_type = columns.begin(); + name_and_type = requested_columns.begin(); for (size_t pos = 0; pos < num_columns; ++pos, ++name_and_type) res_columns[pos] = std::move(additional_columns.getByName(name_and_type->name).column); } @@ -128,18 +134,33 @@ String IMergeTreeReader::getColumnNameInPart(const NameAndTypePair & required_co NameAndTypePair IMergeTreeReader::getColumnInPart(const NameAndTypePair & required_column) const { - auto column_in_part = data_part->tryGetColumn(getColumnNameInPart(required_column)); + auto name_in_part = getColumnNameInPart(required_column); + auto column_in_part = part_columns.tryGetColumnOrSubcolumn(GetColumnsOptions::AllPhysical, name_in_part); if (column_in_part) return *column_in_part; return required_column; } +SerializationPtr IMergeTreeReader::getSerializationInPart(const NameAndTypePair & required_column) const +{ + auto name_in_part = getColumnNameInPart(required_column); + auto column_in_part = part_columns.tryGetColumnOrSubcolumn(GetColumnsOptions::AllPhysical, name_in_part); + if (!column_in_part) + return IDataType::getSerialization(required_column); + + const auto & infos = data_part->getSerializationInfos(); + if (auto it = infos.find(column_in_part->getNameInStorage()); it != infos.end()) + return IDataType::getSerialization(*column_in_part, *it->second); + + return IDataType::getSerialization(*column_in_part); +} + void IMergeTreeReader::performRequiredConversions(Columns & res_columns) const { try { - size_t num_columns = columns.size(); + size_t num_columns = requested_columns.size(); if (res_columns.size() != num_columns) { @@ -154,7 +175,7 @@ void IMergeTreeReader::performRequiredConversions(Columns & res_columns) const } Block copy_block; - auto name_and_type = columns.begin(); + auto name_and_type = requested_columns.begin(); for (size_t pos = 0; pos < num_columns; ++pos, ++name_and_type) { @@ -164,14 +185,12 @@ void IMergeTreeReader::performRequiredConversions(Columns & res_columns) const copy_block.insert({res_columns[pos], getColumnInPart(*name_and_type).type, name_and_type->name}); } - DB::performRequiredConversions(copy_block, columns, storage.getContext()); + DB::performRequiredConversions(copy_block, requested_columns, storage.getContext()); /// Move columns from block. - name_and_type = columns.begin(); + name_and_type = requested_columns.begin(); for (size_t pos = 0; pos < num_columns; ++pos, ++name_and_type) - { res_columns[pos] = std::move(copy_block.getByName(name_and_type->name).column); - } } catch (Exception & e) { @@ -199,9 +218,9 @@ IMergeTreeReader::ColumnPosition IMergeTreeReader::findColumnForOffsets(const St void IMergeTreeReader::checkNumberOfColumns(size_t num_columns_to_read) const { - if (num_columns_to_read != columns.size()) + if (num_columns_to_read != requested_columns.size()) throw Exception("invalid number of columns passed to MergeTreeReader::readRows. " - "Expected " + toString(columns.size()) + ", " + "Expected " + toString(requested_columns.size()) + ", " "got " + toString(num_columns_to_read), ErrorCodes::LOGICAL_ERROR); } diff --git a/src/Storages/MergeTree/IMergeTreeReader.h b/src/Storages/MergeTree/IMergeTreeReader.h index f844946fe22..0d6c0e607cd 100644 --- a/src/Storages/MergeTree/IMergeTreeReader.h +++ b/src/Storages/MergeTree/IMergeTreeReader.h @@ -37,7 +37,7 @@ public: virtual bool canReadIncompleteGranules() const = 0; - virtual ~IMergeTreeReader(); + virtual ~IMergeTreeReader() = default; const ValueSizeMap & getAvgValueSizeHints() const; @@ -52,8 +52,8 @@ public: /// try to perform conversions of columns. void performRequiredConversions(Columns & res_columns) const; - const NamesAndTypesList & getColumns() const { return columns; } - size_t numColumnsInResult() const { return columns.size(); } + const NamesAndTypesList & getColumns() const { return requested_columns; } + size_t numColumnsInResult() const { return requested_columns.size(); } size_t getFirstMarkToRead() const { return all_mark_ranges.front().begin; } @@ -65,6 +65,8 @@ protected: /// Returns actual column name and type in part, which can differ from table metadata. NameAndTypePair getColumnInPart(const NameAndTypePair & required_column) const; + /// Returns actual serialization in part, which can differ from table metadata. + SerializationPtr getSerializationInPart(const NameAndTypePair & required_column) const; void checkNumberOfColumns(size_t num_columns_to_read) const; @@ -73,8 +75,8 @@ protected: /// Stores states for IDataType::deserializeBinaryBulk DeserializeBinaryBulkStateMap deserialize_binary_bulk_state_map; - /// Columns that are read. - NamesAndTypesList columns; + NamesAndTypes columns_to_read; + Serializations serializations; UncompressedCache * uncompressed_cache; MarkCache * mark_cache; @@ -91,6 +93,12 @@ protected: private: /// Alter conversions, which must be applied on fly if required MergeTreeData::AlterConversions alter_conversions; + + /// Columns that are requested to read. + NamesAndTypesList requested_columns; + + /// Actual columns description in part. + ColumnsDescription part_columns; }; } diff --git a/src/Storages/MergeTree/IMergedBlockOutputStream.cpp b/src/Storages/MergeTree/IMergedBlockOutputStream.cpp index 48cf720ad67..31c6a635b18 100644 --- a/src/Storages/MergeTree/IMergedBlockOutputStream.cpp +++ b/src/Storages/MergeTree/IMergedBlockOutputStream.cpp @@ -48,10 +48,10 @@ NameSet IMergedBlockOutputStream::removeEmptyColumnsFromPart( std::map stream_counts; for (const auto & column : columns) { - data_part->getSerialization(column)->enumerateStreams( + data_part->getSerialization(column.name)->enumerateStreams( [&](const ISerialization::SubstreamPath & substream_path) { - ++stream_counts[ISerialization::getFileNameForStream(column, substream_path)]; + ++stream_counts[ISerialization::getFileNameForStream(column.name, substream_path)]; }); } @@ -59,13 +59,13 @@ NameSet IMergedBlockOutputStream::removeEmptyColumnsFromPart( const String mrk_extension = data_part->getMarksFileExtension(); for (const auto & column_name : empty_columns) { - auto column_with_type = columns.tryGetByName(column_name); - if (!column_with_type) - continue; + auto serialization = data_part->tryGetSerialization(column_name); + if (!serialization) + continue; ISerialization::StreamCallback callback = [&](const ISerialization::SubstreamPath & substream_path) { - String stream_name = ISerialization::getFileNameForStream(*column_with_type, substream_path); + String stream_name = ISerialization::getFileNameForStream(column_name, substream_path); /// Delete files if they are no longer shared with another column. if (--stream_counts[stream_name] == 0) { @@ -74,7 +74,7 @@ NameSet IMergedBlockOutputStream::removeEmptyColumnsFromPart( } }; - data_part->getSerialization(*column_with_type)->enumerateStreams(callback); + serialization->enumerateStreams(callback); serialization_infos.erase(column_name); } diff --git a/src/Storages/MergeTree/MergeTask.cpp b/src/Storages/MergeTree/MergeTask.cpp index dc468174dfa..2c21e5de2b0 100644 --- a/src/Storages/MergeTree/MergeTask.cpp +++ b/src/Storages/MergeTree/MergeTask.cpp @@ -199,8 +199,7 @@ bool MergeTask::ExecuteAndFinalizeHorizontalPart::prepare() infos.add(part->getSerializationInfos()); } - global_ctx->new_data_part->setColumns(global_ctx->storage_columns); - global_ctx->new_data_part->setSerializationInfos(infos); + global_ctx->new_data_part->setColumns(global_ctx->storage_columns, infos); 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/MergeTreeDataPartInMemory.cpp b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp index b22356a38ed..1c5006f4211 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp @@ -78,7 +78,7 @@ DataPartStoragePtr MergeTreeDataPartInMemory::flushToDisk(const String & new_rel auto new_data_part = storage.createPart(name, new_type, info, new_data_part_storage); new_data_part->uuid = uuid; - new_data_part->setColumns(columns); + new_data_part->setColumns(columns, {}); new_data_part->partition.value = partition.value; new_data_part->minmax_idx = minmax_idx; @@ -118,7 +118,7 @@ DataPartStoragePtr MergeTreeDataPartInMemory::flushToDisk(const String & new_rel auto projection_data_part = storage.createPart(projection_name, projection_type, projection_info, projection_part_storage_builder->getStorage(), parent_part); projection_data_part->is_temp = false; // clean up will be done on parent part - projection_data_part->setColumns(projection->getColumns()); + projection_data_part->setColumns(projection->getColumns(), {}); projection_part_storage_builder->createDirectories(); const auto & desc = projections.get(name); diff --git a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp index b70d67de3b4..c7b6ff0c4dd 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp @@ -81,7 +81,7 @@ ColumnSize MergeTreeDataPartWide::getColumnSizeImpl( if (checksums.empty()) return size; - getSerialization(column)->enumerateStreams([&](const ISerialization::SubstreamPath & substream_path) + getSerialization(column.name)->enumerateStreams([&](const ISerialization::SubstreamPath & substream_path) { String file_name = ISerialization::getFileNameForStream(column, substream_path); @@ -169,9 +169,9 @@ void MergeTreeDataPartWide::checkConsistency(bool require_part_metadata) const { if (require_part_metadata) { - for (const NameAndTypePair & name_type : columns) + for (const auto & name_type : columns) { - getSerialization(name_type)->enumerateStreams([&](const ISerialization::SubstreamPath & substream_path) + getSerialization(name_type.name)->enumerateStreams([&](const ISerialization::SubstreamPath & substream_path) { String file_name = ISerialization::getFileNameForStream(name_type, substream_path); String mrk_file_name = file_name + index_granularity_info.marks_file_extension; @@ -196,9 +196,9 @@ void MergeTreeDataPartWide::checkConsistency(bool require_part_metadata) const { /// Check that all marks are nonempty and have the same size. std::optional marks_size; - for (const NameAndTypePair & name_type : columns) + for (const auto & name_type : columns) { - getSerialization(name_type)->enumerateStreams([&](const ISerialization::SubstreamPath & substream_path) + getSerialization(name_type.name)->enumerateStreams([&](const ISerialization::SubstreamPath & substream_path) { auto file_path = ISerialization::getFileNameForStream(name_type, substream_path) + index_granularity_info.marks_file_extension; @@ -237,7 +237,7 @@ bool MergeTreeDataPartWide::hasColumnFiles(const NameAndTypePair & column) const }; bool res = true; - getSerialization(column)->enumerateStreams([&](const auto & substream_path) + getSerialization(column.name)->enumerateStreams([&](const auto & substream_path) { String file_name = ISerialization::getFileNameForStream(column, substream_path); if (!check_stream_exists(file_name)) @@ -250,7 +250,7 @@ bool MergeTreeDataPartWide::hasColumnFiles(const NameAndTypePair & column) const String MergeTreeDataPartWide::getFileNameForColumn(const NameAndTypePair & column) const { String filename; - getSerialization(column)->enumerateStreams([&](const ISerialization::SubstreamPath & substream_path) + getSerialization(column.name)->enumerateStreams([&](const ISerialization::SubstreamPath & substream_path) { if (filename.empty()) filename = ISerialization::getFileNameForStream(column, substream_path); @@ -261,7 +261,7 @@ String MergeTreeDataPartWide::getFileNameForColumn(const NameAndTypePair & colum void MergeTreeDataPartWide::calculateEachColumnSizes(ColumnSizeByName & each_columns_size, ColumnSize & total_size) const { std::unordered_set processed_substreams; - for (const NameAndTypePair & column : columns) + for (const auto & column : columns) { ColumnSize size = getColumnSizeImpl(column, &processed_substreams); each_columns_size[column.name] = size; @@ -272,7 +272,7 @@ void MergeTreeDataPartWide::calculateEachColumnSizes(ColumnSizeByName & each_col if (rows_count != 0 && column.type->isValueRepresentedByNumber() && !column.type->haveSubtypes() - && getSerialization(column)->getKind() == ISerialization::Kind::DEFAULT) + && getSerialization(column.name)->getKind() == ISerialization::Kind::DEFAULT) { size_t rows_in_column = size.data_uncompressed / column.type->getSizeOfValueInMemory(); if (rows_in_column != rows_count) diff --git a/src/Storages/MergeTree/MergeTreeDataPartWriterCompact.cpp b/src/Storages/MergeTree/MergeTreeDataPartWriterCompact.cpp index d181a15d08f..771248b99c6 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWriterCompact.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWriterCompact.cpp @@ -67,7 +67,7 @@ void MergeTreeDataPartWriterCompact::addStreams(const NameAndTypePair & column, }; ISerialization::SubstreamPath path; - data_part->getSerialization(column)->enumerateStreams(path, callback, column.type); + data_part->getSerialization(column.name)->enumerateStreams(path, callback, column.type); } namespace @@ -208,7 +208,7 @@ void MergeTreeDataPartWriterCompact::writeDataBlock(const Block & block, const G writeIntBinary(static_cast(0), marks); writeColumnSingleGranule( - block.getByName(name_and_type->name), data_part->getSerialization(*name_and_type), + block.getByName(name_and_type->name), data_part->getSerialization(name_and_type->name), stream_getter, granule.start_row, granule.rows_to_write); /// Each type always have at least one substream diff --git a/src/Storages/MergeTree/MergeTreeDataPartWriterWide.cpp b/src/Storages/MergeTree/MergeTreeDataPartWriterWide.cpp index e3925940553..712c3f74bdd 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWriterWide.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWriterWide.cpp @@ -121,7 +121,7 @@ void MergeTreeDataPartWriterWide::addStreams( }; ISerialization::SubstreamPath path; - data_part->getSerialization(column)->enumerateStreams(path, callback, column.type); + data_part->getSerialization(column.name)->enumerateStreams(path, callback, column.type); } @@ -216,7 +216,7 @@ void MergeTreeDataPartWriterWide::write(const Block & block, const IColumn::Perm { auto & column = block_to_write.getByName(it->name); - if (data_part->getSerialization(*it)->getKind() != ISerialization::Kind::SPARSE) + if (data_part->getSerialization(it->name)->getKind() != ISerialization::Kind::SPARSE) column.column = recursiveRemoveSparse(column.column); if (permutation) @@ -278,7 +278,7 @@ StreamsWithMarks MergeTreeDataPartWriterWide::getCurrentMarksForColumn( ISerialization::SubstreamPath & path) { StreamsWithMarks result; - data_part->getSerialization(column)->enumerateStreams([&] (const ISerialization::SubstreamPath & substream_path) + data_part->getSerialization(column.name)->enumerateStreams([&] (const ISerialization::SubstreamPath & substream_path) { bool is_offsets = !substream_path.empty() && substream_path.back().type == ISerialization::Substream::ArraySizes; @@ -313,7 +313,7 @@ void MergeTreeDataPartWriterWide::writeSingleGranule( ISerialization::SerializeBinaryBulkSettings & serialize_settings, const Granule & granule) { - const auto & serialization = data_part->getSerialization(name_and_type); + const auto & serialization = data_part->getSerialization(name_and_type.name); serialization->serializeBinaryBulkWithMultipleStreams(column, granule.start_row, granule.rows_to_write, serialize_settings, serialization_state); /// So that instead of the marks pointing to the end of the compressed block, there were marks pointing to the beginning of the next one. @@ -343,7 +343,7 @@ void MergeTreeDataPartWriterWide::writeColumn( const auto & [name, type] = name_and_type; auto [it, inserted] = serialization_states.emplace(name, nullptr); - auto serialization = data_part->getSerialization(name_and_type); + auto serialization = data_part->getSerialization(name_and_type.name); if (inserted) { @@ -405,7 +405,7 @@ void MergeTreeDataPartWriterWide::writeColumn( void MergeTreeDataPartWriterWide::validateColumnOfFixedSize(const NameAndTypePair & name_type) { const auto & [name, type] = name_type; - const auto & serialization = data_part->getSerialization(name_type); + const auto & serialization = data_part->getSerialization(name_type.name); if (!type->isValueRepresentedByNumber() || type->haveSubtypes() || serialization->getKind() != ISerialization::Kind::DEFAULT) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot validate column of non fixed type {}", type->getName()); @@ -549,7 +549,7 @@ void MergeTreeDataPartWriterWide::fillDataChecksums(IMergeTreeDataPart::Checksum if (!serialization_states.empty()) { serialize_settings.getter = createStreamGetter(*it, written_offset_columns ? *written_offset_columns : offset_columns); - data_part->getSerialization(*it)->serializeBinaryBulkStateSuffix(serialize_settings, serialization_states[it->name]); + data_part->getSerialization(it->name)->serializeBinaryBulkStateSuffix(serialize_settings, serialization_states[it->name]); } if (write_final_mark) @@ -583,7 +583,7 @@ void MergeTreeDataPartWriterWide::finishDataSerialization(bool sync) { if (column.type->isValueRepresentedByNumber() && !column.type->haveSubtypes() - && data_part->getSerialization(column)->getKind() == ISerialization::Kind::DEFAULT) + && data_part->getSerialization(columnn.name)->getKind() == ISerialization::Kind::DEFAULT) { validateColumnOfFixedSize(column); } @@ -623,7 +623,7 @@ void MergeTreeDataPartWriterWide::writeFinalMark( { writeSingleMark(column, offset_columns, 0, path); /// Memoize information about offsets - data_part->getSerialization(column)->enumerateStreams([&] (const ISerialization::SubstreamPath & substream_path) + data_part->getSerialization(column.name)->enumerateStreams([&] (const ISerialization::SubstreamPath & substream_path) { bool is_offsets = !substream_path.empty() && substream_path.back().type == ISerialization::Substream::ArraySizes; if (is_offsets) diff --git a/src/Storages/MergeTree/MergeTreeDataWriter.cpp b/src/Storages/MergeTree/MergeTreeDataWriter.cpp index 89042e25a0e..957958e0a50 100644 --- a/src/Storages/MergeTree/MergeTreeDataWriter.cpp +++ b/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -395,8 +395,7 @@ MergeTreeDataWriter::TemporaryPart MergeTreeDataWriter::writeTempPart( SerializationInfoByName infos(columns, settings); infos.add(block); - new_data_part->setColumns(columns); - new_data_part->setSerializationInfos(infos); + new_data_part->setColumns(columns, infos); new_data_part->rows_count = block.rows(); new_data_part->partition = std::move(partition); new_data_part->minmax_idx = std::move(minmax_idx); @@ -523,8 +522,7 @@ MergeTreeDataWriter::TemporaryPart MergeTreeDataWriter::writeProjectionPartImpl( SerializationInfoByName infos(columns, settings); infos.add(block); - new_data_part->setColumns(columns); - new_data_part->setSerializationInfos(infos); + new_data_part->setColumns(columns, infos); if (new_data_part->isStoredOnDisk()) { diff --git a/src/Storages/MergeTree/MergeTreeReaderCompact.cpp b/src/Storages/MergeTree/MergeTreeReaderCompact.cpp index 5cb37b0515a..3c05804c3f5 100644 --- a/src/Storages/MergeTree/MergeTreeReaderCompact.cpp +++ b/src/Storages/MergeTree/MergeTreeReaderCompact.cpp @@ -45,30 +45,30 @@ MergeTreeReaderCompact::MergeTreeReaderCompact( { try { - size_t columns_num = columns.size(); + size_t columns_num = columns_to_read.size(); column_positions.resize(columns_num); read_only_offsets.resize(columns_num); - auto name_and_type = columns.begin(); - for (size_t i = 0; i < columns_num; ++i, ++name_and_type) + + for (size_t i = 0; i < columns_num; ++i) { - if (name_and_type->isSubcolumn()) + const auto & column_to_read = columns_to_read[i]; + + if (column_to_read.isSubcolumn()) { auto storage_column_from_part = getColumnInPart( - {name_and_type->getNameInStorage(), name_and_type->getTypeInStorage()}); + {column_to_read.getNameInStorage(), column_to_read.getTypeInStorage()}); - if (!storage_column_from_part.type->tryGetSubcolumnType(name_and_type->getSubcolumnName())) + if (!storage_column_from_part.type->tryGetSubcolumnType(column_to_read.getSubcolumnName())) continue; } - auto column_from_part = getColumnInPart(*name_and_type); - - auto position = data_part->getColumnPosition(column_from_part.getNameInStorage()); - if (!position && typeid_cast(column_from_part.type.get())) + auto position = data_part->getColumnPosition(column_to_read.getNameInStorage()); + if (!position && typeid_cast(column_to_read.type.get())) { /// If array of Nested column is missing in part, /// we have to read its offsets if they exist. - position = findColumnForOffsets(column_from_part.name); + position = findColumnForOffsets(column_to_read.name); read_only_offsets[i] = (position != std::nullopt); } @@ -143,42 +143,31 @@ size_t MergeTreeReaderCompact::readRows( from_mark = next_mark; size_t read_rows = 0; - size_t num_columns = columns.size(); + size_t num_columns = columns_to_read.size(); checkNumberOfColumns(num_columns); MutableColumns mutable_columns(num_columns); - auto column_it = columns.begin(); - for (size_t i = 0; i < num_columns; ++i, ++column_it) + for (size_t i = 0; i < num_columns; ++i) { - if (!column_positions[i]) - continue; - - auto column_from_part = getColumnInPart(*column_it); - if (res_columns[i] == nullptr) - { - auto serialization = data_part->getSerialization(column_from_part); - res_columns[i] = column_from_part.type->createColumn(*serialization); - } + if (column_positions[i] && res_columns[i] == nullptr) + res_columns[i] = columns_to_read[i].type->createColumn(*serializations[i]); } while (read_rows < max_rows_to_read) { size_t rows_to_read = data_part->index_granularity.getMarkRows(from_mark); - auto name_and_type = columns.begin(); - for (size_t pos = 0; pos < num_columns; ++pos, ++name_and_type) + for (size_t pos = 0; pos < num_columns; ++pos) { if (!res_columns[pos]) continue; - auto column_from_part = getColumnInPart(*name_and_type); - try { auto & column = res_columns[pos]; size_t column_size_before_reading = column->size(); - readData(column_from_part, column, from_mark, current_task_last_mark, *column_positions[pos], rows_to_read, read_only_offsets[pos]); + readData(columns_to_read[pos], column, from_mark, current_task_last_mark, *column_positions[pos], rows_to_read, read_only_offsets[pos]); size_t read_rows_in_column = column->size() - column_size_before_reading; if (read_rows_in_column != rows_to_read) @@ -192,7 +181,7 @@ size_t MergeTreeReaderCompact::readRows( storage.reportBrokenPart(data_part); /// Better diagnostics. - e.addMessage("(while reading column " + column_from_part.name + ")"); + e.addMessage("(while reading column " + columns_to_read[pos].name + ")"); throw; } catch (...) @@ -240,7 +229,7 @@ void MergeTreeReaderCompact::readData( const auto & type_in_storage = name_and_type.getTypeInStorage(); const auto & name_in_storage = name_and_type.getNameInStorage(); - auto serialization = data_part->getSerialization(NameAndTypePair{name_in_storage, type_in_storage}); + auto serialization = getSerializationInPart({name_in_storage, type_in_storage}); ColumnPtr temp_column = type_in_storage->createColumn(*serialization); serialization->deserializeBinaryBulkStatePrefix(deserialize_settings, state); @@ -256,7 +245,7 @@ void MergeTreeReaderCompact::readData( } else { - auto serialization = data_part->getSerialization(name_and_type); + auto serialization = getSerializationInPart(name_and_type); serialization->deserializeBinaryBulkStatePrefix(deserialize_settings, state); serialization->deserializeBinaryBulkWithMultipleStreams(column, rows_to_read, deserialize_settings, state, nullptr); } diff --git a/src/Storages/MergeTree/MergeTreeReaderInMemory.cpp b/src/Storages/MergeTree/MergeTreeReaderInMemory.cpp index d8e0b6e4d6c..766c28c99b9 100644 --- a/src/Storages/MergeTree/MergeTreeReaderInMemory.cpp +++ b/src/Storages/MergeTree/MergeTreeReaderInMemory.cpp @@ -32,10 +32,8 @@ MergeTreeReaderInMemory::MergeTreeReaderInMemory( {}) , part_in_memory(std::move(data_part_)) { - for (const auto & name_and_type : columns) + for (const auto & [name, type] : columns_to_read) { - auto [name, type] = getColumnInPart(name_and_type); - /// If array of Nested column is missing in part, /// we have to read its offsets if they exist. if (!part_in_memory->block.has(name) && typeid_cast(type.get())) @@ -64,20 +62,19 @@ size_t MergeTreeReaderInMemory::readRows( + toString(total_rows_read) + ". Rows in part: " + toString(part_rows), ErrorCodes::CANNOT_READ_ALL_DATA); size_t rows_to_read = std::min(max_rows_to_read, part_rows - total_rows_read); - auto column_it = columns.begin(); - for (size_t i = 0; i < num_columns; ++i, ++column_it) + for (size_t i = 0; i < num_columns; ++i) { - auto name_type = getColumnInPart(*column_it); + const auto & column_to_read = columns_to_read[i]; /// Copy offsets, if array of Nested column is missing in part. - auto offsets_it = positions_for_offsets.find(name_type.name); - if (offsets_it != positions_for_offsets.end() && !name_type.isSubcolumn()) + auto offsets_it = positions_for_offsets.find(column_to_read.name); + if (offsets_it != positions_for_offsets.end() && !column_to_read.isSubcolumn()) { const auto & source_offsets = assert_cast( *part_in_memory->block.getByPosition(offsets_it->second).column).getOffsets(); if (res_columns[i] == nullptr) - res_columns[i] = name_type.type->createColumn(); + res_columns[i] = column_to_read.type->createColumn(); auto mutable_column = res_columns[i]->assumeMutable(); auto & res_offstes = assert_cast(*mutable_column).getOffsets(); @@ -87,9 +84,9 @@ size_t MergeTreeReaderInMemory::readRows( res_columns[i] = std::move(mutable_column); } - else if (part_in_memory->hasColumnFiles(name_type)) + else if (part_in_memory->hasColumnFiles(column_to_read)) { - auto block_column = getColumnFromBlock(part_in_memory->block, name_type); + auto block_column = getColumnFromBlock(part_in_memory->block, column_to_read); if (rows_to_read == part_rows) { res_columns[i] = block_column; @@ -97,7 +94,7 @@ size_t MergeTreeReaderInMemory::readRows( else { if (res_columns[i] == nullptr) - res_columns[i] = name_type.type->createColumn(); + res_columns[i] = column_to_read.type->createColumn(); auto mutable_column = res_columns[i]->assumeMutable(); mutable_column->insertRangeFrom(*block_column, total_rows_read, rows_to_read); diff --git a/src/Storages/MergeTree/MergeTreeReaderWide.cpp b/src/Storages/MergeTree/MergeTreeReaderWide.cpp index aed9a906a5c..1274017b865 100644 --- a/src/Storages/MergeTree/MergeTreeReaderWide.cpp +++ b/src/Storages/MergeTree/MergeTreeReaderWide.cpp @@ -48,11 +48,8 @@ MergeTreeReaderWide::MergeTreeReaderWide( { try { - for (const NameAndTypePair & column : columns) - { - auto column_from_part = getColumnInPart(column); - addStreams(column_from_part, profile_callback_, clock_type_); - } + for (size_t i = 0; i < columns_to_read.size(); ++i) + addStreams(columns_to_read[i], serializations[i], profile_callback_, clock_type_); } catch (...) { @@ -61,20 +58,6 @@ MergeTreeReaderWide::MergeTreeReaderWide( } } -String MergeTreeReaderWide::getNameForSubstreamCache(const NameAndTypePair & column) const -{ - if (!column.isSubcolumn() && isArray(column.type)) - { - auto split = Nested::splitName(column.name); - const auto & part_columns = data_part->getColumnsDescription(); - - if (!split.second.empty() && part_columns.hasNested(split.first)) - return split.first; - } - - return column.getNameInStorage(); -} - size_t MergeTreeReaderWide::readRows( size_t from_mark, size_t current_task_last_mark, bool continue_reading, size_t max_rows_to_read, Columns & res_columns) { @@ -94,47 +77,40 @@ size_t MergeTreeReaderWide::readRows( { /// Request reading of data in advance, /// so if reading can be asynchronous, it will also be performed in parallel for all columns. - auto name_and_type = columns.begin(); - for (size_t pos = 0; pos < num_columns; ++pos, ++name_and_type) + for (size_t pos = 0; pos < num_columns; ++pos) { - auto column_from_part = getColumnInPart(*name_and_type); try { - auto & cache = caches[getNameForSubstreamCache(column_from_part)]; - prefetch(column_from_part, from_mark, continue_reading, current_task_last_mark, cache, prefetched_streams); + auto & cache = caches[columns_to_read[pos].getNameInStorage()]; + prefetch(columns_to_read[pos], serializations[pos], from_mark, continue_reading, current_task_last_mark, cache, prefetched_streams); } catch (Exception & e) { /// Better diagnostics. - e.addMessage("(while reading column " + column_from_part.name + ")"); + e.addMessage("(while reading column " + columns_to_read[pos].name + ")"); throw; } } } - auto name_and_type = columns.begin(); - - for (size_t pos = 0; pos < num_columns; ++pos, ++name_and_type) + for (size_t pos = 0; pos < num_columns; ++pos) { - auto column_from_part = getColumnInPart(*name_and_type); - const auto & [name, type] = column_from_part; + const auto & column_to_read = columns_to_read[pos]; /// The column is already present in the block so we will append the values to the end. bool append = res_columns[pos] != nullptr; if (!append) - { - auto serialization = data_part->getSerialization(column_from_part); - res_columns[pos] = type->createColumn(*serialization); - } + res_columns[pos] = column_to_read.type->createColumn(*serializations[pos]); auto & column = res_columns[pos]; try { size_t column_size_before_reading = column->size(); - auto & cache = caches[getNameForSubstreamCache(column_from_part)]; + auto & cache = caches[column_to_read.getNameInStorage()]; readData( - column_from_part, column, from_mark, continue_reading, current_task_last_mark, + column_to_read, serializations[pos], column, + from_mark, continue_reading, current_task_last_mark, max_rows_to_read, cache, /* was_prefetched =*/ !prefetched_streams.empty()); /// For elements of Nested, column_size_before_reading may be greater than column size @@ -145,7 +121,7 @@ size_t MergeTreeReaderWide::readRows( catch (Exception & e) { /// Better diagnostics. - e.addMessage("(while reading column " + name + ")"); + e.addMessage("(while reading column " + column_to_read.name + ")"); throw; } @@ -178,8 +154,11 @@ size_t MergeTreeReaderWide::readRows( return read_rows; } -void MergeTreeReaderWide::addStreams(const NameAndTypePair & name_and_type, - const ReadBufferFromFileBase::ProfileCallback & profile_callback, clockid_t clock_type) +void MergeTreeReaderWide::addStreams( + const NameAndTypePair & name_and_type, + const SerializationPtr & serialization, + const ReadBufferFromFileBase::ProfileCallback & profile_callback, + clockid_t clock_type) { ISerialization::StreamCallback callback = [&] (const ISerialization::SubstreamPath & substream_path) { @@ -206,7 +185,7 @@ void MergeTreeReaderWide::addStreams(const NameAndTypePair & name_and_type, profile_callback, clock_type, is_lc_dict)); }; - data_part->getSerialization(name_and_type)->enumerateStreams(callback); + serialization->enumerateStreams(callback); } @@ -260,13 +239,13 @@ void MergeTreeReaderWide::deserializePrefix( void MergeTreeReaderWide::prefetch( const NameAndTypePair & name_and_type, + const SerializationPtr & serialization, size_t from_mark, bool continue_reading, size_t current_task_last_mark, ISerialization::SubstreamsCache & cache, std::unordered_set & prefetched_streams) { - auto serialization = data_part->getSerialization(name_and_type); deserializePrefix(serialization, name_and_type, current_task_last_mark, cache); serialization->enumerateStreams([&](const ISerialization::SubstreamPath & substream_path) @@ -286,7 +265,7 @@ void MergeTreeReaderWide::prefetch( void MergeTreeReaderWide::readData( - const NameAndTypePair & name_and_type, ColumnPtr & column, + const NameAndTypePair & name_and_type, const SerializationPtr & serialization, ColumnPtr & column, size_t from_mark, bool continue_reading, size_t current_task_last_mark, size_t max_rows_to_read, ISerialization::SubstreamsCache & cache, bool was_prefetched) { @@ -294,9 +273,6 @@ void MergeTreeReaderWide::readData( ISerialization::DeserializeBinaryBulkSettings deserialize_settings; deserialize_settings.avg_value_size_hint = avg_value_size_hint; - const auto & name = name_and_type.name; - auto serialization = data_part->getSerialization(name_and_type); - deserializePrefix(serialization, name_and_type, current_task_last_mark, cache); deserialize_settings.getter = [&](const ISerialization::SubstreamPath & substream_path) @@ -308,7 +284,7 @@ void MergeTreeReaderWide::readData( seek_to_mark, current_task_last_mark, cache); }; deserialize_settings.continuous_reading = continue_reading; - auto & deserialize_state = deserialize_binary_bulk_state_map[name]; + auto & deserialize_state = deserialize_binary_bulk_state_map[name_and_type.name]; serialization->deserializeBinaryBulkWithMultipleStreams(column, max_rows_to_read, deserialize_settings, deserialize_state, &cache); IDataType::updateAvgValueSizeHint(*column, avg_value_size_hint); diff --git a/src/Storages/MergeTree/MergeTreeReaderWide.h b/src/Storages/MergeTree/MergeTreeReaderWide.h index e382f3f1dde..2137695b6d7 100644 --- a/src/Storages/MergeTree/MergeTreeReaderWide.h +++ b/src/Storages/MergeTree/MergeTreeReaderWide.h @@ -38,19 +38,21 @@ public: private: FileStreams streams; - String getNameForSubstreamCache(const NameAndTypePair & column) const; - - void addStreams(const NameAndTypePair & name_and_type, - const ReadBufferFromFileBase::ProfileCallback & profile_callback, clockid_t clock_type); + void addStreams( + const NameAndTypePair & name_and_type, + const SerializationPtr & serialization, + const ReadBufferFromFileBase::ProfileCallback & profile_callback, + clockid_t clock_type); void readData( - const NameAndTypePair & name_and_type, ColumnPtr & column, + const NameAndTypePair & name_and_type, const SerializationPtr & serialization, ColumnPtr & column, size_t from_mark, bool continue_reading, size_t current_task_last_mark, size_t max_rows_to_read, ISerialization::SubstreamsCache & cache, bool was_prefetched); /// Make next readData more simple by calling 'prefetch' of all related ReadBuffers (column streams). void prefetch( const NameAndTypePair & name_and_type, + const SerializationPtr & serialization, size_t from_mark, bool continue_reading, size_t current_task_last_mark, diff --git a/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp b/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp index 9ed8fe0ad14..9b79f89ff98 100644 --- a/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp +++ b/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp @@ -212,7 +212,7 @@ MergeTreeData::MutableDataPartsVector MergeTreeWriteAheadLog::restore(const Stor 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(), {}); 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 f7da1eb2585..6e5d16b8c7b 100644 --- a/src/Storages/MergeTree/MergedBlockOutputStream.cpp +++ b/src/Storages/MergeTree/MergedBlockOutputStream.cpp @@ -162,8 +162,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); - new_part->setSerializationInfos(serialization_infos); + new_part->setColumns(part_columns, serialization_infos); } auto finalizer = std::make_unique(*writer, new_part, data_part_storage_builder, files_to_remove_after_sync, sync); diff --git a/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp b/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp index deab5e748c7..21e01223e2c 100644 --- a/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp +++ b/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp @@ -87,8 +87,7 @@ MergedColumnOnlyOutputStream::fillChecksums( all_checksums.files.erase(removed_file); } - new_part->setColumns(columns); - new_part->setSerializationInfos(serialization_infos); + new_part->setColumns(columns, serialization_infos); return checksums; } diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 3a5aa2f8860..ea8fce19991 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -441,7 +441,8 @@ NameSet collectFilesToSkip( files_to_skip.insert(stream_name + mrk_extension); }; - source_part->getSerialization({entry.name, entry.type})->enumerateStreams(callback); + if (auto serialization = source_part->tryGetSerialization(entry.name)) + serialization->enumerateStreams(callback); } for (const auto & index : indices_to_recalc) { @@ -466,11 +467,14 @@ static NameToNameVector collectFilesForRenames( std::map stream_counts; for (const auto & column : source_part->getColumns()) { - source_part->getSerialization(column)->enumerateStreams( - [&](const ISerialization::SubstreamPath & substream_path) - { - ++stream_counts[ISerialization::getFileNameForStream(column, substream_path)]; - }); + if (auto serialization = source_part->tryGetSerialization(column.name)) + { + serialization->enumerateStreams( + [&](const ISerialization::SubstreamPath & substream_path) + { + ++stream_counts[ISerialization::getFileNameForStream(column, substream_path)]; + }); + } } NameToNameVector rename_vector; @@ -508,9 +512,9 @@ static NameToNameVector collectFilesForRenames( } }; - auto column = source_part->getColumns().tryGetByName(command.column_name); - if (column) - source_part->getSerialization(*column)->enumerateStreams(callback); + + if (auto serialization = source_part->tryGetSerialization(command.column_name)) + serialization->enumerateStreams(callback); } else if (command.type == MutationCommand::Type::RENAME_COLUMN) { @@ -530,9 +534,8 @@ static NameToNameVector collectFilesForRenames( } }; - auto column = source_part->getColumns().tryGetByName(command.column_name); - if (column) - source_part->getSerialization(*column)->enumerateStreams(callback); + if (auto serialization = source_part->tryGetSerialization(command.column_name)) + serialization->enumerateStreams(callback); } } @@ -1494,8 +1497,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); - ctx->new_data_part->setSerializationInfos(new_infos); + ctx->new_data_part->setColumns(new_columns, new_infos); 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/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 219093e8d75..eff9fce2e27 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -7862,7 +7862,7 @@ bool StorageReplicatedMergeTree::createEmptyPartInsteadOfLost(zkutil::ZooKeeperP if (settings->assign_part_uuids) new_data_part->uuid = UUIDHelpers::generateV4(); - new_data_part->setColumns(columns); + new_data_part->setColumns(columns, {}); new_data_part->rows_count = block.rows(); { diff --git a/src/Storages/System/StorageSystemPartsColumns.cpp b/src/Storages/System/StorageSystemPartsColumns.cpp index cebcfc492bf..292d8087c10 100644 --- a/src/Storages/System/StorageSystemPartsColumns.cpp +++ b/src/Storages/System/StorageSystemPartsColumns.cpp @@ -227,7 +227,7 @@ void StorageSystemPartsColumns::processNextStorage( if (columns_mask[src_index++]) columns[res_index++]->insert(column_size.marks); - auto serialization = part->getSerialization(column); + auto serialization = part->getSerialization(column.name); if (columns_mask[src_index++]) columns[res_index++]->insert(ISerialization::kindToString(serialization->getKind())); From 56039c87806a44d65d73bcd2d0338352561680f4 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 27 Jul 2022 15:35:09 +0000 Subject: [PATCH 147/672] fix alter of LowCardinality --- .../Serializations/SerializationInfo.cpp | 9 +- src/Storages/MergeTree/IMergeTreeDataPart.h | 1 + src/Storages/MergeTree/MutateTask.cpp | 102 +++++++++--------- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/DataTypes/Serializations/SerializationInfo.cpp b/src/DataTypes/Serializations/SerializationInfo.cpp index 543b4a75f0a..1b845533aaf 100644 --- a/src/DataTypes/Serializations/SerializationInfo.cpp +++ b/src/DataTypes/Serializations/SerializationInfo.cpp @@ -239,13 +239,8 @@ void SerializationInfoByName::readJSON(ReadBuffer & in) "Missed field '{}' in SerializationInfo of columns", KEY_NAME); auto name = elem_object->getValue(KEY_NAME); - auto it = find(name); - - if (it == end()) - throw Exception(ErrorCodes::CORRUPTED_DATA, - "There is no column {} in serialization infos", name); - - it->second->fromJSON(*elem_object); + if (auto it = find(name); it != end()) + it->second->fromJSON(*elem_object); } } } diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index cbef544f816..bf4eba1c354 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -136,6 +136,7 @@ public: void setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos); const NamesAndTypesList & getColumns() const { return columns; } + const ColumnsDescription & getColumnsDescription() const { return columns_description; } NameAndTypePair getColumn(const String & name) const; std::optional tryGetColumn(const String & column_name) const; diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index ea8fce19991..5b54c1e6ae7 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -57,7 +57,7 @@ static void splitMutationCommands( MutationCommands & for_interpreter, MutationCommands & for_file_renames) { - ColumnsDescription part_columns(part->getColumns()); + auto part_columns = part->getColumnsDescription(); if (!isWidePart(part)) { @@ -139,18 +139,11 @@ static void splitMutationCommands( else if (part_columns.has(command.column_name)) { 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); - } - else - { - for_file_renames.push_back(command); - } + + for_file_renames.push_back(command); } } } @@ -436,7 +429,7 @@ NameSet collectFilesToSkip( { ISerialization::StreamCallback callback = [&](const ISerialization::SubstreamPath & substream_path) { - String stream_name = ISerialization::getFileNameForStream({entry.name, entry.type}, substream_path); + String stream_name = ISerialization::getFileNameForStream(entry.name, substream_path); files_to_skip.insert(stream_name + ".bin"); files_to_skip.insert(stream_name + mrk_extension); }; @@ -461,23 +454,14 @@ NameSet collectFilesToSkip( /// from filesystem and in-memory checksums. Ordered result is important, /// because we can apply renames that affects each other: x -> z, y -> x. static NameToNameVector collectFilesForRenames( - MergeTreeData::DataPartPtr source_part, const MutationCommands & commands_for_removes, const String & mrk_extension) + MergeTreeData::DataPartPtr source_part, + MergeTreeData::DataPartPtr new_part, + const MutationCommands & commands_for_removes, + const String & mrk_extension) { - /// Collect counts for shared streams of different columns. As an example, Nested columns have shared stream with array sizes. - std::map stream_counts; - for (const auto & column : source_part->getColumns()) - { - if (auto serialization = source_part->tryGetSerialization(column.name)) - { - serialization->enumerateStreams( - [&](const ISerialization::SubstreamPath & substream_path) - { - ++stream_counts[ISerialization::getFileNameForStream(column, substream_path)]; - }); - } - } - NameToNameVector rename_vector; + NameSet renamed_streams; + /// Remove old data for (const auto & command : commands_for_removes) { @@ -499,23 +483,6 @@ static NameToNameVector collectFilesForRenames( if (source_part->checksums.has(command.column_name + ".proj")) rename_vector.emplace_back(command.column_name + ".proj", ""); } - else if (command.type == MutationCommand::Type::DROP_COLUMN) - { - ISerialization::StreamCallback callback = [&](const ISerialization::SubstreamPath & substream_path) - { - String stream_name = ISerialization::getFileNameForStream({command.column_name, command.data_type}, substream_path); - /// 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, ""); - } - }; - - - if (auto serialization = source_part->tryGetSerialization(command.column_name)) - serialization->enumerateStreams(callback); - } else if (command.type == MutationCommand::Type::RENAME_COLUMN) { String escaped_name_from = escapeForFileName(command.column_name); @@ -523,12 +490,12 @@ static NameToNameVector collectFilesForRenames( ISerialization::StreamCallback callback = [&](const ISerialization::SubstreamPath & substream_path) { - String stream_from = ISerialization::getFileNameForStream({command.column_name, command.data_type}, substream_path); - + String stream_from = ISerialization::getFileNameForStream(command.column_name, substream_path); String stream_to = boost::replace_first_copy(stream_from, escaped_name_from, escaped_name_to); if (stream_from != stream_to) { + renamed_streams.insert(stream_from); rename_vector.emplace_back(stream_from + ".bin", stream_to + ".bin"); rename_vector.emplace_back(stream_from + mrk_extension, stream_to + mrk_extension); } @@ -539,6 +506,41 @@ static NameToNameVector collectFilesForRenames( } } + auto collect_all_stream_names = [&](const auto & data_part) + { + NameSet res; + for (const auto & column : data_part->getColumns()) + { + if (auto serialization = data_part->tryGetSerialization(column.name)) + { + serialization->enumerateStreams( + [&](const ISerialization::SubstreamPath & substream_path) + { + res.insert(ISerialization::getFileNameForStream(column.name, substream_path)); + }); + } + } + + return res; + }; + + /// Remove files for streams that exists in source part, + /// but were removed in new_part by DROP COLUMN + /// or MODIFY COLUMN from type with higher number of streams + /// (e.g. LowCardinality -> String). + + auto old_streams = collect_all_stream_names(source_part); + auto new_streams = collect_all_stream_names(new_part); + + for (const auto & old_stream : old_streams) + { + if (!new_streams.contains(old_stream) && !renamed_streams.contains(old_stream)) + { + rename_vector.emplace_back(old_stream + ".bin", ""); + rename_vector.emplace_back(old_stream + mrk_extension, ""); + } + } + return rename_vector; } @@ -552,9 +554,6 @@ void finalizeMutatedPart( const CompressionCodecPtr & codec, ContextPtr context) { - //auto disk = new_data_part->volume->getDisk(); - //auto part_path = fs::path(new_data_part->getRelativePath()); - if (new_data_part->uuid != UUIDHelpers::Nil) { auto out = data_part_storage_builder->writeFile(IMergeTreeDataPart::UUID_FILE_NAME, 4096, context->getWriteSettings()); @@ -1535,7 +1534,12 @@ bool MutateTask::prepare() ctx->indices_to_recalc, ctx->mrk_extension, ctx->projections_to_recalc); - ctx->files_to_rename = MutationHelpers::collectFilesForRenames(ctx->source_part, ctx->for_file_renames, ctx->mrk_extension); + + ctx->files_to_rename = MutationHelpers::collectFilesForRenames( + ctx->source_part, + ctx->new_data_part, + ctx->for_file_renames, + ctx->mrk_extension); if (ctx->indices_to_recalc.empty() && ctx->projections_to_recalc.empty() && From 51c4c9c957bff6afe2119ec7a44441fc501f81ac Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 27 Jul 2022 15:45:23 +0000 Subject: [PATCH 148/672] Fixing build. --- src/Storages/StorageMerge.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Storages/StorageMerge.cpp b/src/Storages/StorageMerge.cpp index 3fc7ca36569..666717e50a0 100644 --- a/src/Storages/StorageMerge.cpp +++ b/src/Storages/StorageMerge.cpp @@ -37,8 +37,6 @@ #include -using std::operator""sv; - namespace DB { From 6206e468a495490c39e76a5dec39058083b346b8 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 27 Jul 2022 17:02:48 +0000 Subject: [PATCH 149/672] Ignor materialize function for ActionsDAG in PK analysis. --- src/Storages/MergeTree/KeyCondition.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index d79861e2916..5cd193eb1c9 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -732,6 +732,14 @@ static const ActionsDAG::Node & cloneASTWithInversionPushDown( return arg; } + if (name == "materialize") + { + /// Ignore materialize + const auto & arg = cloneASTWithInversionPushDown(*node.children.front(), inverted_dag, to_inverted, context, need_inversion); + to_inverted[&node] = &arg; + return arg; + } + if (name == "indexHint") { ActionsDAG::NodeRawConstPtrs children; From 24725b5f95519cf8d4ca1dde4de01b9f3cd40344 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 27 Jul 2022 19:15:00 +0200 Subject: [PATCH 150/672] fix synchronous queries --- src/Core/Settings.h | 1 + src/Databases/DatabaseReplicatedWorker.cpp | 1 + src/Interpreters/DDLTask.cpp | 18 +++++ src/Interpreters/DDLTask.h | 4 ++ src/Interpreters/DDLWorker.cpp | 7 ++ src/Interpreters/executeDDLQueryOnCluster.cpp | 12 +++- tests/config/users.d/database_replicated.xml | 1 + .../test_replicated_database/test.py | 65 +++++++++++++++++++ 8 files changed, 108 insertions(+), 1 deletion(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index e527676c53f..966bd15b17d 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -537,6 +537,7 @@ static constexpr UInt64 operator""_GiB(unsigned long long value) M(Bool, engine_file_allow_create_multiple_files, false, "Enables or disables creating a new file on each insert in file engine tables if format has suffix.", 0) \ M(Bool, allow_experimental_database_replicated, false, "Allow to create databases with Replicated engine", 0) \ M(UInt64, database_replicated_initial_query_timeout_sec, 300, "How long initial DDL query should wait for Replicated database to precess previous DDL queue entries", 0) \ + M(Bool, database_replicated_enforce_synchronous_settings, false, "Enforces synchronous waiting for some queries (see also database_atomic_wait_for_drop_and_detach_synchronously, mutation_sync, replication_alter_partitions_sync). Not recommended to enable these settings.", 0) \ M(UInt64, max_distributed_depth, 5, "Maximum distributed query depth", 0) \ M(Bool, database_replicated_always_detach_permanently, false, "Execute DETACH TABLE as DETACH TABLE PERMANENTLY if database engine is Replicated", 0) \ M(Bool, database_replicated_allow_only_replicated_engine, false, "Allow to create only Replicated tables in database with engine Replicated", 0) \ diff --git a/src/Databases/DatabaseReplicatedWorker.cpp b/src/Databases/DatabaseReplicatedWorker.cpp index a81bd86fd2b..81f9ebb97e0 100644 --- a/src/Databases/DatabaseReplicatedWorker.cpp +++ b/src/Databases/DatabaseReplicatedWorker.cpp @@ -181,6 +181,7 @@ String DatabaseReplicatedDDLWorker::enqueueQueryImpl(const ZooKeeperPtr & zookee /// Create status dirs ops.emplace_back(zkutil::makeCreateRequest(node_path + "/active", "", zkutil::CreateMode::Persistent)); ops.emplace_back(zkutil::makeCreateRequest(node_path + "/finished", "", zkutil::CreateMode::Persistent)); + ops.emplace_back(zkutil::makeCreateRequest(node_path + "/synced", "", zkutil::CreateMode::Persistent)); zookeeper->multi(ops); diff --git a/src/Interpreters/DDLTask.cpp b/src/Interpreters/DDLTask.cpp index 55a4dbf9ff4..26343f7a8bb 100644 --- a/src/Interpreters/DDLTask.cpp +++ b/src/Interpreters/DDLTask.cpp @@ -402,6 +402,24 @@ Coordination::RequestPtr DatabaseReplicatedTask::getOpToUpdateLogPointer() return zkutil::makeSetRequest(database->replica_path + "/log_ptr", toString(getLogEntryNumber(entry_name)), -1); } +void DatabaseReplicatedTask::createSyncedNodeIfNeed(const ZooKeeperPtr & zookeeper) +{ + assert(!completely_processed); + if (!entry.settings) + return; + + Field value; + if (!entry.settings->tryGet("database_replicated_enforce_synchronous_settings", value)) + return; + + /// Bool type is really weird, sometimes it's Bool and sometimes it's UInt64... + assert(value.getType() == Field::Types::Bool || value.getType() == Field::Types::UInt64); + if (!value.get()) + return; + + zookeeper->createIfNotExists(getSyncedNodePath(), ""); +} + String DDLTaskBase::getLogEntryName(UInt32 log_entry_number) { return zkutil::getSequentialNodeName("query-", log_entry_number); diff --git a/src/Interpreters/DDLTask.h b/src/Interpreters/DDLTask.h index 6b78f347235..50cfb4e2281 100644 --- a/src/Interpreters/DDLTask.h +++ b/src/Interpreters/DDLTask.h @@ -113,9 +113,12 @@ struct DDLTaskBase virtual ContextMutablePtr makeQueryContext(ContextPtr from_context, const ZooKeeperPtr & zookeeper); virtual Coordination::RequestPtr getOpToUpdateLogPointer() { return nullptr; } + virtual void createSyncedNodeIfNeed(const ZooKeeperPtr & /*zookeeper*/) {} + inline String getActiveNodePath() const { return fs::path(entry_path) / "active" / host_id_str; } inline String getFinishedNodePath() const { return fs::path(entry_path) / "finished" / host_id_str; } inline String getShardNodePath() const { return fs::path(entry_path) / "shards" / getShardID(); } + inline String getSyncedNodePath() const { return fs::path(entry_path) / "synced" / host_id_str; } static String getLogEntryName(UInt32 log_entry_number); static UInt32 getLogEntryNumber(const String & log_entry_name); @@ -151,6 +154,7 @@ struct DatabaseReplicatedTask : public DDLTaskBase void parseQueryFromEntry(ContextPtr context) override; ContextMutablePtr makeQueryContext(ContextPtr from_context, const ZooKeeperPtr & zookeeper) override; Coordination::RequestPtr getOpToUpdateLogPointer() override; + void createSyncedNodeIfNeed(const ZooKeeperPtr & zookeeper) override; DatabaseReplicated * database; }; diff --git a/src/Interpreters/DDLWorker.cpp b/src/Interpreters/DDLWorker.cpp index eeab557c4b1..84de11bea9d 100644 --- a/src/Interpreters/DDLWorker.cpp +++ b/src/Interpreters/DDLWorker.cpp @@ -265,7 +265,13 @@ void DDLWorker::scheduleTasks(bool reinitialized) /// but we lost connection while waiting for the response. /// Yeah, distributed systems is a zoo. if (status_written) + { + /// TODO We cannot guarantee that query was actually executed synchronously if connection was lost. + /// Let's simple create synced/ node for now, but it would be better ot pass UNFINISHED status to initiator + /// or wait for query to actually finish (requires https://github.com/ClickHouse/ClickHouse/issues/23513) + task->createSyncedNodeIfNeed(zookeeper); task->completely_processed = true; + } else processTask(*task, zookeeper); ++task_it; @@ -641,6 +647,7 @@ void DDLWorker::processTask(DDLTaskBase & task, const ZooKeeperPtr & zookeeper) /// Active node was removed in multi ops active_node->setAlreadyRemoved(); + task.createSyncedNodeIfNeed(zookeeper); task.completely_processed = true; updateMaxDDLEntryID(task.entry_name); } diff --git a/src/Interpreters/executeDDLQueryOnCluster.cpp b/src/Interpreters/executeDDLQueryOnCluster.cpp index d1f4068c477..7cc4efcb64d 100644 --- a/src/Interpreters/executeDDLQueryOnCluster.cpp +++ b/src/Interpreters/executeDDLQueryOnCluster.cpp @@ -368,6 +368,10 @@ Chunk DDLQueryStatusSource::generate() if (all_hosts_finished || timeout_exceeded) return {}; + String node_to_wait = "finished"; + if (is_replicated_database && context->getSettingsRef().database_replicated_enforce_synchronous_settings) + node_to_wait = "synced"; + auto zookeeper = context->getZooKeeper(); size_t try_number = 0; @@ -423,7 +427,7 @@ Chunk DDLQueryStatusSource::generate() return {}; } - Strings new_hosts = getNewAndUpdate(getChildrenAllowNoNode(zookeeper, fs::path(node_path) / "finished")); + Strings new_hosts = getNewAndUpdate(getChildrenAllowNoNode(zookeeper, fs::path(node_path) / node_to_wait)); ++try_number; if (new_hosts.empty()) continue; @@ -434,11 +438,17 @@ Chunk DDLQueryStatusSource::generate() for (const String & host_id : new_hosts) { ExecutionStatus status(-1, "Cannot obtain error message"); + + if (node_to_wait == "finished") { String status_data; if (zookeeper->tryGet(fs::path(node_path) / "finished" / host_id, status_data)) status.tryDeserializeText(status_data); } + else + { + status = ExecutionStatus{0}; + } if (status.code != 0 && !first_exception diff --git a/tests/config/users.d/database_replicated.xml b/tests/config/users.d/database_replicated.xml index c833ce3e343..2b96e7418b6 100644 --- a/tests/config/users.d/database_replicated.xml +++ b/tests/config/users.d/database_replicated.xml @@ -6,6 +6,7 @@ 120 120 1 + 1 diff --git a/tests/integration/test_replicated_database/test.py b/tests/integration/test_replicated_database/test.py index 01aedba66c8..ad28430ba7d 100644 --- a/tests/integration/test_replicated_database/test.py +++ b/tests/integration/test_replicated_database/test.py @@ -3,6 +3,7 @@ import shutil import time import re import pytest +import threading from helpers.cluster import ClickHouseCluster from helpers.test_tools import assert_eq_with_retry, assert_logs_contain @@ -867,3 +868,67 @@ def test_sync_replica(started_cluster): ) assert lp1 == max_lp assert lp2 == max_lp + + +def test_force_synchronous_settings(started_cluster): + main_node.query( + "CREATE DATABASE test_force_synchronous_settings ENGINE = Replicated('/clickhouse/databases/test2', 'shard1', 'replica1');" + ) + dummy_node.query( + "CREATE DATABASE test_force_synchronous_settings ENGINE = Replicated('/clickhouse/databases/test2', 'shard1', 'replica2');" + ) + snapshotting_node.query( + "CREATE DATABASE test_force_synchronous_settings ENGINE = Replicated('/clickhouse/databases/test2', 'shard2', 'replica1');" + ) + main_node.query( + "CREATE TABLE test_force_synchronous_settings.t (n int) ENGINE=ReplicatedMergeTree('/test/same/path/{shard}', '{replica}') ORDER BY tuple()" + ) + main_node.query( + "INSERT INTO test_force_synchronous_settings.t SELECT * FROM numbers(10)" + ) + snapshotting_node.query( + "INSERT INTO test_force_synchronous_settings.t SELECT * FROM numbers(10)" + ) + snapshotting_node.query( + "SYSTEM SYNC DATABASE REPLICA test_force_synchronous_settings" + ) + dummy_node.query("SYSTEM SYNC DATABASE REPLICA test_force_synchronous_settings") + + snapshotting_node.query("SYSTEM STOP MERGES test_force_synchronous_settings.t") + + def start_merges_func(): + time.sleep(5) + snapshotting_node.query("SYSTEM START MERGES test_force_synchronous_settings.t") + + start_merges_thread = threading.Thread(target=start_merges_func) + start_merges_thread.start() + + settings = { + "mutations_sync": 2, + "database_replicated_enforce_synchronous_settings": 1, + } + main_node.query( + "ALTER TABLE test_force_synchronous_settings.t UPDATE n = n * 10 WHERE 1", + settings=settings, + ) + assert "10\t450\n" == snapshotting_node.query( + "SELECT count(), sum(n) FROM test_force_synchronous_settings.t" + ) + start_merges_thread.join() + + def select_func(): + dummy_node.query( + "SELECT sleepEachRow(1) FROM test_force_synchronous_settings.t" + ) + + select_thread = threading.Thread(target=select_func) + select_thread.start() + + settings = {"database_replicated_enforce_synchronous_settings": 1} + snapshotting_node.query( + "DROP TABLE test_force_synchronous_settings.t SYNC", settings=settings + ) + main_node.query( + "CREATE TABLE test_force_synchronous_settings.t (n String) ENGINE=ReplicatedMergeTree('/test/same/path/{shard}', '{replica}') ORDER BY tuple()" + ) + select_thread.join() From c7fb6aa6d52aee36c790d90806e7a19aa83ed7d2 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 28 Jul 2022 13:07:46 +0200 Subject: [PATCH 151/672] fix style check so maybe CI checks will strart --- src/Interpreters/DDLWorker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Interpreters/DDLWorker.cpp b/src/Interpreters/DDLWorker.cpp index 84de11bea9d..514c2740dd1 100644 --- a/src/Interpreters/DDLWorker.cpp +++ b/src/Interpreters/DDLWorker.cpp @@ -267,7 +267,7 @@ void DDLWorker::scheduleTasks(bool reinitialized) if (status_written) { /// TODO We cannot guarantee that query was actually executed synchronously if connection was lost. - /// Let's simple create synced/ node for now, but it would be better ot pass UNFINISHED status to initiator + /// Let's simple create synced/ node for now, but it would be better to pass UNFINISHED status to initiator /// or wait for query to actually finish (requires https://github.com/ClickHouse/ClickHouse/issues/23513) task->createSyncedNodeIfNeed(zookeeper); task->completely_processed = true; From 2338d01a20d6267300d4069d56d507f503d73ac6 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 28 Jul 2022 14:23:59 +0200 Subject: [PATCH 152/672] Deploy workflow_approve_rerun_lambda as zip package --- tests/ci/.gitignore | 3 +++ tests/ci/team_keys_lambda/.gitignore | 2 -- .../build_and_deploy_archive.sh | 23 +++++++++++++------ .../workflow_approve_rerun_lambda/Dockerfile | 13 ----------- .../build_and_deploy_archive.sh | 1 + 5 files changed, 20 insertions(+), 22 deletions(-) create mode 100644 tests/ci/.gitignore delete mode 100644 tests/ci/team_keys_lambda/.gitignore delete mode 100644 tests/ci/workflow_approve_rerun_lambda/Dockerfile create mode 120000 tests/ci/workflow_approve_rerun_lambda/build_and_deploy_archive.sh diff --git a/tests/ci/.gitignore b/tests/ci/.gitignore new file mode 100644 index 00000000000..b1230661a88 --- /dev/null +++ b/tests/ci/.gitignore @@ -0,0 +1,3 @@ +*_lambda/lambda-venv +*_lambda/lambda-package +*_lambda/lambda-package.zip diff --git a/tests/ci/team_keys_lambda/.gitignore b/tests/ci/team_keys_lambda/.gitignore deleted file mode 100644 index 4c845d295ee..00000000000 --- a/tests/ci/team_keys_lambda/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -lambda-venv -lambda-package.zip diff --git a/tests/ci/team_keys_lambda/build_and_deploy_archive.sh b/tests/ci/team_keys_lambda/build_and_deploy_archive.sh index 7b89ea11ede..cd5b0d26e3f 100644 --- a/tests/ci/team_keys_lambda/build_and_deploy_archive.sh +++ b/tests/ci/team_keys_lambda/build_and_deploy_archive.sh @@ -1,15 +1,24 @@ #!/usr/bin/env bash set -xeo pipefail +WORKDIR=$(dirname "$0") +cd "$WORKDIR" + +PY_EXEC=python3.9 +LAMBDA_NAME=$(basename "$PWD") +LAMBDA_NAME=${LAMBDA_NAME//_/-} VENV=lambda-venv -py_exec=$(which python3) -py_version=$(basename "$(readlink -f "$py_exec")") rm -rf "$VENV" lambda-package.zip -virtualenv "$VENV" +"$PY_EXEC" -m venv "$VENV" +#virtualenv "$VENV" +# shellcheck disable=SC1091 source "$VENV/bin/activate" pip install -r requirements.txt -PACKAGES="$VENV/lib/$py_version/site-packages" -cp app.py "$PACKAGES/" -( cd "$PACKAGES" && zip -r ../../../../lambda-package.zip . ) +PACKAGE=lambda-package +rm -rf "$PACKAGE" "$PACKAGE".zip +cp -r "$VENV/lib/$PY_EXEC/site-packages" "$PACKAGE" +cp app.py "$PACKAGE" +rm -r "$PACKAGE"/{pip,pip-*,setuptools,setuptools-*} +( cd "$PACKAGE" && zip -r ../"$PACKAGE".zip . ) -aws lambda update-function-code --function-name team-keys-lambda --zip-file fileb://lambda-package.zip +aws lambda update-function-code --function-name "$LAMBDA_NAME" --zip-file fileb://"$PACKAGE".zip diff --git a/tests/ci/workflow_approve_rerun_lambda/Dockerfile b/tests/ci/workflow_approve_rerun_lambda/Dockerfile deleted file mode 100644 index 0d50224c51d..00000000000 --- a/tests/ci/workflow_approve_rerun_lambda/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM public.ecr.aws/lambda/python:3.9 - -# Install the function's dependencies using file requirements.txt -# from your project folder. - -COPY requirements.txt . -RUN pip3 install -r requirements.txt --target "${LAMBDA_TASK_ROOT}" - -# Copy function code -COPY app.py ${LAMBDA_TASK_ROOT} - -# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) -CMD [ "app.handler" ] diff --git a/tests/ci/workflow_approve_rerun_lambda/build_and_deploy_archive.sh b/tests/ci/workflow_approve_rerun_lambda/build_and_deploy_archive.sh new file mode 120000 index 00000000000..96ba3fa024e --- /dev/null +++ b/tests/ci/workflow_approve_rerun_lambda/build_and_deploy_archive.sh @@ -0,0 +1 @@ +../team_keys_lambda/build_and_deploy_archive.sh \ No newline at end of file From 6919ae7c919fdb985fb14d190bb1fc21e3d4816e Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Thu, 28 Jul 2022 12:24:16 +0000 Subject: [PATCH 153/672] Fixing a test with indexHint --- src/Interpreters/RequiredSourceColumnsData.h | 1 + src/Interpreters/RequiredSourceColumnsVisitor.cpp | 9 ++++++--- src/Interpreters/TreeRewriter.cpp | 9 +++++---- src/Interpreters/TreeRewriter.h | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Interpreters/RequiredSourceColumnsData.h b/src/Interpreters/RequiredSourceColumnsData.h index 501f6961efa..dd4e2dc3d68 100644 --- a/src/Interpreters/RequiredSourceColumnsData.h +++ b/src/Interpreters/RequiredSourceColumnsData.h @@ -36,6 +36,7 @@ struct RequiredSourceColumnsData bool has_table_join = false; bool has_array_join = false; + bool visit_index_hint = false; bool addColumnAliasIfAny(const IAST & ast); void addColumnIdentifier(const ASTIdentifier & node); diff --git a/src/Interpreters/RequiredSourceColumnsVisitor.cpp b/src/Interpreters/RequiredSourceColumnsVisitor.cpp index f4305692eb6..fe0ee6f97ac 100644 --- a/src/Interpreters/RequiredSourceColumnsVisitor.cpp +++ b/src/Interpreters/RequiredSourceColumnsVisitor.cpp @@ -52,10 +52,8 @@ bool RequiredSourceColumnsMatcher::needChildVisit(const ASTPtr & node, const AST if (const auto * f = node->as()) { - /// "indexHint" is a special function for index analysis. - /// Everything that is inside it is not calculated. See KeyCondition /// "lambda" visit children itself. - if (f->name == "indexHint" || f->name == "lambda") + if (f->name == "lambda") return false; } @@ -73,6 +71,11 @@ void RequiredSourceColumnsMatcher::visit(const ASTPtr & ast, Data & data) } if (auto * t = ast->as()) { + /// "indexHint" is a special function for index analysis. + /// Everything that is inside it is not calculated. See KeyCondition + if (!data.visit_index_hint && t->name == "indexHint") + return; + data.addColumnAliasIfAny(*ast); visit(*t, ast, data); return; diff --git a/src/Interpreters/TreeRewriter.cpp b/src/Interpreters/TreeRewriter.cpp index b389c3eb705..53d015f79a8 100644 --- a/src/Interpreters/TreeRewriter.cpp +++ b/src/Interpreters/TreeRewriter.cpp @@ -963,12 +963,13 @@ void TreeRewriterResult::collectSourceColumns(bool add_special) /// Calculate which columns are required to execute the expression. /// Then, delete all other columns from the list of available columns. /// After execution, columns will only contain the list of columns needed to read from the table. -void TreeRewriterResult::collectUsedColumns(const ASTPtr & query, bool is_select) +void TreeRewriterResult::collectUsedColumns(const ASTPtr & query, bool is_select, bool visit_index_hint) { /// We calculate required_source_columns with source_columns modifications and swap them on exit required_source_columns = source_columns; RequiredSourceColumnsVisitor::Data columns_context; + columns_context.visit_index_hint = visit_index_hint; RequiredSourceColumnsVisitor(columns_context).visit(query); NameSet source_column_names; @@ -1285,7 +1286,7 @@ TreeRewriterResultPtr TreeRewriter::analyzeSelect( result.aggregates = getAggregates(query, *select_query); result.window_function_asts = getWindowFunctions(query, *select_query); result.expressions_with_window_function = getExpressionsWithWindowFunctions(query); - result.collectUsedColumns(query, true); + result.collectUsedColumns(query, true, settings.query_plan_optimize_primary_key); result.required_source_columns_before_expanding_alias_columns = result.required_source_columns.getNames(); /// rewrite filters for select query, must go after getArrayJoinedColumns @@ -1309,7 +1310,7 @@ TreeRewriterResultPtr TreeRewriter::analyzeSelect( result.aggregates = getAggregates(query, *select_query); result.window_function_asts = getWindowFunctions(query, *select_query); result.expressions_with_window_function = getExpressionsWithWindowFunctions(query); - result.collectUsedColumns(query, true); + result.collectUsedColumns(query, true, settings.query_plan_optimize_primary_key); } } @@ -1375,7 +1376,7 @@ TreeRewriterResultPtr TreeRewriter::analyze( else assertNoAggregates(query, "in wrong place"); - result.collectUsedColumns(query, false); + result.collectUsedColumns(query, false, settings.query_plan_optimize_primary_key); return std::make_shared(result); } diff --git a/src/Interpreters/TreeRewriter.h b/src/Interpreters/TreeRewriter.h index b84756260a8..16ff7f8b6c3 100644 --- a/src/Interpreters/TreeRewriter.h +++ b/src/Interpreters/TreeRewriter.h @@ -88,7 +88,7 @@ struct TreeRewriterResult bool add_special = true); void collectSourceColumns(bool add_special); - void collectUsedColumns(const ASTPtr & query, bool is_select); + void collectUsedColumns(const ASTPtr & query, bool is_select, bool visit_index_hint); Names requiredSourceColumns() const { return required_source_columns.getNames(); } const Names & requiredSourceColumnsForAccessCheck() const { return required_source_columns_before_expanding_alias_columns; } NameSet getArrayJoinSourceNameSet() const; From f0474f9e46d021606f8e7ce30d62fd980216937f Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 28 Jul 2022 14:24:42 +0200 Subject: [PATCH 154/672] Improve lambda logic, don't fail on non-uniq PR --- tests/ci/workflow_approve_rerun_lambda/app.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/ci/workflow_approve_rerun_lambda/app.py b/tests/ci/workflow_approve_rerun_lambda/app.py index d1910f2ea3e..ef0759be6e1 100644 --- a/tests/ci/workflow_approve_rerun_lambda/app.py +++ b/tests/ci/workflow_approve_rerun_lambda/app.py @@ -455,11 +455,9 @@ def main(event): ) print("Got pull requests for workflow", len(pull_requests)) - if len(pull_requests) > 1: - raise Exception("Received more than one PR for workflow run") - - if len(pull_requests) < 1: - raise Exception("Cannot find any pull requests for workflow run") + if len(pull_requests) != 1: + print(f"Can't continue with non-uniq PRs: {pull_requests}") + return pull_request = pull_requests[0] print("Pull request for workflow number", pull_request["number"]) @@ -484,4 +482,8 @@ def main(event): def handler(event, _): - main(event) + try: + main(event) + except Exception: + print("Received event: ", event) + raise From 1090d6bca797a826d817abbd2452f5c43d539ba7 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 28 Jul 2022 12:50:51 +0000 Subject: [PATCH 155/672] Rollback request if storing log fails --- contrib/NuRaft | 2 +- src/Coordination/KeeperServer.cpp | 13 +++++++++++++ src/Coordination/KeeperStateMachine.cpp | 5 +++++ src/Coordination/KeeperStateMachine.h | 2 ++ src/Coordination/KeeperStorage.cpp | 18 +++++++++++++++--- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/contrib/NuRaft b/contrib/NuRaft index e1dc47c1cfd..bef4644837b 160000 --- a/contrib/NuRaft +++ b/contrib/NuRaft @@ -1 +1 @@ -Subproject commit e1dc47c1cfd529801a8c94a396a3921a71ae3ccf +Subproject commit bef4644837ba7ef9ced2ccf5c750a936c58d424d diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 587ab9c8f66..0cb595c76d7 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -573,6 +573,19 @@ nuraft::cb_func::ReturnCode KeeperServer::callbackFunc(nuraft::cb_func::Type typ entry = nuraft::cs_new(entry->get_term(), getZooKeeperLogEntry(request_for_session), entry->get_val_type()); break; } + case nuraft::cb_func::AppendLogFailed: + { + // we are relying on the fact that request are being processed under a mutex + // and not a RW lock + auto & entry = *static_cast(param->ctx); + + assert(entry->get_val_type() == nuraft::app_log); + + auto & entry_buf = entry->get_buf(); + auto request_for_session = state_machine->parseRequest(entry_buf); + state_machine->rollbackRequest(request_for_session); + break; + } default: break; } diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index f43a3dbb319..4fcb17d9d5d 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -311,6 +311,11 @@ void KeeperStateMachine::rollback(uint64_t log_idx, nuraft::buffer & data) if (!request_for_session.zxid) request_for_session.zxid = log_idx; + rollbackRequest(request_for_session); +} + +void KeeperStateMachine::rollbackRequest(const KeeperStorage::RequestForSession & request_for_session) +{ if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) return; diff --git a/src/Coordination/KeeperStateMachine.h b/src/Coordination/KeeperStateMachine.h index adcf34c2aba..6435fc39ceb 100644 --- a/src/Coordination/KeeperStateMachine.h +++ b/src/Coordination/KeeperStateMachine.h @@ -44,6 +44,8 @@ public: void rollback(uint64_t log_idx, nuraft::buffer & data) override; + void rollbackRequest(const KeeperStorage::RequestForSession & request_for_session); + uint64_t last_commit_index() override { return last_committed_idx; } /// Apply preliminarily saved (save_logical_snp_obj) snapshot to our state. diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 9c6f54dc5bf..07201ec95d5 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -6,7 +6,7 @@ #include #include #include -#include "Common/ZooKeeper/ZooKeeperCommon.h" +#include #include #include #include @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -2119,8 +2120,19 @@ void KeeperStorage::rollbackRequest(int64_t rollback_zxid) throw Exception( ErrorCodes::LOGICAL_ERROR, "Trying to rollback invalid ZXID ({}). It should be the last preprocessed.", rollback_zxid); - uncommitted_transactions.pop_back(); - uncommitted_state.rollback(rollback_zxid); + // if an exception occurs during rollback, the best option is to terminate because we can end up in an incosistent state + // we block memory tracking so we can avoid terminating if we're rollbacking because of memory limit + MemoryTrackerBlockerInThread temporarily_ignore_any_memory_limits(VariableContext::Global); + try + { + uncommitted_transactions.pop_back(); + uncommitted_state.rollback(rollback_zxid); + } + catch (...) + { + LOG_FATAL(&Poco::Logger::get("KeeperStorage"), "Failed to rollback log. Terminating to avoid incosistencies"); + std::terminate(); + } } KeeperStorage::Digest KeeperStorage::getNodesDigest(bool committed) const From 04fd72cdef67c72cf79dad618daf4ca4d7b16b29 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 28 Jul 2022 13:10:43 +0000 Subject: [PATCH 156/672] fix alter column rename --- src/Storages/MergeTree/IMergeTreeDataPart.h | 3 +- src/Storages/MergeTree/IMergeTreeReader.cpp | 2 + src/Storages/MergeTree/IMergeTreeReader.h | 3 + .../MergeTree/MergeTreeDataPartWriterWide.cpp | 2 +- src/Storages/MergeTree/MutateTask.cpp | 91 ++++++++++++------- 5 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index bf4eba1c354..010a97625d1 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -524,9 +524,10 @@ private: /// Map from name of column to its serialization info. SerializationInfoByName serialization_infos; + /// Serializations for every columns and subcolumns by their names. SerializationByName serializations; - /// Columns description for more convenient access + /// Columns description for more convenient access /// to columns by name and getting subcolumns. ColumnsDescription columns_description; diff --git a/src/Storages/MergeTree/IMergeTreeReader.cpp b/src/Storages/MergeTree/IMergeTreeReader.cpp index 30969939622..8c861248580 100644 --- a/src/Storages/MergeTree/IMergeTreeReader.cpp +++ b/src/Storages/MergeTree/IMergeTreeReader.cpp @@ -40,6 +40,8 @@ IMergeTreeReader::IMergeTreeReader( , metadata_snapshot(metadata_snapshot_) , all_mark_ranges(all_mark_ranges_) , alter_conversions(storage.getAlterConversionsForPart(data_part)) + /// For wide parts convert plain arrays of Nested to subcolumns + /// to allow to use shared offset column from cache. , requested_columns(isWidePart(data_part) ? Nested::convertToSubcolumns(columns_) : columns_) , part_columns(isWidePart(data_part) ? Nested::collect(data_part->getColumns()) : data_part->getColumns()) { diff --git a/src/Storages/MergeTree/IMergeTreeReader.h b/src/Storages/MergeTree/IMergeTreeReader.h index 0d6c0e607cd..453563522a5 100644 --- a/src/Storages/MergeTree/IMergeTreeReader.h +++ b/src/Storages/MergeTree/IMergeTreeReader.h @@ -75,7 +75,10 @@ protected: /// Stores states for IDataType::deserializeBinaryBulk DeserializeBinaryBulkStateMap deserialize_binary_bulk_state_map; + /// Actual column names and types of columns in part, + /// which may differ from table metadata. NamesAndTypes columns_to_read; + /// Actual serialization of columns in part. Serializations serializations; UncompressedCache * uncompressed_cache; diff --git a/src/Storages/MergeTree/MergeTreeDataPartWriterWide.cpp b/src/Storages/MergeTree/MergeTreeDataPartWriterWide.cpp index 712c3f74bdd..3d4aa0a7707 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWriterWide.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWriterWide.cpp @@ -583,7 +583,7 @@ void MergeTreeDataPartWriterWide::finishDataSerialization(bool sync) { if (column.type->isValueRepresentedByNumber() && !column.type->haveSubtypes() - && data_part->getSerialization(columnn.name)->getKind() == ISerialization::Kind::DEFAULT) + && data_part->getSerialization(column.name)->getKind() == ISerialization::Kind::DEFAULT) { validateColumnOfFixedSize(column); } diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 5b54c1e6ae7..9b41c7bc623 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -459,8 +459,21 @@ static NameToNameVector collectFilesForRenames( const MutationCommands & commands_for_removes, const String & mrk_extension) { + /// Collect counts for shared streams of different columns. As an example, Nested columns have shared stream with array sizes. + std::unordered_map stream_counts; + for (const auto & column : source_part->getColumns()) + { + if (auto serialization = source_part->tryGetSerialization(column.name)) + { + serialization->enumerateStreams( + [&](const ISerialization::SubstreamPath & substream_path) + { + ++stream_counts[ISerialization::getFileNameForStream(column, substream_path)]; + }); + } + } + NameToNameVector rename_vector; - NameSet renamed_streams; /// Remove old data for (const auto & command : commands_for_removes) @@ -483,6 +496,22 @@ static NameToNameVector collectFilesForRenames( if (source_part->checksums.has(command.column_name + ".proj")) rename_vector.emplace_back(command.column_name + ".proj", ""); } + else if (command.type == MutationCommand::Type::DROP_COLUMN) + { + ISerialization::StreamCallback callback = [&](const ISerialization::SubstreamPath & substream_path) + { + String stream_name = ISerialization::getFileNameForStream({command.column_name, command.data_type}, substream_path); + /// 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, ""); + } + }; + + if (auto serialization = source_part->tryGetSerialization(command.column_name)) + serialization->enumerateStreams(callback); + } else if (command.type == MutationCommand::Type::RENAME_COLUMN) { String escaped_name_from = escapeForFileName(command.column_name); @@ -495,7 +524,6 @@ static NameToNameVector collectFilesForRenames( if (stream_from != stream_to) { - renamed_streams.insert(stream_from); rename_vector.emplace_back(stream_from + ".bin", stream_to + ".bin"); rename_vector.emplace_back(stream_from + mrk_extension, stream_to + mrk_extension); } @@ -504,41 +532,38 @@ static NameToNameVector collectFilesForRenames( if (auto serialization = source_part->tryGetSerialization(command.column_name)) serialization->enumerateStreams(callback); } - } - - auto collect_all_stream_names = [&](const auto & data_part) - { - NameSet res; - for (const auto & column : data_part->getColumns()) + else if (command.type == MutationCommand::Type::READ_COLUMN) { - if (auto serialization = data_part->tryGetSerialization(column.name)) + /// Remove files for streams that exist in source_part, + /// but were removed in new_part by MODIFY COLUMN from + /// type with higher number of streams (e.g. LowCardinality -> String). + + auto collect_stream_names = [&](const auto & data_part) { - serialization->enumerateStreams( - [&](const ISerialization::SubstreamPath & substream_path) - { - res.insert(ISerialization::getFileNameForStream(column.name, substream_path)); - }); + NameSet res; + if (auto serialization = data_part->tryGetSerialization(command.column_name)) + { + serialization->enumerateStreams( + [&](const ISerialization::SubstreamPath & substream_path) + { + res.insert(ISerialization::getFileNameForStream(command.column_name, substream_path)); + }); + } + return res; + }; + + auto old_streams = collect_stream_names(source_part); + auto new_streams = collect_stream_names(new_part); + + for (const auto & old_stream : old_streams) + { + if (!new_streams.contains(old_stream)) + { + rename_vector.emplace_back(old_stream + ".bin", ""); + rename_vector.emplace_back(old_stream + mrk_extension, ""); + } } } - - return res; - }; - - /// Remove files for streams that exists in source part, - /// but were removed in new_part by DROP COLUMN - /// or MODIFY COLUMN from type with higher number of streams - /// (e.g. LowCardinality -> String). - - auto old_streams = collect_all_stream_names(source_part); - auto new_streams = collect_all_stream_names(new_part); - - for (const auto & old_stream : old_streams) - { - if (!new_streams.contains(old_stream) && !renamed_streams.contains(old_stream)) - { - rename_vector.emplace_back(old_stream + ".bin", ""); - rename_vector.emplace_back(old_stream + mrk_extension, ""); - } } return rename_vector; From c0a5d45258824e1b7d3780fdf5c24358b08b0c76 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 28 Jul 2022 14:06:45 +0000 Subject: [PATCH 157/672] Rollback on failed PreAppend --- contrib/NuRaft | 2 +- src/Coordination/KeeperServer.cpp | 2 +- src/Coordination/KeeperStateMachine.cpp | 6 +++--- src/Coordination/KeeperStateMachine.h | 4 +++- src/Coordination/KeeperStorage.cpp | 17 +++++++++++------ src/Coordination/KeeperStorage.h | 2 +- 6 files changed, 20 insertions(+), 13 deletions(-) diff --git a/contrib/NuRaft b/contrib/NuRaft index bef4644837b..2ef198694e1 160000 --- a/contrib/NuRaft +++ b/contrib/NuRaft @@ -1 +1 @@ -Subproject commit bef4644837ba7ef9ced2ccf5c750a936c58d424d +Subproject commit 2ef198694e10c86175ee6ead389346d199060437 diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 0cb595c76d7..7238c86cc50 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -583,7 +583,7 @@ nuraft::cb_func::ReturnCode KeeperServer::callbackFunc(nuraft::cb_func::Type typ auto & entry_buf = entry->get_buf(); auto request_for_session = state_machine->parseRequest(entry_buf); - state_machine->rollbackRequest(request_for_session); + state_machine->rollbackRequest(request_for_session, true); break; } default: diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index 4fcb17d9d5d..d2f140bee2d 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -311,16 +311,16 @@ void KeeperStateMachine::rollback(uint64_t log_idx, nuraft::buffer & data) if (!request_for_session.zxid) request_for_session.zxid = log_idx; - rollbackRequest(request_for_session); + rollbackRequest(request_for_session, false); } -void KeeperStateMachine::rollbackRequest(const KeeperStorage::RequestForSession & request_for_session) +void KeeperStateMachine::rollbackRequest(const KeeperStorage::RequestForSession & request_for_session, bool allow_missing) { if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) return; std::lock_guard lock(storage_and_responses_lock); - storage->rollbackRequest(request_for_session.zxid); + storage->rollbackRequest(request_for_session.zxid, allow_missing); } nuraft::ptr KeeperStateMachine::last_snapshot() diff --git a/src/Coordination/KeeperStateMachine.h b/src/Coordination/KeeperStateMachine.h index 6435fc39ceb..9ddc4372d3b 100644 --- a/src/Coordination/KeeperStateMachine.h +++ b/src/Coordination/KeeperStateMachine.h @@ -44,7 +44,9 @@ public: void rollback(uint64_t log_idx, nuraft::buffer & data) override; - void rollbackRequest(const KeeperStorage::RequestForSession & request_for_session); + // allow_missing - whether the transaction we want to rollback can be missing from storage + // (can happen in case of exception during preprocessing) + void rollbackRequest(const KeeperStorage::RequestForSession & request_for_session, bool allow_missing); uint64_t last_commit_index() override { return last_committed_idx; } diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 07201ec95d5..bbc647fd951 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -366,10 +366,10 @@ void KeeperStorage::UncommittedState::addDeltas(std::vector new_deltas) { for (auto & delta : new_deltas) { - if (!delta.path.empty()) - applyDelta(delta); + const auto & added_delta = deltas.emplace_back(std::move(delta)); - deltas.push_back(std::move(delta)); + if (!added_delta.path.empty()) + applyDelta(added_delta); } } @@ -2114,15 +2114,20 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( return results; } -void KeeperStorage::rollbackRequest(int64_t rollback_zxid) +void KeeperStorage::rollbackRequest(int64_t rollback_zxid, bool allow_missing) { + if (allow_missing && (uncommitted_transactions.empty() || uncommitted_transactions.back().zxid < rollback_zxid)) + return; + if (uncommitted_transactions.empty() || uncommitted_transactions.back().zxid != rollback_zxid) + { throw Exception( ErrorCodes::LOGICAL_ERROR, "Trying to rollback invalid ZXID ({}). It should be the last preprocessed.", rollback_zxid); + } - // if an exception occurs during rollback, the best option is to terminate because we can end up in an incosistent state + // if an exception occurs during rollback, the best option is to terminate because we can end up in an inconsistent state // we block memory tracking so we can avoid terminating if we're rollbacking because of memory limit - MemoryTrackerBlockerInThread temporarily_ignore_any_memory_limits(VariableContext::Global); + MemoryTrackerBlockerInThread temporarily_ignore_any_memory_limits; try { uncommitted_transactions.pop_back(); diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index 1fbe52cfbea..b0d08446e2c 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -380,7 +380,7 @@ public: int64_t new_last_zxid, bool check_acl = true, std::optional digest = std::nullopt); - void rollbackRequest(int64_t rollback_zxid); + void rollbackRequest(int64_t rollback_zxid, bool allow_missing); void finalize(); From bec7408a0cb775f597eade86909d136d557fd50a Mon Sep 17 00:00:00 2001 From: root Date: Thu, 28 Jul 2022 09:20:28 -0700 Subject: [PATCH 158/672] reflected change requests asked on July 27 --- programs/server/config.xml | 2 +- src/Daemon/BaseDaemon.cpp | 2 +- src/Loggers/Loggers.cpp | 8 +-- src/Loggers/OwnFormattingChannel.cpp | 13 ++--- src/Loggers/OwnJSONPatternFormatter.cpp | 70 ++++++------------------- src/Loggers/OwnJSONPatternFormatter.h | 2 +- src/Loggers/OwnPatternFormatter.cpp | 4 -- src/Loggers/OwnPatternFormatter.h | 3 +- 8 files changed, 30 insertions(+), 74 deletions(-) diff --git a/programs/server/config.xml b/programs/server/config.xml index d261c1e3694..ef68741f056 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -68,7 +68,7 @@ To enable JSON logging support, just uncomment tag. Having the tag will make it work. For better understanding/visibility, you can add "true" or "1". --> - + diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index e162360ddaa..61ad1785b2d 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -1014,7 +1014,7 @@ void BaseDaemon::setupWatchdog() if (config().getRawString("logger.stream_compress", "false") == "true") { Poco::AutoPtr pf; - if (config().has("logger.json")) + if (config().getString("logger.formatting", "") == "json") pf = new OwnJSONPatternFormatter; else pf = new OwnPatternFormatter(true); diff --git a/src/Loggers/Loggers.cpp b/src/Loggers/Loggers.cpp index 6f8c88a7e87..35415fb91ba 100644 --- a/src/Loggers/Loggers.cpp +++ b/src/Loggers/Loggers.cpp @@ -99,7 +99,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log Poco::AutoPtr pf; - if (config.has("logger.json")) + if (config.getString("logger.formatting", "") == "json") pf = new OwnJSONPatternFormatter; else pf = new OwnPatternFormatter(true); @@ -140,7 +140,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log Poco::AutoPtr pf; - if (config.has("logger.json")) + if (config.getString("logger.formatting", "") == "json") pf = new OwnJSONPatternFormatter; else pf = new OwnPatternFormatter(true); @@ -184,7 +184,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log Poco::AutoPtr pf; - if (config.has("logger.json")) + if (config.getString("logger.formatting", "") == "json") pf = new OwnJSONPatternFormatter; else pf = new OwnPatternFormatter(true); @@ -211,7 +211,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log } Poco::AutoPtr pf; - if (config.has("logger.json")) + if (config.getString("logger.formatting", "") == "json") pf = new OwnJSONPatternFormatter; else pf = new OwnPatternFormatter(color_enabled); diff --git a/src/Loggers/OwnFormattingChannel.cpp b/src/Loggers/OwnFormattingChannel.cpp index 1487c5ed03b..f03d155bde7 100644 --- a/src/Loggers/OwnFormattingChannel.cpp +++ b/src/Loggers/OwnFormattingChannel.cpp @@ -1,20 +1,17 @@ #include "OwnFormattingChannel.h" -#include "OwnJSONPatternFormatter.h" #include "OwnPatternFormatter.h" + + namespace DB { + void OwnFormattingChannel::logExtended(const ExtendedLogMessage & msg) { if (pChannel && priority >= msg.base.getPriority()) { - std::string text; - if (auto * formatter = dynamic_cast(pFormatter.get())) - { - formatter->formatExtended(msg, text); - pChannel->log(Poco::Message(msg.base, text)); - } - else if (pFormatter) + if (pFormatter) { + std::string text; pFormatter->formatExtended(msg, text); pChannel->log(Poco::Message(msg.base, text)); } diff --git a/src/Loggers/OwnJSONPatternFormatter.cpp b/src/Loggers/OwnJSONPatternFormatter.cpp index 63f2c60f70e..afbdc2e7ecf 100644 --- a/src/Loggers/OwnJSONPatternFormatter.cpp +++ b/src/Loggers/OwnJSONPatternFormatter.cpp @@ -13,19 +13,16 @@ OwnJSONPatternFormatter::OwnJSONPatternFormatter() : OwnPatternFormatter("") } -void OwnJSONPatternFormatter::formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text) +void OwnJSONPatternFormatter::formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text) const { DB::WriteBufferFromString wb(text); DB::FormatSettings settings; - char key_name[] = "a placeholder for key names in structured logging"; - char empty_string[] = ""; - + const Poco::Message & msg = msg_ext.base; DB::writeChar('{', wb); - strcpy(key_name, "date_time"); - writeJSONString(key_name, key_name + strlen(key_name), wb, settings); + writeJSONString("date_time", wb, settings); DB::writeChar(':', wb); DB::writeChar('\"', wb); @@ -42,20 +39,14 @@ void OwnJSONPatternFormatter::formatExtended(const DB::ExtendedLogMessage & msg_ DB::writeChar(',', wb); - strcpy(key_name, "thread_name"); - writeJSONString(key_name, key_name + strlen(key_name), wb, settings); + writeJSONString("thread_name", wb, settings); DB::writeChar(':', wb); - const char * thread_name = msg.getThread().c_str(); - if (thread_name != nullptr) - writeJSONString(thread_name, thread_name + strlen(thread_name), wb, settings); - else - writeJSONString(empty_string, empty_string + strlen(empty_string), wb, settings); + writeJSONString(msg.getThread(), wb, settings); DB::writeChar(',', wb); - strcpy(key_name, "thread_id"); - writeJSONString(key_name, key_name + strlen(key_name), wb, settings); + writeJSONString("thread_id", wb, settings); DB::writeChar(':', wb); DB::writeChar('\"', wb); DB::writeIntText(msg_ext.thread_id, wb); @@ -63,65 +54,38 @@ void OwnJSONPatternFormatter::formatExtended(const DB::ExtendedLogMessage & msg_ DB::writeChar(',', wb); - strcpy(key_name, "level"); - writeJSONString(key_name, key_name + strlen(key_name), wb, settings); + writeJSONString("level", wb, settings); DB::writeChar(':', wb); - int priority_int = static_cast(msg.getPriority()); - String priority_str = std::to_string(priority_int); - const char * priority = priority_str.c_str(); - if (priority != nullptr) - writeJSONString(priority, priority + strlen(priority), wb, settings); - else - writeJSONString(empty_string, empty_string + strlen(empty_string), wb, settings); - + int priority = static_cast(msg.getPriority()); + writeJSONString(std::to_string(priority), wb, settings); DB::writeChar(',', wb); /// We write query_id even in case when it is empty (no query context) /// just to be convenient for various log parsers. - strcpy(key_name, "query_id"); - writeJSONString(key_name, key_name + strlen(key_name), wb, settings); + writeJSONString("query_id", wb, settings); DB::writeChar(':', wb); writeJSONString(msg_ext.query_id, wb, settings); DB::writeChar(',', wb); - strcpy(key_name, "logger_name"); - writeJSONString(key_name, key_name + strlen(key_name), wb, settings); + writeJSONString("logger_name", wb, settings); DB::writeChar(':', wb); - const char * logger_name = msg.getSource().c_str(); - if (logger_name != nullptr) - writeJSONString(logger_name, logger_name + strlen(logger_name), wb, settings); - else - writeJSONString(empty_string, empty_string + strlen(empty_string), wb, settings); - + writeJSONString(msg.getSource(), wb, settings); DB::writeChar(',', wb); - strcpy(key_name, "message"); - writeJSONString(key_name, key_name + strlen(key_name), wb, settings); + writeJSONString("message", wb, settings); DB::writeChar(':', wb); - const char * msg_text = msg.getText().c_str(); - if (msg_text != nullptr) - writeJSONString(msg_text, msg_text + strlen(msg_text), wb, settings); - else - writeJSONString(empty_string, empty_string + strlen(empty_string), wb, settings); - + writeJSONString(msg.getText(), wb, settings); DB::writeChar(',', wb); - strcpy(key_name, "source_file"); - writeJSONString(key_name, key_name + strlen(key_name), wb, settings); + writeJSONString("source_file", wb, settings); DB::writeChar(':', wb); - const char * source_file = msg.getSourceFile(); - if (source_file != nullptr) - writeJSONString(source_file, source_file + strlen(source_file), wb, settings); - else - writeJSONString(empty_string, empty_string + strlen(empty_string), wb, settings); - + writeJSONString(msg.getSourceFile(), wb, settings); DB::writeChar(',', wb); - strcpy(key_name, "source_line"); - writeJSONString(key_name, key_name + strlen(key_name), wb, settings); + writeJSONString("source_line", wb, settings); DB::writeChar(':', wb); DB::writeChar('\"', wb); DB::writeIntText(msg.getSourceLine(), wb); diff --git a/src/Loggers/OwnJSONPatternFormatter.h b/src/Loggers/OwnJSONPatternFormatter.h index 76a0104317e..032506b15e3 100644 --- a/src/Loggers/OwnJSONPatternFormatter.h +++ b/src/Loggers/OwnJSONPatternFormatter.h @@ -28,5 +28,5 @@ public: OwnJSONPatternFormatter(); void format(const Poco::Message & msg, std::string & text) override; - static void formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text); + void formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text) const override; }; diff --git a/src/Loggers/OwnPatternFormatter.cpp b/src/Loggers/OwnPatternFormatter.cpp index f5ee60a2113..67e038f4ea1 100644 --- a/src/Loggers/OwnPatternFormatter.cpp +++ b/src/Loggers/OwnPatternFormatter.cpp @@ -13,10 +13,6 @@ OwnPatternFormatter::OwnPatternFormatter(bool color_) : Poco::PatternFormatter(" { } -OwnPatternFormatter::OwnPatternFormatter() : Poco::PatternFormatter("") -{ -} - void OwnPatternFormatter::formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text) const { DB::WriteBufferFromString wb(text); diff --git a/src/Loggers/OwnPatternFormatter.h b/src/Loggers/OwnPatternFormatter.h index 154068f75fe..d776b097cb2 100644 --- a/src/Loggers/OwnPatternFormatter.h +++ b/src/Loggers/OwnPatternFormatter.h @@ -25,10 +25,9 @@ class OwnPatternFormatter : public Poco::PatternFormatter { public: OwnPatternFormatter(bool color_ = false); - OwnPatternFormatter(); void format(const Poco::Message & msg, std::string & text) override; - void formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text) const; + virtual void formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text) const; private: bool color; From bb8f7f6f0bb58d8db1f41a2db62eec7fb62aca46 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 28 Jul 2022 09:52:30 -0700 Subject: [PATCH 159/672] style correction in OwnJSONPatternFormatter.cpp --- src/Loggers/OwnJSONPatternFormatter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Loggers/OwnJSONPatternFormatter.cpp b/src/Loggers/OwnJSONPatternFormatter.cpp index afbdc2e7ecf..4ab2066b548 100644 --- a/src/Loggers/OwnJSONPatternFormatter.cpp +++ b/src/Loggers/OwnJSONPatternFormatter.cpp @@ -18,7 +18,7 @@ void OwnJSONPatternFormatter::formatExtended(const DB::ExtendedLogMessage & msg_ DB::WriteBufferFromString wb(text); DB::FormatSettings settings; - + const Poco::Message & msg = msg_ext.base; DB::writeChar('{', wb); From 45da56d8022a61135761f75dd98c3c39d8fdad20 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 28 Jul 2022 19:12:00 +0000 Subject: [PATCH 160/672] support hash functions with Map type --- src/Functions/FunctionsHashing.h | 12 ++++++++++++ .../02292_hash_array_tuples.reference | 14 ++++++++++++++ .../0_stateless/02292_hash_array_tuples.sql | 19 +++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/src/Functions/FunctionsHashing.h b/src/Functions/FunctionsHashing.h index c6e66a3d46d..862592254c1 100644 --- a/src/Functions/FunctionsHashing.h +++ b/src/Functions/FunctionsHashing.h @@ -31,12 +31,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -1085,6 +1087,16 @@ private: executeForArgument(tuple_types[i].get(), tmp.get(), vec_to, is_first); } } + else if (const auto * map = checkAndGetColumn(column)) + { + const auto & type_map = assert_cast(*type); + executeForArgument(type_map.getNestedType().get(), map->getNestedColumnPtr().get(), vec_to, is_first); + } + else if (const auto * const_map = checkAndGetColumnConstData(column)) + { + const auto & type_map = assert_cast(*type); + executeForArgument(type_map.getNestedType().get(), const_map->getNestedColumnPtr().get(), vec_to, is_first); + } else { if (is_first) diff --git a/tests/queries/0_stateless/02292_hash_array_tuples.reference b/tests/queries/0_stateless/02292_hash_array_tuples.reference index 52d52b54a5c..0ec624dda01 100644 --- a/tests/queries/0_stateless/02292_hash_array_tuples.reference +++ b/tests/queries/0_stateless/02292_hash_array_tuples.reference @@ -1,5 +1,19 @@ +arrays 14617701568871014978 12913842429399915005 8351543757058688770 12732328028874882204 12371801021764949421 Array(Tuple(UInt8, Array(Tuple(UInt8, Tuple(UInt8, UInt8, Array(Tuple(UInt8, UInt8))))))) +14617701568871014978 +12913842429399915005 +8351543757058688770 +12732328028874882204 +maps +14617701568871014978 +12913842429399915005 +8351543757058688770 +12732328028874882204 +14617701568871014978 +12913842429399915005 +8351543757058688770 +12732328028874882204 diff --git a/tests/queries/0_stateless/02292_hash_array_tuples.sql b/tests/queries/0_stateless/02292_hash_array_tuples.sql index 31c409dc6ba..ea368f56fe6 100644 --- a/tests/queries/0_stateless/02292_hash_array_tuples.sql +++ b/tests/queries/0_stateless/02292_hash_array_tuples.sql @@ -1,6 +1,25 @@ +SELECT 'arrays'; + SELECT cityHash64([(1, 'a'), (2, 'b')]); SELECT cityHash64([(1, 'c'), (2, 'b')]); SELECT sipHash64([(1, 'a'), (2, 'b')]); SELECT murmurHash2_64([(1, 'a'), (2, 'b'), (3, 'c')]); SELECT cityHash64([(1, [(1, (3, 4, [(5, 6), (7, 8)]))]), (2, [])] AS c), toTypeName(c); + +SELECT cityHash64(materialize([(1, 'a'), (2, 'b')])); +SELECT cityHash64(materialize([(1, 'c'), (2, 'b')])); +SELECT sipHash64(materialize([(1, 'a'), (2, 'b')])); +SELECT murmurHash2_64(materialize([(1, 'a'), (2, 'b'), (3, 'c')])); + +SELECT 'maps'; + +SELECT cityHash64(map(1, 'a', 2, 'b')); +SELECT cityHash64(map(1, 'c', 2, 'b')); +SELECT sipHash64(map(1, 'a', 2, 'b')); +SELECT murmurHash2_64(map(1, 'a', 2, 'b', 3, 'c')); + +SELECT cityHash64(materialize(map(1, 'a', 2, 'b'))); +SELECT cityHash64(materialize(map(1, 'c', 2, 'b'))); +SELECT sipHash64(materialize(map(1, 'a', 2, 'b'))); +SELECT murmurHash2_64(materialize(map(1, 'a', 2, 'b', 3, 'c'))); From e3cb65a99aec218976b1a9bd4b3a499271896dee Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Fri, 29 Jul 2022 06:51:53 +0000 Subject: [PATCH 161/672] Rollback on failed preprocess --- src/Coordination/KeeperStateMachine.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index d2f140bee2d..a52b4360092 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -196,13 +196,21 @@ void KeeperStateMachine::preprocess(const KeeperStorage::RequestForSession & req return; std::lock_guard lock(storage_and_responses_lock); - storage->preprocessRequest( - request_for_session.request, - request_for_session.session_id, - request_for_session.time, - request_for_session.zxid, - true /* check_acl */, - request_for_session.digest); + try + { + storage->preprocessRequest( + request_for_session.request, + request_for_session.session_id, + request_for_session.time, + request_for_session.zxid, + true /* check_acl */, + request_for_session.digest); + } + catch (...) + { + rollbackRequest(request_for_session, true); + throw; + } if (keeper_context->digest_enabled && request_for_session.digest) assertDigest(*request_for_session.digest, storage->getNodesDigest(false), *request_for_session.request, false); From f9e4d6370db9ac181b683c6c6772c94ec7da9f72 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 28 Jul 2022 23:51:59 -0700 Subject: [PATCH 162/672] modified integration test as suggested --- programs/server/config.xml | 9 ++++----- .../configs/config_json.xml | 14 ++++++++++++++ .../test_structured_logging_json/test.py | 10 +--------- 3 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 tests/integration/test_structured_logging_json/configs/config_json.xml diff --git a/programs/server/config.xml b/programs/server/config.xml index ef68741f056..0f759caafef 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -61,14 +61,13 @@ --> - + json diff --git a/tests/integration/test_structured_logging_json/configs/config_json.xml b/tests/integration/test_structured_logging_json/configs/config_json.xml new file mode 100644 index 00000000000..a0b158fece3 --- /dev/null +++ b/tests/integration/test_structured_logging_json/configs/config_json.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/tests/integration/test_structured_logging_json/test.py b/tests/integration/test_structured_logging_json/test.py index 34507a605c6..3a673254051 100644 --- a/tests/integration/test_structured_logging_json/test.py +++ b/tests/integration/test_structured_logging_json/test.py @@ -1,11 +1,9 @@ import pytest from helpers.cluster import ClickHouseCluster -import logging import json -from xml.etree import ElementTree cluster = ClickHouseCluster(__file__) -node = cluster.add_instance("node", stay_alive=True) +node = cluster.add_instance("node", main_configs=["configs/config_json.xml"]) @pytest.fixture(scope="module") @@ -37,12 +35,6 @@ def is_json(log_json): def test_structured_logging_json_format(start_cluster): - config = node.exec_in_container(["cat", "/etc/clickhouse-server/config.xml"]) - root = ElementTree.fromstring(config) - for logger in root.findall("logger"): - if logger.find("json") is None: - pytest.skip("JSON is not activated in config.xml") - node.query("SELECT 1") logs = node.grep_in_log(" ") From 39369be318aec04685d5c26793ba26eaa01c8f89 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 28 Jul 2022 23:56:26 -0700 Subject: [PATCH 163/672] commented out formatting tag in config.xml --- programs/server/config.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/programs/server/config.xml b/programs/server/config.xml index 0f759caafef..1247eb0e60f 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -62,12 +62,12 @@ --> - json + From 16c32b8b3f01b0ab25a0f549e6286e39bee5fa6f Mon Sep 17 00:00:00 2001 From: root Date: Fri, 29 Jul 2022 00:07:31 -0700 Subject: [PATCH 164/672] removed True from assert --- tests/integration/test_structured_logging_json/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_structured_logging_json/test.py b/tests/integration/test_structured_logging_json/test.py index 3a673254051..fdbf3651a0a 100644 --- a/tests/integration/test_structured_logging_json/test.py +++ b/tests/integration/test_structured_logging_json/test.py @@ -46,4 +46,4 @@ def test_structured_logging_json_format(start_cluster): # we will test maximum 5 logs if i >= min(4, len(log_array) - 1): break - assert result == True + assert result From cb68180cdf39f8ba8c14d5e49e96c2d6e9f05e90 Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt <952130278@qq.com> Date: Fri, 29 Jul 2022 15:22:03 +0800 Subject: [PATCH 165/672] fix align of AggregateFunctionDistinct --- src/AggregateFunctions/AggregateFunctionDistinct.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/AggregateFunctions/AggregateFunctionDistinct.h b/src/AggregateFunctions/AggregateFunctionDistinct.h index 91e25ddfdfd..5afe104bcc0 100644 --- a/src/AggregateFunctions/AggregateFunctionDistinct.h +++ b/src/AggregateFunctions/AggregateFunctionDistinct.h @@ -152,8 +152,8 @@ template class AggregateFunctionDistinct : public IAggregateFunctionDataHelper> { private: - static constexpr auto prefix_size = sizeof(Data); AggregateFunctionPtr nested_func; + size_t prefix_size; size_t arguments_num; AggregateDataPtr getNestedPlace(AggregateDataPtr __restrict place) const noexcept @@ -170,7 +170,11 @@ public: AggregateFunctionDistinct(AggregateFunctionPtr nested_func_, const DataTypes & arguments, const Array & params_) : IAggregateFunctionDataHelper(arguments, params_) , nested_func(nested_func_) - , arguments_num(arguments.size()) {} + , arguments_num(arguments.size()) + { + size_t nested_size = nested_func->alignOfData(); + prefix_size = (sizeof(Data) + nested_size - 1) / nested_size * nested_size; + } void add(AggregateDataPtr __restrict place, const IColumn ** columns, size_t row_num, Arena * arena) const override { From 21becb3030e3d5dfc8fec82ffcaa740bd61cb807 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Fri, 29 Jul 2022 07:36:19 +0000 Subject: [PATCH 166/672] Update version for digest --- src/Coordination/KeeperStateMachine.cpp | 5 ++--- src/Coordination/KeeperStorage.h | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index f43a3dbb319..5ff35660203 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -125,9 +125,8 @@ void assertDigest( { LOG_FATAL( &Poco::Logger::get("KeeperStateMachine"), - "Digest for nodes is not matching after {} request of type '{}'.\nExpected digest - {}, actual digest - {} (digest version " - "{}). Keeper will " - "terminate to avoid inconsistencies.\nExtra information about the request:\n{}", + "Digest for nodes is not matching after {} request of type '{}'.\nExpected digest - {}, actual digest - {} (digest " + "{}). Keeper will terminate to avoid inconsistencies.\nExtra information about the request:\n{}", committing ? "committing" : "preprocessing", request.getOpNum(), first.value, diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index 1fbe52cfbea..a96387c5def 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -73,10 +73,11 @@ public: enum DigestVersion : uint8_t { NO_DIGEST = 0, - V0 = 1 + V0 = 1, + V1 = 2 // added system nodes that modify the digest on startup so digest from V0 is invalid }; - static constexpr auto CURRENT_DIGEST_VERSION = DigestVersion::V0; + static constexpr auto CURRENT_DIGEST_VERSION = DigestVersion::V1; struct ResponseForSession { From d9c585ecf17618c81a73a19cb59dd78394d4b594 Mon Sep 17 00:00:00 2001 From: Dale Mcdiarmid Date: Fri, 29 Jul 2022 12:25:03 +0100 Subject: [PATCH 167/672] Escape credentials for diagnostics tool --- programs/diagnostics/internal/platform/database/native.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/programs/diagnostics/internal/platform/database/native.go b/programs/diagnostics/internal/platform/database/native.go index e512a634fbf..45b9af0349e 100644 --- a/programs/diagnostics/internal/platform/database/native.go +++ b/programs/diagnostics/internal/platform/database/native.go @@ -3,6 +3,7 @@ package database import ( "database/sql" "fmt" + "net/url" "strings" "github.com/ClickHouse/ClickHouse/programs/diagnostics/internal/platform/data" @@ -17,7 +18,7 @@ type ClickhouseNativeClient struct { func NewNativeClient(host string, port uint16, username string, password string) (*ClickhouseNativeClient, error) { // debug output ?debug=true - connection, err := sql.Open("clickhouse", fmt.Sprintf("clickhouse://%s:%s@%s:%d/", username, password, host, port)) + connection, err := sql.Open("clickhouse", fmt.Sprintf("clickhouse://%s:%s@%s:%d/", url.QueryEscape(username), url.QueryEscape(password), host, port)) if err != nil { return &ClickhouseNativeClient{}, err } From bb551566e7c29430339a7d8fef5e4a2ee670c4c4 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 29 Jul 2022 11:41:53 +0000 Subject: [PATCH 168/672] fix build --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 30ec9bbcdd4..16ffc15d292 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1016,7 +1016,7 @@ void IMergeTreeDataPart::loadRowsCount() /// Most trivial types if (column.type->isValueRepresentedByNumber() && !column.type->haveSubtypes() - && getSerialization(column)->getKind() == ISerialization::Kind::DEFAULT) + && getSerialization(column.name)->getKind() == ISerialization::Kind::DEFAULT) { auto size = getColumnSize(column.name); From 68f825acd4ea10861ef8d3b56d909d33d57f0374 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 29 Jul 2022 05:09:48 -0700 Subject: [PATCH 169/672] uncommented formatting tag --- .../test_structured_logging_json/configs/config_json.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_structured_logging_json/configs/config_json.xml b/tests/integration/test_structured_logging_json/configs/config_json.xml index a0b158fece3..c0bd4e86a01 100644 --- a/tests/integration/test_structured_logging_json/configs/config_json.xml +++ b/tests/integration/test_structured_logging_json/configs/config_json.xml @@ -8,7 +8,7 @@ {"date_time":"1650918987.180175","thread_name":"#1","thread_id":"254545","level":"Trace","query_id":"","logger_name":"BaseDaemon","message":"Received signal 2","source_file":"../base/daemon/BaseDaemon.cpp; virtual void SignalListener::run()","source_line":"192"} To enable JSON logging support, just uncomment tag below. --> - + json From ae7fd5bf937e350af37a25c92e1c6c3960989117 Mon Sep 17 00:00:00 2001 From: Anton Kozlov Date: Wed, 27 Jul 2022 16:57:56 +0000 Subject: [PATCH 170/672] Default database resolution in distributed reads --- src/Server/TCPHandler.cpp | 7 +++ .../__init__.py | 0 .../configs/remote_servers.xml | 18 +++++++ .../configs/users.xml | 24 +++++++++ .../test_distributed_default_database/test.py | 51 +++++++++++++++++++ 5 files changed, 100 insertions(+) create mode 100644 tests/integration/test_distributed_default_database/__init__.py create mode 100644 tests/integration/test_distributed_default_database/configs/remote_servers.xml create mode 100644 tests/integration/test_distributed_default_database/configs/users.xml create mode 100644 tests/integration/test_distributed_default_database/test.py diff --git a/src/Server/TCPHandler.cpp b/src/Server/TCPHandler.cpp index 05565063893..ed46abd9c36 100644 --- a/src/Server/TCPHandler.cpp +++ b/src/Server/TCPHandler.cpp @@ -760,6 +760,13 @@ void TCPHandler::processTablesStatusRequest() request.read(*in, client_tcp_protocol_version); ContextPtr context_to_resolve_table_names = (session && session->sessionContext()) ? session->sessionContext() : server.context(); + if (is_interserver_mode && !default_database.empty()) + { + // in interserver mode, the session doesn't exist. + ContextMutablePtr interserver_context = Context::createCopy(context_to_resolve_table_names); + interserver_context->setCurrentDatabase(default_database); + context_to_resolve_table_names = interserver_context; + } TablesStatusResponse response; for (const QualifiedTableName & table_name: request.tables) diff --git a/tests/integration/test_distributed_default_database/__init__.py b/tests/integration/test_distributed_default_database/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_distributed_default_database/configs/remote_servers.xml b/tests/integration/test_distributed_default_database/configs/remote_servers.xml new file mode 100644 index 00000000000..58641d93fc6 --- /dev/null +++ b/tests/integration/test_distributed_default_database/configs/remote_servers.xml @@ -0,0 +1,18 @@ + + + + foo + + node1 + 9000 + r0 + + + node2 + 9000 + r1 + + + + + diff --git a/tests/integration/test_distributed_default_database/configs/users.xml b/tests/integration/test_distributed_default_database/configs/users.xml new file mode 100644 index 00000000000..a49b1363433 --- /dev/null +++ b/tests/integration/test_distributed_default_database/configs/users.xml @@ -0,0 +1,24 @@ + + + + + false + + + + + + + + ::/0 + + default + default + + + + + + + + diff --git a/tests/integration/test_distributed_default_database/test.py b/tests/integration/test_distributed_default_database/test.py new file mode 100644 index 00000000000..ef69533416b --- /dev/null +++ b/tests/integration/test_distributed_default_database/test.py @@ -0,0 +1,51 @@ +""" +This test is similar to test_cross_replication, except +in this test we write into per-node tables and read from the distributed table. + +The default database in the distributed table definition is left empty on purpose to test +default database deduction. +""" +import pytest + +from helpers.client import QueryRuntimeException +from helpers.cluster import ClickHouseCluster +from helpers.test_tools import TSV + +from contextlib import contextmanager + + +def bootstrap(cluster): + for i, node in enumerate(list(cluster.instances.values())): + node.query(f"CREATE DATABASE IF NOT EXISTS r{i}") + node.query(f"CREATE TABLE r{i}.test_data(v UInt64) ENGINE = Memory()") + node.query(f"INSERT INTO r{i}.test_data SELECT * FROM numbers(10)") + node.query( + f"""CREATE TABLE default.test AS r{i}.test_data ENGINE = Distributed(secure, '', test_data, rand())""" + ) + + +@contextmanager +def start_cluster(): + cluster_disabled = ClickHouseCluster(__file__) + cluster_disabled.add_instance( + "node1", + main_configs=["configs/remote_servers.xml"], + user_configs=["configs/users.xml"], + ) + cluster_disabled.add_instance( + "node2", + main_configs=["configs/remote_servers.xml"], + user_configs=["configs/users.xml"], + ) + try: + cluster_disabled.start() + bootstrap(cluster_disabled) + yield cluster_disabled + finally: + cluster_disabled.shutdown() + + +def test_query(): + with start_cluster() as cluster: + node1 = cluster.instances["node1"] + assert TSV(node1.query("SELECT count() FROM default.test")) == TSV("20") From 59fbd21024067263a34fd61e562d8cda52dcc7d7 Mon Sep 17 00:00:00 2001 From: Arthur Passos Date: Fri, 29 Jul 2022 12:03:09 -0300 Subject: [PATCH 171/672] Unwrap LC column in IExecutablefunction::executeWithoutSparseColumns --- src/Functions/IFunction.cpp | 2 ++ tests/integration/test_cte_lc/__init__.py | 0 tests/integration/test_cte_lc/test.py | 26 +++++++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 tests/integration/test_cte_lc/__init__.py create mode 100644 tests/integration/test_cte_lc/test.py diff --git a/src/Functions/IFunction.cpp b/src/Functions/IFunction.cpp index 5be2ea3c5e3..e079fc626f3 100644 --- a/src/Functions/IFunction.cpp +++ b/src/Functions/IFunction.cpp @@ -285,6 +285,8 @@ ColumnPtr IExecutableFunction::executeWithoutSparseColumns(const ColumnsWithType ? res->cloneResized(1)->convertToFullColumnIfConst() : res; + keys = keys->convertToFullColumnIfLowCardinality(); + auto res_mut_dictionary = DataTypeLowCardinality::createColumnUnique(*res_low_cardinality_type->getDictionaryType()); ColumnPtr res_indexes = res_mut_dictionary->uniqueInsertRangeFrom(*keys, 0, keys->size()); ColumnUniquePtr res_dictionary = std::move(res_mut_dictionary); diff --git a/tests/integration/test_cte_lc/__init__.py b/tests/integration/test_cte_lc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_cte_lc/test.py b/tests/integration/test_cte_lc/test.py new file mode 100644 index 00000000000..e7c607b3e02 --- /dev/null +++ b/tests/integration/test_cte_lc/test.py @@ -0,0 +1,26 @@ +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_lc_of_string(start_cluster): + result = node.query("WITH ( SELECT toLowCardinality('a') ) AS bar SELECT bar") + + assert result == 'a\n' + + +def test_lc_of_int(start_cluster): + result = node.query("WITH ( SELECT toLowCardinality(1) ) AS bar SELECT bar") + + assert result == '1\n' From 6f88065cc1a8f4c75871b80a9b878a9f1910d224 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 29 Jul 2022 18:33:16 +0200 Subject: [PATCH 172/672] check metadata consistency --- src/Databases/DatabaseAtomic.cpp | 10 +- src/Databases/DatabaseAtomic.h | 1 + src/Databases/DatabaseOnDisk.cpp | 2 +- src/Databases/DatabaseOnDisk.h | 2 +- src/Databases/DatabaseReplicated.cpp | 187 +++++++++++++++++- src/Databases/DatabaseReplicated.h | 10 +- src/Databases/DatabaseReplicatedSettings.h | 1 + src/Databases/DatabaseReplicatedWorker.cpp | 18 +- src/Storages/StorageReplicatedMergeTree.cpp | 37 +++- src/Storages/StorageReplicatedMergeTree.h | 3 +- .../test_replicated_database/test.py | 142 ++++++++----- 11 files changed, 345 insertions(+), 68 deletions(-) diff --git a/src/Databases/DatabaseAtomic.cpp b/src/Databases/DatabaseAtomic.cpp index 8899f7ccaa2..efaa7702d1c 100644 --- a/src/Databases/DatabaseAtomic.cpp +++ b/src/Databases/DatabaseAtomic.cpp @@ -117,13 +117,19 @@ void DatabaseAtomic::dropTable(ContextPtr local_context, const String & table_na if (table) table->dropInnerTableIfAny(sync, local_context); else - throw Exception(ErrorCodes::UNKNOWN_TABLE, "Table {}.{} doesn't exist", - backQuote(getDatabaseName()), backQuote(table_name)); + throw Exception(ErrorCodes::UNKNOWN_TABLE, "Table {}.{} doesn't exist", backQuote(getDatabaseName()), backQuote(table_name)); + dropTableImpl(local_context, table_name, sync); +} + +void DatabaseAtomic::dropTableImpl(ContextPtr local_context, const String & table_name, bool sync) +{ String table_metadata_path = getObjectMetadataPath(table_name); String table_metadata_path_drop; + StoragePtr table; { std::lock_guard lock(mutex); + table = getTableUnlocked(table_name); table_metadata_path_drop = DatabaseCatalog::instance().getPathForDroppedMetadata(table->getStorageID()); auto txn = local_context->getZooKeeperMetadataTransaction(); if (txn && !local_context->isInternalSubquery()) diff --git a/src/Databases/DatabaseAtomic.h b/src/Databases/DatabaseAtomic.h index 1caf3b03a72..cb275812098 100644 --- a/src/Databases/DatabaseAtomic.h +++ b/src/Databases/DatabaseAtomic.h @@ -36,6 +36,7 @@ public: bool dictionary) override; void dropTable(ContextPtr context, const String & table_name, bool sync) override; + void dropTableImpl(ContextPtr context, const String & table_name, bool sync); void attachTable(ContextPtr context, const String & name, const StoragePtr & table, const String & relative_table_path) override; StoragePtr detachTable(ContextPtr context, const String & name) override; diff --git a/src/Databases/DatabaseOnDisk.cpp b/src/Databases/DatabaseOnDisk.cpp index fe229ba6ee9..b5bb6c7c759 100644 --- a/src/Databases/DatabaseOnDisk.cpp +++ b/src/Databases/DatabaseOnDisk.cpp @@ -230,7 +230,7 @@ void DatabaseOnDisk::createTable( /// If the table was detached permanently we will have a flag file with /// .sql.detached extension, is not needed anymore since we attached the table back -void DatabaseOnDisk::removeDetachedPermanentlyFlag(ContextPtr, const String & table_name, const String & table_metadata_path, bool) const +void DatabaseOnDisk::removeDetachedPermanentlyFlag(ContextPtr, const String & table_name, const String & table_metadata_path, bool) { try { diff --git a/src/Databases/DatabaseOnDisk.h b/src/Databases/DatabaseOnDisk.h index 90aba6be169..0db6a94b86d 100644 --- a/src/Databases/DatabaseOnDisk.h +++ b/src/Databases/DatabaseOnDisk.h @@ -94,7 +94,7 @@ protected: virtual void commitCreateTable(const ASTCreateQuery & query, const StoragePtr & table, const String & table_metadata_tmp_path, const String & table_metadata_path, ContextPtr query_context); - virtual void removeDetachedPermanentlyFlag(ContextPtr context, const String & table_name, const String & table_metadata_path, bool attach) const; + virtual void removeDetachedPermanentlyFlag(ContextPtr context, const String & table_name, const String & table_metadata_path, bool attach); virtual void setDetachedTableNotInUseForce(const UUID & /*uuid*/) {} const String metadata_path; diff --git a/src/Databases/DatabaseReplicated.cpp b/src/Databases/DatabaseReplicated.cpp index c4cfe5ef340..92affbb4115 100644 --- a/src/Databases/DatabaseReplicated.cpp +++ b/src/Databases/DatabaseReplicated.cpp @@ -53,6 +53,7 @@ static constexpr const char * DROPPED_MARK = "DROPPED"; static constexpr const char * BROKEN_TABLES_SUFFIX = "_broken_tables"; static constexpr const char * BROKEN_REPLICATED_TABLES_SUFFIX = "_broken_replicated_tables"; +static constexpr size_t METADATA_FILE_BUFFER_SIZE = 32768; zkutil::ZooKeeperPtr DatabaseReplicated::getZooKeeper() const { @@ -64,6 +65,13 @@ static inline String getHostID(ContextPtr global_context, const UUID & db_uuid) return Cluster::Address::toString(getFQDNOrHostName(), global_context->getTCPPort()) + ':' + toString(db_uuid); } +static inline UInt64 getMetadataHash(const String & table_name, const String & metadata) +{ + SipHash hash; + hash.update(table_name); + hash.update(metadata); + return hash.get64(); +} DatabaseReplicated::~DatabaseReplicated() = default; @@ -394,6 +402,7 @@ void DatabaseReplicated::createReplicaNodesInZooKeeper(const zkutil::ZooKeeperPt Coordination::Requests ops; ops.emplace_back(zkutil::makeCreateRequest(replica_path, host_id, zkutil::CreateMode::Persistent)); ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/log_ptr", "0", zkutil::CreateMode::Persistent)); + ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/digest", "0", zkutil::CreateMode::Persistent)); /// In addition to creating the replica nodes, we record the max_log_ptr at the instant where /// we declared ourself as an existing replica. We'll need this during recoverLostReplica to /// notify other nodes that issued new queries while this node was recovering. @@ -426,15 +435,74 @@ void DatabaseReplicated::loadStoredObjects( DatabaseAtomic::loadStoredObjects(local_context, mode, skip_startup_tables); } +UInt64 DatabaseReplicated::getMetadataHash(const String & table_name) const +{ + return DB::getMetadataHash(table_name, readMetadataFile(table_name)); +} + void DatabaseReplicated::startupTables(ThreadPool & thread_pool, LoadingStrictnessLevel mode) { DatabaseAtomic::startupTables(thread_pool, mode); + + /// TSA: No concurrent writes are possible during loading + UInt64 digest = 0; + for (const auto & table : TSA_SUPPRESS_WARNING_FOR_READ(tables)) + digest += getMetadataHash(table.first); + + LOG_DEBUG(log, "Calculated metadata digest of {} tables: {}", TSA_SUPPRESS_WARNING_FOR_READ(tables).size(), digest); + chassert(!tables_metadata_digest); + tables_metadata_digest = digest; + ddl_worker = std::make_unique(this, getContext()); if (is_probably_dropped) return; ddl_worker->startup(); } +bool DatabaseReplicated::debugCheckDigest(const ContextPtr & local_context) const +{ + /// Reduce number of debug checks + //if (thread_local_rng() % 16) + // return true; + + LOG_TEST(log, "Current in-memory metadata digest: {}", tables_metadata_digest); + + /// Database is probably being dropped + if (!local_context->getZooKeeperMetadataTransaction() && !ddl_worker->isCurrentlyActive()) + return true; + + UInt64 local_digest = 0; + { + std::lock_guard lock{mutex}; + for (const auto & table : TSA_SUPPRESS_WARNING_FOR_READ(tables)) + local_digest += getMetadataHash(table.first); + } + + if (local_digest != tables_metadata_digest) + { + LOG_ERROR(log, "Digest of local metadata ({}) is not equal to in-memory digest ({})", local_digest, tables_metadata_digest); + return false; + } + + /// Do not check digest in Keeper after internal subquery, it's probably not committed yet + if (local_context->isInternalSubquery()) + return true; + + /// Check does not make sense to check digest in Keeper during recovering + if (is_recovering) + return true; + + String zk_digest = getZooKeeper()->get(replica_path + "/digest"); + String local_digest_str = toString(local_digest); + if (zk_digest != local_digest_str) + { + LOG_ERROR(log, "Digest of local metadata ({}) is not equal to digest in Keeper ({})", local_digest_str, zk_digest); + return false; + } + + return true; +} + void DatabaseReplicated::checkQueryValid(const ASTPtr & query, ContextPtr query_context) const { /// Replicas will set correct name of current database in query context (database name can be different on replicas) @@ -637,7 +705,7 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep auto in_zk = table_name_to_metadata.find(name); if (in_zk == table_name_to_metadata.end() || in_zk->second != readMetadataFile(name)) { - /// Local table does not exits in ZooKeeper or has different metadata + /// Local table does not exist in ZooKeeper or has different metadata tables_to_detach.emplace_back(std::move(name)); } } @@ -699,7 +767,13 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep assert(db_name < to_database_name); DDLGuardPtr to_table_guard = DatabaseCatalog::instance().getDDLGuard(to_database_name, to_name); auto to_db_ptr = DatabaseCatalog::instance().getDatabase(to_database_name); - DatabaseAtomic::renameTable(make_query_context(), broken_table_name, *to_db_ptr, to_name, false, false); + + std::lock_guard lock{metadata_mutex}; + UInt64 new_digest = tables_metadata_digest; + new_digest -= getMetadataHash(broken_table_name); + DatabaseAtomic::renameTable(make_query_context(), broken_table_name, *to_db_ptr, to_name, /* exchange */ false, /* dictionary */ false); + tables_metadata_digest = new_digest; + assert(debugCheckDigest(getContext())); ++moved_tables; }; @@ -708,9 +782,24 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep LOG_DEBUG(log, "Will DROP TABLE {}, because it does not store data on disk and can be safely dropped", backQuoteIfNeed(table_name)); dropped_tables.push_back(tryGetTableUUID(table_name)); dropped_dictionaries += table->isDictionary(); - table->flushAndShutdown(); - DatabaseAtomic::dropTable(make_query_context(), table_name, true); + + if (table->getName() == "MaterializedView") + { + /// We have to drop MV inner table, so MV will not try to do it implicitly breaking some invariants. + /// Also we have to commit metadata transaction, because it's not committed by default for inner tables of MVs. + /// Yep, I hate inner tables of materialized views. + auto mv_drop_inner_table_context = make_query_context(); + table->dropInnerTableIfAny(sync, mv_drop_inner_table_context); + mv_drop_inner_table_context->getZooKeeperMetadataTransaction()->commit(); + } + + std::lock_guard lock{metadata_mutex}; + UInt64 new_digest = tables_metadata_digest; + new_digest -= getMetadataHash(table_name); + DatabaseAtomic::dropTableImpl(make_query_context(), table_name, /* sync */ true); + tables_metadata_digest = new_digest; + assert(debugCheckDigest(getContext())); } else if (!table->supportsReplication()) { @@ -736,7 +825,15 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep /// TODO Maybe we should do it in two steps: rename all tables to temporary names and then rename them to actual names? DDLGuardPtr table_guard = DatabaseCatalog::instance().getDDLGuard(db_name, std::min(from, to)); DDLGuardPtr to_table_guard = DatabaseCatalog::instance().getDDLGuard(db_name, std::max(from, to)); + + std::lock_guard lock{metadata_mutex}; + UInt64 new_digest = tables_metadata_digest; + String statement = readMetadataFile(from); + new_digest -= DB::getMetadataHash(from, statement); + new_digest += DB::getMetadataHash(to, statement); DatabaseAtomic::renameTable(make_query_context(), from, *this, to, false, false); + tables_metadata_digest = new_digest; + assert(debugCheckDigest(getContext())); } for (const auto & id : dropped_tables) @@ -771,6 +868,10 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep LOG_INFO(log, "Marked recovered {} as finished", entry_name); } } + + std::lock_guard lock{metadata_mutex}; + chassert(debugCheckDigest(getContext())); + current_zookeeper->set(replica_path + "/digest", toString(tables_metadata_digest)); } std::map DatabaseReplicated::tryGetConsistentMetadataSnapshot(const ZooKeeperPtr & zookeeper, UInt32 & max_log_ptr) @@ -881,7 +982,17 @@ void DatabaseReplicated::dropTable(ContextPtr local_context, const String & tabl String metadata_zk_path = zookeeper_path + "/metadata/" + escapeForFileName(table_name); txn->addOp(zkutil::makeRemoveRequest(metadata_zk_path, -1)); } + + std::lock_guard lock{metadata_mutex}; + UInt64 new_digest = tables_metadata_digest; + new_digest -= getMetadataHash(table_name); + if (txn) + txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1)); + DatabaseAtomic::dropTable(local_context, table_name, sync); + tables_metadata_digest = new_digest; + + assert(debugCheckDigest(local_context)); } void DatabaseReplicated::renameTable(ContextPtr local_context, const String & table_name, IDatabase & to_database, @@ -890,6 +1001,11 @@ void DatabaseReplicated::renameTable(ContextPtr local_context, const String & ta auto txn = local_context->getZooKeeperMetadataTransaction(); assert(txn); + String statement = readMetadataFile(table_name); + String statement_to; + if (exchange) + statement_to = readMetadataFile(to_table_name); + if (txn->isInitialQuery()) { if (this != &to_database) @@ -901,20 +1017,32 @@ void DatabaseReplicated::renameTable(ContextPtr local_context, const String & ta if (exchange && !to_database.isTableExist(to_table_name, local_context)) throw Exception(ErrorCodes::UNKNOWN_TABLE, "Table {} does not exist", to_table_name); - String statement = readMetadataFile(table_name); String metadata_zk_path = zookeeper_path + "/metadata/" + escapeForFileName(table_name); String metadata_zk_path_to = zookeeper_path + "/metadata/" + escapeForFileName(to_table_name); txn->addOp(zkutil::makeRemoveRequest(metadata_zk_path, -1)); if (exchange) { - String statement_to = readMetadataFile(to_table_name); txn->addOp(zkutil::makeRemoveRequest(metadata_zk_path_to, -1)); txn->addOp(zkutil::makeCreateRequest(metadata_zk_path, statement_to, zkutil::CreateMode::Persistent)); } txn->addOp(zkutil::makeCreateRequest(metadata_zk_path_to, statement, zkutil::CreateMode::Persistent)); } + std::lock_guard lock{metadata_mutex}; + UInt64 new_digest = tables_metadata_digest; + new_digest -= DB::getMetadataHash(table_name, statement); + new_digest += DB::getMetadataHash(to_table_name, statement); + if (exchange) + { + new_digest -= DB::getMetadataHash(to_table_name, statement_to); + new_digest += DB::getMetadataHash(table_name, statement_to); + } + if (txn) + txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1)); + DatabaseAtomic::renameTable(local_context, table_name, to_database, to_table_name, exchange, dictionary); + tables_metadata_digest = new_digest; + assert(debugCheckDigest(local_context)); } void DatabaseReplicated::commitCreateTable(const ASTCreateQuery & query, const StoragePtr & table, @@ -923,14 +1051,24 @@ void DatabaseReplicated::commitCreateTable(const ASTCreateQuery & query, const S { auto txn = query_context->getZooKeeperMetadataTransaction(); assert(!ddl_worker->isCurrentlyActive() || txn); + + String statement = getObjectDefinitionFromCreateQuery(query.clone()); if (txn && txn->isInitialQuery()) { String metadata_zk_path = zookeeper_path + "/metadata/" + escapeForFileName(query.getTable()); - String statement = getObjectDefinitionFromCreateQuery(query.clone()); /// zk::multi(...) will throw if `metadata_zk_path` exists txn->addOp(zkutil::makeCreateRequest(metadata_zk_path, statement, zkutil::CreateMode::Persistent)); } + + std::lock_guard lock{metadata_mutex}; + UInt64 new_digest = tables_metadata_digest; + new_digest += DB::getMetadataHash(query.getTable(), statement); + if (txn) + txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1)); + DatabaseAtomic::commitCreateTable(query, table, table_metadata_tmp_path, table_metadata_path, query_context); + tables_metadata_digest = new_digest; + assert(debugCheckDigest(query_context)); } void DatabaseReplicated::commitAlterTable(const StorageID & table_id, @@ -943,7 +1081,17 @@ void DatabaseReplicated::commitAlterTable(const StorageID & table_id, String metadata_zk_path = zookeeper_path + "/metadata/" + escapeForFileName(table_id.table_name); txn->addOp(zkutil::makeSetRequest(metadata_zk_path, statement, -1)); } + + std::lock_guard lock{metadata_mutex}; + UInt64 new_digest = tables_metadata_digest; + new_digest -= getMetadataHash(table_id.table_name); + new_digest += DB::getMetadataHash(table_id.table_name, statement); + if (txn) + txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1)); + DatabaseAtomic::commitAlterTable(table_id, table_metadata_tmp_path, table_metadata_path, statement, query_context); + tables_metadata_digest = new_digest; + assert(debugCheckDigest(query_context)); } void DatabaseReplicated::detachTablePermanently(ContextPtr local_context, const String & table_name) @@ -957,10 +1105,19 @@ void DatabaseReplicated::detachTablePermanently(ContextPtr local_context, const String metadata_zk_path = zookeeper_path + "/metadata/" + escapeForFileName(table_name); txn->addOp(zkutil::makeRemoveRequest(metadata_zk_path, -1)); } + + std::lock_guard lock{metadata_mutex}; + UInt64 new_digest = tables_metadata_digest; + new_digest -= getMetadataHash(table_name); + if (txn) + txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1)); + DatabaseAtomic::detachTablePermanently(local_context, table_name); + tables_metadata_digest = new_digest; + assert(debugCheckDigest(local_context)); } -void DatabaseReplicated::removeDetachedPermanentlyFlag(ContextPtr local_context, const String & table_name, const String & table_metadata_path, bool attach) const +void DatabaseReplicated::removeDetachedPermanentlyFlag(ContextPtr local_context, const String & table_name, const String & table_metadata_path, bool attach) { auto txn = local_context->getZooKeeperMetadataTransaction(); assert(!ddl_worker->isCurrentlyActive() || txn); @@ -970,14 +1127,26 @@ void DatabaseReplicated::removeDetachedPermanentlyFlag(ContextPtr local_context, String statement = readMetadataFile(table_name); txn->addOp(zkutil::makeCreateRequest(metadata_zk_path, statement, zkutil::CreateMode::Persistent)); } + + std::lock_guard lock{metadata_mutex}; + UInt64 new_digest = tables_metadata_digest; + if (attach) + { + new_digest += getMetadataHash(table_name); + if (txn) + txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1)); + } + DatabaseAtomic::removeDetachedPermanentlyFlag(local_context, table_name, table_metadata_path, attach); + tables_metadata_digest = new_digest; + assert(debugCheckDigest(local_context)); } String DatabaseReplicated::readMetadataFile(const String & table_name) const { String statement; - ReadBufferFromFile in(getObjectMetadataPath(table_name), 4096); + ReadBufferFromFile in(getObjectMetadataPath(table_name), METADATA_FILE_BUFFER_SIZE); readStringUntilEOF(statement, in); return statement; } diff --git a/src/Databases/DatabaseReplicated.h b/src/Databases/DatabaseReplicated.h index 1c977cab895..8256d54a796 100644 --- a/src/Databases/DatabaseReplicated.h +++ b/src/Databases/DatabaseReplicated.h @@ -40,7 +40,7 @@ public: const String & table_metadata_tmp_path, const String & table_metadata_path, const String & statement, ContextPtr query_context) override; void detachTablePermanently(ContextPtr context, const String & table_name) override; - void removeDetachedPermanentlyFlag(ContextPtr context, const String & table_name, const String & table_metadata_path, bool attach) const override; + void removeDetachedPermanentlyFlag(ContextPtr context, const String & table_name, const String & table_metadata_path, bool attach) override; bool waitForReplicaToProcessAllEntries(UInt64 timeout_ms); @@ -111,6 +111,9 @@ private: return is_recovering && typeid_cast(&to_database); } + UInt64 getMetadataHash(const String & table_name) const; + bool debugCheckDigest(const ContextPtr & local_context) const; + String zookeeper_path; String shard_name; String replica_name; @@ -125,6 +128,11 @@ private: std::unique_ptr ddl_worker; UInt32 max_log_ptr_at_creation = 0; + /// Usually operation with metadata are single-threaded because of the way replication works, + /// but StorageReplicatedMergeTree may call alterTable outside from DatabaseReplicatedDDLWorker causing race conditions. + std::mutex metadata_mutex; + UInt64 tables_metadata_digest = 0; + mutable ClusterPtr cluster; }; diff --git a/src/Databases/DatabaseReplicatedSettings.h b/src/Databases/DatabaseReplicatedSettings.h index 8bed1ababf6..c19ec1fb7a4 100644 --- a/src/Databases/DatabaseReplicatedSettings.h +++ b/src/Databases/DatabaseReplicatedSettings.h @@ -12,6 +12,7 @@ class ASTStorage; M(UInt64, max_replication_lag_to_enqueue, 10, "Replica will throw exception on attempt to execute query if its replication lag greater", 0) \ M(UInt64, wait_entry_commited_timeout_sec, 3600, "Replicas will try to cancel query if timeout exceed, but initiator host has not executed it yet", 0) \ M(String, collection_name, "", "A name of a collection defined in server's config where all info for cluster authentication is defined", 0) \ + M(Bool, check_consistency, true, "Check consistency of local metadata and metadata in Keeper, do replica recovery on inconsistency", 0) \ DECLARE_SETTINGS_TRAITS(DatabaseReplicatedSettingsTraits, LIST_OF_DATABASE_REPLICATED_SETTINGS) diff --git a/src/Databases/DatabaseReplicatedWorker.cpp b/src/Databases/DatabaseReplicatedWorker.cpp index 81f9ebb97e0..161e262a574 100644 --- a/src/Databases/DatabaseReplicatedWorker.cpp +++ b/src/Databases/DatabaseReplicatedWorker.cpp @@ -66,8 +66,24 @@ void DatabaseReplicatedDDLWorker::initializeReplication() UInt32 our_log_ptr = parse(log_ptr_str); UInt32 max_log_ptr = parse(zookeeper->get(database->zookeeper_path + "/max_log_ptr")); logs_to_keep = parse(zookeeper->get(database->zookeeper_path + "/logs_to_keep")); - if (our_log_ptr == 0 || our_log_ptr + logs_to_keep < max_log_ptr) + + String digest; + if (!zookeeper->tryGet(database->replica_path + "/digest", digest)) { + /// Database was created by old ClickHouse versions, let's create the node + digest = toString(database->tables_metadata_digest); + zookeeper->create(database->replica_path + "/digest", digest, zkutil::CreateMode::Persistent); + } + + bool is_new_replica = our_log_ptr == 0; + bool lost_according_to_log_ptr = our_log_ptr + logs_to_keep < max_log_ptr; + bool lost_according_to_digest = database->db_settings.check_consistency && database->tables_metadata_digest != parse(digest); + + if (is_new_replica || lost_according_to_log_ptr || lost_according_to_digest) + { + if (!is_new_replica) + LOG_WARNING(log, "Replica seems to be lost: our_log_ptr={}, max_log_ptr={}, local_digest={}, zk_digest={}", + our_log_ptr, max_log_ptr, database->tables_metadata_digest, digest); database->recoverLostReplica(zookeeper, our_log_ptr, max_log_ptr); zookeeper->set(database->replica_path + "/log_ptr", toString(max_log_ptr)); initializeLogPointer(DDLTaskBase::getLogEntryName(max_log_ptr)); diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 1ec14f643d4..e669ca129be 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -41,6 +41,7 @@ #include #include +#include #include #include @@ -1097,19 +1098,18 @@ void StorageReplicatedMergeTree::checkTableStructure(const String & zookeeper_pr } } -void StorageReplicatedMergeTree::setTableStructure( +void StorageReplicatedMergeTree::setTableStructure(const StorageID & table_id, const ContextPtr & local_context, ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & metadata_diff) { StorageInMemoryMetadata old_metadata = getInMemoryMetadata(); - StorageInMemoryMetadata new_metadata = metadata_diff.getNewMetadata(new_columns, getContext(), old_metadata); + StorageInMemoryMetadata new_metadata = metadata_diff.getNewMetadata(new_columns, local_context, old_metadata); /// 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); - auto table_id = getStorageID(); - DatabaseCatalog::instance().getDatabase(table_id.database_name)->alterTable(getContext(), table_id, new_metadata); + DatabaseCatalog::instance().getDatabase(table_id.database_name)->alterTable(local_context, table_id, new_metadata); } @@ -4604,7 +4604,31 @@ bool StorageReplicatedMergeTree::executeMetadataAlter(const StorageReplicatedMer requests.emplace_back(zkutil::makeSetRequest(fs::path(replica_path) / "columns", entry.columns_str, -1)); requests.emplace_back(zkutil::makeSetRequest(fs::path(replica_path) / "metadata", entry.metadata_str, -1)); - zookeeper->multi(requests); + auto table_id = getStorageID(); + auto alter_context = getContext(); + + auto database = DatabaseCatalog::instance().getDatabase(table_id.database_name); + bool is_in_replicated_database = database->getEngineName() == "Replicated"; + + if (is_in_replicated_database) + { + auto mutable_alter_context = Context::createCopy(getContext()); + const auto * replicated = dynamic_cast(database.get()); + mutable_alter_context->makeQueryContext(); + auto alter_txn = std::make_shared(zookeeper, replicated->getZooKeeperPath(), + /* is_initial_query */ false, /* task_zk_path */ ""); + mutable_alter_context->initZooKeeperMetadataTransaction(alter_txn); + alter_context = mutable_alter_context; + + for (auto & op : requests) + alter_txn->addOp(std::move(op)); + requests.clear(); + /// Requests will be executed by database in setTableStructure + } + else + { + zookeeper->multi(requests); + } { auto table_lock_holder = lockForShare(RWLockImpl::NO_QUERY, getSettings()->lock_acquire_timeout_for_background_operations); @@ -4612,13 +4636,14 @@ 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(std::move(columns_from_entry), metadata_diff); + setTableStructure(table_id, alter_context, std::move(columns_from_entry), metadata_diff); metadata_version = entry.alter_version; LOG_INFO(log, "Applied changes to the metadata of the table. Current metadata version: {}", metadata_version); } /// 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); return true; diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index c35e2d5cf5c..d4eb49eba0d 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -486,7 +486,8 @@ private: /// A part of ALTER: apply metadata changes only (data parts are altered separately). /// Must be called under IStorage::lockForAlter() lock. - void setTableStructure(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); /** 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. diff --git a/tests/integration/test_replicated_database/test.py b/tests/integration/test_replicated_database/test.py index ad28430ba7d..f716fac8508 100644 --- a/tests/integration/test_replicated_database/test.py +++ b/tests/integration/test_replicated_database/test.py @@ -554,6 +554,62 @@ def test_alters_from_different_replicas(started_cluster): snapshot_recovering_node.query("DROP DATABASE testdb SYNC") +def create_some_tables(db): + settings = {"distributed_ddl_task_timeout": 0} + main_node.query( + "CREATE TABLE {}.t1 (n int) ENGINE=Memory".format(db), settings=settings + ) + dummy_node.query( + "CREATE TABLE {}.t2 (s String) ENGINE=Memory".format(db), settings=settings + ) + main_node.query( + "CREATE TABLE {}.mt1 (n int) ENGINE=MergeTree order by n".format(db), + settings=settings, + ) + dummy_node.query( + "CREATE TABLE {}.mt2 (n int) ENGINE=MergeTree order by n".format(db), + settings=settings, + ) + main_node.query( + "CREATE TABLE {}.rmt1 (n int) ENGINE=ReplicatedMergeTree order by n".format(db), + settings=settings, + ) + dummy_node.query( + "CREATE TABLE {}.rmt2 (n int) ENGINE=ReplicatedMergeTree order by n".format(db), + settings=settings, + ) + main_node.query( + "CREATE TABLE {}.rmt3 (n int) ENGINE=ReplicatedMergeTree order by n".format(db), + settings=settings, + ) + dummy_node.query( + "CREATE TABLE {}.rmt5 (n int) ENGINE=ReplicatedMergeTree order by n".format(db), + settings=settings, + ) + main_node.query( + "CREATE MATERIALIZED VIEW {}.mv1 (n int) ENGINE=ReplicatedMergeTree order by n AS SELECT n FROM recover.rmt1".format( + db + ), + settings=settings, + ) + dummy_node.query( + "CREATE MATERIALIZED VIEW {}.mv2 (n int) ENGINE=ReplicatedMergeTree order by n AS SELECT n FROM recover.rmt2".format( + db + ), + settings=settings, + ) + main_node.query( + "CREATE DICTIONARY {}.d1 (n int DEFAULT 0, m int DEFAULT 1) PRIMARY KEY n " + "SOURCE(CLICKHOUSE(HOST 'localhost' PORT 9000 USER 'default' TABLE 'rmt1' PASSWORD '' DB 'recover')) " + "LIFETIME(MIN 1 MAX 10) LAYOUT(FLAT())".format(db) + ) + dummy_node.query( + "CREATE DICTIONARY {}.d2 (n int DEFAULT 0, m int DEFAULT 1) PRIMARY KEY n " + "SOURCE(CLICKHOUSE(HOST 'localhost' PORT 9000 USER 'default' TABLE 'rmt2' PASSWORD '' DB 'recover')) " + "LIFETIME(MIN 1 MAX 10) LAYOUT(FLAT())".format(db) + ) + + def test_recover_staled_replica(started_cluster): main_node.query( "CREATE DATABASE recover ENGINE = Replicated('/clickhouse/databases/recover', 'shard1', 'replica1');" @@ -566,52 +622,7 @@ def test_recover_staled_replica(started_cluster): ) settings = {"distributed_ddl_task_timeout": 0} - main_node.query("CREATE TABLE recover.t1 (n int) ENGINE=Memory", settings=settings) - dummy_node.query( - "CREATE TABLE recover.t2 (s String) ENGINE=Memory", settings=settings - ) - main_node.query( - "CREATE TABLE recover.mt1 (n int) ENGINE=MergeTree order by n", - settings=settings, - ) - dummy_node.query( - "CREATE TABLE recover.mt2 (n int) ENGINE=MergeTree order by n", - settings=settings, - ) - main_node.query( - "CREATE TABLE recover.rmt1 (n int) ENGINE=ReplicatedMergeTree order by n", - settings=settings, - ) - dummy_node.query( - "CREATE TABLE recover.rmt2 (n int) ENGINE=ReplicatedMergeTree order by n", - settings=settings, - ) - main_node.query( - "CREATE TABLE recover.rmt3 (n int) ENGINE=ReplicatedMergeTree order by n", - settings=settings, - ) - dummy_node.query( - "CREATE TABLE recover.rmt5 (n int) ENGINE=ReplicatedMergeTree order by n", - settings=settings, - ) - main_node.query( - "CREATE MATERIALIZED VIEW recover.mv1 (n int) ENGINE=ReplicatedMergeTree order by n AS SELECT n FROM recover.rmt1", - settings=settings, - ) - dummy_node.query( - "CREATE MATERIALIZED VIEW recover.mv2 (n int) ENGINE=ReplicatedMergeTree order by n AS SELECT n FROM recover.rmt2", - settings=settings, - ) - main_node.query( - "CREATE DICTIONARY recover.d1 (n int DEFAULT 0, m int DEFAULT 1) PRIMARY KEY n " - "SOURCE(CLICKHOUSE(HOST 'localhost' PORT 9000 USER 'default' TABLE 'rmt1' PASSWORD '' DB 'recover')) " - "LIFETIME(MIN 1 MAX 10) LAYOUT(FLAT())" - ) - dummy_node.query( - "CREATE DICTIONARY recover.d2 (n int DEFAULT 0, m int DEFAULT 1) PRIMARY KEY n " - "SOURCE(CLICKHOUSE(HOST 'localhost' PORT 9000 USER 'default' TABLE 'rmt2' PASSWORD '' DB 'recover')) " - "LIFETIME(MIN 1 MAX 10) LAYOUT(FLAT())" - ) + create_some_tables("recover") for table in ["t1", "t2", "mt1", "mt2", "rmt1", "rmt2", "rmt3", "rmt5"]: main_node.query("INSERT INTO recover.{} VALUES (42)".format(table)) @@ -932,3 +943,42 @@ def test_force_synchronous_settings(started_cluster): "CREATE TABLE test_force_synchronous_settings.t (n String) ENGINE=ReplicatedMergeTree('/test/same/path/{shard}', '{replica}') ORDER BY tuple()" ) select_thread.join() + + +def test_recover_digest_mismatch(started_cluster): + main_node.query( + "CREATE DATABASE recover_digest_mismatch ENGINE = Replicated('/clickhouse/databases/recover_digest_mismatch', 'shard1', 'replica1');" + ) + dummy_node.query( + "CREATE DATABASE recover_digest_mismatch ENGINE = Replicated('/clickhouse/databases/recover_digest_mismatch', 'shard1', 'replica2');" + ) + + create_some_tables("recover_digest_mismatch") + + ways_to_corrupt_metadata = [ + f"mv /var/lib/clickhouse/metadata/recover_digest_mismatch/t1.sql /var/lib/clickhouse/metadata/recover_digest_mismatch/m1.sql", + f"sed --follow-symlinks -i 's/Int32/String/' /var/lib/clickhouse/metadata/recover_digest_mismatch/mv1.sql", + f"rm -f /var/lib/clickhouse/metadata/recover_digest_mismatch/d1.sql", + # f"rm -rf /var/lib/clickhouse/metadata/recover_digest_mismatch/", # Directory already exists + f"rm -rf /var/lib/clickhouse/store", + ] + + for command in ways_to_corrupt_metadata: + need_remove_is_active_node = "rm -rf" in command + dummy_node.stop_clickhouse(kill=not need_remove_is_active_node) + dummy_node.exec_in_container(["bash", "-c", command]) + dummy_node.start_clickhouse() + + query = ( + "SELECT name, uuid, create_table_query FROM system.tables WHERE database='recover_digest_mismatch' AND name NOT LIKE '.inner_id.%' " + "ORDER BY name SETTINGS show_table_uuid_in_table_create_query_if_not_nil=1" + ) + expected = main_node.query(query) + + if "rm -rf" in command: + # NOTE Otherwise it fails to recreate ReplicatedMergeTree table due to "Replica already exists" + main_node.query( + "SYSTEM DROP REPLICA '2' FROM DATABASE recover_digest_mismatch" + ) + + assert_eq_with_retry(dummy_node, query, expected) From f0f19874dafd84666af75818eb0dd8f1cdbbfb48 Mon Sep 17 00:00:00 2001 From: Arthur Passos Date: Fri, 29 Jul 2022 13:56:01 -0300 Subject: [PATCH 173/672] fix style --- src/Functions/IFunction.cpp | 2 +- tests/integration/test_cte_lc/test.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Functions/IFunction.cpp b/src/Functions/IFunction.cpp index e079fc626f3..4e73f7588c5 100644 --- a/src/Functions/IFunction.cpp +++ b/src/Functions/IFunction.cpp @@ -286,7 +286,7 @@ ColumnPtr IExecutableFunction::executeWithoutSparseColumns(const ColumnsWithType : res; keys = keys->convertToFullColumnIfLowCardinality(); - + auto res_mut_dictionary = DataTypeLowCardinality::createColumnUnique(*res_low_cardinality_type->getDictionaryType()); ColumnPtr res_indexes = res_mut_dictionary->uniqueInsertRangeFrom(*keys, 0, keys->size()); ColumnUniquePtr res_dictionary = std::move(res_mut_dictionary); diff --git a/tests/integration/test_cte_lc/test.py b/tests/integration/test_cte_lc/test.py index e7c607b3e02..12fd47ee842 100644 --- a/tests/integration/test_cte_lc/test.py +++ b/tests/integration/test_cte_lc/test.py @@ -17,10 +17,10 @@ def start_cluster(): def test_lc_of_string(start_cluster): result = node.query("WITH ( SELECT toLowCardinality('a') ) AS bar SELECT bar") - assert result == 'a\n' + assert result == "a\n"" def test_lc_of_int(start_cluster): result = node.query("WITH ( SELECT toLowCardinality(1) ) AS bar SELECT bar") - assert result == '1\n' + assert result == "1\n" From d1de8d0dfbd143d67ac0e7a731a6de55ab5a32a6 Mon Sep 17 00:00:00 2001 From: Arthur Passos Date: Fri, 29 Jul 2022 13:59:29 -0300 Subject: [PATCH 174/672] remove extra double quotes --- tests/integration/test_cte_lc/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_cte_lc/test.py b/tests/integration/test_cte_lc/test.py index 12fd47ee842..36244446fff 100644 --- a/tests/integration/test_cte_lc/test.py +++ b/tests/integration/test_cte_lc/test.py @@ -17,7 +17,7 @@ def start_cluster(): def test_lc_of_string(start_cluster): result = node.query("WITH ( SELECT toLowCardinality('a') ) AS bar SELECT bar") - assert result == "a\n"" + assert result == "a\n" def test_lc_of_int(start_cluster): From 6126bd60ed7f38fe757a091c46c9e21c0ab5e873 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Fri, 29 Jul 2022 19:27:38 +0200 Subject: [PATCH 175/672] Fix cherry-pick for cases, when assignee is not set for PR --- tests/ci/cherry_pick.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/ci/cherry_pick.py b/tests/ci/cherry_pick.py index 334a24ed7af..5382106d26b 100644 --- a/tests/ci/cherry_pick.py +++ b/tests/ci/cherry_pick.py @@ -206,7 +206,8 @@ Merge it only if you intend to backport changes to the target branch, otherwise ) self.cherrypick_pr.add_to_labels(Labels.LABEL_CHERRYPICK) self.cherrypick_pr.add_to_labels(Labels.LABEL_DO_NOT_TEST) - self.cherrypick_pr.add_to_assignees(self.pr.assignee) + if self.pr.assignee is not None: + self.cherrypick_pr.add_to_assignees(self.pr.assignee) self.cherrypick_pr.add_to_assignees(self.pr.user) def create_backport(self): @@ -238,7 +239,8 @@ Merge it only if you intend to backport changes to the target branch, otherwise head=self.backport_branch, ) self.backport_pr.add_to_labels(Labels.LABEL_BACKPORT) - self.backport_pr.add_to_assignees(self.pr.assignee) + if self.pr.assignee is not None: + self.cherrypick_pr.add_to_assignees(self.pr.assignee) self.backport_pr.add_to_assignees(self.pr.user) @property From 573d9c1b57037e9fc2dac5824bbd64f53a2352b4 Mon Sep 17 00:00:00 2001 From: avogar Date: Fri, 29 Jul 2022 18:30:40 +0000 Subject: [PATCH 176/672] Fix stress test --- docker/test/stress/stress | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/stress/stress b/docker/test/stress/stress index 6d90b9d5437..64cca4beb3a 100755 --- a/docker/test/stress/stress +++ b/docker/test/stress/stress @@ -77,7 +77,7 @@ def run_func_test( pipes = [] for i in range(0, len(output_paths)): f = open(output_paths[i], "w") - full_command = "{} {} {} {} {}".format( + full_command = "{} {} {} {} {} --stress".format( cmd, get_options(i, backward_compatibility_check), global_time_limit_option, From a11fad1561305b17966681f7a94ee0eb264ade82 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 29 Jul 2022 20:41:23 +0200 Subject: [PATCH 177/672] fix --- src/Databases/DatabaseReplicated.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Databases/DatabaseReplicated.cpp b/src/Databases/DatabaseReplicated.cpp index 92affbb4115..40654281543 100644 --- a/src/Databases/DatabaseReplicated.cpp +++ b/src/Databases/DatabaseReplicated.cpp @@ -784,7 +784,7 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep dropped_dictionaries += table->isDictionary(); table->flushAndShutdown(); - if (table->getName() == "MaterializedView") + if (table->getName() == "MaterializedView" || table->getName() == "WindowView") { /// We have to drop MV inner table, so MV will not try to do it implicitly breaking some invariants. /// Also we have to commit metadata transaction, because it's not committed by default for inner tables of MVs. From 864725e55f7bb06f26eafbd334d81a410961dd76 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Fri, 29 Jul 2022 20:44:46 +0200 Subject: [PATCH 178/672] Skip jepsen tests in PRs w/o jepsen-test label --- .github/workflows/pull_request.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 26726302beb..cdad9d4f23d 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -3348,6 +3348,10 @@ jobs: ###################################### JEPSEN TESTS ######################################### ############################################################################################# Jepsen: + # This is special test NOT INCLUDED in FinishCheck + # When it's skipped, all dependent tasks will be skipped too. + # DO NOT add it there + if: contains(github.event.pull_request.labels.*.name, 'jepsen-test') needs: [BuilderBinRelease] uses: ./.github/workflows/jepsen.yml @@ -3419,7 +3423,6 @@ jobs: - SplitBuildSmokeTest - CompatibilityCheck - IntegrationTestsFlakyCheck - - Jepsen runs-on: [self-hosted, style-checker] steps: - name: Clear repository From f5f6c746487dadaa321ce30aa455c4694c843a4a Mon Sep 17 00:00:00 2001 From: kssenii Date: Fri, 29 Jul 2022 22:00:21 +0300 Subject: [PATCH 179/672] One more update --- contrib/azure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/azure b/contrib/azure index 9030dea3088..ef75afc075f 160000 --- a/contrib/azure +++ b/contrib/azure @@ -1 +1 @@ -Subproject commit 9030dea30881e561b06dc80e0453b9b2a593cdbe +Subproject commit ef75afc075fc71fbcd8fe28dcda3794ae265fd1c From 72be640cfb49b8c83faa5f438336fd84a10044f1 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy <99031427+yakov-olkhovskiy@users.noreply.github.com> Date: Wed, 27 Jul 2022 23:45:46 -0400 Subject: [PATCH 180/672] clickhouse may be compressed - run once to decompress --- docker/test/fuzzer/run-fuzzer.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/test/fuzzer/run-fuzzer.sh b/docker/test/fuzzer/run-fuzzer.sh index f74760e3339..392d8110576 100755 --- a/docker/test/fuzzer/run-fuzzer.sh +++ b/docker/test/fuzzer/run-fuzzer.sh @@ -69,6 +69,8 @@ function download wget_with_retry "$BINARY_URL_TO_DOWNLOAD" chmod +x clickhouse + # clickhouse may be compressed - run once to decompress + ./clickhouse ||: ln -s ./clickhouse ./clickhouse-server ln -s ./clickhouse ./clickhouse-client From f5442cba863d4608a7c241185d67f6a6f8e0fd7f Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy <99031427+yakov-olkhovskiy@users.noreply.github.com> Date: Thu, 28 Jul 2022 17:01:43 -0400 Subject: [PATCH 181/672] clickhouse may be compressed - run once to decompress --- docker/test/performance-comparison/compare.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/test/performance-comparison/compare.sh b/docker/test/performance-comparison/compare.sh index 3b0b7a4d95a..c5f68f166b5 100755 --- a/docker/test/performance-comparison/compare.sh +++ b/docker/test/performance-comparison/compare.sh @@ -1338,6 +1338,8 @@ EOF set -x } +# clickhouse may be compressed - run once to decompress +/workspace/right/clickhouse ||: # Check that local and client are in PATH clickhouse-local --version > /dev/null clickhouse-client --version > /dev/null From 7b0b38e997e424f95e306c34684549415a02d3ac Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Fri, 29 Jul 2022 19:42:22 +0000 Subject: [PATCH 182/672] DistinctSortedTransform works only if columns contains sort prefix of sort description --- .../Transforms/DistinctSortedTransform.cpp | 84 +++++++++++++------ .../Transforms/DistinctSortedTransform.h | 12 ++- src/Storages/MergeTree/MergeTask.cpp | 2 +- 3 files changed, 70 insertions(+), 28 deletions(-) diff --git a/src/Processors/Transforms/DistinctSortedTransform.cpp b/src/Processors/Transforms/DistinctSortedTransform.cpp index 3762504fda5..8227b6d581a 100644 --- a/src/Processors/Transforms/DistinctSortedTransform.cpp +++ b/src/Processors/Transforms/DistinctSortedTransform.cpp @@ -6,19 +6,14 @@ namespace DB namespace ErrorCodes { extern const int SET_SIZE_LIMIT_EXCEEDED; + extern const int LOGICAL_ERROR; } -DistinctSortedTransform::DistinctSortedTransform( - Block header_, SortDescription sort_description, const SizeLimits & set_size_limits_, UInt64 limit_hint_, const Names & columns) - : ISimpleTransform(header_, header_, true) - , header(std::move(header_)) - , description(std::move(sort_description)) - , column_names(columns) - , limit_hint(limit_hint_) - , set_size_limits(set_size_limits_) +static void calcColumnPositionsInHeader(const Block& header, const Names & column_names, ColumnNumbers& column_positions) { /// pre-calculate column positions to use during chunk transformation const size_t num_columns = column_names.empty() ? header.columns() : column_names.size(); + column_positions.clear(); column_positions.reserve(num_columns); for (size_t i = 0; i < num_columns; ++i) { @@ -27,11 +22,46 @@ DistinctSortedTransform::DistinctSortedTransform( if (col && !isColumnConst(*col)) column_positions.emplace_back(pos); } +} + +bool DistinctSortedTransform::isApplicable(const Block & header, const SortDescription & sort_description, const Names & column_names) +{ + if (sort_description.empty()) + return false; + + /// check if first sorted column in header + const SortColumnDescription & column_sort_descr = sort_description.front(); + if (!header.has(column_sort_descr.column_name)) + return false; + + ColumnNumbers column_positions; + calcColumnPositionsInHeader(header, column_names, column_positions); + + /// check if sorted column matches any DISTINCT column + const auto pos = header.getPositionByName(column_sort_descr.column_name); + return std::find(begin(column_positions), end(column_positions), pos) != column_positions.end(); +} + +DistinctSortedTransform::DistinctSortedTransform( + const Block & header_, + const SortDescription & sort_description_, + const SizeLimits & set_size_limits_, + UInt64 limit_hint_, + const Names & columns) + : ISimpleTransform(header_, header_, true) + , header(header_) + , sort_description(sort_description_) + , column_names(columns) + , limit_hint(limit_hint_) + , set_size_limits(set_size_limits_) +{ + /// pre-calculate column positions to use during chunk transformation + calcColumnPositionsInHeader(header, column_names, column_positions); column_ptrs.reserve(column_positions.size()); /// pre-calculate DISTINCT column positions which form sort prefix of sort description - sort_prefix_positions.reserve(description.size()); - for (const auto & column_sort_descr : description) + sort_prefix_positions.reserve(sort_description.size()); + for (const auto & column_sort_descr : sort_description) { /// check if there is such column in header if (!header.has(column_sort_descr.column_name)) @@ -44,6 +74,10 @@ DistinctSortedTransform::DistinctSortedTransform( sort_prefix_positions.emplace_back(pos); } + if (sort_prefix_positions.empty()) + throw Exception( + ErrorCodes::LOGICAL_ERROR, "DistinctSortedTransform: columns have to form a sort prefix for provided sort description"); + sort_prefix_columns.reserve(sort_prefix_positions.size()); } @@ -111,7 +145,7 @@ void DistinctSortedTransform::transform(Chunk & chunk) prev_chunk.chunk = std::move(chunk); prev_chunk.clearing_hint_columns = std::move(sort_prefix_columns); - size_t all_columns = prev_chunk.chunk.getNumColumns(); + const size_t all_columns = prev_chunk.chunk.getNumColumns(); Chunk res_chunk; for (size_t i = 0; i < all_columns; ++i) res_chunk.addColumn(prev_chunk.chunk.getColumns().at(i)->filter(filter, -1)); @@ -119,38 +153,40 @@ void DistinctSortedTransform::transform(Chunk & chunk) chunk = std::move(res_chunk); } - template bool DistinctSortedTransform::buildFilter( Method & method, const ColumnRawPtrs & columns, const ColumnRawPtrs & clearing_hint_columns, IColumn::Filter & filter, - size_t rows, + const size_t rows, ClearableSetVariants & variants) const { typename Method::State state(columns, key_sizes, nullptr); /// Compare last row of previous block and first row of current block, - /// If rows not equal, we can clear HashSet, - /// If clearing_hint_columns is empty, we CAN'T clear HashSet. - if (!clearing_hint_columns.empty() && !prev_chunk.clearing_hint_columns.empty() - && !rowsEqual(clearing_hint_columns, 0, prev_chunk.clearing_hint_columns, prev_chunk.chunk.getNumRows() - 1)) + /// If rows are NOT equal, we can clear HashSet + if (!prev_chunk.clearing_hint_columns.empty()) /// it's not first chunk in stream { - method.data.clear(); + if (!rowsEqual(clearing_hint_columns, 0, prev_chunk.clearing_hint_columns, prev_chunk.chunk.getNumRows() - 1)) + method.data.clear(); } bool has_new_data = false; - for (size_t i = 0; i < rows; ++i) + { /// handle 0-indexed row to avoid index check in loop below + const auto emplace_result = state.emplaceKey(method.data, 0, variants.string_pool); + if (emplace_result.isInserted()) + has_new_data = true; + filter[0] = emplace_result.isInserted(); + } + for (size_t i = 1; i < rows; ++i) { /// Compare i-th row and i-1-th row, - /// If rows are not equal, we can clear HashSet, - /// If clearing_hint_columns is empty, we CAN'T clear HashSet. - if (i > 0 && !clearing_hint_columns.empty() && !rowsEqual(clearing_hint_columns, i, clearing_hint_columns, i - 1)) + /// If rows are not equal, we can clear HashSet + if (!rowsEqual(clearing_hint_columns, i, clearing_hint_columns, i - 1)) method.data.clear(); - auto emplace_result = state.emplaceKey(method.data, i, variants.string_pool); - + const auto emplace_result = state.emplaceKey(method.data, i, variants.string_pool); if (emplace_result.isInserted()) has_new_data = true; diff --git a/src/Processors/Transforms/DistinctSortedTransform.h b/src/Processors/Transforms/DistinctSortedTransform.h index 2fe40408683..86e4f6f6ed7 100644 --- a/src/Processors/Transforms/DistinctSortedTransform.h +++ b/src/Processors/Transforms/DistinctSortedTransform.h @@ -24,10 +24,16 @@ class DistinctSortedTransform : public ISimpleTransform public: /// Empty columns_ means all columns. DistinctSortedTransform( - Block header_, SortDescription sort_description, const SizeLimits & set_size_limits_, UInt64 limit_hint_, const Names & columns); + const Block & header_, + const SortDescription & sort_description, + const SizeLimits & set_size_limits_, + UInt64 limit_hint_, + const Names & columns); String getName() const override { return "DistinctSortedTransform"; } + static bool isApplicable(const Block & header, const SortDescription & sort_description, const Names & column_names); + protected: void transform(Chunk & chunk) override; @@ -45,7 +51,7 @@ private: ClearableSetVariants & variants) const; Block header; - SortDescription description; + SortDescription sort_description; struct PreviousChunk { @@ -58,7 +64,7 @@ private: ColumnNumbers column_positions; /// DISTINCT columns positions in header ColumnNumbers sort_prefix_positions; /// DISTINCT columns positions which form sort prefix of sort description ColumnRawPtrs column_ptrs; /// DISTINCT columns from chunk - ColumnRawPtrs sort_prefix_columns; /// DISTINCT columns from chunk which form sort prefix of sort description + ColumnRawPtrs sort_prefix_columns; /// DISTINCT columns from chunk which form sort prefix of sort description ClearableSetVariants data; Sizes key_sizes; diff --git a/src/Storages/MergeTree/MergeTask.cpp b/src/Storages/MergeTree/MergeTask.cpp index 8c6d47378a7..04945e20b65 100644 --- a/src/Storages/MergeTree/MergeTask.cpp +++ b/src/Storages/MergeTree/MergeTask.cpp @@ -916,7 +916,7 @@ void MergeTask::ExecuteAndFinalizeHorizontalPart::createMergedStream() if (global_ctx->deduplicate) { - if (!sort_description.empty()) + if (DistinctSortedTransform::isApplicable(header, sort_description, global_ctx->deduplicate_by_columns)) res_pipe.addTransform(std::make_shared( res_pipe.getHeader(), sort_description, SizeLimits(), 0 /*limit_hint*/, global_ctx->deduplicate_by_columns)); else From cc73b531168f869b19000c189d832a6e0f008537 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Fri, 29 Jul 2022 13:21:50 -0700 Subject: [PATCH 183/672] Fix Endian issue in SipHash for s390x --- base/base/unaligned.h | 32 ++++++++++++++++++++++++++++++++ src/Common/SipHash.h | 6 +++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/base/base/unaligned.h b/base/base/unaligned.h index 013aa5274e9..7fff0ddb20c 100644 --- a/base/base/unaligned.h +++ b/base/base/unaligned.h @@ -2,7 +2,39 @@ #include #include +#include +inline void reverseMemcpy(void *dst, const void *src, int length) +{ + uint8_t *d = reinterpret_cast(dst); + const uint8_t *s = reinterpret_cast(src); + + d += length; + while (length--) + *--d = *s++; +} + +template +inline T unalignedLoadLE(const void * address) +{ + T res {}; + if constexpr (std::endian::native == std::endian::little) + memcpy(&res, address, sizeof(res)); + else + reverseMemcpy(&res, address, sizeof(res)); + return res; +} + +template +inline void unalignedStoreLE(void * address, + const typename std::enable_if::type & src) +{ + static_assert(std::is_trivially_copyable_v); + if constexpr (std::endian::native == std::endian::little) + memcpy(address, &src, sizeof(src)); + else + reverseMemcpy(address, &src, sizeof(src)); +} template inline T unalignedLoad(const void * address) diff --git a/src/Common/SipHash.h b/src/Common/SipHash.h index 38188a022f8..1f26cba31fb 100644 --- a/src/Common/SipHash.h +++ b/src/Common/SipHash.h @@ -111,7 +111,7 @@ public: while (data + 8 <= end) { - current_word = unalignedLoad(data); + current_word = unalignedLoadLE(data); v3 ^= current_word; SIPROUND; @@ -157,8 +157,8 @@ public: void get128(char * out) { finalize(); - unalignedStore(out, v0 ^ v1); - unalignedStore(out + 8, v2 ^ v3); + unalignedStoreLE(out, v0 ^ v1); + unalignedStoreLE(out + 8, v2 ^ v3); } template From 13dc1697fb045fd88ebd8ba690585598ef152d2d Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Fri, 29 Jul 2022 20:34:23 +0000 Subject: [PATCH 184/672] Remove unnecessary initialization --- src/Processors/Transforms/DistinctSortedTransform.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Processors/Transforms/DistinctSortedTransform.h b/src/Processors/Transforms/DistinctSortedTransform.h index 05beae647bd..b415657d238 100644 --- a/src/Processors/Transforms/DistinctSortedTransform.h +++ b/src/Processors/Transforms/DistinctSortedTransform.h @@ -72,7 +72,7 @@ private: /// Restrictions on the maximum size of the output data. SizeLimits set_size_limits; - bool all_columns_const = true; + bool all_columns_const; }; } From d951154ef4e948f46f81e1ac93c93809591defa9 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Fri, 29 Jul 2022 22:12:03 +0000 Subject: [PATCH 185/672] Proved NULLs direction when compare rows --- .../Transforms/DistinctSortedTransform.cpp | 56 ++++++++++--------- .../Transforms/DistinctSortedTransform.h | 8 +-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Processors/Transforms/DistinctSortedTransform.cpp b/src/Processors/Transforms/DistinctSortedTransform.cpp index 8558573873e..61f6e1b329e 100644 --- a/src/Processors/Transforms/DistinctSortedTransform.cpp +++ b/src/Processors/Transforms/DistinctSortedTransform.cpp @@ -43,7 +43,7 @@ bool DistinctSortedTransform::isApplicable(const Block & header, const SortDescr if (sort_description.empty()) return false; - /// check if first sorted column in header + /// check if at least first sorted column in header const SortColumnDescription & column_sort_descr = sort_description.front(); if (!header.has(column_sort_descr.column_name)) return false; @@ -51,21 +51,18 @@ bool DistinctSortedTransform::isApplicable(const Block & header, const SortDescr ColumnNumbers column_positions; calcColumnPositionsInHeader(header, column_names, column_positions); - /// check if sorted column matches any DISTINCT column + /// check if the sorted column matches any DISTINCT column const auto pos = header.getPositionByName(column_sort_descr.column_name); return std::find(begin(column_positions), end(column_positions), pos) != column_positions.end(); } DistinctSortedTransform::DistinctSortedTransform( - const Block & header_, - const SortDescription & sort_description_, + const Block & header, + const SortDescription & sort_description, const SizeLimits & set_size_limits_, UInt64 limit_hint_, - const Names & columns) - : ISimpleTransform(header_, header_, true) - , header(header_) - , sort_description(sort_description_) - , column_names(columns) + const Names & column_names) + : ISimpleTransform(header, header, true) , limit_hint(limit_hint_) , set_size_limits(set_size_limits_) { @@ -74,26 +71,31 @@ DistinctSortedTransform::DistinctSortedTransform( column_ptrs.reserve(column_positions.size()); all_columns_const = column_positions.empty(); - /// pre-calculate DISTINCT column positions which form sort prefix of sort description - sort_prefix_positions.reserve(sort_description.size()); - for (const auto & column_sort_descr : sort_description) + if (!all_columns_const) { - /// check if there is such column in header - if (!header.has(column_sort_descr.column_name)) - break; + /// pre-calculate DISTINCT column positions which form sort prefix of sort description + sort_prefix_positions.reserve(sort_description.size()); + sorted_columns_nulls_direction.reserve(sort_description.size()); + for (const auto & column_sort_descr : sort_description) + { + /// check if there is such column in header + if (!header.has(column_sort_descr.column_name)) + break; - /// check if sorted column position matches any DISTINCT column - const auto pos = header.getPositionByName(column_sort_descr.column_name); - if (std::find(begin(column_positions), end(column_positions), pos) == column_positions.end()) - break; + /// check if sorted column position matches any DISTINCT column + const auto pos = header.getPositionByName(column_sort_descr.column_name); + if (std::find(begin(column_positions), end(column_positions), pos) == column_positions.end()) + break; - sort_prefix_positions.emplace_back(pos); + sort_prefix_positions.emplace_back(pos); + sorted_columns_nulls_direction.emplace_back(column_sort_descr.nulls_direction); + } + if (sort_prefix_positions.empty()) + throw Exception( + ErrorCodes::LOGICAL_ERROR, "DistinctSortedTransform: columns have to form a sort prefix for provided sort description"); + + sort_prefix_columns.reserve(sort_prefix_positions.size()); } - if (sort_prefix_positions.empty()) - throw Exception( - ErrorCodes::LOGICAL_ERROR, "DistinctSortedTransform: columns have to form a sort prefix for provided sort description"); - - sort_prefix_columns.reserve(sort_prefix_positions.size()); } void DistinctSortedTransform::transform(Chunk & chunk) @@ -220,13 +222,13 @@ bool DistinctSortedTransform::buildFilter( return has_new_data; } -bool DistinctSortedTransform::rowsEqual(const ColumnRawPtrs & lhs, size_t n, const ColumnRawPtrs & rhs, size_t m) +bool DistinctSortedTransform::rowsEqual(const ColumnRawPtrs & lhs, size_t n, const ColumnRawPtrs & rhs, size_t m) const { for (size_t column_index = 0, num_columns = lhs.size(); column_index < num_columns; ++column_index) { const auto & lhs_column = *lhs[column_index]; const auto & rhs_column = *rhs[column_index]; - if (lhs_column.compareAt(n, m, rhs_column, 0) != 0) /// not equal + if (lhs_column.compareAt(n, m, rhs_column, sorted_columns_nulls_direction[column_index]) != 0) /// not equal return false; } return true; diff --git a/src/Processors/Transforms/DistinctSortedTransform.h b/src/Processors/Transforms/DistinctSortedTransform.h index b415657d238..7bd4a9d825e 100644 --- a/src/Processors/Transforms/DistinctSortedTransform.h +++ b/src/Processors/Transforms/DistinctSortedTransform.h @@ -28,7 +28,7 @@ public: const SortDescription & sort_description, const SizeLimits & set_size_limits_, UInt64 limit_hint_, - const Names & columns); + const Names & column_names); String getName() const override { return "DistinctSortedTransform"; } @@ -38,7 +38,7 @@ protected: void transform(Chunk & chunk) override; private: - static bool rowsEqual(const ColumnRawPtrs & lhs, size_t n, const ColumnRawPtrs & rhs, size_t m); + bool rowsEqual(const ColumnRawPtrs & lhs, size_t n, const ColumnRawPtrs & rhs, size_t m) const; /// return true if has new data template @@ -50,8 +50,7 @@ private: size_t rows, ClearableSetVariants & variants) const; - Block header; - SortDescription sort_description; + std::vector sorted_columns_nulls_direction; struct PreviousChunk { @@ -60,7 +59,6 @@ private: }; PreviousChunk prev_chunk; - Names column_names; ColumnNumbers column_positions; /// DISTINCT columns positions in header ColumnNumbers sort_prefix_positions; /// DISTINCT columns positions which form sort prefix of sort description ColumnRawPtrs column_ptrs; /// DISTINCT columns from chunk From 7cdad883278f9764b6888ec2d7d7dc5d7967f75a Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Sat, 30 Jul 2022 11:34:17 +0000 Subject: [PATCH 186/672] Create snapshot on shutdown --- src/Coordination/KeeperContext.h | 3 ++- src/Coordination/KeeperServer.cpp | 9 ++++++++- src/Coordination/KeeperServer.h | 2 ++ src/Coordination/KeeperStateMachine.cpp | 8 +++++++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Coordination/KeeperContext.h b/src/Coordination/KeeperContext.h index 84ec65cecde..64fa8cea6ec 100644 --- a/src/Coordination/KeeperContext.h +++ b/src/Coordination/KeeperContext.h @@ -8,7 +8,8 @@ struct KeeperContext enum class Phase : uint8_t { INIT, - RUNNING + RUNNING, + SHUTDOWN }; Phase server_state{Phase::INIT}; diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 587ab9c8f66..20ce7e42afc 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -107,8 +107,9 @@ KeeperServer::KeeperServer( : server_id(configuration_and_settings_->server_id) , coordination_settings(configuration_and_settings_->coordination_settings) , log(&Poco::Logger::get("KeeperServer")) - , is_recovering(config.has("keeper_server.force_recovery") && config.getBool("keeper_server.force_recovery")) + , is_recovering(config.getBool("keeper_server.force_recovery", false)) , keeper_context{std::make_shared()} + , create_snapshot_on_exit(config.getBool("keeper_server.create_snapshot_on_exit", true)) { if (coordination_settings->quorum_reads) LOG_WARNING(log, "Quorum reads enabled, Keeper will work slower."); @@ -367,6 +368,12 @@ void KeeperServer::shutdownRaftServer() } raft_instance->shutdown(); + + keeper_context->server_state = KeeperContext::Phase::SHUTDOWN; + + if (create_snapshot_on_exit) + raft_instance->create_snapshot(); + raft_instance.reset(); if (asio_listener) diff --git a/src/Coordination/KeeperServer.h b/src/Coordination/KeeperServer.h index 74dd05631f0..1fb3e579214 100644 --- a/src/Coordination/KeeperServer.h +++ b/src/Coordination/KeeperServer.h @@ -64,6 +64,8 @@ private: std::shared_ptr keeper_context; + const bool create_snapshot_on_exit; + public: KeeperServer( const KeeperConfigurationAndSettingsPtr & settings_, diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index f43a3dbb319..a55acaf9b91 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -383,7 +383,13 @@ void KeeperStateMachine::create_snapshot(nuraft::snapshot & s, nuraft::async_res }; - LOG_DEBUG(log, "In memory snapshot {} created, queueing task to flash to disk", s.get_last_log_idx()); + if (keeper_context->server_state == KeeperContext::Phase::SHUTDOWN) + { + snapshot_task.create_snapshot(std::move(snapshot_task.snapshot)); + return; + } + + LOG_DEBUG(log, "In memory snapshot {} created, queueing task to flush to disk", s.get_last_log_idx()); /// Flush snapshot to disk in a separate thread. if (!snapshots_queue.push(std::move(snapshot_task))) LOG_WARNING(log, "Cannot push snapshot task into queue"); From 3be51a6dea2defd97c9d0e5aade5d99c25e05320 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Sat, 30 Jul 2022 19:22:28 +0000 Subject: [PATCH 187/672] Construct DistinctSortedTransform only when it makes sense otherwise fallback to DistinctTransform (i.e. ordinary distinct) --- src/Processors/QueryPlan/DistinctStep.cpp | 22 +-- .../Transforms/DistinctSortedTransform.cpp | 135 +++++++++--------- .../Transforms/DistinctSortedTransform.h | 10 +- 3 files changed, 86 insertions(+), 81 deletions(-) diff --git a/src/Processors/QueryPlan/DistinctStep.cpp b/src/Processors/QueryPlan/DistinctStep.cpp index c268cb44267..f4b474f83a3 100644 --- a/src/Processors/QueryPlan/DistinctStep.cpp +++ b/src/Processors/QueryPlan/DistinctStep.cpp @@ -140,15 +140,19 @@ void DistinctStep::transformPipeline(QueryPipelineBuilder & pipeline, const Buil if (distinct_sort_desc.size() < columns.size()) { - pipeline.addSimpleTransform( - [&](const Block & header, QueryPipelineBuilder::StreamType stream_type) -> ProcessorPtr - { - if (stream_type != QueryPipelineBuilder::StreamType::Main) - return nullptr; + if (DistinctSortedTransform::isApplicable(pipeline.getHeader(), distinct_sort_desc, columns)) + { + pipeline.addSimpleTransform( + [&](const Block & header, QueryPipelineBuilder::StreamType stream_type) -> ProcessorPtr + { + if (stream_type != QueryPipelineBuilder::StreamType::Main) + return nullptr; - return std::make_shared( - header, distinct_sort_desc, set_size_limits, limit_hint, columns); - }); + return std::make_shared( + header, distinct_sort_desc, set_size_limits, limit_hint, columns); + }); + return; + } } else { @@ -161,8 +165,8 @@ void DistinctStep::transformPipeline(QueryPipelineBuilder & pipeline, const Buil return std::make_shared( header, set_size_limits, limit_hint, distinct_sort_desc, columns, true); }); + return; } - return; } } } diff --git a/src/Processors/Transforms/DistinctSortedTransform.cpp b/src/Processors/Transforms/DistinctSortedTransform.cpp index 61f6e1b329e..12f8da67025 100644 --- a/src/Processors/Transforms/DistinctSortedTransform.cpp +++ b/src/Processors/Transforms/DistinctSortedTransform.cpp @@ -9,93 +9,103 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -static void handleAllColumnsConst(Chunk & chunk) +/// calculate column positions to use during chunk transformation +static void calcColumnPositionsInHeader(const Block& header, const Names & column_names, ColumnNumbers& column_positions, ColumnNumbers& const_column_positions) { - const size_t rows = chunk.getNumRows(); - IColumn::Filter filter(rows); - - Chunk res_chunk; - std::fill(filter.begin(), filter.end(), 0); - filter[0] = 1; - for (const auto & column : chunk.getColumns()) - res_chunk.addColumn(column->filter(filter, -1)); - - chunk = std::move(res_chunk); -} - -static void calcColumnPositionsInHeader(const Block& header, const Names & column_names, ColumnNumbers& column_positions) -{ - /// pre-calculate column positions to use during chunk transformation const size_t num_columns = column_names.empty() ? header.columns() : column_names.size(); column_positions.clear(); column_positions.reserve(num_columns); + const_column_positions.clear(); + const_column_positions.reserve(num_columns); for (size_t i = 0; i < num_columns; ++i) { auto pos = column_names.empty() ? i : header.getPositionByName(column_names[i]); const auto & column = header.getByPosition(pos).column; - if (column && !isColumnConst(*column)) - column_positions.emplace_back(pos); + if (column) + { + if (isColumnConst(*column)) + const_column_positions.emplace_back(pos); + else + column_positions.emplace_back(pos); + } } } +/// calculate DISTINCT column positions which form sort prefix of sort description +static void calcSortPrefixPositionsInHeader( + const Block & header, + const SortDescription & sort_description, + const ColumnNumbers & column_positions, + const ColumnNumbers & const_column_positions, + ColumnNumbers & sort_prefix_positions) +{ + sort_prefix_positions.reserve(sort_description.size()); + for (const auto & column_sort_descr : sort_description) + { + /// check if there is such column in header + if (!header.has(column_sort_descr.column_name)) + break; + + /// check if sorted column position matches any DISTINCT column + const auto pos = header.getPositionByName(column_sort_descr.column_name); + if (std::find(begin(column_positions), end(column_positions), pos) == column_positions.end()) + { + /// if sorted column found in const columns then we can skip it + if (std::find(begin(const_column_positions), end(const_column_positions), pos) != const_column_positions.end()) + continue; + + break; + } + + sort_prefix_positions.emplace_back(pos); + } +} + +/// check if distinct sorted is applicable for provided header, sort description and distinct columns bool DistinctSortedTransform::isApplicable(const Block & header, const SortDescription & sort_description, const Names & column_names) { if (sort_description.empty()) return false; - /// check if at least first sorted column in header - const SortColumnDescription & column_sort_descr = sort_description.front(); - if (!header.has(column_sort_descr.column_name)) + ColumnNumbers column_positions; + ColumnNumbers const_column_positions; + calcColumnPositionsInHeader(header, column_names, column_positions, const_column_positions); + if (column_positions.empty()) return false; - ColumnNumbers column_positions; - calcColumnPositionsInHeader(header, column_names, column_positions); - - /// check if the sorted column matches any DISTINCT column - const auto pos = header.getPositionByName(column_sort_descr.column_name); - return std::find(begin(column_positions), end(column_positions), pos) != column_positions.end(); + /// check if sorted columns matches DISTINCT columns + ColumnNumbers sort_prefix_positions; + calcSortPrefixPositionsInHeader(header, sort_description, column_positions, const_column_positions, sort_prefix_positions); + return !sort_prefix_positions.empty(); } DistinctSortedTransform::DistinctSortedTransform( const Block & header, const SortDescription & sort_description, const SizeLimits & set_size_limits_, - UInt64 limit_hint_, + const UInt64 limit_hint_, const Names & column_names) : ISimpleTransform(header, header, true) , limit_hint(limit_hint_) , set_size_limits(set_size_limits_) { + if (sort_description.empty()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "DistinctSortedTransform: sort description can't be empty"); + /// pre-calculate column positions to use during chunk transformation - calcColumnPositionsInHeader(header, column_names, column_positions); + ColumnNumbers const_column_positions; + calcColumnPositionsInHeader(header, column_names, column_positions, const_column_positions); + if (column_positions.empty()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "DistinctSortedTransform: all columns can't be const. DistinctTransform should be used instead"); + + /// pre-calculate DISTINCT column positions which form sort prefix of sort description + calcSortPrefixPositionsInHeader(header, sort_description, column_positions, const_column_positions, sort_prefix_positions); + if (sort_prefix_positions.empty()) + throw Exception( + ErrorCodes::LOGICAL_ERROR, "DistinctSortedTransform: columns have to form a sort prefix for provided sort description"); + column_ptrs.reserve(column_positions.size()); - all_columns_const = column_positions.empty(); - - if (!all_columns_const) - { - /// pre-calculate DISTINCT column positions which form sort prefix of sort description - sort_prefix_positions.reserve(sort_description.size()); - sorted_columns_nulls_direction.reserve(sort_description.size()); - for (const auto & column_sort_descr : sort_description) - { - /// check if there is such column in header - if (!header.has(column_sort_descr.column_name)) - break; - - /// check if sorted column position matches any DISTINCT column - const auto pos = header.getPositionByName(column_sort_descr.column_name); - if (std::find(begin(column_positions), end(column_positions), pos) == column_positions.end()) - break; - - sort_prefix_positions.emplace_back(pos); - sorted_columns_nulls_direction.emplace_back(column_sort_descr.nulls_direction); - } - if (sort_prefix_positions.empty()) - throw Exception( - ErrorCodes::LOGICAL_ERROR, "DistinctSortedTransform: columns have to form a sort prefix for provided sort description"); - - sort_prefix_columns.reserve(sort_prefix_positions.size()); - } + sort_prefix_columns.reserve(sort_prefix_positions.size()); } void DistinctSortedTransform::transform(Chunk & chunk) @@ -103,14 +113,6 @@ void DistinctSortedTransform::transform(Chunk & chunk) if (unlikely(!chunk.hasRows())) return; - /// special case - all column constant - if (unlikely(all_columns_const)) - { - handleAllColumnsConst(chunk); - stopReading(); - return; - } - /// get DISTINCT columns from chunk column_ptrs.clear(); for (const auto pos : column_positions) @@ -177,7 +179,6 @@ void DistinctSortedTransform::transform(Chunk & chunk) chunk = std::move(res_chunk); } - template bool DistinctSortedTransform::buildFilter( Method & method, @@ -222,13 +223,13 @@ bool DistinctSortedTransform::buildFilter( return has_new_data; } -bool DistinctSortedTransform::rowsEqual(const ColumnRawPtrs & lhs, size_t n, const ColumnRawPtrs & rhs, size_t m) const +bool DistinctSortedTransform::rowsEqual(const ColumnRawPtrs & lhs, size_t n, const ColumnRawPtrs & rhs, size_t m) { for (size_t column_index = 0, num_columns = lhs.size(); column_index < num_columns; ++column_index) { const auto & lhs_column = *lhs[column_index]; const auto & rhs_column = *rhs[column_index]; - if (lhs_column.compareAt(n, m, rhs_column, sorted_columns_nulls_direction[column_index]) != 0) /// not equal + if (lhs_column.compareAt(n, m, rhs_column, -1) != 0) /// not equal return false; } return true; diff --git a/src/Processors/Transforms/DistinctSortedTransform.h b/src/Processors/Transforms/DistinctSortedTransform.h index 7bd4a9d825e..86c5c968e4b 100644 --- a/src/Processors/Transforms/DistinctSortedTransform.h +++ b/src/Processors/Transforms/DistinctSortedTransform.h @@ -12,6 +12,9 @@ namespace DB /** This class is intended for implementation of SELECT DISTINCT clause and * leaves only unique rows in the stream. * + * DistinctSortedTransform::isApplicable() have to be used to check if DistinctSortedTransform can be constructed with particular arguments, + * otherwise the constructor can throw LOGICAL_ERROR exception + * * Implementation for case, when input stream has rows for same DISTINCT key or at least its prefix, * grouped together (going consecutively). * @@ -24,7 +27,7 @@ class DistinctSortedTransform : public ISimpleTransform public: /// Empty columns_ means all columns. DistinctSortedTransform( - const Block & header_, + const Block & header, const SortDescription & sort_description, const SizeLimits & set_size_limits_, UInt64 limit_hint_, @@ -38,7 +41,7 @@ protected: void transform(Chunk & chunk) override; private: - bool rowsEqual(const ColumnRawPtrs & lhs, size_t n, const ColumnRawPtrs & rhs, size_t m) const; + static bool rowsEqual(const ColumnRawPtrs & lhs, size_t n, const ColumnRawPtrs & rhs, size_t m); /// return true if has new data template @@ -50,8 +53,6 @@ private: size_t rows, ClearableSetVariants & variants) const; - std::vector sorted_columns_nulls_direction; - struct PreviousChunk { Chunk chunk; @@ -70,7 +71,6 @@ private: /// Restrictions on the maximum size of the output data. SizeLimits set_size_limits; - bool all_columns_const; }; } From 7245ddcc20d92a29c92bc7ef9f2b4443fb57dc23 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Sat, 30 Jul 2022 20:25:56 +0000 Subject: [PATCH 188/672] Simple refactoring: ordinary DISTINCT implementation --- .../Transforms/DistinctTransform.cpp | 28 +++++++++---------- src/Processors/Transforms/DistinctTransform.h | 4 +-- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/Processors/Transforms/DistinctTransform.cpp b/src/Processors/Transforms/DistinctTransform.cpp index 0108b7d6547..a5cd179ec6a 100644 --- a/src/Processors/Transforms/DistinctTransform.cpp +++ b/src/Processors/Transforms/DistinctTransform.cpp @@ -33,7 +33,7 @@ void DistinctTransform::buildFilter( Method & method, const ColumnRawPtrs & columns, IColumn::Filter & filter, - size_t rows, + const size_t rows, SetVariants & variants) const { typename Method::State state(columns, key_sizes, nullptr); @@ -53,11 +53,21 @@ void DistinctTransform::transform(Chunk & chunk) /// Convert to full column, because SetVariant for sparse column is not implemented. convertToFullIfSparse(chunk); - auto num_rows = chunk.getNumRows(); auto columns = chunk.detachColumns(); + /// Special case, - only const columns, return single row + if (unlikely(key_columns_pos.empty())) + { + for (auto & column : columns) + column = column->cut(0, 1); + + chunk.setColumns(std::move(columns), 1); + stopReading(); + return; + } + /// Stop reading if we already reach the limit. - if (no_more_rows || (limit_hint && data.getTotalRowCount() >= limit_hint)) + if (limit_hint && data.getTotalRowCount() >= limit_hint) { stopReading(); return; @@ -68,21 +78,11 @@ void DistinctTransform::transform(Chunk & chunk) for (auto pos : key_columns_pos) column_ptrs.emplace_back(columns[pos].get()); - if (column_ptrs.empty()) - { - /// Only constants. We need to return single row. - no_more_rows = true; - for (auto & column : columns) - column = column->cut(0, 1); - - chunk.setColumns(std::move(columns), 1); - return; - } - if (data.empty()) data.init(SetVariants::chooseMethod(column_ptrs, key_sizes)); const auto old_set_size = data.getTotalRowCount(); + const auto num_rows = chunk.getNumRows(); IColumn::Filter filter(num_rows); switch (data.type) diff --git a/src/Processors/Transforms/DistinctTransform.h b/src/Processors/Transforms/DistinctTransform.h index d80fdb5bc22..ac53243ea3f 100644 --- a/src/Processors/Transforms/DistinctTransform.h +++ b/src/Processors/Transforms/DistinctTransform.h @@ -25,9 +25,7 @@ private: ColumnNumbers key_columns_pos; SetVariants data; Sizes key_sizes; - UInt64 limit_hint; - - bool no_more_rows = false; + const UInt64 limit_hint; /// Restrictions on the maximum size of the output data. SizeLimits set_size_limits; From baa8b7c15809a248054a149dd319350f752744b8 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Sat, 30 Jul 2022 20:36:27 +0000 Subject: [PATCH 189/672] Add test: ordinary DISTINCT with all const columns --- tests/queries/0_stateless/00413_distinct.reference | 8 ++++++++ tests/queries/0_stateless/00413_distinct.sql | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/tests/queries/0_stateless/00413_distinct.reference b/tests/queries/0_stateless/00413_distinct.reference index 577a73349f0..9bbda183b0a 100644 --- a/tests/queries/0_stateless/00413_distinct.reference +++ b/tests/queries/0_stateless/00413_distinct.reference @@ -1,8 +1,16 @@ +-- { echoOn } +-- String field +SELECT Name FROM (SELECT DISTINCT Name FROM distinct) ORDER BY Name; Bill John Mary +-- Num field +SELECT Num FROM (SELECT DISTINCT Num FROM distinct) ORDER BY Num; 1 3 4 5 7 +-- all const columns +SELECT DISTINCT 1 as a, 2 as b FROM distinct; +1 2 diff --git a/tests/queries/0_stateless/00413_distinct.sql b/tests/queries/0_stateless/00413_distinct.sql index 8c6cb6fead2..79577cb0bda 100644 --- a/tests/queries/0_stateless/00413_distinct.sql +++ b/tests/queries/0_stateless/00413_distinct.sql @@ -14,9 +14,13 @@ INSERT INTO distinct (Num, Name) VALUES (7, 'Bill'); INSERT INTO distinct (Num, Name) VALUES (7, 'Mary'); INSERT INTO distinct (Num, Name) VALUES (7, 'John'); +-- { echoOn } -- String field SELECT Name FROM (SELECT DISTINCT Name FROM distinct) ORDER BY Name; -- Num field SELECT Num FROM (SELECT DISTINCT Num FROM distinct) ORDER BY Num; +-- all const columns +SELECT DISTINCT 1 as a, 2 as b FROM distinct; +-- { echoOff } DROP TABLE IF EXISTS distinct; From a30dbed6b8dba4c08f9a27b786d2cad59f66becf Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 31 Jul 2022 03:02:20 +0300 Subject: [PATCH 190/672] Update programs/local/LocalServer.cpp Co-authored-by: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> --- programs/local/LocalServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index de1f08dd5f5..75cc9d0ceb4 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -325,7 +325,7 @@ void LocalServer::setupUsers() access_control.setPlaintextPasswordAllowed(config().getBool("allow_plaintext_password", true)); if (config().has("config-file") || fs::exists("config.xml")) { - String config_path = config().getString("config-file",""); + String config_path = config().getString("config-file", ""); bool has_user_directories = config().has("user_directories"); const auto config_dir = std::filesystem::path{config_path}.remove_filename().string(); String users_config_path = config().getString("users_config",""); From fa9c3dcc4899227156d877cf9b676629103a537f Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 31 Jul 2022 03:02:27 +0300 Subject: [PATCH 191/672] Update programs/local/LocalServer.cpp Co-authored-by: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> --- programs/local/LocalServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index 75cc9d0ceb4..c0c8aa64b20 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -328,7 +328,7 @@ void LocalServer::setupUsers() String config_path = config().getString("config-file", ""); bool has_user_directories = config().has("user_directories"); const auto config_dir = std::filesystem::path{config_path}.remove_filename().string(); - String users_config_path = config().getString("users_config",""); + String users_config_path = config().getString("users_config", ""); if (users_config_path.empty() && !has_user_directories) users_config = getConfigurationFromXMLString(minimal_default_user_xml); else From 16a98d8eeff0820e653f37b5e6d488c2184508f3 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Sun, 31 Jul 2022 09:11:46 +0000 Subject: [PATCH 192/672] Add test for creating snapshot on exit --- .../configs/enable_keeper1.xml | 28 +++++++++++ .../configs/enable_keeper2.xml | 28 +++++++++++ .../test_keeper_snapshot_on_exit/test.py | 50 +++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 tests/integration/test_keeper_snapshot_on_exit/configs/enable_keeper1.xml create mode 100644 tests/integration/test_keeper_snapshot_on_exit/configs/enable_keeper2.xml create mode 100644 tests/integration/test_keeper_snapshot_on_exit/test.py diff --git a/tests/integration/test_keeper_snapshot_on_exit/configs/enable_keeper1.xml b/tests/integration/test_keeper_snapshot_on_exit/configs/enable_keeper1.xml new file mode 100644 index 00000000000..e4d5ae9aa82 --- /dev/null +++ b/tests/integration/test_keeper_snapshot_on_exit/configs/enable_keeper1.xml @@ -0,0 +1,28 @@ + + + 9181 + 1 + /var/lib/clickhouse/coordination/log + /var/lib/clickhouse/coordination/snapshots + true + + + 5000 + 10000 + trace + + + + + 1 + node1 + 9234 + + + 2 + node2 + 9234 + + + + diff --git a/tests/integration/test_keeper_snapshot_on_exit/configs/enable_keeper2.xml b/tests/integration/test_keeper_snapshot_on_exit/configs/enable_keeper2.xml new file mode 100644 index 00000000000..216e6905a56 --- /dev/null +++ b/tests/integration/test_keeper_snapshot_on_exit/configs/enable_keeper2.xml @@ -0,0 +1,28 @@ + + + 9181 + 2 + /var/lib/clickhouse/coordination/log + /var/lib/clickhouse/coordination/snapshots + false + + + 5000 + 10000 + trace + + + + + 1 + node1 + 9234 + + + 2 + node2 + 9234 + + + + diff --git a/tests/integration/test_keeper_snapshot_on_exit/test.py b/tests/integration/test_keeper_snapshot_on_exit/test.py new file mode 100644 index 00000000000..ad373011136 --- /dev/null +++ b/tests/integration/test_keeper_snapshot_on_exit/test.py @@ -0,0 +1,50 @@ +import pytest +from helpers.cluster import ClickHouseCluster +import os +from kazoo.client import KazooClient + +cluster = ClickHouseCluster(__file__) +CONFIG_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "configs") + +node1 = cluster.add_instance( + "node1", main_configs=["configs/enable_keeper1.xml"], stay_alive=True +) + +node2 = cluster.add_instance( + "node2", main_configs=["configs/enable_keeper2.xml"], stay_alive=True +) + +def get_fake_zk(node, timeout=30.0): + _fake_zk_instance = KazooClient( + hosts=cluster.get_instance_ip(node.name) + ":9181", timeout=timeout + ) + _fake_zk_instance.start() + return _fake_zk_instance + + +@pytest.fixture(scope="module") +def started_cluster(): + try: + cluster.start() + + yield cluster + + finally: + cluster.shutdown() + +def test_snapshot_on_exit(started_cluster): + zk_conn = get_fake_zk(node1); + + zk_conn.create("/some_path", b"some_data") + + node1.stop_clickhouse() + assert node1.contains_in_log("Created persistent snapshot") + + node1.start_clickhouse() + assert node1.contains_in_log("Loaded snapshot") + + node2.stop_clickhouse() + assert not node2.contains_in_log("Created persistent snapshot") + + node2.start_clickhouse() + assert node2.contains_in_log("No existing snapshots") From 0b1554c2e76a439918c535bc278848c206649350 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Sun, 31 Jul 2022 09:24:53 +0000 Subject: [PATCH 193/672] Automatic style fix --- tests/integration/test_keeper_snapshot_on_exit/test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_keeper_snapshot_on_exit/test.py b/tests/integration/test_keeper_snapshot_on_exit/test.py index ad373011136..1ca5888ab4d 100644 --- a/tests/integration/test_keeper_snapshot_on_exit/test.py +++ b/tests/integration/test_keeper_snapshot_on_exit/test.py @@ -14,6 +14,7 @@ node2 = cluster.add_instance( "node2", main_configs=["configs/enable_keeper2.xml"], stay_alive=True ) + def get_fake_zk(node, timeout=30.0): _fake_zk_instance = KazooClient( hosts=cluster.get_instance_ip(node.name) + ":9181", timeout=timeout @@ -32,8 +33,9 @@ def started_cluster(): finally: cluster.shutdown() + def test_snapshot_on_exit(started_cluster): - zk_conn = get_fake_zk(node1); + zk_conn = get_fake_zk(node1) zk_conn.create("/some_path", b"some_data") From 729d19fa4fcb74a17039292851aa1a4b1b7e5d6c Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Fri, 29 Jul 2022 12:30:40 +0000 Subject: [PATCH 194/672] Rename "splitted build" to "shared libraries build" in CI tools - The old name made sense for (dev option) "-DUSE_STATIC_LIBRARIES=0 -DSPLIT_SHARED_LIBRARIES=1 -DSPLIT_BINARY=1" but that was removed with #39520. - What still exists is "-DUSE_STATIC_LIBRARIES=0 -DSPLIT_SHARED_LIBRARIES=1" which does a shared library build --- docker/packager/packager | 16 ++++++++-------- tests/ci/build_check.py | 6 +++--- tests/ci/build_report_check.py | 8 ++++---- tests/ci/ci_config.py | 32 ++++++++++++++++---------------- tests/ci/report.py | 4 ++-- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docker/packager/packager b/docker/packager/packager index 16a56e9766f..3b9d181d3e0 100755 --- a/docker/packager/packager +++ b/docker/packager/packager @@ -100,12 +100,12 @@ def run_docker_image_with_env( subprocess.check_call(cmd, shell=True) -def is_release_build(build_type, package_type, sanitizer, split_binary): +def is_release_build(build_type, package_type, sanitizer, shared_libraries): return ( build_type == "" and package_type == "deb" and sanitizer == "" - and not split_binary + and not shared_libraries ) @@ -116,7 +116,7 @@ def parse_env_variables( package_type, cache, distcc_hosts, - split_binary, + shared_libraries, clang_tidy, version, author, @@ -202,7 +202,7 @@ def parse_env_variables( cmake_flags.append("-DCMAKE_INSTALL_PREFIX=/usr") cmake_flags.append("-DCMAKE_INSTALL_SYSCONFDIR=/etc") cmake_flags.append("-DCMAKE_INSTALL_LOCALSTATEDIR=/var") - if is_release_build(build_type, package_type, sanitizer, split_binary): + if is_release_build(build_type, package_type, sanitizer, shared_libraries): cmake_flags.append("-DSPLIT_DEBUG_SYMBOLS=ON") result.append("WITH_PERFORMANCE=1") if is_cross_arm: @@ -219,7 +219,7 @@ def parse_env_variables( if package_type == "coverity": result.append("COMBINED_OUTPUT=coverity") result.append('COVERITY_TOKEN="$COVERITY_TOKEN"') - elif split_binary: + elif shared_libraries: result.append("COMBINED_OUTPUT=shared_build") if sanitizer: @@ -264,7 +264,7 @@ def parse_env_variables( result.append("BINARY_OUTPUT=tests") cmake_flags.append("-DENABLE_TESTS=1") - if split_binary: + if shared_libraries: cmake_flags.append( "-DUSE_STATIC_LIBRARIES=0 -DSPLIT_SHARED_LIBRARIES=1" ) @@ -351,7 +351,7 @@ if __name__ == "__main__": default="", ) - parser.add_argument("--split-binary", action="store_true") + parser.add_argument("--shared-libraries", action="store_true") parser.add_argument("--clang-tidy", action="store_true") parser.add_argument("--cache", choices=("ccache", "distcc", ""), default="") parser.add_argument( @@ -404,7 +404,7 @@ if __name__ == "__main__": args.package_type, args.cache, args.distcc_hosts, - args.split_binary, + args.shared_libraries, args.clang_tidy, args.version, args.author, diff --git a/tests/ci/build_check.py b/tests/ci/build_check.py index 3976e2ba916..488fd1bbb34 100644 --- a/tests/ci/build_check.py +++ b/tests/ci/build_check.py @@ -39,7 +39,7 @@ def _can_export_binaries(build_config: BuildConfig) -> bool: return False if build_config["bundled"] != "bundled": return False - if build_config["splitted"] == "splitted": + if build_config["libraries"] == "shared": return False if build_config["sanitizer"] != "": return True @@ -68,8 +68,8 @@ def get_packager_cmd( cmd += f" --build-type={build_config['build_type']}" if build_config["sanitizer"]: cmd += f" --sanitizer={build_config['sanitizer']}" - if build_config["splitted"] == "splitted": - cmd += " --split-binary" + if build_config["libraries"] == "shared": + cmd += " --shared-libraries" if build_config["tidy"] == "enable": cmd += " --clang-tidy" diff --git a/tests/ci/build_report_check.py b/tests/ci/build_report_check.py index 4bb7a619b9f..4246673de3e 100644 --- a/tests/ci/build_report_check.py +++ b/tests/ci/build_report_check.py @@ -37,7 +37,7 @@ class BuildResult: build_type, sanitizer, bundled, - splitted, + libraries, status, elapsed_seconds, with_coverage, @@ -46,7 +46,7 @@ class BuildResult: self.build_type = build_type self.sanitizer = sanitizer self.bundled = bundled - self.splitted = splitted + self.libraries = libraries self.status = status self.elapsed_seconds = elapsed_seconds self.with_coverage = with_coverage @@ -91,7 +91,7 @@ def get_failed_report( build_type="unknown", sanitizer="unknown", bundled="unknown", - splitted="unknown", + libraries="unknown", status=message, elapsed_seconds=0, with_coverage=False, @@ -108,7 +108,7 @@ def process_report( build_type=build_config["build_type"], sanitizer=build_config["sanitizer"], bundled=build_config["bundled"], - splitted=build_config["splitted"], + libraries=build_config["libraries"], status="success" if build_report["status"] else "failure", elapsed_seconds=build_report["elapsed_seconds"], with_coverage=False, diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index 10db5d05ad4..c9d665d0e7f 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -14,7 +14,7 @@ CI_CONFIG = { "package_type": "deb", "static_binary_name": "amd64", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "additional_pkgs": True, "tidy": "disable", "with_coverage": False, @@ -25,7 +25,7 @@ CI_CONFIG = { "sanitizer": "", "package_type": "coverity", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, "official": False, @@ -37,7 +37,7 @@ CI_CONFIG = { "package_type": "deb", "static_binary_name": "aarch64", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "additional_pkgs": True, "tidy": "disable", "with_coverage": False, @@ -48,7 +48,7 @@ CI_CONFIG = { "sanitizer": "address", "package_type": "deb", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, }, @@ -58,7 +58,7 @@ CI_CONFIG = { "sanitizer": "undefined", "package_type": "deb", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, }, @@ -68,7 +68,7 @@ CI_CONFIG = { "sanitizer": "thread", "package_type": "deb", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, }, @@ -78,7 +78,7 @@ CI_CONFIG = { "sanitizer": "memory", "package_type": "deb", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, }, @@ -88,7 +88,7 @@ CI_CONFIG = { "sanitizer": "", "package_type": "deb", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, }, @@ -98,7 +98,7 @@ CI_CONFIG = { "sanitizer": "", "package_type": "binary", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, }, @@ -109,7 +109,7 @@ CI_CONFIG = { "package_type": "binary", "static_binary_name": "debug-amd64", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "enable", "with_coverage": False, }, @@ -119,7 +119,7 @@ CI_CONFIG = { "sanitizer": "", "package_type": "binary", "bundled": "bundled", - "splitted": "splitted", + "libraries": "shared", "tidy": "disable", "with_coverage": False, }, @@ -130,7 +130,7 @@ CI_CONFIG = { "package_type": "binary", "static_binary_name": "macos", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, }, @@ -140,7 +140,7 @@ CI_CONFIG = { "sanitizer": "", "package_type": "binary", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, }, @@ -151,7 +151,7 @@ CI_CONFIG = { "package_type": "binary", "static_binary_name": "freebsd", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, }, @@ -162,7 +162,7 @@ CI_CONFIG = { "package_type": "binary", "static_binary_name": "macos-aarch64", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, }, @@ -173,7 +173,7 @@ CI_CONFIG = { "package_type": "binary", "static_binary_name": "powerpc64le", "bundled": "bundled", - "splitted": "unsplitted", + "libraries": "static", "tidy": "disable", "with_coverage": False, }, diff --git a/tests/ci/report.py b/tests/ci/report.py index 83d89e628a2..d924be5885b 100644 --- a/tests/ci/report.py +++ b/tests/ci/report.py @@ -290,7 +290,7 @@ tr:hover td {{filter: brightness(95%);}} Build type Sanitizer Bundled -Splitted +Libraries Status Build log Build time @@ -335,7 +335,7 @@ def create_build_html_report( row += "{}".format("none") row += "{}".format(build_result.bundled) - row += "{}".format(build_result.splitted) + row += "{}".format(build_result.libraries) if build_result.status: style = _get_status_style(build_result.status) From 31550436e4b9a9e05017c09716214b10bec2043d Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Sun, 31 Jul 2022 14:38:13 +0200 Subject: [PATCH 195/672] try clang-15 for build with tsan --- docker/packager/binary/Dockerfile | 8 ++++---- docker/packager/packager | 2 +- tests/ci/ci_config.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/packager/binary/Dockerfile b/docker/packager/binary/Dockerfile index 91d1354a10c..9e7a5cfdfa4 100644 --- a/docker/packager/binary/Dockerfile +++ b/docker/packager/binary/Dockerfile @@ -119,18 +119,18 @@ ENV GOCACHE=/workdir/ RUN mkdir /workdir && chmod 777 /workdir WORKDIR /workdir -# FIXME: thread sanitizer is broken in clang-14, we have to build it with clang-13 +# NOTE: thread sanitizer is broken in clang-14, we have to build it with clang-15 # https://github.com/ClickHouse/ClickHouse/pull/39450 # https://github.com/google/sanitizers/issues/1540 # https://github.com/google/sanitizers/issues/1552 RUN export CODENAME="$(lsb_release --codename --short | tr 'A-Z' 'a-z')" \ - && echo "deb [trusted=yes] https://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-13 main" >> \ + && echo "deb [trusted=yes] https://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME} main" >> \ /etc/apt/sources.list.d/clang.list \ && apt-get update \ && apt-get install \ - clang-13 \ - clang-tidy-13 \ + clang-15 \ + clang-tidy-15 \ --yes --no-install-recommends \ && apt-get clean diff --git a/docker/packager/packager b/docker/packager/packager index 0b00bc4e9c0..e1fa14d3535 100755 --- a/docker/packager/packager +++ b/docker/packager/packager @@ -323,7 +323,7 @@ if __name__ == "__main__": parser.add_argument( "--compiler", choices=( - "clang-13", # For TSAN builds, see #39450 + "clang-15", # For TSAN builds, see #39450 "clang-14", "clang-14-darwin", "clang-14-darwin-aarch64", diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index 10db5d05ad4..80971d3f076 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -63,7 +63,7 @@ CI_CONFIG = { "with_coverage": False, }, "package_tsan": { - "compiler": "clang-13", + "compiler": "clang-15", "build_type": "", "sanitizer": "thread", "package_type": "deb", From 687f0fabfc162e1f61815930a5aebcf7a6428048 Mon Sep 17 00:00:00 2001 From: kssenii Date: Sun, 31 Jul 2022 20:16:15 +0300 Subject: [PATCH 196/672] Move some dependencies --- src/CMakeLists.txt | 3 +- src/Disks/DiskWebServer.cpp | 15 +- .../IO/ReadBufferFromAzureBlobStorage.cpp | 2 +- .../IO/ReadBufferFromAzureBlobStorage.h | 0 src/Disks/IO/ReadBufferFromRemoteFSGather.cpp | 142 ++++------------- src/Disks/IO/ReadBufferFromRemoteFSGather.h | 144 +++--------------- src/Disks/IO/ReadBufferFromWebServer.cpp | 2 +- src/Disks/IO/ReadBufferFromWebServer.h | 6 +- .../IO/WriteBufferFromAzureBlobStorage.cpp | 2 +- .../IO/WriteBufferFromAzureBlobStorage.h | 0 .../IO/WriteIndirectBufferFromRemoteFS.cpp | 6 - .../AzureBlobStorage/AzureObjectStorage.cpp | 25 ++- .../AzureBlobStorage/AzureObjectStorage.h | 3 + .../ObjectStorages/HDFS/HDFSObjectStorage.cpp | 14 +- .../ObjectStorages/S3/S3ObjectStorage.cpp | 24 ++- 15 files changed, 124 insertions(+), 264 deletions(-) rename src/{ => Disks}/IO/ReadBufferFromAzureBlobStorage.cpp (98%) rename src/{ => Disks}/IO/ReadBufferFromAzureBlobStorage.h (100%) rename src/{ => Disks}/IO/WriteBufferFromAzureBlobStorage.cpp (97%) rename src/{ => Disks}/IO/WriteBufferFromAzureBlobStorage.h (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 49acf55892a..26f3f44e110 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -488,7 +488,7 @@ if (TARGET ch_contrib::aws_s3) endif() if (TARGET ch_contrib::azure_sdk) - target_link_libraries (clickhouse_common_io PUBLIC ch_contrib::azure_sdk) + dbms_target_link_libraries (PRIVATE ch_contrib::azure_sdk) endif() if (TARGET ch_contrib::s2) @@ -611,4 +611,3 @@ if (ENABLE_TESTS) add_check(unit_tests_dbms) endif () - diff --git a/src/Disks/DiskWebServer.cpp b/src/Disks/DiskWebServer.cpp index b6cda8288d7..74e96cb5e30 100644 --- a/src/Disks/DiskWebServer.cpp +++ b/src/Disks/DiskWebServer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -172,7 +173,19 @@ std::unique_ptr DiskWebServer::readFile(const String & p StoredObjects objects; objects.emplace_back(remote_path, iter->second.size); - auto web_impl = std::make_unique(url, objects, getContext(), read_settings); + auto read_buffer_creator = + [this, read_settings] + (const std::string & path_, size_t read_until_position) -> std::shared_ptr + { + return std::make_shared( + fs::path(url) / path_, + getContext(), + read_settings, + /* use_external_buffer */true, + read_until_position); + }; + + auto web_impl = std::make_unique(std::move(read_buffer_creator), objects, read_settings); if (read_settings.remote_fs_method == RemoteFSReadMethod::threadpool) { diff --git a/src/IO/ReadBufferFromAzureBlobStorage.cpp b/src/Disks/IO/ReadBufferFromAzureBlobStorage.cpp similarity index 98% rename from src/IO/ReadBufferFromAzureBlobStorage.cpp rename to src/Disks/IO/ReadBufferFromAzureBlobStorage.cpp index 3e6581cd786..069d0aed312 100644 --- a/src/IO/ReadBufferFromAzureBlobStorage.cpp +++ b/src/Disks/IO/ReadBufferFromAzureBlobStorage.cpp @@ -2,7 +2,7 @@ #if USE_AZURE_BLOB_STORAGE -#include +#include #include #include #include diff --git a/src/IO/ReadBufferFromAzureBlobStorage.h b/src/Disks/IO/ReadBufferFromAzureBlobStorage.h similarity index 100% rename from src/IO/ReadBufferFromAzureBlobStorage.h rename to src/Disks/IO/ReadBufferFromAzureBlobStorage.h diff --git a/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp b/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp index 14614871185..3ac4ea07945 100644 --- a/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp +++ b/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp @@ -1,28 +1,13 @@ #include "ReadBufferFromRemoteFSGather.h" #include -#include - -#if USE_AWS_S3 -#include -#endif - -#if USE_AZURE_BLOB_STORAGE -#include -#endif - -#if USE_HDFS -#include -#endif #include #include -#include #include #include #include - -namespace fs = std::filesystem; +#include namespace DB @@ -33,97 +18,12 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -SeekableReadBufferPtr ReadBufferFromRemoteFSGather::createImplementationBuffer(const String & path, size_t file_size) -{ - if (!current_file_path.empty() && !with_cache && enable_cache_log) - { - appendFilesystemCacheLog(); - } - - current_file_path = path; - current_file_size = file_size; - total_bytes_read_from_current_file = 0; - - return createImplementationBufferImpl(path, file_size); -} - -#if USE_AWS_S3 -SeekableReadBufferPtr ReadBufferFromS3Gather::createImplementationBufferImpl(const String & path, size_t file_size) -{ - auto remote_file_reader_creator = [=, this]() - { - return std::make_unique( - client_ptr, - bucket, - path, - version_id, - max_single_read_retries, - settings, - /* use_external_buffer */true, - /* offset */0, - read_until_position, - /* restricted_seek */true); - }; - - if (with_cache) - { - return std::make_shared( - path, - settings.remote_fs_cache, - remote_file_reader_creator, - settings, - query_id, - read_until_position ? read_until_position : file_size); - } - - return remote_file_reader_creator(); -} -#endif - - -#if USE_AZURE_BLOB_STORAGE -SeekableReadBufferPtr ReadBufferFromAzureBlobStorageGather::createImplementationBufferImpl(const String & path, size_t /* file_size */) -{ - return std::make_unique( - blob_container_client, - path, - settings, - max_single_read_retries, - max_single_download_retries, - /* use_external_buffer */true, - read_until_position); -} -#endif - - -SeekableReadBufferPtr ReadBufferFromWebServerGather::createImplementationBufferImpl(const String & path, size_t /* file_size */) -{ - return std::make_unique( - fs::path(uri) / path, - context, - settings, - /* use_external_buffer */true, - read_until_position); -} - - -#if USE_HDFS -SeekableReadBufferPtr ReadBufferFromHDFSGather::createImplementationBufferImpl(const String & path, size_t /* file_size */) -{ - size_t begin_of_path = path.find('/', path.find("//") + 2); - auto hdfs_path = path.substr(begin_of_path); - auto hdfs_uri = path.substr(0, begin_of_path); - LOG_TEST(log, "HDFS uri: {}, path: {}", hdfs_path, hdfs_uri); - - return std::make_unique(hdfs_uri, hdfs_path, config, settings); -} -#endif - - ReadBufferFromRemoteFSGather::ReadBufferFromRemoteFSGather( + ReadBufferCreator && read_buffer_creator_, const StoredObjects & blobs_to_read_, const ReadSettings & settings_) : ReadBuffer(nullptr, 0) + , read_buffer_creator(std::move(read_buffer_creator_)) , blobs_to_read(blobs_to_read_) , settings(settings_) , query_id(CurrentThread::isInitialized() && CurrentThread::get().getQueryContext() != nullptr ? CurrentThread::getQueryId() : "") @@ -138,6 +38,33 @@ ReadBufferFromRemoteFSGather::ReadBufferFromRemoteFSGather( && (!IFileCache::isReadOnly() || settings.read_from_filesystem_cache_if_exists_otherwise_bypass_cache); } +SeekableReadBufferPtr ReadBufferFromRemoteFSGather::createImplementationBuffer(const String & path, size_t file_size) +{ + if (!current_file_path.empty() && !with_cache && enable_cache_log) + { + appendFilesystemCacheLog(); + } + + current_file_path = path; + current_file_size = file_size; + total_bytes_read_from_current_file = 0; + + size_t current_read_until_position = read_until_position ? read_until_position : file_size; + auto current_read_buffer_creator = [path, current_read_until_position, this]() { return read_buffer_creator(path, current_read_until_position); }; + + if (with_cache) + { + return std::make_shared( + path, + settings.remote_fs_cache, + current_read_buffer_creator, + settings, + query_id, + current_read_until_position); + } + + return current_read_buffer_creator(); +} void ReadBufferFromRemoteFSGather::appendFilesystemCacheLog() { @@ -156,7 +83,6 @@ void ReadBufferFromRemoteFSGather::appendFilesystemCacheLog() cache_log->add(elem); } - IAsynchronousReader::Result ReadBufferFromRemoteFSGather::readInto(char * data, size_t size, size_t offset, size_t ignore) { /** @@ -178,7 +104,6 @@ IAsynchronousReader::Result ReadBufferFromRemoteFSGather::readInto(char * data, return {0, 0}; } - void ReadBufferFromRemoteFSGather::initialize() { /// One clickhouse file can be split into multiple files in remote fs. @@ -206,7 +131,6 @@ void ReadBufferFromRemoteFSGather::initialize() current_buf = nullptr; } - bool ReadBufferFromRemoteFSGather::nextImpl() { /// Find first available buffer that fits to given offset. @@ -232,7 +156,6 @@ bool ReadBufferFromRemoteFSGather::nextImpl() return readImpl(); } - bool ReadBufferFromRemoteFSGather::moveToNextBuffer() { /// If there is no available buffers - nothing to read. @@ -247,7 +170,6 @@ bool ReadBufferFromRemoteFSGather::moveToNextBuffer() return true; } - bool ReadBufferFromRemoteFSGather::readImpl() { swap(*current_buf); @@ -294,13 +216,11 @@ bool ReadBufferFromRemoteFSGather::readImpl() return result; } - size_t ReadBufferFromRemoteFSGather::getFileOffsetOfBufferEnd() const { return file_offset_of_buffer_end; } - void ReadBufferFromRemoteFSGather::setReadUntilPosition(size_t position) { if (position != read_until_position) @@ -310,7 +230,6 @@ void ReadBufferFromRemoteFSGather::setReadUntilPosition(size_t position) } } - void ReadBufferFromRemoteFSGather::reset() { current_buf.reset(); @@ -321,7 +240,6 @@ String ReadBufferFromRemoteFSGather::getFileName() const return current_file_path; } - size_t ReadBufferFromRemoteFSGather::getFileSize() const { size_t size = 0; diff --git a/src/Disks/IO/ReadBufferFromRemoteFSGather.h b/src/Disks/IO/ReadBufferFromRemoteFSGather.h index e065c714e04..4ed61501281 100644 --- a/src/Disks/IO/ReadBufferFromRemoteFSGather.h +++ b/src/Disks/IO/ReadBufferFromRemoteFSGather.h @@ -6,12 +6,6 @@ #include #include -#if USE_AZURE_BLOB_STORAGE -#include -#endif - -namespace Aws { namespace S3 { class S3Client; } } - namespace Poco { class Logger; } namespace DB @@ -21,12 +15,15 @@ namespace DB * Remote disk might need to split one clickhouse file into multiple files in remote fs. * This class works like a proxy to allow transition from one file into multiple. */ -class ReadBufferFromRemoteFSGather : public ReadBuffer +class ReadBufferFromRemoteFSGather final : public ReadBuffer { friend class ReadIndirectBufferFromRemoteFS; public: + using ReadBufferCreator = std::function(const std::string & path, size_t read_until_position)>; + ReadBufferFromRemoteFSGather( + ReadBufferCreator && read_buffer_creator_, const StoredObjects & blobs_to_read_, const ReadSettings & settings_); @@ -50,8 +47,20 @@ public: size_t getImplementationBufferOffset() const; -protected: - virtual SeekableReadBufferPtr createImplementationBufferImpl(const String & path, size_t file_size) = 0; +private: + SeekableReadBufferPtr createImplementationBuffer(const String & path, size_t file_size); + + bool nextImpl() override; + + void initialize(); + + bool readImpl(); + + bool moveToNextBuffer(); + + void appendFilesystemCacheLog(); + + ReadBufferCreator read_buffer_creator; StoredObjects blobs_to_read; @@ -68,19 +77,6 @@ protected: Poco::Logger * log; -private: - SeekableReadBufferPtr createImplementationBuffer(const String & path, size_t file_size); - - bool nextImpl() override; - - void initialize(); - - bool readImpl(); - - bool moveToNextBuffer(); - - void appendFilesystemCacheLog(); - SeekableReadBufferPtr current_buf; size_t current_buf_idx = 0; @@ -99,108 +95,4 @@ private: bool enable_cache_log = false; }; - -#if USE_AWS_S3 -/// Reads data from S3 using stored paths in metadata. -class ReadBufferFromS3Gather final : public ReadBufferFromRemoteFSGather -{ -public: - ReadBufferFromS3Gather( - std::shared_ptr client_ptr_, - const String & bucket_, - const String & version_id_, - const StoredObjects & blobs_to_read_, - size_t max_single_read_retries_, - const ReadSettings & settings_) - : ReadBufferFromRemoteFSGather(blobs_to_read_, settings_) - , client_ptr(std::move(client_ptr_)) - , bucket(bucket_) - , version_id(version_id_) - , max_single_read_retries(max_single_read_retries_) - { - } - - SeekableReadBufferPtr createImplementationBufferImpl(const String & path, size_t file_size) override; - -private: - std::shared_ptr client_ptr; - String bucket; - String version_id; - UInt64 max_single_read_retries; -}; -#endif - - -#if USE_AZURE_BLOB_STORAGE -/// Reads data from AzureBlob Storage using paths stored in metadata. -class ReadBufferFromAzureBlobStorageGather final : public ReadBufferFromRemoteFSGather -{ -public: - ReadBufferFromAzureBlobStorageGather( - std::shared_ptr blob_container_client_, - const StoredObjects & blobs_to_read_, - size_t max_single_read_retries_, - size_t max_single_download_retries_, - const ReadSettings & settings_) - : ReadBufferFromRemoteFSGather(blobs_to_read_, settings_) - , blob_container_client(blob_container_client_) - , max_single_read_retries(max_single_read_retries_) - , max_single_download_retries(max_single_download_retries_) - { - } - - SeekableReadBufferPtr createImplementationBufferImpl(const String & path, size_t file_size) override; - -private: - std::shared_ptr blob_container_client; - size_t max_single_read_retries; - size_t max_single_download_retries; -}; -#endif - - -class ReadBufferFromWebServerGather final : public ReadBufferFromRemoteFSGather -{ -public: - ReadBufferFromWebServerGather( - const String & uri_, - const StoredObjects & blobs_to_read_, - ContextPtr context_, - const ReadSettings & settings_) - : ReadBufferFromRemoteFSGather(blobs_to_read_, settings_) - , uri(uri_) - , context(context_) - { - } - - SeekableReadBufferPtr createImplementationBufferImpl(const String & path, size_t file_size) override; - -private: - String uri; - ContextPtr context; -}; - - -#if USE_HDFS -/// Reads data from HDFS using stored paths in metadata. -class ReadBufferFromHDFSGather final : public ReadBufferFromRemoteFSGather -{ -public: - ReadBufferFromHDFSGather( - const Poco::Util::AbstractConfiguration & config_, - const StoredObjects & blobs_to_read_, - const ReadSettings & settings_) - : ReadBufferFromRemoteFSGather(blobs_to_read_, settings_) - , config(config_) - { - } - - SeekableReadBufferPtr createImplementationBufferImpl(const String & path, size_t file_size) override; - -private: - const Poco::Util::AbstractConfiguration & config; -}; - -#endif - } diff --git a/src/Disks/IO/ReadBufferFromWebServer.cpp b/src/Disks/IO/ReadBufferFromWebServer.cpp index b9614601a72..5e9d4f63807 100644 --- a/src/Disks/IO/ReadBufferFromWebServer.cpp +++ b/src/Disks/IO/ReadBufferFromWebServer.cpp @@ -27,7 +27,7 @@ ReadBufferFromWebServer::ReadBufferFromWebServer( const ReadSettings & settings_, bool use_external_buffer_, size_t read_until_position_) - : SeekableReadBuffer(nullptr, 0) + : ReadBufferFromFileBase(settings_.remote_fs_buffer_size, nullptr, 0) , log(&Poco::Logger::get("ReadBufferFromWebServer")) , context(context_) , url(url_) diff --git a/src/Disks/IO/ReadBufferFromWebServer.h b/src/Disks/IO/ReadBufferFromWebServer.h index 1e4219d53ee..99a9df9ebcb 100644 --- a/src/Disks/IO/ReadBufferFromWebServer.h +++ b/src/Disks/IO/ReadBufferFromWebServer.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include @@ -15,7 +15,7 @@ namespace DB * * Usage: ReadIndirectBufferFromRemoteFS -> SeekAvoidingReadBuffer -> ReadBufferFromWebServer -> ReadWriteBufferFromHTTP. */ -class ReadBufferFromWebServer : public SeekableReadBuffer +class ReadBufferFromWebServer : public ReadBufferFromFileBase { public: explicit ReadBufferFromWebServer( @@ -33,6 +33,8 @@ public: size_t getFileOffsetOfBufferEnd() const override { return offset; } + String getFileName() const override { return url; } + private: std::unique_ptr initialize(); diff --git a/src/IO/WriteBufferFromAzureBlobStorage.cpp b/src/Disks/IO/WriteBufferFromAzureBlobStorage.cpp similarity index 97% rename from src/IO/WriteBufferFromAzureBlobStorage.cpp rename to src/Disks/IO/WriteBufferFromAzureBlobStorage.cpp index bc7b505cd91..8130d742ee5 100644 --- a/src/IO/WriteBufferFromAzureBlobStorage.cpp +++ b/src/Disks/IO/WriteBufferFromAzureBlobStorage.cpp @@ -2,7 +2,7 @@ #if USE_AZURE_BLOB_STORAGE -#include +#include #include #include #include diff --git a/src/IO/WriteBufferFromAzureBlobStorage.h b/src/Disks/IO/WriteBufferFromAzureBlobStorage.h similarity index 100% rename from src/IO/WriteBufferFromAzureBlobStorage.h rename to src/Disks/IO/WriteBufferFromAzureBlobStorage.h diff --git a/src/Disks/IO/WriteIndirectBufferFromRemoteFS.cpp b/src/Disks/IO/WriteIndirectBufferFromRemoteFS.cpp index bf99934dd73..a909e8f109c 100644 --- a/src/Disks/IO/WriteIndirectBufferFromRemoteFS.cpp +++ b/src/Disks/IO/WriteIndirectBufferFromRemoteFS.cpp @@ -1,11 +1,5 @@ #include "WriteIndirectBufferFromRemoteFS.h" -#include -#include -#include -#include - - namespace DB { diff --git a/src/Disks/ObjectStorages/AzureBlobStorage/AzureObjectStorage.cpp b/src/Disks/ObjectStorages/AzureBlobStorage/AzureObjectStorage.cpp index 37fbc0fb05c..1726f4eeed7 100644 --- a/src/Disks/ObjectStorages/AzureBlobStorage/AzureObjectStorage.cpp +++ b/src/Disks/ObjectStorages/AzureBlobStorage/AzureObjectStorage.cpp @@ -3,8 +3,8 @@ #if USE_AZURE_BLOB_STORAGE #include -#include -#include +#include +#include #include #include @@ -79,11 +79,24 @@ std::unique_ptr AzureObjectStorage::readObjects( /// NOL { ReadSettings disk_read_settings = patchSettings(read_settings); auto settings_ptr = settings.get(); - auto reader_impl = std::make_unique( - client.get(), + + auto read_buffer_creator = + [this, settings_ptr, disk_read_settings] + (const std::string & path, size_t read_until_position) -> std::shared_ptr + { + return std::make_unique( + client.get(), + path, + disk_read_settings, + settings_ptr->max_single_read_retries, + settings_ptr->max_single_download_retries, + /* use_external_buffer */true, + read_until_position); + }; + + auto reader_impl = std::make_unique( + std::move(read_buffer_creator), objects, - settings_ptr->max_single_read_retries, - settings_ptr->max_single_download_retries, disk_read_settings); if (disk_read_settings.remote_fs_method == RemoteFSReadMethod::threadpool) diff --git a/src/Disks/ObjectStorages/AzureBlobStorage/AzureObjectStorage.h b/src/Disks/ObjectStorages/AzureBlobStorage/AzureObjectStorage.h index 34b3d86b355..8564e2dcd36 100644 --- a/src/Disks/ObjectStorages/AzureBlobStorage/AzureObjectStorage.h +++ b/src/Disks/ObjectStorages/AzureBlobStorage/AzureObjectStorage.h @@ -12,6 +12,9 @@ #include #include +#if USE_AZURE_BLOB_STORAGE +#include +#endif namespace DB { diff --git a/src/Disks/ObjectStorages/HDFS/HDFSObjectStorage.cpp b/src/Disks/ObjectStorages/HDFS/HDFSObjectStorage.cpp index 4ffbf5b2ceb..78bbed21e39 100644 --- a/src/Disks/ObjectStorages/HDFS/HDFSObjectStorage.cpp +++ b/src/Disks/ObjectStorages/HDFS/HDFSObjectStorage.cpp @@ -61,7 +61,19 @@ std::unique_ptr HDFSObjectStorage::readObjects( /// NOLI std::optional, std::optional) const { - auto hdfs_impl = std::make_unique(config, objects, patchSettings(read_settings)); + auto disk_read_settings = patchSettings(read_settings); + auto read_buffer_creator = + [this, disk_read_settings] + (const std::string & path, size_t /* read_until_position */) -> std::shared_ptr + { + size_t begin_of_path = path.find('/', path.find("//") + 2); + auto hdfs_path = path.substr(begin_of_path); + auto hdfs_uri = path.substr(0, begin_of_path); + + return std::make_unique(hdfs_uri, hdfs_path, config, disk_read_settings); + }; + + auto hdfs_impl = std::make_unique(std::move(read_buffer_creator), objects, disk_read_settings); auto buf = std::make_unique(std::move(hdfs_impl)); return std::make_unique(std::move(buf), settings->min_bytes_for_seek); } diff --git a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp index d36bf655c02..25dafac4120 100644 --- a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp +++ b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp @@ -144,12 +144,26 @@ std::unique_ptr S3ObjectStorage::readObjects( /// NOLINT auto settings_ptr = s3_settings.get(); - auto s3_impl = std::make_unique( - client.get(), - bucket, - version_id, + auto read_buffer_creator = + [this, settings_ptr, disk_read_settings] + (const std::string & path, size_t read_until_position) -> std::shared_ptr + { + return std::make_shared( + client.get(), + bucket, + path, + version_id, + settings_ptr->s3_settings.max_single_read_retries, + disk_read_settings, + /* use_external_buffer */true, + /* offset */0, + read_until_position, + /* restricted_seek */true); + }; + + auto s3_impl = std::make_unique( + std::move(read_buffer_creator), objects, - settings_ptr->s3_settings.max_single_read_retries, disk_read_settings); if (read_settings.remote_fs_method == RemoteFSReadMethod::threadpool) From 3e627e2861e08ac511435dc79e78681f97486bc3 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 13 Jul 2022 16:29:22 +0300 Subject: [PATCH 197/672] Add profile events for fsync The following new provile events had been added: - FileSync - Number of times the F_FULLFSYNC/fsync/fdatasync function was called for files. - DirectorySync - Number of times the F_FULLFSYNC/fsync/fdatasync function was called for directories. - FileSyncElapsedMicroseconds - Total time spent waiting for F_FULLFSYNC/fsync/fdatasync syscall for files. - DirectorySyncElapsedMicroseconds - Total time spent waiting for F_FULLFSYNC/fsync/fdatasync syscall for directories. v2: rewrite test to sh with retries Signed-off-by: Azat Khuzhin --- src/Common/ProfileEvents.cpp | 4 + src/Dictionaries/SSDCacheDictionaryStorage.h | 6 ++ src/Disks/LocalDirectorySyncGuard.cpp | 14 ++++ src/IO/WriteBufferFromFileDescriptor.cpp | 8 ++ .../02361_fsync_profile_events.reference | 0 .../0_stateless/02361_fsync_profile_events.sh | 73 +++++++++++++++++++ 6 files changed, 105 insertions(+) create mode 100644 tests/queries/0_stateless/02361_fsync_profile_events.reference create mode 100755 tests/queries/0_stateless/02361_fsync_profile_events.sh diff --git a/src/Common/ProfileEvents.cpp b/src/Common/ProfileEvents.cpp index 580e5f94952..453ed9ec37c 100644 --- a/src/Common/ProfileEvents.cpp +++ b/src/Common/ProfileEvents.cpp @@ -25,6 +25,10 @@ M(WriteBufferFromFileDescriptorWrite, "Number of writes (write/pwrite) to a file descriptor. Does not include sockets.") \ M(WriteBufferFromFileDescriptorWriteFailed, "Number of times the write (write/pwrite) to a file descriptor have failed.") \ M(WriteBufferFromFileDescriptorWriteBytes, "Number of bytes written to file descriptors. If the file is compressed, this will show compressed data size.") \ + M(FileSync, "Number of times the F_FULLFSYNC/fsync/fdatasync function was called for files.") \ + M(DirectorySync, "Number of times the F_FULLFSYNC/fsync/fdatasync function was called for directories.") \ + M(FileSyncElapsedMicroseconds, "Total time spent waiting for F_FULLFSYNC/fsync/fdatasync syscall for files.") \ + M(DirectorySyncElapsedMicroseconds, "Total time spent waiting for F_FULLFSYNC/fsync/fdatasync syscall for directories.") \ M(ReadCompressedBytes, "Number of bytes (the number of bytes before decompression) read from compressed sources (files, network).") \ M(CompressedReadBufferBlocks, "Number of compressed blocks (the blocks of data that are compressed independent of each other) read from compressed sources (files, network).") \ M(CompressedReadBufferBytes, "Number of uncompressed bytes (the number of bytes after decompression) read from compressed sources (files, network).") \ diff --git a/src/Dictionaries/SSDCacheDictionaryStorage.h b/src/Dictionaries/SSDCacheDictionaryStorage.h index 459c4c44668..5f73352a4c9 100644 --- a/src/Dictionaries/SSDCacheDictionaryStorage.h +++ b/src/Dictionaries/SSDCacheDictionaryStorage.h @@ -34,6 +34,8 @@ namespace ProfileEvents extern const Event AIOWriteBytes; extern const Event AIORead; extern const Event AIOReadBytes; + extern const Event FileSync; + extern const Event FileSyncElapsedMicroseconds; } namespace DB @@ -544,6 +546,9 @@ public: file_path, std::to_string(bytes_written)); + ProfileEvents::increment(ProfileEvents::FileSync); + + Stopwatch watch; #if defined(OS_DARWIN) if (::fsync(file.fd) < 0) throwFromErrnoWithPath("Cannot fsync " + file_path, file_path, ErrorCodes::CANNOT_FSYNC); @@ -551,6 +556,7 @@ public: if (::fdatasync(file.fd) < 0) throwFromErrnoWithPath("Cannot fdatasync " + file_path, file_path, ErrorCodes::CANNOT_FSYNC); #endif + ProfileEvents::increment(ProfileEvents::FileSyncElapsedMicroseconds, watch.elapsedMicroseconds()); current_block_index += buffer_size_in_blocks; diff --git a/src/Disks/LocalDirectorySyncGuard.cpp b/src/Disks/LocalDirectorySyncGuard.cpp index 2610cd7c37f..843d1c1ed43 100644 --- a/src/Disks/LocalDirectorySyncGuard.cpp +++ b/src/Disks/LocalDirectorySyncGuard.cpp @@ -1,6 +1,8 @@ #include +#include #include #include +#include #include // O_RDWR /// OSX does not have O_DIRECTORY @@ -8,6 +10,12 @@ #define O_DIRECTORY O_RDWR #endif +namespace ProfileEvents +{ + extern const Event DirectorySync; + extern const Event DirectorySyncElapsedMicroseconds; +} + namespace DB { @@ -29,8 +37,12 @@ LocalDirectorySyncGuard::LocalDirectorySyncGuard(const String & full_path) LocalDirectorySyncGuard::~LocalDirectorySyncGuard() { + ProfileEvents::increment(ProfileEvents::DirectorySync); + try { + Stopwatch watch; + #if defined(OS_DARWIN) if (fcntl(fd, F_FULLFSYNC, 0)) throwFromErrno("Cannot fcntl(F_FULLFSYNC)", ErrorCodes::CANNOT_FSYNC); @@ -40,6 +52,8 @@ LocalDirectorySyncGuard::~LocalDirectorySyncGuard() #endif if (-1 == ::close(fd)) throw Exception("Cannot close file", ErrorCodes::CANNOT_CLOSE_FILE); + + ProfileEvents::increment(ProfileEvents::DirectorySyncElapsedMicroseconds, watch.elapsedMicroseconds()); } catch (...) { diff --git a/src/IO/WriteBufferFromFileDescriptor.cpp b/src/IO/WriteBufferFromFileDescriptor.cpp index 25acb71596a..ba49c16c11f 100644 --- a/src/IO/WriteBufferFromFileDescriptor.cpp +++ b/src/IO/WriteBufferFromFileDescriptor.cpp @@ -18,6 +18,8 @@ namespace ProfileEvents extern const Event WriteBufferFromFileDescriptorWriteFailed; extern const Event WriteBufferFromFileDescriptorWriteBytes; extern const Event DiskWriteElapsedMicroseconds; + extern const Event FileSync; + extern const Event FileSyncElapsedMicroseconds; } namespace CurrentMetrics @@ -113,12 +115,18 @@ void WriteBufferFromFileDescriptor::sync() /// If buffer has pending data - write it. next(); + ProfileEvents::increment(ProfileEvents::FileSync); + + Stopwatch watch; + /// Request OS to sync data with storage medium. #if defined(OS_DARWIN) int res = ::fsync(fd); #else int res = ::fdatasync(fd); #endif + ProfileEvents::increment(ProfileEvents::FileSyncElapsedMicroseconds, watch.elapsedMicroseconds()); + if (-1 == res) throwFromErrnoWithPath("Cannot fsync " + getFileName(), getFileName(), ErrorCodes::CANNOT_FSYNC); } diff --git a/tests/queries/0_stateless/02361_fsync_profile_events.reference b/tests/queries/0_stateless/02361_fsync_profile_events.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02361_fsync_profile_events.sh b/tests/queries/0_stateless/02361_fsync_profile_events.sh new file mode 100755 index 00000000000..d54da9a49e5 --- /dev/null +++ b/tests/queries/0_stateless/02361_fsync_profile_events.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# Tags: no-s3-storage +# Tag no-s3-storage: s3 does not have fsync + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT -nm -q " + drop table if exists data_fsync_pe; + + create table data_fsync_pe (key Int) engine=MergeTree() + order by key + settings + min_rows_for_wide_part=2, + fsync_after_insert=1, + fsync_part_directory=1; +" + +ret=1 +# Retry in case of fsync/fdatasync was too fast +# (FileSyncElapsedMicroseconds/DirectorySyncElapsedMicroseconds was 0) +for i in {1..100}; do + query_id="insert-$i-$CLICKHOUSE_DATABASE" + + $CLICKHOUSE_CLIENT --query_id "$query_id" -q "insert into data_fsync_pe values (1)" + + read -r FileSync FileOpen DirectorySync FileSyncElapsedMicroseconds DirectorySyncElapsedMicroseconds <<<"$( + $CLICKHOUSE_CLIENT -nm --param_query_id "$query_id" -q " + system flush logs; + + select + ProfileEvents['FileSync'], + ProfileEvents['FileOpen'], + ProfileEvents['DirectorySync'], + ProfileEvents['FileSyncElapsedMicroseconds']>0, + ProfileEvents['DirectorySyncElapsedMicroseconds']>0 + from system.query_log + where + event_date >= yesterday() and + current_database = currentDatabase() and + query_id = {query_id:String} and + type = 'QueryFinish'; + ")" + + # Non retriable errors + if [[ $FileSync -ne 7 ]]; then + exit 2 + fi + # Check that all files was synced + if [[ $FileSync -ne $FileOpen ]]; then + exit 3 + fi + if [[ $DirectorySync -ne 2 ]]; then + exit 4 + fi + + # Retriable errors + if [[ $FileSyncElapsedMicroseconds -eq 0 ]]; then + continue + fi + if [[ $DirectorySyncElapsedMicroseconds -eq 0 ]]; then + continue + fi + + # Everything is OK + ret=0 + break +done + +$CLICKHOUSE_CLIENT -q "drop table data_fsync_pe" + +exit $ret From 3731a415e6af99858b46557a69c49a8704bb8017 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Sun, 31 Jul 2022 20:07:33 -0400 Subject: [PATCH 198/672] run resulting executable with execvp --- .../decompressor.cpp | 81 ++++++------------- 1 file changed, 26 insertions(+), 55 deletions(-) diff --git a/utils/self-extracting-executable/decompressor.cpp b/utils/self-extracting-executable/decompressor.cpp index 679dc144f13..1e19d47d47d 100644 --- a/utils/self-extracting-executable/decompressor.cpp +++ b/utils/self-extracting-executable/decompressor.cpp @@ -295,45 +295,30 @@ int decompressFiles(int input_fd, char * path, char * name, bool & have_compress return 0; } -/// Copy particular part of command and update shift -void fill(char * dest, char * source, size_t length, size_t& shift) +int do_system(const char * cmd) { - memcpy(dest + shift, source, length); - shift += length; -} - -/// Set command to `mv filename.decompressed filename && filename agrs...` -void fillCommand(char command[], int argc, char * argv[], size_t length) -{ - memset(command, '\0', 3 + strlen(argv[0]) + 14 + strlen(argv[0]) + 4 + strlen(argv[0]) + length + argc); - - /// position in command - size_t shift = 0; - - /// Support variables to create command - char mv[] = "mv "; - char decompressed[] = ".decompressed "; - char add_command[] = " && "; - char space[] = " "; - - fill(command, mv, 3, shift); - fill(command, argv[0], strlen(argv[0]), shift); - fill(command, decompressed, 14, shift); - fill(command, argv[0], strlen(argv[0]), shift); - fill(command, add_command, 4, shift); - fill(command, argv[0], strlen(argv[0]), shift); - fill(command, space, 1, shift); - - /// forward all arguments - for (int i = 1; i < argc; ++i) + int ret = system(cmd); + if (ret == -1) { - fill(command, argv[i], strlen(argv[i]), shift); - if (i != argc - 1) - fill(command, space, 1, shift); + perror(nullptr); + return 1; } + + if (WIFEXITED(ret) && WEXITSTATUS(ret)) + { + std::cerr << "Command [" << cmd << "] exited with code " << WEXITSTATUS(ret) << std::endl; + return 1; + } + else if (WIFSIGNALED(ret)) + { + std::cerr << "Command [" << cmd << "] killed by signal " << WTERMSIG(ret) << std::endl; + return 1; + } + + return 0; } -int main(int argc, char* argv[]) +int main(int/* argc*/, char* argv[]) { char file_path[strlen(argv[0]) + 1]; memset(file_path, 0, sizeof(file_path)); @@ -371,12 +356,6 @@ int main(int argc, char* argv[]) if (0 != close(input_fd)) perror(nullptr); - /// According to documentation `mv` will rename file if it - /// doesn't move to other directory. - /// Sometimes `rename` doesn't exist by default and - /// `rename.ul` is set instead. It will lead to errors - /// that can be easily avoided with help of `mv` - if (!have_compressed_analoge) { printf("No target executable - decompression only was performed.\n"); @@ -387,22 +366,14 @@ int main(int argc, char* argv[]) } else { - /// move decompressed file instead of this binary and apply command - char bash[] = "sh"; - char executable[] = "-c"; + const char * const cmd_fmt = "mv %s %s.delete && cp %s.decompressed %s && rm %s.delete %s.decompressed"; + int cmd_len = snprintf(nullptr, 0, cmd_fmt, argv[0], argv[0], argv[0], argv[0], argv[0], argv[0]); + char command[cmd_len + 1]; + snprintf(command, cmd_len + 1, cmd_fmt, argv[0], argv[0], argv[0], argv[0], argv[0], argv[0]); + if (do_system(command)) + return 1; - /// length of forwarded args - size_t length = 0; - for (int i = 1; i < argc; ++i) - length += strlen(argv[i]); - - /// mv filename.decompressed filename && filename agrs... - char command[3 + strlen(argv[0]) + 14 + strlen(argv[0]) + 4 + strlen(argv[0]) + length + argc]; - fillCommand(command, argc, argv, length); - - /// replace file and call executable - char * newargv[] = { bash, executable, command, nullptr }; - execvp(bash, newargv); + execvp(argv[0], argv); /// This part of code will be reached only if error happened perror(nullptr); From b05be56eefa2f7de09aaa9cbd2ccf3dd394489bf Mon Sep 17 00:00:00 2001 From: Wangyang Guo Date: Mon, 1 Aug 2022 10:15:49 +0800 Subject: [PATCH 199/672] ColumnVector: naming style fix --- src/Columns/ColumnVector.cpp | 34 +++++++++++------------ src/Columns/tests/gtest_column_vector.cpp | 25 +++++++++-------- src/Common/TargetSpecific.h | 2 +- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/Columns/ColumnVector.cpp b/src/Columns/ColumnVector.cpp index 77b736fe054..31dea90cc80 100644 --- a/src/Columns/ColumnVector.cpp +++ b/src/Columns/ColumnVector.cpp @@ -479,17 +479,17 @@ void ColumnVector::insertRangeFrom(const IColumn & src, size_t start, size_t DECLARE_AVX512VBMI2_SPECIFIC_CODE( -template +template inline void compressStoreAVX512(const void *src, void *dst, const UInt64 mask) { __m512i vsrc = _mm512_loadu_si512(src); - if (ELEMENT_SIZE == 1) + if constexpr (ELEMENT_WIDTH == 1) _mm512_mask_compressstoreu_epi8(dst, static_cast<__mmask64>(mask), vsrc); - else if (ELEMENT_SIZE == 2) + else if constexpr (ELEMENT_WIDTH == 2) _mm512_mask_compressstoreu_epi16(dst, static_cast<__mmask32>(mask), vsrc); - else if (ELEMENT_SIZE == 4) + else if constexpr (ELEMENT_WIDTH == 4) _mm512_mask_compressstoreu_epi32(dst, static_cast<__mmask16>(mask), vsrc); - else if (ELEMENT_SIZE == 8) + else if constexpr (ELEMENT_WIDTH == 8) _mm512_mask_compressstoreu_epi64(dst, static_cast<__mmask8>(mask), vsrc); } @@ -497,9 +497,9 @@ template inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_aligned, const T *& data_pos, Container & res_data) { static constexpr size_t VEC_LEN = 64; /// AVX512 vector length - 64 bytes - static constexpr size_t ELEMENT_SIZE = sizeof(T); - static constexpr size_t ELEMENT_PER_VEC = VEC_LEN / ELEMENT_SIZE; - static constexpr UInt64 kmask = 0xffffffffffffffff >> (64 - ELEMENT_PER_VEC); + static constexpr size_t ELEMENT_WIDTH = sizeof(T); + static constexpr size_t ELEMENTS_PER_VEC = VEC_LEN / ELEMENT_WIDTH; + static constexpr UInt64 KMASK = 0xffffffffffffffff >> (64 - ELEMENTS_PER_VEC); size_t current_offset = res_data.size(); size_t reserve_size = res_data.size(); @@ -519,7 +519,7 @@ inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_ali if (0xffffffffffffffff == mask) { - for (size_t i = 0; i < SIMD_BYTES; i += ELEMENT_PER_VEC) + for (size_t i = 0; i < SIMD_BYTES; i += ELEMENTS_PER_VEC) _mm512_storeu_si512(reinterpret_cast(&res_data[current_offset + i]), _mm512_loadu_si512(reinterpret_cast(data_pos + i))); current_offset += SIMD_BYTES; @@ -528,15 +528,15 @@ inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_ali { if (mask) { - for (size_t i = 0; i < SIMD_BYTES; i += ELEMENT_PER_VEC) + for (size_t i = 0; i < SIMD_BYTES; i += ELEMENTS_PER_VEC) { - compressStoreAVX512(reinterpret_cast(data_pos + i), - reinterpret_cast(&res_data[current_offset]), mask & kmask); - current_offset += std::popcount(mask & kmask); - /// prepare mask for next iter, if ELEMENT_PER_VEC = 64, no next iter - if (ELEMENT_PER_VEC < 64) + compressStoreAVX512(reinterpret_cast(data_pos + i), + reinterpret_cast(&res_data[current_offset]), mask & KMASK); + current_offset += std::popcount(mask & KMASK); + /// prepare mask for next iter, if ELEMENTS_PER_VEC = 64, no next iter + if (ELEMENTS_PER_VEC < 64) { - mask >>= ELEMENT_PER_VEC; + mask >>= ELEMENTS_PER_VEC; } } } @@ -576,7 +576,7 @@ ColumnPtr ColumnVector::filter(const IColumn::Filter & filt, ssize_t result_s const UInt8 * filt_end_aligned = filt_pos + size / SIMD_BYTES * SIMD_BYTES; #if USE_MULTITARGET_CODE - if (sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8) + if constexpr (sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8) { if (isArchSupported(TargetArch::AVX512VBMI2)) TargetSpecific::AVX512VBMI2::doFilterAligned(filt_pos, filt_end_aligned, data_pos, res_data); diff --git a/src/Columns/tests/gtest_column_vector.cpp b/src/Columns/tests/gtest_column_vector.cpp index a7d8de3fc00..5a8fd1fad60 100644 --- a/src/Columns/tests/gtest_column_vector.cpp +++ b/src/Columns/tests/gtest_column_vector.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -10,8 +11,8 @@ static pcg64 rng(randomSeed()); static constexpr int error_code = 12345; static constexpr size_t TEST_RUNS = 500; static constexpr size_t MAX_ROWS = 10000; -static constexpr size_t filter_ratios[] = {1, 2, 5, 11, 32, 64, 100, 1000}; -static constexpr size_t K = sizeof(filter_ratios) / sizeof(filter_ratios[0]); +static const std::vector filter_ratios = {1, 2, 5, 11, 32, 64, 100, 1000}; +static const size_t K = filter_ratios.size(); template static MutableColumnPtr createColumn(size_t n) @@ -47,15 +48,15 @@ bool checkFilter(const PaddedPODArray &flit, const IColumn & src, const I template static void testFilter() { - auto test_case = [&](size_t n, size_t m) + auto test_case = [&](size_t rows, size_t filter_ratio) { - auto vector_column = createColumn(n); - PaddedPODArray flit(n); - for (size_t i = 0; i < n; ++i) - flit[i] = rng() % m == 0; - auto filt_column = vector_column->filter(flit, -1); + auto vector_column = createColumn(rows); + PaddedPODArray flit(rows); + for (size_t i = 0; i < rows; ++i) + flit[i] = rng() % filter_ratio == 0; + auto res_column = vector_column->filter(flit, -1); - if (!checkFilter(flit, *vector_column, *filt_column)) + if (!checkFilter(flit, *vector_column, *res_column)) throw Exception(error_code, "VectorColumn filter failure, type: {}", typeid(T).name()); }; @@ -63,10 +64,10 @@ static void testFilter() { for (size_t i = 0; i < TEST_RUNS; ++i) { - size_t n = rng() % MAX_ROWS + 1; - size_t m = filter_ratios[rng() % K]; + size_t rows = rng() % MAX_ROWS + 1; + size_t filter_ratio = filter_ratios[rng() % K]; - test_case(n, m); + test_case(rows, filter_ratio); } } catch (const Exception & e) diff --git a/src/Common/TargetSpecific.h b/src/Common/TargetSpecific.h index 4cd47bb1d3a..250642f6ee4 100644 --- a/src/Common/TargetSpecific.h +++ b/src/Common/TargetSpecific.h @@ -31,7 +31,7 @@ * int funcImpl() { * return 2; * } - * ) // DECLARE_DEFAULT_CODE + * ) // DECLARE_AVX2_SPECIFIC_CODE * * int func() { * #if USE_MULTITARGET_CODE From c534bdd42ff8fa95fa5937e41db88030bc432ec3 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Mon, 1 Aug 2022 01:13:50 -0400 Subject: [PATCH 200/672] clang tidy treats call to system as security issue - replace with fork/exec --- .../decompressor.cpp | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/utils/self-extracting-executable/decompressor.cpp b/utils/self-extracting-executable/decompressor.cpp index 1e19d47d47d..00dd784dabf 100644 --- a/utils/self-extracting-executable/decompressor.cpp +++ b/utils/self-extracting-executable/decompressor.cpp @@ -297,21 +297,37 @@ int decompressFiles(int input_fd, char * path, char * name, bool & have_compress int do_system(const char * cmd) { - int ret = system(cmd); - if (ret == -1) - { - perror(nullptr); + pid_t pid = fork(); + if (pid == -1) return 1; + + if (pid == 0) + { + execlp( "sh" , "sh", "-c", cmd, NULL); + + perror(nullptr); + exit(127); } - if (WIFEXITED(ret) && WEXITSTATUS(ret)) + int ret = 0; + int status = 0; + while ((ret = waitpid(pid, &status, 0)) == -1) { - std::cerr << "Command [" << cmd << "] exited with code " << WEXITSTATUS(ret) << std::endl; + if ( errno != EINTR) + { + perror(nullptr); + return 1; + } + } + + if (WIFEXITED(status) && WEXITSTATUS(status)) + { + std::cerr << "Command [" << cmd << "] exited with code " << WEXITSTATUS(status) << std::endl; return 1; } - else if (WIFSIGNALED(ret)) + else if (WIFSIGNALED(status)) { - std::cerr << "Command [" << cmd << "] killed by signal " << WTERMSIG(ret) << std::endl; + std::cerr << "Command [" << cmd << "] killed by signal " << WTERMSIG(status) << std::endl; return 1; } From 7a2d969850862beeddb35f776d60cb4523b55974 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Mon, 1 Aug 2022 01:20:40 -0400 Subject: [PATCH 201/672] fix clang tidy unused return --- utils/self-extracting-executable/decompressor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/self-extracting-executable/decompressor.cpp b/utils/self-extracting-executable/decompressor.cpp index 00dd784dabf..6563aa49541 100644 --- a/utils/self-extracting-executable/decompressor.cpp +++ b/utils/self-extracting-executable/decompressor.cpp @@ -385,7 +385,7 @@ int main(int/* argc*/, char* argv[]) const char * const cmd_fmt = "mv %s %s.delete && cp %s.decompressed %s && rm %s.delete %s.decompressed"; int cmd_len = snprintf(nullptr, 0, cmd_fmt, argv[0], argv[0], argv[0], argv[0], argv[0], argv[0]); char command[cmd_len + 1]; - snprintf(command, cmd_len + 1, cmd_fmt, argv[0], argv[0], argv[0], argv[0], argv[0], argv[0]); + cmd_len = snprintf(command, cmd_len + 1, cmd_fmt, argv[0], argv[0], argv[0], argv[0], argv[0], argv[0]); if (do_system(command)) return 1; From 6a67147584b1f57cc24e05f6bfc1eb69ee831c64 Mon Sep 17 00:00:00 2001 From: Wangyang Guo Date: Mon, 1 Aug 2022 13:17:11 +0800 Subject: [PATCH 202/672] ColumnVector: refactory to use TargetSpecific::Default::doFilterAligned --- src/Columns/ColumnVector.cpp | 72 ++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/src/Columns/ColumnVector.cpp b/src/Columns/ColumnVector.cpp index 31dea90cc80..66274cfbf9e 100644 --- a/src/Columns/ColumnVector.cpp +++ b/src/Columns/ColumnVector.cpp @@ -477,6 +477,42 @@ void ColumnVector::insertRangeFrom(const IColumn & src, size_t start, size_t memcpy(data.data() + old_size, &src_vec.data[start], length * sizeof(data[0])); } +static inline UInt64 blsr(UInt64 mask) +{ +#ifdef __BMI__ + return _blsr_u64(mask); +#else + return mask & (mask-1); +#endif +} + +DECLARE_DEFAULT_CODE( +template +inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_aligned, const T *& data_pos, Container & res_data) +{ + while (filt_pos < filt_end_aligned) + { + UInt64 mask = bytes64MaskToBits64Mask(filt_pos); + + if (0xffffffffffffffff == mask) + { + res_data.insert(data_pos, data_pos + SIMD_BYTES); + } + else + { + while (mask) + { + size_t index = std::countr_zero(mask); + res_data.push_back(data_pos[index]); + mask = blsr(mask); + } + } + + filt_pos += SIMD_BYTES; + data_pos += SIMD_BYTES; + } +} +) DECLARE_AVX512VBMI2_SPECIFIC_CODE( template @@ -576,38 +612,12 @@ ColumnPtr ColumnVector::filter(const IColumn::Filter & filt, ssize_t result_s const UInt8 * filt_end_aligned = filt_pos + size / SIMD_BYTES * SIMD_BYTES; #if USE_MULTITARGET_CODE - if constexpr (sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8) - { - if (isArchSupported(TargetArch::AVX512VBMI2)) - TargetSpecific::AVX512VBMI2::doFilterAligned(filt_pos, filt_end_aligned, data_pos, res_data); - } + static constexpr bool VBMI2_CAPABLE = sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8; + if (VBMI2_CAPABLE && isArchSupported(TargetArch::AVX512VBMI2)) + TargetSpecific::AVX512VBMI2::doFilterAligned(filt_pos, filt_end_aligned, data_pos, res_data); + else #endif - - while (filt_pos < filt_end_aligned) - { - UInt64 mask = bytes64MaskToBits64Mask(filt_pos); - - if (0xffffffffffffffff == mask) - { - res_data.insert(data_pos, data_pos + SIMD_BYTES); - } - else - { - while (mask) - { - size_t index = __builtin_ctzll(mask); - res_data.push_back(data_pos[index]); - #ifdef __BMI__ - mask = _blsr_u64(mask); - #else - mask = mask & (mask-1); - #endif - } - } - - filt_pos += SIMD_BYTES; - data_pos += SIMD_BYTES; - } + TargetSpecific::Default::doFilterAligned(filt_pos, filt_end_aligned, data_pos, res_data); while (filt_pos < filt_end) { From 3aeb5250367f37e0b6a3152dd50a8d5164145f70 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Mon, 1 Aug 2022 01:45:02 -0400 Subject: [PATCH 203/672] style fix --- utils/self-extracting-executable/decompressor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/self-extracting-executable/decompressor.cpp b/utils/self-extracting-executable/decompressor.cpp index 6563aa49541..9967a9f1904 100644 --- a/utils/self-extracting-executable/decompressor.cpp +++ b/utils/self-extracting-executable/decompressor.cpp @@ -303,7 +303,7 @@ int do_system(const char * cmd) if (pid == 0) { - execlp( "sh" , "sh", "-c", cmd, NULL); + execlp("sh" , "sh", "-c", cmd, NULL); perror(nullptr); exit(127); @@ -313,7 +313,7 @@ int do_system(const char * cmd) int status = 0; while ((ret = waitpid(pid, &status, 0)) == -1) { - if ( errno != EINTR) + if (errno != EINTR) { perror(nullptr); return 1; From 0e154ed1df6f50aa681b61790c6849b83fa45130 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Sun, 31 Jul 2022 12:43:50 +0000 Subject: [PATCH 204/672] More renamings --- .github/workflows/master.yml | 14 +++++++------- .github/workflows/pull_request.yml | 14 +++++++------- tests/ci/ci_config.py | 6 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index fa046ed40c7..2acc1468328 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -151,8 +151,8 @@ jobs: # shellcheck disable=SC2046 docker rm -f $(docker ps -a -q) ||: sudo rm -fr "$TEMP_PATH" - SplitBuildSmokeTest: - needs: [BuilderDebSplitted] + SharedBuildSmokeTest: + needs: [BuilderDebShared] runs-on: [self-hosted, style-checker] steps: - name: Set envs @@ -171,7 +171,7 @@ jobs: uses: actions/download-artifact@v2 with: path: ${{ env.REPORTS_PATH }} - - name: Split build check + - name: Shared build check run: | sudo rm -fr "$TEMP_PATH" mkdir -p "$TEMP_PATH" @@ -598,7 +598,7 @@ jobs: ########################################################################################## ##################################### SPECIAL BUILDS ##################################### ########################################################################################## - BuilderDebSplitted: + BuilderDebShared: needs: [DockerHubPush] runs-on: [self-hosted, builder] steps: @@ -609,7 +609,7 @@ jobs: IMAGES_PATH=${{runner.temp}}/images_path REPO_COPY=${{runner.temp}}/build_check/ClickHouse CACHES_PATH=${{runner.temp}}/../ccaches - BUILD_NAME=binary_splitted + BUILD_NAME=binary_shared EOF - name: Download changed images uses: actions/download-artifact@v2 @@ -1012,7 +1012,7 @@ jobs: # - BuilderBinGCC - BuilderBinPPC64 - BuilderBinClangTidy - - BuilderDebSplitted + - BuilderDebShared runs-on: [self-hosted, style-checker] steps: - name: Set envs @@ -3153,7 +3153,7 @@ jobs: - UnitTestsMsan - UnitTestsUBsan - UnitTestsReleaseClang - - SplitBuildSmokeTest + - SharedBuildSmokeTest runs-on: [self-hosted, style-checker] steps: - name: Clear repository diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 26726302beb..3d1ebe1fb8b 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -216,8 +216,8 @@ jobs: # shellcheck disable=SC2046 docker rm -f $(docker ps -a -q) ||: sudo rm -fr "$TEMP_PATH" - SplitBuildSmokeTest: - needs: [BuilderDebSplitted] + SharedBuildSmokeTest: + needs: [BuilderDebShared] runs-on: [self-hosted, style-checker] steps: - name: Set envs @@ -236,7 +236,7 @@ jobs: uses: actions/download-artifact@v2 with: path: ${{ env.REPORTS_PATH }} - - name: Split build check + - name: Shared build check run: | sudo rm -fr "$TEMP_PATH" mkdir -p "$TEMP_PATH" @@ -620,7 +620,7 @@ jobs: ########################################################################################## ##################################### SPECIAL BUILDS ##################################### ########################################################################################## - BuilderDebSplitted: + BuilderDebShared: needs: [DockerHubPush, FastTest, StyleCheck] runs-on: [self-hosted, builder] steps: @@ -631,7 +631,7 @@ jobs: IMAGES_PATH=${{runner.temp}}/images_path REPO_COPY=${{runner.temp}}/build_check/ClickHouse CACHES_PATH=${{runner.temp}}/../ccaches - BUILD_NAME=binary_splitted + BUILD_NAME=binary_shared EOF - name: Download changed images uses: actions/download-artifact@v2 @@ -1024,7 +1024,7 @@ jobs: # - BuilderBinGCC - BuilderBinPPC64 - BuilderBinClangTidy - - BuilderDebSplitted + - BuilderDebShared runs-on: [self-hosted, style-checker] if: ${{ success() || failure() }} steps: @@ -3416,7 +3416,7 @@ jobs: - UnitTestsMsan - UnitTestsUBsan - UnitTestsReleaseClang - - SplitBuildSmokeTest + - SharedBuildSmokeTest - CompatibilityCheck - IntegrationTestsFlakyCheck - Jepsen diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index c9d665d0e7f..5f2832156cb 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -113,7 +113,7 @@ CI_CONFIG = { "tidy": "enable", "with_coverage": False, }, - "binary_splitted": { + "binary_shared": { "compiler": "clang-14", "build_type": "", "sanitizer": "", @@ -192,7 +192,7 @@ CI_CONFIG = { ], "ClickHouse special build check": [ "binary_tidy", - "binary_splitted", + "binary_shared", "binary_darwin", "binary_aarch64", "binary_freebsd", @@ -297,7 +297,7 @@ CI_CONFIG = { "required_build": "package_release", }, "Split build smoke test": { - "required_build": "binary_splitted", + "required_build": "binary_shared", }, "Unit tests (release-clang)": { "required_build": "binary_release", From 4e56889a48aa90fd184eef1ce33039ae5f93e635 Mon Sep 17 00:00:00 2001 From: avogar Date: Mon, 1 Aug 2022 10:49:46 +0000 Subject: [PATCH 205/672] Update test --- tests/queries/0_stateless/02375_pretty_formats.sh | 11 ----------- tests/queries/0_stateless/02375_pretty_formats.sql.j2 | 8 ++++++++ 2 files changed, 8 insertions(+), 11 deletions(-) delete mode 100755 tests/queries/0_stateless/02375_pretty_formats.sh create mode 100755 tests/queries/0_stateless/02375_pretty_formats.sql.j2 diff --git a/tests/queries/0_stateless/02375_pretty_formats.sh b/tests/queries/0_stateless/02375_pretty_formats.sh deleted file mode 100755 index 9c150343fa7..00000000000 --- a/tests/queries/0_stateless/02375_pretty_formats.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -# shellcheck source=../shell_config.sh -. "$CURDIR"/../shell_config.sh - -for format in Pretty PrettyNoEscapes PrettyMonoBlock PrettyNoEscapesMonoBlock PrettyCompact PrettyCompactNoEscapes PrettyCompactMonoBlock PrettyCompactNoEscapesMonoBlock PrettySpace PrettySpaceNoEscapes PrettySpaceMonoBlock PrettySpaceNoEscapesMonoBlock -do - echo $format - $CLICKHOUSE_CLIENT -q "select number as x, number + 1 as y from numbers(4) settings max_block_size=2 format $format" -done diff --git a/tests/queries/0_stateless/02375_pretty_formats.sql.j2 b/tests/queries/0_stateless/02375_pretty_formats.sql.j2 new file mode 100755 index 00000000000..cc61346d267 --- /dev/null +++ b/tests/queries/0_stateless/02375_pretty_formats.sql.j2 @@ -0,0 +1,8 @@ +{% for format in ['Pretty', 'PrettyNoEscapes', 'PrettyMonoBlock', 'PrettyNoEscapesMonoBlock', 'PrettyCompact', 'PrettyCompactNoEscapes', + 'PrettyCompactMonoBlock', 'PrettyCompactNoEscapesMonoBlock', 'PrettySpace', 'PrettySpaceNoEscapes', 'PrettySpaceMonoBlock', + 'PrettySpaceNoEscapesMonoBlock'] -%} + +select '{{ format }}'; +select number as x, number + 1 as y from numbers(4) settings max_block_size=2 format {{ format }}; + +{% endfor -%} From 74f87a95c707f4c28c046511b6da08f781b325ab Mon Sep 17 00:00:00 2001 From: avogar Date: Mon, 1 Aug 2022 10:57:55 +0000 Subject: [PATCH 206/672] Fis style --- tests/queries/0_stateless/02366_cancel_write_into_file.sh | 4 ++-- tests/queries/0_stateless/02367_cancel_write_into_s3.sh | 4 ++-- tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/queries/0_stateless/02366_cancel_write_into_file.sh b/tests/queries/0_stateless/02366_cancel_write_into_file.sh index f7aca608aab..060fe1ec74b 100755 --- a/tests/queries/0_stateless/02366_cancel_write_into_file.sh +++ b/tests/queries/0_stateless/02366_cancel_write_into_file.sh @@ -13,7 +13,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" 2>&1 > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" > /dev/null 2>&1 for i in $(seq 1 10); do @@ -22,5 +22,5 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" 2>&1 > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02366_') sync" > /dev/null 2>&1 diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh index bea990e32e3..54f6d2a941b 100755 --- a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh +++ b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh @@ -13,7 +13,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" 2>&1 > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null 2>&1 for i in $(seq 1 10); do @@ -22,4 +22,4 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" 2>&1 > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null 2>&1 diff --git a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh index 405f059b52e..8262cd7eab5 100755 --- a/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh +++ b/tests/queries/0_stateless/02368_cancel_write_into_hdfs.sh @@ -13,7 +13,7 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" 2>&1 > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null 2>&1 for i in $(seq 1 10); do @@ -22,4 +22,4 @@ done sleep 2 -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" 2>&1 > /dev/null +$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02368_') sync" > /dev/null 2>&1 From 7bbc6c72929d35c517c43b0ed1160a4cb5e524b4 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 1 Aug 2022 13:00:44 +0200 Subject: [PATCH 207/672] Update aspell-dict.txt --- utils/check-style/aspell-ignore/en/aspell-dict.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/utils/check-style/aspell-ignore/en/aspell-dict.txt b/utils/check-style/aspell-ignore/en/aspell-dict.txt index cc22b712c62..b6778fce0f0 100644 --- a/utils/check-style/aspell-ignore/en/aspell-dict.txt +++ b/utils/check-style/aspell-ignore/en/aspell-dict.txt @@ -108,6 +108,11 @@ PrettyCompactNoEscapes PrettyNoEscapes PrettySpace PrettySpaceNoEscapes +PrettyCompactNoEscapesMonoBlock +PrettyMonoBlock +PrettyNoEscapesMonoBlock +PrettySpaceMonoBlock +PrettySpaceNoEscapesMonoBlock Protobuf ProtobufSingle QTCreator @@ -374,6 +379,11 @@ prettycompactnoescapes prettynoescapes prettyspace prettyspacenoescapes +prettycompactnoescapesmonoblock +prettymonoblock +prettynoescapesmonoblock +prettyspacemonoblock +prettyspacenoescapesmonoblock prlimit prometheus proto From a0d51601bf9aade0bd9303588b7d032b804f2442 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 1 Aug 2022 13:07:48 +0200 Subject: [PATCH 208/672] Update EscapingRuleUtils.cpp --- src/Formats/EscapingRuleUtils.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Formats/EscapingRuleUtils.cpp b/src/Formats/EscapingRuleUtils.cpp index 69684b67071..f8c002e87ee 100644 --- a/src/Formats/EscapingRuleUtils.cpp +++ b/src/Formats/EscapingRuleUtils.cpp @@ -327,7 +327,6 @@ void transformInferredTypesIfNeededImpl(DataTypes & types, const FormatSettings /// Check settings specific for JSON formats. /// If we have numbers and strings, convert numbers to strings. - /// (Actually numbers could not be parsed from if (settings.json.try_infer_numbers_from_strings) { bool have_strings = false; From 1c1e7eb0cd2d5f841979723ba27a7abc7ab47d6e Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 1 Aug 2022 13:16:23 +0200 Subject: [PATCH 209/672] Fix duplicate lines --- src/DataTypes/transformTypesRecursively.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/DataTypes/transformTypesRecursively.cpp b/src/DataTypes/transformTypesRecursively.cpp index 48e9dc60c19..3544c7e477d 100644 --- a/src/DataTypes/transformTypesRecursively.cpp +++ b/src/DataTypes/transformTypesRecursively.cpp @@ -87,9 +87,6 @@ void transformTypesRecursively(DataTypes & types, std::function(transposed_nested_types[i]); - - if (transform_complex_types) - transform_complex_types(types); } if (transform_complex_types) From 2a73ccb3f681b73cbd5f5abcb4eaa9ad1a885ac4 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Wed, 20 Jul 2022 22:13:06 +0800 Subject: [PATCH 210/672] Normalize AggregateFunctionCount type comparison --- .../test_normalized_count_comparison.py | 37 +++++++++++++++++++ ...unt_projection_distributed_query.reference | 1 + ...max_count_projection_distributed_query.sql | 7 ++++ 3 files changed, 45 insertions(+) create mode 100644 tests/integration/test_backward_compatibility/test_normalized_count_comparison.py create mode 100644 tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.reference create mode 100644 tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.sql diff --git a/tests/integration/test_backward_compatibility/test_normalized_count_comparison.py b/tests/integration/test_backward_compatibility/test_normalized_count_comparison.py new file mode 100644 index 00000000000..eeb31a39931 --- /dev/null +++ b/tests/integration/test_backward_compatibility/test_normalized_count_comparison.py @@ -0,0 +1,37 @@ +import pytest + +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__) +node1 = cluster.add_instance("node1", with_zookeeper=False) +node2 = cluster.add_instance( + "node2", + with_zookeeper=False, + image="yandex/clickhouse-server", + tag="21.7.2.7", + stay_alive=True, + with_installed_binary=True, +) + + +@pytest.fixture(scope="module") +def start_cluster(): + try: + cluster.start() + yield cluster + + finally: + cluster.shutdown() + + +def test_select_aggregate_alias_column(start_cluster): + node1.query("create table tab (x UInt64, y String, z Nullable(Int64)) engine = Memory") + node2.query("create table tab (x UInt64, y String, z Nullable(Int64)) engine = Memory") + node1.query("insert into tab values (1, 'a', null)") + node2.query("insert into tab values (1, 'a', null)") + + node1.query("select count(), count(1), count(x), count(y), count(z) from remote('node{1,2}', default, tab)") + node2.query("select count(), count(1), count(x), count(y), count(z) from remote('node{1,2}', default, tab)") + + node1.query("drop table tab") + node2.query("drop table tab") diff --git a/tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.reference b/tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.reference new file mode 100644 index 00000000000..5782593a455 --- /dev/null +++ b/tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.reference @@ -0,0 +1 @@ +2 2 2 diff --git a/tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.sql b/tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.sql new file mode 100644 index 00000000000..283a8c831d0 --- /dev/null +++ b/tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.sql @@ -0,0 +1,7 @@ +-- Tags: no-s3-storage + +drop table if exists t; +create table t (n int, s String) engine MergeTree order by n; +insert into t values (1, 'a'); +select count(), count(n), count(s) from cluster('test_cluster_two_shards', currentDatabase(), t); +drop table t; From f11d0484f3ad0ddbf829e695056fdd154d12a2cc Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Thu, 21 Jul 2022 23:08:42 +0800 Subject: [PATCH 211/672] Normalize everything else --- .../AggregateFunctionArray.h | 10 ++++++ .../AggregateFunctionCount.h | 26 +++++++++++++++ src/AggregateFunctions/AggregateFunctionIf.h | 10 ++++++ .../AggregateFunctionMerge.cpp | 32 +++++++++++++------ .../AggregateFunctionMerge.h | 5 +++ .../AggregateFunctionQuantile.h | 19 +++++++++-- .../AggregateFunctionSequenceMatch.h | 2 +- .../AggregateFunctionSequenceNextNode.h | 2 +- .../AggregateFunctionState.h | 5 +++ src/AggregateFunctions/IAggregateFunction.cpp | 7 ++++ src/AggregateFunctions/IAggregateFunction.h | 5 ++- src/DataTypes/DataTypeAggregateFunction.cpp | 15 ++++++++- .../GatherFunctionQuantileVisitor.cpp | 21 +++++++----- .../GatherFunctionQuantileVisitor.h | 3 +- 14 files changed, 138 insertions(+), 24 deletions(-) diff --git a/src/AggregateFunctions/AggregateFunctionArray.h b/src/AggregateFunctions/AggregateFunctionArray.h index c32cf8f2418..6b06accbbf8 100644 --- a/src/AggregateFunctions/AggregateFunctionArray.h +++ b/src/AggregateFunctions/AggregateFunctionArray.h @@ -49,6 +49,16 @@ public: return nested_func->getReturnType(); } + const IAggregateFunction & getBaseAggregateFunctionWithSameStateRepresentation() const override + { + return nested_func->getBaseAggregateFunctionWithSameStateRepresentation(); + } + + DataTypePtr getStateType() const override + { + return nested_func->getStateType(); + } + bool isVersioned() const override { return nested_func->isVersioned(); diff --git a/src/AggregateFunctions/AggregateFunctionCount.h b/src/AggregateFunctions/AggregateFunctionCount.h index 4199cb55409..abde8f65de0 100644 --- a/src/AggregateFunctions/AggregateFunctionCount.h +++ b/src/AggregateFunctions/AggregateFunctionCount.h @@ -5,9 +5,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -102,6 +104,18 @@ public: } } + bool haveSameStateRepresentationImpl(const IAggregateFunction & rhs) const override + { + return this->getName() == rhs.getName(); + } + + DataTypePtr getStateType() const override + { + AggregateFunctionProperties properties; + return std::make_shared( + AggregateFunctionFactory::instance().get(getName(), {}, {}, properties), DataTypes{}, Array{}); + } + void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, Arena *) const override { data(place).count += data(rhs).count; @@ -240,6 +254,18 @@ public: } } + bool haveSameStateRepresentationImpl(const IAggregateFunction & rhs) const override + { + return this->getName() == rhs.getName(); + } + + DataTypePtr getStateType() const override + { + AggregateFunctionProperties properties; + return std::make_shared( + AggregateFunctionFactory::instance().get(getName(), {}, {}, properties), DataTypes{}, Array{}); + } + void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, Arena *) const override { data(place).count += data(rhs).count; diff --git a/src/AggregateFunctions/AggregateFunctionIf.h b/src/AggregateFunctions/AggregateFunctionIf.h index f3221ae66e9..22854519970 100644 --- a/src/AggregateFunctions/AggregateFunctionIf.h +++ b/src/AggregateFunctions/AggregateFunctionIf.h @@ -56,6 +56,16 @@ public: return nested_func->getReturnType(); } + const IAggregateFunction & getBaseAggregateFunctionWithSameStateRepresentation() const override + { + return nested_func->getBaseAggregateFunctionWithSameStateRepresentation(); + } + + DataTypePtr getStateType() const override + { + return nested_func->getStateType(); + } + bool isVersioned() const override { return nested_func->isVersioned(); diff --git a/src/AggregateFunctions/AggregateFunctionMerge.cpp b/src/AggregateFunctions/AggregateFunctionMerge.cpp index cdf399585f5..a6d3a0771ab 100644 --- a/src/AggregateFunctions/AggregateFunctionMerge.cpp +++ b/src/AggregateFunctions/AggregateFunctionMerge.cpp @@ -23,14 +23,20 @@ public: DataTypes transformArguments(const DataTypes & arguments) const override { if (arguments.size() != 1) - throw Exception("Incorrect number of arguments for aggregate function with " + getName() + " suffix", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + throw Exception( + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, + "Incorrect number of arguments for aggregate function with {} suffix", + getName()); const DataTypePtr & argument = arguments[0]; const DataTypeAggregateFunction * function = typeid_cast(argument.get()); if (!function) - throw Exception("Illegal type " + argument->getName() + " of argument for aggregate function with " + getName() + " suffix" - + " must be AggregateFunction(...)", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + throw Exception( + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal type {} of argument for aggregate function with {} suffix. It must be AggregateFunction(...)", + argument->getName(), + getName()); return function->getArgumentsDataTypes(); } @@ -45,13 +51,21 @@ public: const DataTypeAggregateFunction * function = typeid_cast(argument.get()); if (!function) - throw Exception("Illegal type " + argument->getName() + " of argument for aggregate function with " + getName() + " suffix" - + " must be AggregateFunction(...)", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + throw Exception( + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal type {} of argument for aggregate function with {} suffix. It must be AggregateFunction(...)", + argument->getName(), + getName()); - if (nested_function->getName() != function->getFunctionName()) - throw Exception("Illegal type " + argument->getName() + " of argument for aggregate function with " + getName() + " suffix" - + ", because it corresponds to different aggregate function: " + function->getFunctionName() + " instead of " + nested_function->getName(), - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + if (!nested_function->haveSameStateRepresentation(*function->getFunction())) + throw Exception( + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal type {} of argument for aggregate function with {} suffix. because it corresponds to different aggregate " + "function: {} instead of {}", + argument->getName(), + getName(), + function->getFunctionName(), + nested_function->getName()); return std::make_shared(nested_function, argument, params); } diff --git a/src/AggregateFunctions/AggregateFunctionMerge.h b/src/AggregateFunctions/AggregateFunctionMerge.h index 8058e104415..7bf0f5ea00f 100644 --- a/src/AggregateFunctions/AggregateFunctionMerge.h +++ b/src/AggregateFunctions/AggregateFunctionMerge.h @@ -50,6 +50,11 @@ public: return nested_func->getReturnType(); } + const IAggregateFunction & getBaseAggregateFunctionWithSameStateRepresentation() const override + { + return nested_func->getBaseAggregateFunctionWithSameStateRepresentation(); + } + bool isVersioned() const override { return nested_func->isVersioned(); diff --git a/src/AggregateFunctions/AggregateFunctionQuantile.h b/src/AggregateFunctions/AggregateFunctionQuantile.h index bb2a68da4e6..fca0ba03a36 100644 --- a/src/AggregateFunctions/AggregateFunctionQuantile.h +++ b/src/AggregateFunctions/AggregateFunctionQuantile.h @@ -1,6 +1,7 @@ #pragma once #include +#include /// These must be exposed in header for the purpose of dynamic compilation. #include @@ -20,9 +21,11 @@ #include #include #include +#include #include #include #include +#include #include @@ -105,9 +108,21 @@ public: return res; } - bool haveSameStateRepresentation(const IAggregateFunction & rhs) const override + bool haveSameStateRepresentationImpl(const IAggregateFunction & rhs) const override { - return getName() == rhs.getName() && this->haveEqualArgumentTypes(rhs); + return GatherFunctionQuantileData::toFusedNameOrSelf(getName()) == GatherFunctionQuantileData::toFusedNameOrSelf(rhs.getName()) + && this->haveEqualArgumentTypes(rhs); + } + + DataTypePtr getStateType() const override + { + Array params{1}; + AggregateFunctionProperties properties; + return std::make_shared( + AggregateFunctionFactory::instance().get( + GatherFunctionQuantileData::toFusedNameOrSelf(getName()), this->argument_types, params, properties), + this->argument_types, + params); } bool allocatesMemoryInArena() const override { return false; } diff --git a/src/AggregateFunctions/AggregateFunctionSequenceMatch.h b/src/AggregateFunctions/AggregateFunctionSequenceMatch.h index 8b0f7ccfbd1..5c1ab803f19 100644 --- a/src/AggregateFunctions/AggregateFunctionSequenceMatch.h +++ b/src/AggregateFunctions/AggregateFunctionSequenceMatch.h @@ -163,7 +163,7 @@ public: this->data(place).deserialize(buf); } - bool haveSameStateRepresentation(const IAggregateFunction & rhs) const override + bool haveSameStateRepresentationImpl(const IAggregateFunction & rhs) const override { return this->getName() == rhs.getName() && this->haveEqualArgumentTypes(rhs); } diff --git a/src/AggregateFunctions/AggregateFunctionSequenceNextNode.h b/src/AggregateFunctions/AggregateFunctionSequenceNextNode.h index a4222b5471f..a627afaaa85 100644 --- a/src/AggregateFunctions/AggregateFunctionSequenceNextNode.h +++ b/src/AggregateFunctions/AggregateFunctionSequenceNextNode.h @@ -194,7 +194,7 @@ public: DataTypePtr getReturnType() const override { return data_type; } - bool haveSameStateRepresentation(const IAggregateFunction & rhs) const override + bool haveSameStateRepresentationImpl(const IAggregateFunction & rhs) const override { return this->getName() == rhs.getName() && this->haveEqualArgumentTypes(rhs); } diff --git a/src/AggregateFunctions/AggregateFunctionState.h b/src/AggregateFunctions/AggregateFunctionState.h index 9a1d4528d96..a598e4838cc 100644 --- a/src/AggregateFunctions/AggregateFunctionState.h +++ b/src/AggregateFunctions/AggregateFunctionState.h @@ -37,6 +37,11 @@ public: return getStateType(); } + const IAggregateFunction & getBaseAggregateFunctionWithSameStateRepresentation() const override + { + return nested_func->getBaseAggregateFunctionWithSameStateRepresentation(); + } + DataTypePtr getStateType() const override { return nested_func->getStateType(); diff --git a/src/AggregateFunctions/IAggregateFunction.cpp b/src/AggregateFunctions/IAggregateFunction.cpp index ea4f8338fb8..25d2a9a4530 100644 --- a/src/AggregateFunctions/IAggregateFunction.cpp +++ b/src/AggregateFunctions/IAggregateFunction.cpp @@ -59,6 +59,13 @@ bool IAggregateFunction::haveEqualArgumentTypes(const IAggregateFunction & rhs) } bool IAggregateFunction::haveSameStateRepresentation(const IAggregateFunction & rhs) const +{ + const auto & lhs_base = getBaseAggregateFunctionWithSameStateRepresentation(); + const auto & rhs_base = rhs.getBaseAggregateFunctionWithSameStateRepresentation(); + return lhs_base.haveSameStateRepresentationImpl(rhs_base); +} + +bool IAggregateFunction::haveSameStateRepresentationImpl(const IAggregateFunction & rhs) const { bool res = getName() == rhs.getName() && parameters == rhs.parameters diff --git a/src/AggregateFunctions/IAggregateFunction.h b/src/AggregateFunctions/IAggregateFunction.h index cdb3b2f32b7..ae8e32b4daf 100644 --- a/src/AggregateFunctions/IAggregateFunction.h +++ b/src/AggregateFunctions/IAggregateFunction.h @@ -79,7 +79,10 @@ public: /// - quantile(x), quantile(a)(x), quantile(b)(x) - parameter doesn't affect state and used for finalization only /// - foo(x) and fooIf(x) - If combinator doesn't affect state /// By default returns true only if functions have exactly the same names, combinators and parameters. - virtual bool haveSameStateRepresentation(const IAggregateFunction & rhs) const; + bool haveSameStateRepresentation(const IAggregateFunction & rhs) const; + virtual bool haveSameStateRepresentationImpl(const IAggregateFunction & rhs) const; + + virtual const IAggregateFunction & getBaseAggregateFunctionWithSameStateRepresentation() const { return *this; } bool haveEqualArgumentTypes(const IAggregateFunction & rhs) const; diff --git a/src/DataTypes/DataTypeAggregateFunction.cpp b/src/DataTypes/DataTypeAggregateFunction.cpp index c65a30b80ac..70b9c26e0cf 100644 --- a/src/DataTypes/DataTypeAggregateFunction.cpp +++ b/src/DataTypes/DataTypeAggregateFunction.cpp @@ -119,7 +119,20 @@ Field DataTypeAggregateFunction::getDefault() const bool DataTypeAggregateFunction::equals(const IDataType & rhs) const { - return typeid(rhs) == typeid(*this) && getNameWithoutVersion() == typeid_cast(rhs).getNameWithoutVersion(); + if (typeid(rhs) != typeid(*this)) + return false; + + auto lhs_state_type = function->getStateType(); + auto rhs_state_type = typeid_cast(rhs).function->getStateType(); + + if (typeid(lhs_state_type.get()) != typeid(rhs_state_type.get())) + return false; + + if (const auto * lhs_state = typeid_cast(lhs_state_type.get())) + return lhs_state->getNameWithoutVersion() + == typeid_cast(*rhs_state_type).getNameWithoutVersion(); + + return lhs_state_type->equals(*rhs_state_type); } diff --git a/src/Interpreters/GatherFunctionQuantileVisitor.cpp b/src/Interpreters/GatherFunctionQuantileVisitor.cpp index 93b35a3416a..2abd7af1455 100644 --- a/src/Interpreters/GatherFunctionQuantileVisitor.cpp +++ b/src/Interpreters/GatherFunctionQuantileVisitor.cpp @@ -1,8 +1,9 @@ -#include #include + +#include #include -#include #include +#include namespace DB { @@ -30,6 +31,13 @@ static const std::unordered_map quantile_fuse_name_mapping = { {NameQuantileTimingWeighted::name, NameQuantilesTimingWeighted::name}, }; +String GatherFunctionQuantileData::toFusedNameOrSelf(const String & func_name) +{ + if (auto it = quantile_fuse_name_mapping.find(func_name); it != quantile_fuse_name_mapping.end()) + return it->second; + return func_name; +} + String GatherFunctionQuantileData::getFusedName(const String & func_name) { if (auto it = quantile_fuse_name_mapping.find(func_name); it != quantile_fuse_name_mapping.end()) @@ -53,11 +61,9 @@ void GatherFunctionQuantileData::FuseQuantileAggregatesData::addFuncNode(ASTPtr const auto & arguments = func->arguments->children; - bool need_two_args = func->name == NameQuantileDeterministic::name - || func->name == NameQuantileExactWeighted::name - || func->name == NameQuantileTimingWeighted::name - || func->name == NameQuantileTDigestWeighted::name - || func->name == NameQuantileBFloat16Weighted::name; + bool need_two_args = func->name == NameQuantileDeterministic::name || func->name == NameQuantileExactWeighted::name + || func->name == NameQuantileTimingWeighted::name || func->name == NameQuantileTDigestWeighted::name + || func->name == NameQuantileBFloat16Weighted::name; if (arguments.size() != (need_two_args ? 2 : 1)) return; @@ -83,4 +89,3 @@ bool GatherFunctionQuantileData::needChild(const ASTPtr & node, const ASTPtr &) } } - diff --git a/src/Interpreters/GatherFunctionQuantileVisitor.h b/src/Interpreters/GatherFunctionQuantileVisitor.h index d68cc8fe22e..74388e7161a 100644 --- a/src/Interpreters/GatherFunctionQuantileVisitor.h +++ b/src/Interpreters/GatherFunctionQuantileVisitor.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -26,6 +25,8 @@ public: void visit(ASTFunction & function, ASTPtr & ast); + static String toFusedNameOrSelf(const String & func_name); + static String getFusedName(const String & func_name); static bool needChild(const ASTPtr & node, const ASTPtr &); From f23b3d64dcdb8dd044ad4c02a8fad4b2e77f889c Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Thu, 21 Jul 2022 23:09:56 +0800 Subject: [PATCH 212/672] Add tests --- ...366_normalize_aggregate_function_types_and_states.reference | 2 ++ .../02366_normalize_aggregate_function_types_and_states.sql | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 tests/queries/0_stateless/02366_normalize_aggregate_function_types_and_states.reference create mode 100644 tests/queries/0_stateless/02366_normalize_aggregate_function_types_and_states.sql diff --git a/tests/queries/0_stateless/02366_normalize_aggregate_function_types_and_states.reference b/tests/queries/0_stateless/02366_normalize_aggregate_function_types_and_states.reference new file mode 100644 index 00000000000..c29c5f8acf4 --- /dev/null +++ b/tests/queries/0_stateless/02366_normalize_aggregate_function_types_and_states.reference @@ -0,0 +1,2 @@ +7 +1.5 diff --git a/tests/queries/0_stateless/02366_normalize_aggregate_function_types_and_states.sql b/tests/queries/0_stateless/02366_normalize_aggregate_function_types_and_states.sql new file mode 100644 index 00000000000..3d2900a9b74 --- /dev/null +++ b/tests/queries/0_stateless/02366_normalize_aggregate_function_types_and_states.sql @@ -0,0 +1,3 @@ +SELECT countMerge(*) FROM (SELECT countState(0.5) AS a UNION ALL SELECT countState() UNION ALL SELECT countIfState(2, 1) UNION ALL SELECT countArrayState([1, 2]) UNION ALL SELECT countArrayIfState([1, 2], 1)); + +SELECT quantileMerge(*) FROM (SELECT quantilesState(0.5)(1) AS a UNION ALL SELECT quantileStateIf(2, identity(1))); From 2b2ee8a2c3c38c7dea2560a095b8cbf06f3dc489 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Fri, 22 Jul 2022 10:57:54 +0800 Subject: [PATCH 213/672] Format tests --- .../test_normalized_count_comparison.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_backward_compatibility/test_normalized_count_comparison.py b/tests/integration/test_backward_compatibility/test_normalized_count_comparison.py index eeb31a39931..fcdedd29dad 100644 --- a/tests/integration/test_backward_compatibility/test_normalized_count_comparison.py +++ b/tests/integration/test_backward_compatibility/test_normalized_count_comparison.py @@ -25,13 +25,21 @@ def start_cluster(): def test_select_aggregate_alias_column(start_cluster): - node1.query("create table tab (x UInt64, y String, z Nullable(Int64)) engine = Memory") - node2.query("create table tab (x UInt64, y String, z Nullable(Int64)) engine = Memory") + node1.query( + "create table tab (x UInt64, y String, z Nullable(Int64)) engine = Memory" + ) + node2.query( + "create table tab (x UInt64, y String, z Nullable(Int64)) engine = Memory" + ) node1.query("insert into tab values (1, 'a', null)") node2.query("insert into tab values (1, 'a', null)") - node1.query("select count(), count(1), count(x), count(y), count(z) from remote('node{1,2}', default, tab)") - node2.query("select count(), count(1), count(x), count(y), count(z) from remote('node{1,2}', default, tab)") + node1.query( + "select count(), count(1), count(x), count(y), count(z) from remote('node{1,2}', default, tab)" + ) + node2.query( + "select count(), count(1), count(x), count(y), count(z) from remote('node{1,2}', default, tab)" + ) node1.query("drop table tab") node2.query("drop table tab") From 0e746c1afa01b520528d52e97078c41938779fc3 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Fri, 22 Jul 2022 12:52:54 +0800 Subject: [PATCH 214/672] More format refactor --- .../AggregateFunctionQuantile.h | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/AggregateFunctions/AggregateFunctionQuantile.h b/src/AggregateFunctions/AggregateFunctionQuantile.h index fca0ba03a36..854cef73d9d 100644 --- a/src/AggregateFunctions/AggregateFunctionQuantile.h +++ b/src/AggregateFunctions/AggregateFunctionQuantile.h @@ -64,10 +64,9 @@ template < typename FloatReturnType, /// If true, the function will accept multiple parameters with quantile levels /// and return an Array filled with many values of that quantiles. - bool returns_many -> -class AggregateFunctionQuantile final : public IAggregateFunctionDataHelper> + bool returns_many> +class AggregateFunctionQuantile final + : public IAggregateFunctionDataHelper> { private: using ColVecType = ColumnVectorOrDecimal; @@ -84,11 +83,14 @@ private: public: AggregateFunctionQuantile(const DataTypes & argument_types_, const Array & params) - : IAggregateFunctionDataHelper>(argument_types_, params) - , levels(params, returns_many), level(levels.levels[0]), argument_type(this->argument_types[0]) + : IAggregateFunctionDataHelper>( + argument_types_, params) + , levels(params, returns_many) + , level(levels.levels[0]) + , argument_type(this->argument_types[0]) { if (!returns_many && levels.size() > 1) - throw Exception("Aggregate function " + getName() + " require one parameter or less", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, "Aggregate function {} require one parameter or less", getName()); } String getName() const override { return Name::name; } @@ -139,9 +141,7 @@ public: } if constexpr (has_second_arg) - this->data(place).add( - value, - columns[1]->getUInt(row_num)); + this->data(place).add(value, columns[1]->getUInt(row_num)); else this->data(place).add(value); } @@ -164,7 +164,6 @@ public: void insertResultInto(AggregateDataPtr __restrict place, IColumn & to, Arena *) const override { - /// const_cast is required because some data structures apply finalizaton (like sorting) for obtain a result. auto & data = this->data(place); if constexpr (returns_many) @@ -210,7 +209,11 @@ public: { assertBinary(Name::name, types); if (!isUnsignedInteger(types[1])) - throw Exception("Second argument (weight) for function " + std::string(Name::name) + " must be unsigned integer, but it has type " + types[1]->getName(), ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + throw Exception( + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Second argument (weight) for function {} must be unsigned integer, but it has type {}", + Name::name, + types[1]->getName()); } else assertUnary(Name::name, types); From f84e5b68270dd6c4140319c09f92bbb599e5e74a Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Fri, 22 Jul 2022 14:26:46 +0800 Subject: [PATCH 215/672] Allow to format DataTypePtr --- src/DataTypes/IDataType.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/DataTypes/IDataType.h b/src/DataTypes/IDataType.h index a26c703cd8a..666ffcccd52 100644 --- a/src/DataTypes/IDataType.h +++ b/src/DataTypes/IDataType.h @@ -599,3 +599,26 @@ template inline constexpr bool IsDataTypeEnum> = tr M(Float32) \ M(Float64) } + +/// See https://fmt.dev/latest/api.html#formatting-user-defined-types +template <> +struct fmt::formatter +{ + constexpr static auto parse(format_parse_context & ctx) + { + const auto * it = ctx.begin(); + const auto * end = ctx.end(); + + /// Only support {}. + if (it != end && *it != '}') + throw format_error("invalid format"); + + return it; + } + + template + auto format(const DB::DataTypePtr & type, FormatContext & ctx) + { + return format_to(ctx.out(), "{}", type->getName()); + } +}; From 09c99d8440fdd81c023f10e65cadcf8687ffd6ed Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Fri, 22 Jul 2022 14:27:45 +0800 Subject: [PATCH 216/672] Fix tests --- .../AggregateFunctionArray.h | 4 +-- .../AggregateFunctionCount.h | 6 +++-- src/AggregateFunctions/AggregateFunctionIf.h | 4 +-- .../AggregateFunctionQuantile.h | 3 ++- src/AggregateFunctions/IAggregateFunction.h | 3 +++ src/Core/Block.cpp | 25 ++++++++++++++++--- src/DataTypes/DataTypeAggregateFunction.cpp | 4 +-- 7 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/AggregateFunctions/AggregateFunctionArray.h b/src/AggregateFunctions/AggregateFunctionArray.h index 6b06accbbf8..85e0dfc8050 100644 --- a/src/AggregateFunctions/AggregateFunctionArray.h +++ b/src/AggregateFunctions/AggregateFunctionArray.h @@ -54,9 +54,9 @@ public: return nested_func->getBaseAggregateFunctionWithSameStateRepresentation(); } - DataTypePtr getStateType() const override + DataTypePtr getNormalizedStateType() const override { - return nested_func->getStateType(); + return nested_func->getNormalizedStateType(); } bool isVersioned() const override diff --git a/src/AggregateFunctions/AggregateFunctionCount.h b/src/AggregateFunctions/AggregateFunctionCount.h index abde8f65de0..a58eecf5aca 100644 --- a/src/AggregateFunctions/AggregateFunctionCount.h +++ b/src/AggregateFunctions/AggregateFunctionCount.h @@ -109,8 +109,9 @@ public: return this->getName() == rhs.getName(); } - DataTypePtr getStateType() const override + DataTypePtr getNormalizedStateType() const override { + /// Return normalized state type: count() AggregateFunctionProperties properties; return std::make_shared( AggregateFunctionFactory::instance().get(getName(), {}, {}, properties), DataTypes{}, Array{}); @@ -259,8 +260,9 @@ public: return this->getName() == rhs.getName(); } - DataTypePtr getStateType() const override + DataTypePtr getNormalizedStateType() const override { + /// Return normalized state type: count() AggregateFunctionProperties properties; return std::make_shared( AggregateFunctionFactory::instance().get(getName(), {}, {}, properties), DataTypes{}, Array{}); diff --git a/src/AggregateFunctions/AggregateFunctionIf.h b/src/AggregateFunctions/AggregateFunctionIf.h index 22854519970..18104f94fad 100644 --- a/src/AggregateFunctions/AggregateFunctionIf.h +++ b/src/AggregateFunctions/AggregateFunctionIf.h @@ -61,9 +61,9 @@ public: return nested_func->getBaseAggregateFunctionWithSameStateRepresentation(); } - DataTypePtr getStateType() const override + DataTypePtr getNormalizedStateType() const override { - return nested_func->getStateType(); + return nested_func->getNormalizedStateType(); } bool isVersioned() const override diff --git a/src/AggregateFunctions/AggregateFunctionQuantile.h b/src/AggregateFunctions/AggregateFunctionQuantile.h index 854cef73d9d..65edcaea13a 100644 --- a/src/AggregateFunctions/AggregateFunctionQuantile.h +++ b/src/AggregateFunctions/AggregateFunctionQuantile.h @@ -116,8 +116,9 @@ public: && this->haveEqualArgumentTypes(rhs); } - DataTypePtr getStateType() const override + DataTypePtr getNormalizedStateType() const override { + /// Return normalized state type: quantiles*(1)(...) Array params{1}; AggregateFunctionProperties properties; return std::make_shared( diff --git a/src/AggregateFunctions/IAggregateFunction.h b/src/AggregateFunctions/IAggregateFunction.h index ae8e32b4daf..87cb1006d36 100644 --- a/src/AggregateFunctions/IAggregateFunction.h +++ b/src/AggregateFunctions/IAggregateFunction.h @@ -73,6 +73,9 @@ public: /// Get the data type of internal state. By default it is AggregateFunction(name(params), argument_types...). virtual DataTypePtr getStateType() const; + /// Same as the above but normalize state types so that variants with the same binary representation will use the same type. + virtual DataTypePtr getNormalizedStateType() const { return getStateType(); } + /// Returns true if two aggregate functions have the same state representation in memory and the same serialization, /// so state of one aggregate function can be safely used with another. /// Examples: diff --git a/src/Core/Block.cpp b/src/Core/Block.cpp index 34e6c08c718..3b7595eb886 100644 --- a/src/Core/Block.cpp +++ b/src/Core/Block.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -68,9 +69,27 @@ static ReturnType checkColumnStructure(const ColumnWithTypeAndName & actual, con actual_column = &column_sparse->getValuesColumn(); } - if (actual_column->getName() != expected.column->getName()) - return onError("Block structure mismatch in " + std::string(context_description) + " stream: different columns:\n" - + actual.dumpStructure() + "\n" + expected.dumpStructure(), code); + const auto * actual_column_maybe_agg = typeid_cast(actual_column); + const auto * expected_column_maybe_agg = typeid_cast(expected.column.get()); + if (actual_column_maybe_agg && expected_column_maybe_agg) + { + if (!actual_column_maybe_agg->getAggregateFunction()->haveSameStateRepresentation(*expected_column_maybe_agg->getAggregateFunction())) + return onError( + fmt::format( + "Block structure mismatch in {} stream: different columns:\n{}\n{}", + context_description, + actual.dumpStructure(), + expected.dumpStructure()), + code); + } + else if (actual_column->getName() != expected.column->getName()) + return onError( + fmt::format( + "Block structure mismatch in {} stream: different columns:\n{}\n{}", + context_description, + actual.dumpStructure(), + expected.dumpStructure()), + code); if (isColumnConst(*actual.column) && isColumnConst(*expected.column)) { diff --git a/src/DataTypes/DataTypeAggregateFunction.cpp b/src/DataTypes/DataTypeAggregateFunction.cpp index 70b9c26e0cf..fdbd409385f 100644 --- a/src/DataTypes/DataTypeAggregateFunction.cpp +++ b/src/DataTypes/DataTypeAggregateFunction.cpp @@ -122,8 +122,8 @@ bool DataTypeAggregateFunction::equals(const IDataType & rhs) const if (typeid(rhs) != typeid(*this)) return false; - auto lhs_state_type = function->getStateType(); - auto rhs_state_type = typeid_cast(rhs).function->getStateType(); + auto lhs_state_type = function->getNormalizedStateType(); + auto rhs_state_type = typeid_cast(rhs).function->getNormalizedStateType(); if (typeid(lhs_state_type.get()) != typeid(rhs_state_type.get())) return false; From 8ab475ccf379347cafde4183f53fc3d22c78ae41 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Tue, 26 Jul 2022 19:14:38 +0800 Subject: [PATCH 217/672] Fix another case --- src/DataTypes/DataTypeAggregateFunction.cpp | 24 +++++++++++++++++-- .../02366_union_decimal_conversion.reference | 1 + .../02366_union_decimal_conversion.sql | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 tests/queries/0_stateless/02366_union_decimal_conversion.reference create mode 100644 tests/queries/0_stateless/02366_union_decimal_conversion.sql diff --git a/src/DataTypes/DataTypeAggregateFunction.cpp b/src/DataTypes/DataTypeAggregateFunction.cpp index fdbd409385f..8a0a95fc018 100644 --- a/src/DataTypes/DataTypeAggregateFunction.cpp +++ b/src/DataTypes/DataTypeAggregateFunction.cpp @@ -129,8 +129,28 @@ bool DataTypeAggregateFunction::equals(const IDataType & rhs) const return false; if (const auto * lhs_state = typeid_cast(lhs_state_type.get())) - return lhs_state->getNameWithoutVersion() - == typeid_cast(*rhs_state_type).getNameWithoutVersion(); + { + const auto & rhs_state = typeid_cast(*rhs_state_type); + + if (lhs_state->function->getName() != rhs_state.function->getName()) + return false; + + if (lhs_state->parameters.size() != lhs_state->parameters.size()) + return false; + + for (size_t i = 0; i < lhs_state->parameters.size(); ++i) + if (lhs_state->parameters[i] != rhs_state.parameters[i]) + return false; + + if (lhs_state->argument_types.size() != lhs_state->argument_types.size()) + return false; + + for (size_t i = 0; i < lhs_state->argument_types.size(); ++i) + if (!lhs_state->argument_types[i]->equals(*rhs_state.argument_types[i])) + return false; + + return true; + } return lhs_state_type->equals(*rhs_state_type); } diff --git a/tests/queries/0_stateless/02366_union_decimal_conversion.reference b/tests/queries/0_stateless/02366_union_decimal_conversion.reference new file mode 100644 index 00000000000..bfde072a796 --- /dev/null +++ b/tests/queries/0_stateless/02366_union_decimal_conversion.reference @@ -0,0 +1 @@ +2 2 diff --git a/tests/queries/0_stateless/02366_union_decimal_conversion.sql b/tests/queries/0_stateless/02366_union_decimal_conversion.sql new file mode 100644 index 00000000000..4451d07474b --- /dev/null +++ b/tests/queries/0_stateless/02366_union_decimal_conversion.sql @@ -0,0 +1 @@ +select sum(a), sum(b) from cluster(test_cluster_two_shards, view(select cast(number as Decimal(7, 2)) a, 0 as b from numbers(2) union all select 0, cast(number as Decimal(7, 2)) as b from numbers(2))); From 1ac716b7427b684711571374729bba88782087ae Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Sun, 31 Jul 2022 02:45:33 +0800 Subject: [PATCH 218/672] Remove no-s3-storage tag from tests --- .../01710_minmax_count_projection_constant_query.sql | 2 -- .../01710_minmax_count_projection_distributed_query.sql | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/queries/0_stateless/01710_minmax_count_projection_constant_query.sql b/tests/queries/0_stateless/01710_minmax_count_projection_constant_query.sql index 3b8356080b4..895cd957f0a 100644 --- a/tests/queries/0_stateless/01710_minmax_count_projection_constant_query.sql +++ b/tests/queries/0_stateless/01710_minmax_count_projection_constant_query.sql @@ -1,5 +1,3 @@ --- Tags: no-s3-storage - drop table if exists t; create table t (n int) engine MergeTree order by n; insert into t values (1); diff --git a/tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.sql b/tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.sql index 283a8c831d0..723c820908f 100644 --- a/tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.sql +++ b/tests/queries/0_stateless/01710_minmax_count_projection_distributed_query.sql @@ -1,5 +1,3 @@ --- Tags: no-s3-storage - drop table if exists t; create table t (n int, s String) engine MergeTree order by n; insert into t values (1, 'a'); From 55802099bcf42ccca359a1ddc462b20ab72123df Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Mon, 1 Aug 2022 11:37:36 +0800 Subject: [PATCH 219/672] test what will be wrong if state returns norm type --- src/AggregateFunctions/AggregateFunctionState.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AggregateFunctions/AggregateFunctionState.h b/src/AggregateFunctions/AggregateFunctionState.h index a598e4838cc..a31c96ceaf5 100644 --- a/src/AggregateFunctions/AggregateFunctionState.h +++ b/src/AggregateFunctions/AggregateFunctionState.h @@ -34,7 +34,7 @@ public: DataTypePtr getReturnType() const override { - return getStateType(); + return getNormalizedStateType(); } const IAggregateFunction & getBaseAggregateFunctionWithSameStateRepresentation() const override From 800ed546bef57d0ae6d7c7c2809c645aec32f487 Mon Sep 17 00:00:00 2001 From: HeenaBansal2009 Date: Mon, 1 Aug 2022 07:03:36 -0700 Subject: [PATCH 220/672] Updated as per comments --- programs/local/LocalServer.cpp | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index de1f08dd5f5..ed5a5c15cfb 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -325,24 +325,23 @@ void LocalServer::setupUsers() access_control.setPlaintextPasswordAllowed(config().getBool("allow_plaintext_password", true)); if (config().has("config-file") || fs::exists("config.xml")) { - String config_path = config().getString("config-file",""); + String config_path = config().getString("config-file", ""); bool has_user_directories = config().has("user_directories"); - const auto config_dir = std::filesystem::path{config_path}.remove_filename().string(); - String users_config_path = config().getString("users_config",""); - if (users_config_path.empty() && !has_user_directories) + const auto config_dir = fs::path{config_path}.remove_filename().string(); + String users_config_path = config().getString("users_config", ""); + + if (users_config_path.empty() && has_user_directories) + { + users_config_path = config().getString("user_directories.users_xml.path"); + if (fs::path(users_config_path).is_relative() + && fs::exists(fs::path(config_dir) / users_config_path)) + users_config_path = fs::path(config_dir) / users_config_path; + } + + if (users_config_path.empty()) users_config = getConfigurationFromXMLString(minimal_default_user_xml); else { - if (users_config_path.empty()) - { - if (!has_user_directories) - users_config_path = config_path; - } - if (has_user_directories) - users_config_path = config().getString("user_directories.users_xml.path",""); - - if (std::filesystem::path{users_config_path}.is_relative() && std::filesystem::exists(config_dir + users_config_path)) - users_config_path = config_dir + users_config_path; ConfigProcessor config_processor(users_config_path); const auto loaded_config = config_processor.loadConfig(); users_config = loaded_config.configuration; @@ -356,7 +355,6 @@ void LocalServer::setupUsers() throw Exception("Can't load config for users", ErrorCodes::CANNOT_LOAD_CONFIG); } - void LocalServer::connect() { connection_parameters = ConnectionParameters(config()); @@ -593,8 +591,7 @@ void LocalServer::processConfig() * Otherwise, metadata of temporary File(format, EXPLICIT_PATH) tables will pollute metadata/ directory; * if such tables will not be dropped, clickhouse-server will not be able to load them due to security reasons. */ - std::string default_database = config().getString("default_database", ""); - default_database = default_database + "_local"; + std::string default_database = config().getString("default_database", "_local"); DatabaseCatalog::instance().attachDatabase(default_database, std::make_shared(default_database, global_context)); global_context->setCurrentDatabase(default_database); applyCmdOptions(global_context); From 2a841d0860fc599694ebe639550798dd0547dfe1 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 1 Aug 2022 14:21:07 +0000 Subject: [PATCH 221/672] update docs for hash functions --- .../en/sql-reference/functions/hash-functions.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/en/sql-reference/functions/hash-functions.md b/docs/en/sql-reference/functions/hash-functions.md index 78f9d1b8285..ed48a590cd7 100644 --- a/docs/en/sql-reference/functions/hash-functions.md +++ b/docs/en/sql-reference/functions/hash-functions.md @@ -22,7 +22,7 @@ Consider using the [sipHash64](#hash_functions-siphash64) function instead. **Arguments** -The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). +The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). For some data types calculated value of hash function may be the same for the same values even if types of arguments differ (integers of different size, named and unnamed `Tuple` with the same data, `Map` and the corresponding `Array(Tuple(key, value))` type with the same data). **Returned Value** @@ -69,7 +69,7 @@ Function [interprets](../../sql-reference/functions/type-conversion-functions.md **Arguments** -The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). +The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). For some data types calculated value of hash function may be the same for the same values even if types of arguments differ (integers of different size, named and unnamed `Tuple` with the same data, `Map` and the corresponding `Array(Tuple(key, value))` type with the same data). **Returned Value** @@ -99,7 +99,7 @@ sipHash128(par1,...) **Arguments** -The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). +The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). For some data types calculated value of hash function may be the same for the same values even if types of arguments differ (integers of different size, named and unnamed `Tuple` with the same data, `Map` and the corresponding `Array(Tuple(key, value))` type with the same data). **Returned value** @@ -135,7 +135,7 @@ This is a fast non-cryptographic hash function. It uses the CityHash algorithm f **Arguments** -The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). +The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). For some data types calculated value of hash function may be the same for the same values even if types of arguments differ (integers of different size, named and unnamed `Tuple` with the same data, `Map` and the corresponding `Array(Tuple(key, value))` type with the same data). **Returned Value** @@ -275,7 +275,7 @@ These functions use the `Fingerprint64` and `Hash64` methods respectively from a **Arguments** -The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). +The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). For some data types calculated value of hash function may be the same for the same values even if types of arguments differ (integers of different size, named and unnamed `Tuple` with the same data, `Map` and the corresponding `Array(Tuple(key, value))` type with the same data).. **Returned Value** @@ -401,7 +401,7 @@ metroHash64(par1, ...) **Arguments** -The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). +The function takes a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). For some data types calculated value of hash function may be the same for the same values even if types of arguments differ (integers of different size, named and unnamed `Tuple` with the same data, `Map` and the corresponding `Array(Tuple(key, value))` type with the same data). **Returned Value** @@ -436,7 +436,7 @@ murmurHash2_64(par1, ...) **Arguments** -Both functions take a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). +Both functions take a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). For some data types calculated value of hash function may be the same for the same values even if types of arguments differ (integers of different size, named and unnamed `Tuple` with the same data, `Map` and the corresponding `Array(Tuple(key, value))` type with the same data). **Returned Value** @@ -504,7 +504,7 @@ murmurHash3_64(par1, ...) **Arguments** -Both functions take a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). +Both functions take a variable number of input parameters. Arguments can be any of the [supported data types](../../sql-reference/data-types/index.md). For some data types calculated value of hash function may be the same for the same values even if types of arguments differ (integers of different size, named and unnamed `Tuple` with the same data, `Map` and the corresponding `Array(Tuple(key, value))` type with the same data). **Returned Value** From c79893f4ef192b85293cb8007360397de517b77d Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Mon, 1 Aug 2022 10:58:08 -0400 Subject: [PATCH 222/672] remove call to sh, use random names suffix for temporaries --- .../decompressor.cpp | 103 ++++++++++-------- 1 file changed, 57 insertions(+), 46 deletions(-) diff --git a/utils/self-extracting-executable/decompressor.cpp b/utils/self-extracting-executable/decompressor.cpp index 9967a9f1904..63877a4ad83 100644 --- a/utils/self-extracting-executable/decompressor.cpp +++ b/utils/self-extracting-executable/decompressor.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #if (defined(OS_DARWIN) || defined(OS_FREEBSD)) && defined(__GNUC__) # include @@ -27,6 +28,9 @@ #include "types.h" +char decompressed_suffix[7] = {0}; +uint64_t decompressed_umask = 0; + /// decompress part int doDecompress(char * input, char * output, off_t & in_offset, off_t & out_offset, off_t input_size, off_t output_size, ZSTD_DCtx* dctx) @@ -221,7 +225,7 @@ int decompressFiles(int input_fd, char * path, char * name, bool & have_compress files_pointer += sizeof(FileData); size_t file_name_len = - (strcmp(input + files_pointer, name) ? le64toh(file_info.name_length) : le64toh(file_info.name_length) + 13); + (strcmp(input + files_pointer, name) ? le64toh(file_info.name_length) : le64toh(file_info.name_length) + 13 + 7); size_t file_path_len = path ? strlen(path) + 1 + file_name_len : file_name_len; @@ -236,7 +240,16 @@ int decompressFiles(int input_fd, char * path, char * name, bool & have_compress files_pointer += le64toh(file_info.name_length); if (file_name_len != le64toh(file_info.name_length)) { - strcat(file_name, ".decompressed"); + strcat(file_name, ".decompressed.XXXXXX"); + int fd = mkstemp(file_name); + if(fd == -1) + { + perror(nullptr); + return 1; + } + close(fd); + strncpy(decompressed_suffix, file_name + strlen(file_name) - 6, 6); + decompressed_umask = le64toh(file_info.umask); have_compressed_analoge = true; } @@ -295,44 +308,6 @@ int decompressFiles(int input_fd, char * path, char * name, bool & have_compress return 0; } -int do_system(const char * cmd) -{ - pid_t pid = fork(); - if (pid == -1) - return 1; - - if (pid == 0) - { - execlp("sh" , "sh", "-c", cmd, NULL); - - perror(nullptr); - exit(127); - } - - int ret = 0; - int status = 0; - while ((ret = waitpid(pid, &status, 0)) == -1) - { - if (errno != EINTR) - { - perror(nullptr); - return 1; - } - } - - if (WIFEXITED(status) && WEXITSTATUS(status)) - { - std::cerr << "Command [" << cmd << "] exited with code " << WEXITSTATUS(status) << std::endl; - return 1; - } - else if (WIFSIGNALED(status)) - { - std::cerr << "Command [" << cmd << "] killed by signal " << WTERMSIG(status) << std::endl; - return 1; - } - - return 0; -} int main(int/* argc*/, char* argv[]) { @@ -382,14 +357,50 @@ int main(int/* argc*/, char* argv[]) } else { - const char * const cmd_fmt = "mv %s %s.delete && cp %s.decompressed %s && rm %s.delete %s.decompressed"; - int cmd_len = snprintf(nullptr, 0, cmd_fmt, argv[0], argv[0], argv[0], argv[0], argv[0], argv[0]); - char command[cmd_len + 1]; - cmd_len = snprintf(command, cmd_len + 1, cmd_fmt, argv[0], argv[0], argv[0], argv[0], argv[0], argv[0]); - if (do_system(command)) + const char * const delete_name_fmt = "%s.delete.XXXXXX"; + int delete_name_len = snprintf(nullptr, 0, delete_name_fmt, argv[0]); + char delete_name[delete_name_len + 1]; + delete_name_len = snprintf(delete_name, delete_name_len + 1, delete_name_fmt, argv[0]); + int fd = mkstemp(delete_name); + if(fd == -1) + { + perror(nullptr); return 1; + } + close(fd); - execvp(argv[0], argv); + if(rename(argv[0], delete_name)) + { + perror(nullptr); + return 1; + } + + const char * const decompressed_name_fmt = "%s.decompressed.%s"; + int decompressed_name_len = snprintf(nullptr, 0, decompressed_name_fmt, argv[0], decompressed_suffix); + char decompressed_name[decompressed_name_len + 1]; + decompressed_name_len = snprintf(decompressed_name, decompressed_name_len + 1, decompressed_name_fmt, argv[0], decompressed_suffix); + + std::error_code ec; + std::filesystem::copy_file(static_cast(decompressed_name), argv[0], ec); + if (ec) + { + std::cerr << ec.message() << std::endl; + return 1; + } + + if (chmod(argv[0], decompressed_umask)) + { + perror(nullptr); + return 1; + } + + if (unlink(delete_name) || unlink(decompressed_name)) + { + perror(nullptr); + return 1; + } + + execv(argv[0], argv); /// This part of code will be reached only if error happened perror(nullptr); From 2a074288f02b232cdaf9e7d9553ee37f8c1b77ca Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Mon, 1 Aug 2022 11:06:54 -0400 Subject: [PATCH 223/672] style fix --- utils/self-extracting-executable/decompressor.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/self-extracting-executable/decompressor.cpp b/utils/self-extracting-executable/decompressor.cpp index 63877a4ad83..ac9981c7130 100644 --- a/utils/self-extracting-executable/decompressor.cpp +++ b/utils/self-extracting-executable/decompressor.cpp @@ -242,7 +242,7 @@ int decompressFiles(int input_fd, char * path, char * name, bool & have_compress { strcat(file_name, ".decompressed.XXXXXX"); int fd = mkstemp(file_name); - if(fd == -1) + if (fd == -1) { perror(nullptr); return 1; @@ -362,14 +362,14 @@ int main(int/* argc*/, char* argv[]) char delete_name[delete_name_len + 1]; delete_name_len = snprintf(delete_name, delete_name_len + 1, delete_name_fmt, argv[0]); int fd = mkstemp(delete_name); - if(fd == -1) + if (fd == -1) { perror(nullptr); return 1; } close(fd); - if(rename(argv[0], delete_name)) + if (rename(argv[0], delete_name)) { perror(nullptr); return 1; @@ -387,7 +387,7 @@ int main(int/* argc*/, char* argv[]) std::cerr << ec.message() << std::endl; return 1; } - + if (chmod(argv[0], decompressed_umask)) { perror(nullptr); From 0d68b1c67f4707fa97dab2fa9d36ebb7b5e044b9 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 1 Aug 2022 18:00:54 +0200 Subject: [PATCH 224/672] fix build with clang-15 --- .gitmodules | 2 +- contrib/boringssl | 2 +- contrib/curl-cmake/curl_config.h | 2 ++ contrib/krb5 | 2 +- contrib/nats-io | 2 +- contrib/nats-io-cmake/CMakeLists.txt | 2 ++ src/Common/Config/ConfigReloader.cpp | 2 -- src/Dictionaries/PolygonDictionaryUtils.cpp | 3 --- src/Interpreters/JoinToSubqueryTransformVisitor.cpp | 4 ---- src/Interpreters/ProfileEventsExt.cpp | 3 +-- src/Interpreters/SessionLog.cpp | 2 -- src/Parsers/ASTIdentifier.cpp | 2 +- utils/zookeeper-test/main.cpp | 2 -- 13 files changed, 10 insertions(+), 20 deletions(-) diff --git a/.gitmodules b/.gitmodules index b4e21f4a4a8..62b2f9d7766 100644 --- a/.gitmodules +++ b/.gitmodules @@ -201,7 +201,7 @@ [submodule "contrib/boringssl"] path = contrib/boringssl url = https://github.com/ClickHouse/boringssl.git - branch = MergeWithUpstream + branch = unknown_branch_from_artur [submodule "contrib/NuRaft"] path = contrib/NuRaft url = https://github.com/ClickHouse/NuRaft.git diff --git a/contrib/boringssl b/contrib/boringssl index c1e01a441d6..8061ac62d67 160000 --- a/contrib/boringssl +++ b/contrib/boringssl @@ -1 +1 @@ -Subproject commit c1e01a441d6db234f4f12e63a7657d1f9e6db9c1 +Subproject commit 8061ac62d67953e61b793042e33baf1352e67510 diff --git a/contrib/curl-cmake/curl_config.h b/contrib/curl-cmake/curl_config.h index 1efdd88600f..f56ba3eccd5 100644 --- a/contrib/curl-cmake/curl_config.h +++ b/contrib/curl-cmake/curl_config.h @@ -44,6 +44,8 @@ #define HAVE_SETJMP_H #define HAVE_SYS_STAT_H #define HAVE_UNISTD_H +#define HAVE_POLL_H +#define HAVE_PTHREAD_H #define ENABLE_IPV6 #define USE_OPENSSL diff --git a/contrib/krb5 b/contrib/krb5 index 5149dea4e2b..d879821c7a4 160000 --- a/contrib/krb5 +++ b/contrib/krb5 @@ -1 +1 @@ -Subproject commit 5149dea4e2be0f67707383d2682b897c14631374 +Subproject commit d879821c7a4c70b0c3ad739d9951d1a2b1903df7 diff --git a/contrib/nats-io b/contrib/nats-io index 6b2227f3675..1e2597c5461 160000 --- a/contrib/nats-io +++ b/contrib/nats-io @@ -1 +1 @@ -Subproject commit 6b2227f36757da090321e2d317569d2bd42c4cc1 +Subproject commit 1e2597c54616015077e53a26d56b6bac448eb1b6 diff --git a/contrib/nats-io-cmake/CMakeLists.txt b/contrib/nats-io-cmake/CMakeLists.txt index 5588d5750c4..579bf6f8ae4 100644 --- a/contrib/nats-io-cmake/CMakeLists.txt +++ b/contrib/nats-io-cmake/CMakeLists.txt @@ -18,6 +18,8 @@ elseif(WIN32) set(NATS_PLATFORM_INCLUDE "apple") endif() +add_definitions(-DNATS_HAS_TLS) + file(GLOB PS_SOURCES "${NATS_IO_SOURCE_DIR}/${NATS_PLATFORM_INCLUDE}/*.c") set(SRCS "${NATS_IO_SOURCE_DIR}/asynccb.c" diff --git a/src/Common/Config/ConfigReloader.cpp b/src/Common/Config/ConfigReloader.cpp index 65e12e09333..de7011b67bf 100644 --- a/src/Common/Config/ConfigReloader.cpp +++ b/src/Common/Config/ConfigReloader.cpp @@ -13,8 +13,6 @@ namespace fs = std::filesystem; namespace DB { -constexpr decltype(ConfigReloader::reload_interval) ConfigReloader::reload_interval; - ConfigReloader::ConfigReloader( const std::string & path_, const std::string & include_from_path_, diff --git a/src/Dictionaries/PolygonDictionaryUtils.cpp b/src/Dictionaries/PolygonDictionaryUtils.cpp index 313743c09b6..c28c7c15aa7 100644 --- a/src/Dictionaries/PolygonDictionaryUtils.cpp +++ b/src/Dictionaries/PolygonDictionaryUtils.cpp @@ -131,7 +131,6 @@ void SlabsPolygonIndex::indexBuild(const std::vector & polygons) /** Map of interesting edge ids to the index of left x, the index of right x */ std::vector edge_left(m, n), edge_right(m, n); - size_t total_index_edges = 0; size_t edges_it = 0; for (size_t l = 0, r = 1; r < sorted_x.size(); ++l, ++r) { @@ -170,12 +169,10 @@ void SlabsPolygonIndex::indexBuild(const std::vector & polygons) if (l & 1) { edges_index_tree[l++].emplace_back(all_edges[i]); - ++total_index_edges; } if (r & 1) { edges_index_tree[--r].emplace_back(all_edges[i]); - ++total_index_edges; } } } diff --git a/src/Interpreters/JoinToSubqueryTransformVisitor.cpp b/src/Interpreters/JoinToSubqueryTransformVisitor.cpp index e07430c0feb..3426c0a1b17 100644 --- a/src/Interpreters/JoinToSubqueryTransformVisitor.cpp +++ b/src/Interpreters/JoinToSubqueryTransformVisitor.cpp @@ -229,7 +229,6 @@ bool needRewrite(ASTSelectQuery & select, std::vectortable_join->as(); if (join.kind == ASTTableJoin::Kind::Comma) throw Exception("COMMA to CROSS JOIN rewriter is not enabled or cannot rewrite query", ErrorCodes::NOT_IMPLEMENTED); - - if (join.using_expression_list) - ++num_using; } if (num_tables - num_array_join <= 2) diff --git a/src/Interpreters/ProfileEventsExt.cpp b/src/Interpreters/ProfileEventsExt.cpp index 94f67713551..1a8176624d2 100644 --- a/src/Interpreters/ProfileEventsExt.cpp +++ b/src/Interpreters/ProfileEventsExt.cpp @@ -162,9 +162,8 @@ void getProfileEvents( dumpMemoryTracker(group_snapshot, columns, server_display_name); Block curr_block; - size_t rows = 0; - for (; profile_queue->tryPop(curr_block); ++rows) + while (profile_queue->tryPop(curr_block)) { auto curr_columns = curr_block.getColumns(); for (size_t j = 0; j < curr_columns.size(); ++j) diff --git a/src/Interpreters/SessionLog.cpp b/src/Interpreters/SessionLog.cpp index 5f303ab64b4..29357875488 100644 --- a/src/Interpreters/SessionLog.cpp +++ b/src/Interpreters/SessionLog.cpp @@ -182,12 +182,10 @@ void SessionLogElement::appendToBlock(MutableColumns & columns) const auto & names_col = *settings_tuple_col.getColumnPtr(0)->assumeMutable(); auto & values_col = assert_cast(*settings_tuple_col.getColumnPtr(1)->assumeMutable()); - size_t items_added = 0; for (const auto & kv : settings) { names_col.insert(kv.first); values_col.insert(kv.second); - ++items_added; } auto & offsets = settings_array_col.getOffsets(); diff --git a/src/Parsers/ASTIdentifier.cpp b/src/Parsers/ASTIdentifier.cpp index a889680c81f..ca8ac0e8e64 100644 --- a/src/Parsers/ASTIdentifier.cpp +++ b/src/Parsers/ASTIdentifier.cpp @@ -32,7 +32,7 @@ ASTIdentifier::ASTIdentifier(std::vector && name_parts_, bool special, s semantic->legacy_compound = true; if (!name_params.empty()) { - size_t params = 0; + [[maybe_unused]] size_t params = 0; for (const auto & part [[maybe_unused]] : name_parts) { if (part.empty()) diff --git a/utils/zookeeper-test/main.cpp b/utils/zookeeper-test/main.cpp index 5d0b54aa74b..2149dedf594 100644 --- a/utils/zookeeper-test/main.cpp +++ b/utils/zookeeper-test/main.cpp @@ -282,11 +282,9 @@ void createConcurrent(zkutil::ZooKeeper & testzk, const std::string & zkhost, si asyncs.push_back(std::async(std::launch::async, callback)); } - size_t i = 0; for (auto & async : asyncs) { async.wait(); - i++; } } From afcf76e8991d6cfe3e6f7d1604a6a6f96a2db126 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Mon, 1 Aug 2022 18:27:17 +0200 Subject: [PATCH 225/672] Add cloudflare DNS as a fallback --- tests/ci/worker/init_runner.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/ci/worker/init_runner.sh b/tests/ci/worker/init_runner.sh index 74ad4be2547..dddf3bd4174 100644 --- a/tests/ci/worker/init_runner.sh +++ b/tests/ci/worker/init_runner.sh @@ -17,6 +17,20 @@ export RUNNER_URL="https://github.com/ClickHouse" INSTANCE_ID=$(ec2metadata --instance-id) export INSTANCE_ID +# Add cloudflare DNS as a fallback +# Get default gateway interface +IFACE=$(ip -j route l | jq '.[]|select(.dst == "default").dev' -r) +# `Link 2 (eth0): 172.31.0.2` +ETH_DNS=$(resolvectl dns "$IFACE") || : +CLOUDFRONT_NS=1.1.1.1 +if [[ "$ETH_DNS" ]] && [[ "${ETH_DNS#*: }" != *"$CLOUDFRONT_NS"* ]]; then + # Cut the leading legend + ETH_DNS=${ETH_DNS#*: } + # shellcheck disable=SC2206 + new_dns=(${ETH_DNS} "$CLOUDFRONT_NS") + resolvectl dns "$IFACE" "${new_dns[@]}" +fi + # combine labels RUNNER_TYPE=$(/usr/local/bin/aws ec2 describe-tags --filters "Name=resource-id,Values=$INSTANCE_ID" --query "Tags[?Key=='github:runner-type'].Value" --output text) LABELS="self-hosted,Linux,$(uname -m),$RUNNER_TYPE" From 8820774fe35f63dc34032d01fdeebaa728dc6106 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Mon, 1 Aug 2022 13:39:26 -0400 Subject: [PATCH 226/672] do not rename original file - just remove --- .../self-extracting-executable/decompressor.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/utils/self-extracting-executable/decompressor.cpp b/utils/self-extracting-executable/decompressor.cpp index ac9981c7130..312734d3865 100644 --- a/utils/self-extracting-executable/decompressor.cpp +++ b/utils/self-extracting-executable/decompressor.cpp @@ -357,19 +357,7 @@ int main(int/* argc*/, char* argv[]) } else { - const char * const delete_name_fmt = "%s.delete.XXXXXX"; - int delete_name_len = snprintf(nullptr, 0, delete_name_fmt, argv[0]); - char delete_name[delete_name_len + 1]; - delete_name_len = snprintf(delete_name, delete_name_len + 1, delete_name_fmt, argv[0]); - int fd = mkstemp(delete_name); - if (fd == -1) - { - perror(nullptr); - return 1; - } - close(fd); - - if (rename(argv[0], delete_name)) + if (unlink(argv[0])) { perror(nullptr); return 1; @@ -394,7 +382,7 @@ int main(int/* argc*/, char* argv[]) return 1; } - if (unlink(delete_name) || unlink(decompressed_name)) + if (unlink(decompressed_name)) { perror(nullptr); return 1; From 3cc20f05babe5cec22490460b74e1d45a62345f4 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 1 Aug 2022 20:47:14 +0300 Subject: [PATCH 227/672] Update run.sh --- docker/test/stress/run.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index 33b60ae7ef9..42493627934 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -218,6 +218,8 @@ clickhouse-client --query "SELECT 'Server successfully started', 'OK'" >> /test_ || (echo -e 'Server failed to start (see application_errors.txt and clickhouse-server.clean.log)\tFAIL' >> /test_output/test_results.tsv \ && grep -a ".*Application" /var/log/clickhouse-server/clickhouse-server.log > /test_output/application_errors.txt) +stop + [ -f /var/log/clickhouse-server/clickhouse-server.log ] || echo -e "Server log does not exist\tFAIL" [ -f /var/log/clickhouse-server/stderr.log ] || echo -e "Stderr log does not exist\tFAIL" @@ -278,7 +280,6 @@ mkdir previous_release_package_folder echo $previous_release_tag | download_release_packets && echo -e 'Download script exit code\tOK' >> /test_output/test_results.tsv \ || echo -e 'Download script failed\tFAIL' >> /test_output/test_results.tsv -stop mv /var/log/clickhouse-server/clickhouse-server.log /var/log/clickhouse-server/clickhouse-server.clean.log # Check if we cloned previous release repository successfully From 77c143aa235ce4959d01a0c94ff24eb8c7ff56ee Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 1 Aug 2022 17:56:27 +0000 Subject: [PATCH 228/672] Fix extra column after ARRAY JOIN optimization. --- src/Interpreters/ActionsDAG.cpp | 3 +-- .../queries/0_stateless/01881_union_header_mismatch_bug.sql | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Interpreters/ActionsDAG.cpp b/src/Interpreters/ActionsDAG.cpp index b91fd7ac5cf..3a2bc830cd5 100644 --- a/src/Interpreters/ActionsDAG.cpp +++ b/src/Interpreters/ActionsDAG.cpp @@ -1513,8 +1513,7 @@ ActionsDAG::SplitResult ActionsDAG::splitActionsBeforeArrayJoin(const NameSet & } auto res = split(split_nodes); - /// Do not remove array joined columns if they are not used. - /// res.first->project_input = false; + res.second->project_input = project_input; return res; } diff --git a/tests/queries/0_stateless/01881_union_header_mismatch_bug.sql b/tests/queries/0_stateless/01881_union_header_mismatch_bug.sql index 9a220ffd49f..bf8749fb046 100644 --- a/tests/queries/0_stateless/01881_union_header_mismatch_bug.sql +++ b/tests/queries/0_stateless/01881_union_header_mismatch_bug.sql @@ -28,3 +28,8 @@ WHERE number IN SELECT number FROM numbers(5) ) order by label, number; + +SELECT NULL FROM +(SELECT [1048575, NULL] AS ax, 2147483648 AS c) t1 ARRAY JOIN ax +INNER JOIN (SELECT NULL AS c) t2 USING (c); + From bc10725f0209c8782c1786eeee2454fae7a65aa7 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 1 Aug 2022 20:26:43 +0200 Subject: [PATCH 229/672] fix --- src/Databases/DatabaseReplicated.cpp | 33 ++++++++++++------- .../01111_create_drop_replicated_db_stress.sh | 3 +- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Databases/DatabaseReplicated.cpp b/src/Databases/DatabaseReplicated.cpp index 40654281543..c94ab7279e4 100644 --- a/src/Databases/DatabaseReplicated.cpp +++ b/src/Databases/DatabaseReplicated.cpp @@ -266,7 +266,7 @@ void DatabaseReplicated::tryConnectToZooKeeperAndInitDatabase(LoadingStrictnessL { if (replica_host_id == DROPPED_MARK && !is_create_query) { - LOG_WARNING(log, "Database {} exists locally, but marked dropped in ZooKeeper {}. " + LOG_WARNING(log, "Database {} exists locally, but marked dropped in ZooKeeper ({}). " "Will not try to start it up", getDatabaseName(), replica_path); is_probably_dropped = true; return; @@ -290,7 +290,7 @@ void DatabaseReplicated::tryConnectToZooKeeperAndInitDatabase(LoadingStrictnessL { /// It's not CREATE query, but replica does not exist. Probably it was dropped. /// Do not create anything, continue as readonly. - LOG_WARNING(log, "Database {} exists locally, but its replica does not exist in ZooKeeper {}. " + LOG_WARNING(log, "Database {} exists locally, but its replica does not exist in ZooKeeper ({}). " "Assuming it was dropped, will not try to start it up", getDatabaseName(), replica_path); is_probably_dropped = true; return; @@ -961,6 +961,8 @@ void DatabaseReplicated::drop(ContextPtr context_) void DatabaseReplicated::stopReplication() { + if (is_probably_dropped) + return; if (ddl_worker) ddl_worker->shutdown(); } @@ -983,13 +985,20 @@ void DatabaseReplicated::dropTable(ContextPtr local_context, const String & tabl txn->addOp(zkutil::makeRemoveRequest(metadata_zk_path, -1)); } + auto table = tryGetTable(table_name, getContext()); + if (table->getName() == "MaterializedView" || table->getName() == "WindowView") + { + /// Avoid recursive locking of metadata_mutex + table->dropInnerTableIfAny(sync, local_context); + } + std::lock_guard lock{metadata_mutex}; UInt64 new_digest = tables_metadata_digest; new_digest -= getMetadataHash(table_name); if (txn) txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1)); - DatabaseAtomic::dropTable(local_context, table_name, sync); + DatabaseAtomic::dropTableImpl(local_context, table_name, sync); tables_metadata_digest = new_digest; assert(debugCheckDigest(local_context)); @@ -1001,6 +1010,15 @@ void DatabaseReplicated::renameTable(ContextPtr local_context, const String & ta auto txn = local_context->getZooKeeperMetadataTransaction(); assert(txn); + if (this != &to_database) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Moving tables between databases is not supported for Replicated engine"); + if (table_name == to_table_name) + throw Exception(ErrorCodes::INCORRECT_QUERY, "Cannot rename table to itself"); + if (!isTableExist(table_name, local_context)) + throw Exception(ErrorCodes::UNKNOWN_TABLE, "Table {} does not exist", table_name); + if (exchange && !to_database.isTableExist(to_table_name, local_context)) + throw Exception(ErrorCodes::UNKNOWN_TABLE, "Table {} does not exist", to_table_name); + String statement = readMetadataFile(table_name); String statement_to; if (exchange) @@ -1008,15 +1026,6 @@ void DatabaseReplicated::renameTable(ContextPtr local_context, const String & ta if (txn->isInitialQuery()) { - if (this != &to_database) - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Moving tables between databases is not supported for Replicated engine"); - if (table_name == to_table_name) - throw Exception(ErrorCodes::INCORRECT_QUERY, "Cannot rename table to itself"); - if (!isTableExist(table_name, local_context)) - throw Exception(ErrorCodes::UNKNOWN_TABLE, "Table {} does not exist", table_name); - if (exchange && !to_database.isTableExist(to_table_name, local_context)) - throw Exception(ErrorCodes::UNKNOWN_TABLE, "Table {} does not exist", to_table_name); - String metadata_zk_path = zookeeper_path + "/metadata/" + escapeForFileName(table_name); String metadata_zk_path_to = zookeeper_path + "/metadata/" + escapeForFileName(to_table_name); txn->addOp(zkutil::makeRemoveRequest(metadata_zk_path, -1)); diff --git a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh index c3446035c30..addf503e44a 100755 --- a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh +++ b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh @@ -16,7 +16,8 @@ function create_db() # So CREATE TABLE queries will fail on all replicas except one. But it's still makes sense for a stress test. $CLICKHOUSE_CLIENT --allow_experimental_database_replicated=1 --query \ "create database if not exists ${CLICKHOUSE_DATABASE}_repl_$SUFFIX engine=Replicated('/test/01111/$CLICKHOUSE_TEST_ZOOKEEPER_PREFIX', '$SHARD', '$REPLICA')" \ - 2>&1| grep -Fa "Exception: " | grep -Fv "REPLICA_IS_ALREADY_EXIST" | grep -Fiv "Will not try to start it up" | grep -Fv "Coordination::Exception" + 2>&1| grep -Fa "Exception: " | grep -Fv "REPLICA_IS_ALREADY_EXIST" | grep -Fiv "Will not try to start it up" | \ + grep -Fv "Coordination::Exception" | grep -Fv "already contains some data and it does not look like Replicated database path" sleep 0.$RANDOM done } From e65cef79adc79cdf61ab4ccd43f20aa5584f6e50 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Mon, 1 Aug 2022 14:32:32 -0400 Subject: [PATCH 230/672] some refactoring --- utils/self-extracting-executable/decompressor.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/utils/self-extracting-executable/decompressor.cpp b/utils/self-extracting-executable/decompressor.cpp index 312734d3865..5615216e262 100644 --- a/utils/self-extracting-executable/decompressor.cpp +++ b/utils/self-extracting-executable/decompressor.cpp @@ -347,22 +347,16 @@ int main(int/* argc*/, char* argv[]) if (0 != close(input_fd)) perror(nullptr); - if (!have_compressed_analoge) + if (unlink(argv[0])) { - printf("No target executable - decompression only was performed.\n"); - /// remove file - execlp("rm", "rm", argv[0], NULL); perror(nullptr); return 1; } + + if (!have_compressed_analoge) + printf("No target executable - decompression only was performed.\n"); else { - if (unlink(argv[0])) - { - perror(nullptr); - return 1; - } - const char * const decompressed_name_fmt = "%s.decompressed.%s"; int decompressed_name_len = snprintf(nullptr, 0, decompressed_name_fmt, argv[0], decompressed_suffix); char decompressed_name[decompressed_name_len + 1]; From 43e8ca5ba81cd989a5dc5601083e92d60ff8c92b Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 1 Aug 2022 18:40:21 +0000 Subject: [PATCH 231/672] fix CANNOT_READ_ALL_DATA with pread_threadpool --- src/Disks/IO/ThreadPoolReader.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Disks/IO/ThreadPoolReader.cpp b/src/Disks/IO/ThreadPoolReader.cpp index 2004ffbea83..01ff412cec4 100644 --- a/src/Disks/IO/ThreadPoolReader.cpp +++ b/src/Disks/IO/ThreadPoolReader.cpp @@ -1,4 +1,5 @@ #include "ThreadPoolReader.h" +#include #include #include #include @@ -7,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +73,13 @@ namespace ErrorCodes } +/// According to man, Linux 5.9 and 5.10 have a bug in preadv2() with the RWF_NOWAIT. +/// https://manpages.debian.org/testing/manpages-dev/preadv2.2.en.html#BUGS +static bool hasBugInPreadV2() +{ + VersionNumber linux_version(Poco::Environment::osVersion()); + return linux_version >= VersionNumber{5, 9, 0} && linux_version < VersionNumber{5, 11, 0}; +} ThreadPoolReader::ThreadPoolReader(size_t pool_size, size_t queue_size_) : pool(pool_size, pool_size, queue_size_) @@ -88,7 +97,11 @@ std::future ThreadPoolReader::submit(Request reques /// Check if data is already in page cache with preadv2 syscall. /// We don't want to depend on new Linux kernel. - static std::atomic has_pread_nowait_support{true}; + /// But kernels 5.9 and 5.10 have a bug where preadv2() with the + /// RWF_NOWAIT flag may return 0 even when not at end of file. + /// It can't be distinguished from the real eof, so we have to + /// disable pread with nowait. + static std::atomic has_pread_nowait_support = !hasBugInPreadV2(); if (has_pread_nowait_support.load(std::memory_order_relaxed)) { From 69347028c54edcedc9a43e6795c52c15ad6972ec Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Tue, 2 Aug 2022 03:08:25 +0800 Subject: [PATCH 232/672] Another test --- src/AggregateFunctions/AggregateFunctionArray.h | 4 ++-- src/AggregateFunctions/AggregateFunctionCount.h | 4 ++-- src/AggregateFunctions/AggregateFunctionIf.h | 4 ++-- src/AggregateFunctions/AggregateFunctionQuantile.h | 2 +- src/AggregateFunctions/AggregateFunctionState.h | 2 +- src/AggregateFunctions/IAggregateFunction.h | 3 --- src/DataTypes/DataTypeAggregateFunction.cpp | 4 ++-- 7 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/AggregateFunctions/AggregateFunctionArray.h b/src/AggregateFunctions/AggregateFunctionArray.h index 85e0dfc8050..6b06accbbf8 100644 --- a/src/AggregateFunctions/AggregateFunctionArray.h +++ b/src/AggregateFunctions/AggregateFunctionArray.h @@ -54,9 +54,9 @@ public: return nested_func->getBaseAggregateFunctionWithSameStateRepresentation(); } - DataTypePtr getNormalizedStateType() const override + DataTypePtr getStateType() const override { - return nested_func->getNormalizedStateType(); + return nested_func->getStateType(); } bool isVersioned() const override diff --git a/src/AggregateFunctions/AggregateFunctionCount.h b/src/AggregateFunctions/AggregateFunctionCount.h index a58eecf5aca..5ccf7d8c942 100644 --- a/src/AggregateFunctions/AggregateFunctionCount.h +++ b/src/AggregateFunctions/AggregateFunctionCount.h @@ -109,7 +109,7 @@ public: return this->getName() == rhs.getName(); } - DataTypePtr getNormalizedStateType() const override + DataTypePtr getStateType() const override { /// Return normalized state type: count() AggregateFunctionProperties properties; @@ -260,7 +260,7 @@ public: return this->getName() == rhs.getName(); } - DataTypePtr getNormalizedStateType() const override + DataTypePtr getStateType() const override { /// Return normalized state type: count() AggregateFunctionProperties properties; diff --git a/src/AggregateFunctions/AggregateFunctionIf.h b/src/AggregateFunctions/AggregateFunctionIf.h index 18104f94fad..22854519970 100644 --- a/src/AggregateFunctions/AggregateFunctionIf.h +++ b/src/AggregateFunctions/AggregateFunctionIf.h @@ -61,9 +61,9 @@ public: return nested_func->getBaseAggregateFunctionWithSameStateRepresentation(); } - DataTypePtr getNormalizedStateType() const override + DataTypePtr getStateType() const override { - return nested_func->getNormalizedStateType(); + return nested_func->getStateType(); } bool isVersioned() const override diff --git a/src/AggregateFunctions/AggregateFunctionQuantile.h b/src/AggregateFunctions/AggregateFunctionQuantile.h index 65edcaea13a..729da5a52c3 100644 --- a/src/AggregateFunctions/AggregateFunctionQuantile.h +++ b/src/AggregateFunctions/AggregateFunctionQuantile.h @@ -116,7 +116,7 @@ public: && this->haveEqualArgumentTypes(rhs); } - DataTypePtr getNormalizedStateType() const override + DataTypePtr getStateType() const override { /// Return normalized state type: quantiles*(1)(...) Array params{1}; diff --git a/src/AggregateFunctions/AggregateFunctionState.h b/src/AggregateFunctions/AggregateFunctionState.h index a31c96ceaf5..a598e4838cc 100644 --- a/src/AggregateFunctions/AggregateFunctionState.h +++ b/src/AggregateFunctions/AggregateFunctionState.h @@ -34,7 +34,7 @@ public: DataTypePtr getReturnType() const override { - return getNormalizedStateType(); + return getStateType(); } const IAggregateFunction & getBaseAggregateFunctionWithSameStateRepresentation() const override diff --git a/src/AggregateFunctions/IAggregateFunction.h b/src/AggregateFunctions/IAggregateFunction.h index 87cb1006d36..ae8e32b4daf 100644 --- a/src/AggregateFunctions/IAggregateFunction.h +++ b/src/AggregateFunctions/IAggregateFunction.h @@ -73,9 +73,6 @@ public: /// Get the data type of internal state. By default it is AggregateFunction(name(params), argument_types...). virtual DataTypePtr getStateType() const; - /// Same as the above but normalize state types so that variants with the same binary representation will use the same type. - virtual DataTypePtr getNormalizedStateType() const { return getStateType(); } - /// Returns true if two aggregate functions have the same state representation in memory and the same serialization, /// so state of one aggregate function can be safely used with another. /// Examples: diff --git a/src/DataTypes/DataTypeAggregateFunction.cpp b/src/DataTypes/DataTypeAggregateFunction.cpp index 8a0a95fc018..3e6dcadd689 100644 --- a/src/DataTypes/DataTypeAggregateFunction.cpp +++ b/src/DataTypes/DataTypeAggregateFunction.cpp @@ -122,8 +122,8 @@ bool DataTypeAggregateFunction::equals(const IDataType & rhs) const if (typeid(rhs) != typeid(*this)) return false; - auto lhs_state_type = function->getNormalizedStateType(); - auto rhs_state_type = typeid_cast(rhs).function->getNormalizedStateType(); + auto lhs_state_type = function->getStateType(); + auto rhs_state_type = typeid_cast(rhs).function->getStateType(); if (typeid(lhs_state_type.get()) != typeid(rhs_state_type.get())) return false; From 55af8878a52079e969907532cc374380b33d8032 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 1 Aug 2022 22:18:34 +0300 Subject: [PATCH 233/672] Revert "Update arrow to fix possible data race" --- contrib/arrow | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/arrow b/contrib/arrow index 3e03c6de41a..efdcd015cfd 160000 --- a/contrib/arrow +++ b/contrib/arrow @@ -1 +1 @@ -Subproject commit 3e03c6de41a86df2fc54a61e9be1abaefeff6b0e +Subproject commit efdcd015cfdee1b6aa349c9ca227ca12c3d697f5 From 82e78a03e51c7fb8c2bd640eae020414fcddff0d Mon Sep 17 00:00:00 2001 From: Yuko Takagi <70714860+yukotakagi@users.noreply.github.com> Date: Mon, 1 Aug 2022 14:09:28 -0600 Subject: [PATCH 234/672] Add URL for release webinar (#39796) Add URL for release webinar. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93d2da1e39d..20340883853 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,4 @@ ClickHouse® is an open-source column-oriented database management system that a * [Contacts](https://clickhouse.com/company/contact) can help to get your questions answered if there are any. ## Upcoming events -* **v22.8 Release Webinar** Original creator, co-founder, and CTO of ClickHouse Alexey Milovidov will walk us through the highlights of the release, provide live demos, and share vision into what is coming in the roadmap. +* [**v22.8 Release Webinar**](https://clickhouse.com/company/events/v22-8-release-webinar) Original creator, co-founder, and CTO of ClickHouse Alexey Milovidov will walk us through the highlights of the release, provide live demos, and share vision into what is coming in the roadmap. From 64cbecf0c8e32b86d2049f0b41069efdf36458a3 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 2 Aug 2022 00:13:20 +0000 Subject: [PATCH 235/672] fix build on non linux systems --- src/Disks/IO/ThreadPoolReader.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Disks/IO/ThreadPoolReader.cpp b/src/Disks/IO/ThreadPoolReader.cpp index 01ff412cec4..772eb1de371 100644 --- a/src/Disks/IO/ThreadPoolReader.cpp +++ b/src/Disks/IO/ThreadPoolReader.cpp @@ -73,6 +73,7 @@ namespace ErrorCodes } +#if defined(OS_LINUX) /// According to man, Linux 5.9 and 5.10 have a bug in preadv2() with the RWF_NOWAIT. /// https://manpages.debian.org/testing/manpages-dev/preadv2.2.en.html#BUGS static bool hasBugInPreadV2() @@ -80,6 +81,7 @@ static bool hasBugInPreadV2() VersionNumber linux_version(Poco::Environment::osVersion()); return linux_version >= VersionNumber{5, 9, 0} && linux_version < VersionNumber{5, 11, 0}; } +#endif ThreadPoolReader::ThreadPoolReader(size_t pool_size, size_t queue_size_) : pool(pool_size, pool_size, queue_size_) From 488ae322957ffec5a75540eaa86d918bd529e18e Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Tue, 2 Aug 2022 03:23:05 +0300 Subject: [PATCH 236/672] Update init_runner.sh --- tests/ci/worker/init_runner.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/worker/init_runner.sh b/tests/ci/worker/init_runner.sh index dddf3bd4174..20e65d5ac04 100644 --- a/tests/ci/worker/init_runner.sh +++ b/tests/ci/worker/init_runner.sh @@ -19,7 +19,7 @@ export INSTANCE_ID # Add cloudflare DNS as a fallback # Get default gateway interface -IFACE=$(ip -j route l | jq '.[]|select(.dst == "default").dev' -r) +IFACE=$(ip --json route list | jq '.[]|select(.dst == "default").dev' --raw-output) # `Link 2 (eth0): 172.31.0.2` ETH_DNS=$(resolvectl dns "$IFACE") || : CLOUDFRONT_NS=1.1.1.1 From b275aae95d970670c21b89f21ff6295fb7db5d8a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Tue, 2 Aug 2022 03:23:53 +0300 Subject: [PATCH 237/672] Update init_runner.sh --- tests/ci/worker/init_runner.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ci/worker/init_runner.sh b/tests/ci/worker/init_runner.sh index 20e65d5ac04..3dfd1761e88 100644 --- a/tests/ci/worker/init_runner.sh +++ b/tests/ci/worker/init_runner.sh @@ -22,12 +22,12 @@ export INSTANCE_ID IFACE=$(ip --json route list | jq '.[]|select(.dst == "default").dev' --raw-output) # `Link 2 (eth0): 172.31.0.2` ETH_DNS=$(resolvectl dns "$IFACE") || : -CLOUDFRONT_NS=1.1.1.1 -if [[ "$ETH_DNS" ]] && [[ "${ETH_DNS#*: }" != *"$CLOUDFRONT_NS"* ]]; then +CLOUDFLARE_NS=1.1.1.1 +if [[ "$ETH_DNS" ]] && [[ "${ETH_DNS#*: }" != *"$CLOUDFLARE_NS"* ]]; then # Cut the leading legend ETH_DNS=${ETH_DNS#*: } # shellcheck disable=SC2206 - new_dns=(${ETH_DNS} "$CLOUDFRONT_NS") + new_dns=(${ETH_DNS} "$CLOUDFLARE_NS") resolvectl dns "$IFACE" "${new_dns[@]}" fi From b1919d045f45df9f63f527e3323cc66e303a2ff9 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Tue, 2 Aug 2022 05:34:14 +0300 Subject: [PATCH 238/672] Update ThreadPoolReader.cpp --- src/Disks/IO/ThreadPoolReader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Disks/IO/ThreadPoolReader.cpp b/src/Disks/IO/ThreadPoolReader.cpp index 772eb1de371..8e51b1bbfbd 100644 --- a/src/Disks/IO/ThreadPoolReader.cpp +++ b/src/Disks/IO/ThreadPoolReader.cpp @@ -76,10 +76,11 @@ namespace ErrorCodes #if defined(OS_LINUX) /// According to man, Linux 5.9 and 5.10 have a bug in preadv2() with the RWF_NOWAIT. /// https://manpages.debian.org/testing/manpages-dev/preadv2.2.en.html#BUGS +/// We also disable it for older Linux kernels, because according to user's reports, RedHat-patched kernels might be also affected. static bool hasBugInPreadV2() { VersionNumber linux_version(Poco::Environment::osVersion()); - return linux_version >= VersionNumber{5, 9, 0} && linux_version < VersionNumber{5, 11, 0}; + return linux_version < VersionNumber{5, 11, 0}; } #endif From c8aaa32f9ca75f89fe5848e1266fbca120a4ddc4 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Tue, 2 Aug 2022 10:43:48 +0800 Subject: [PATCH 239/672] Revert "Another test" This reverts commit 69347028c54edcedc9a43e6795c52c15ad6972ec. --- src/AggregateFunctions/AggregateFunctionArray.h | 4 ++-- src/AggregateFunctions/AggregateFunctionCount.h | 4 ++-- src/AggregateFunctions/AggregateFunctionIf.h | 4 ++-- src/AggregateFunctions/AggregateFunctionQuantile.h | 2 +- src/AggregateFunctions/AggregateFunctionState.h | 2 +- src/AggregateFunctions/IAggregateFunction.h | 3 +++ src/DataTypes/DataTypeAggregateFunction.cpp | 4 ++-- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/AggregateFunctions/AggregateFunctionArray.h b/src/AggregateFunctions/AggregateFunctionArray.h index 6b06accbbf8..85e0dfc8050 100644 --- a/src/AggregateFunctions/AggregateFunctionArray.h +++ b/src/AggregateFunctions/AggregateFunctionArray.h @@ -54,9 +54,9 @@ public: return nested_func->getBaseAggregateFunctionWithSameStateRepresentation(); } - DataTypePtr getStateType() const override + DataTypePtr getNormalizedStateType() const override { - return nested_func->getStateType(); + return nested_func->getNormalizedStateType(); } bool isVersioned() const override diff --git a/src/AggregateFunctions/AggregateFunctionCount.h b/src/AggregateFunctions/AggregateFunctionCount.h index 5ccf7d8c942..a58eecf5aca 100644 --- a/src/AggregateFunctions/AggregateFunctionCount.h +++ b/src/AggregateFunctions/AggregateFunctionCount.h @@ -109,7 +109,7 @@ public: return this->getName() == rhs.getName(); } - DataTypePtr getStateType() const override + DataTypePtr getNormalizedStateType() const override { /// Return normalized state type: count() AggregateFunctionProperties properties; @@ -260,7 +260,7 @@ public: return this->getName() == rhs.getName(); } - DataTypePtr getStateType() const override + DataTypePtr getNormalizedStateType() const override { /// Return normalized state type: count() AggregateFunctionProperties properties; diff --git a/src/AggregateFunctions/AggregateFunctionIf.h b/src/AggregateFunctions/AggregateFunctionIf.h index 22854519970..18104f94fad 100644 --- a/src/AggregateFunctions/AggregateFunctionIf.h +++ b/src/AggregateFunctions/AggregateFunctionIf.h @@ -61,9 +61,9 @@ public: return nested_func->getBaseAggregateFunctionWithSameStateRepresentation(); } - DataTypePtr getStateType() const override + DataTypePtr getNormalizedStateType() const override { - return nested_func->getStateType(); + return nested_func->getNormalizedStateType(); } bool isVersioned() const override diff --git a/src/AggregateFunctions/AggregateFunctionQuantile.h b/src/AggregateFunctions/AggregateFunctionQuantile.h index 729da5a52c3..65edcaea13a 100644 --- a/src/AggregateFunctions/AggregateFunctionQuantile.h +++ b/src/AggregateFunctions/AggregateFunctionQuantile.h @@ -116,7 +116,7 @@ public: && this->haveEqualArgumentTypes(rhs); } - DataTypePtr getStateType() const override + DataTypePtr getNormalizedStateType() const override { /// Return normalized state type: quantiles*(1)(...) Array params{1}; diff --git a/src/AggregateFunctions/AggregateFunctionState.h b/src/AggregateFunctions/AggregateFunctionState.h index a598e4838cc..a31c96ceaf5 100644 --- a/src/AggregateFunctions/AggregateFunctionState.h +++ b/src/AggregateFunctions/AggregateFunctionState.h @@ -34,7 +34,7 @@ public: DataTypePtr getReturnType() const override { - return getStateType(); + return getNormalizedStateType(); } const IAggregateFunction & getBaseAggregateFunctionWithSameStateRepresentation() const override diff --git a/src/AggregateFunctions/IAggregateFunction.h b/src/AggregateFunctions/IAggregateFunction.h index ae8e32b4daf..87cb1006d36 100644 --- a/src/AggregateFunctions/IAggregateFunction.h +++ b/src/AggregateFunctions/IAggregateFunction.h @@ -73,6 +73,9 @@ public: /// Get the data type of internal state. By default it is AggregateFunction(name(params), argument_types...). virtual DataTypePtr getStateType() const; + /// Same as the above but normalize state types so that variants with the same binary representation will use the same type. + virtual DataTypePtr getNormalizedStateType() const { return getStateType(); } + /// Returns true if two aggregate functions have the same state representation in memory and the same serialization, /// so state of one aggregate function can be safely used with another. /// Examples: diff --git a/src/DataTypes/DataTypeAggregateFunction.cpp b/src/DataTypes/DataTypeAggregateFunction.cpp index 3e6dcadd689..8a0a95fc018 100644 --- a/src/DataTypes/DataTypeAggregateFunction.cpp +++ b/src/DataTypes/DataTypeAggregateFunction.cpp @@ -122,8 +122,8 @@ bool DataTypeAggregateFunction::equals(const IDataType & rhs) const if (typeid(rhs) != typeid(*this)) return false; - auto lhs_state_type = function->getStateType(); - auto rhs_state_type = typeid_cast(rhs).function->getStateType(); + auto lhs_state_type = function->getNormalizedStateType(); + auto rhs_state_type = typeid_cast(rhs).function->getNormalizedStateType(); if (typeid(lhs_state_type.get()) != typeid(rhs_state_type.get())) return false; From 433b961d5c34c55e8b5f0ce773ce3cf1f6c07538 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 1 Aug 2022 20:21:48 -0700 Subject: [PATCH 240/672] changed code for expected seg fault --- src/Loggers/OwnJSONPatternFormatter.cpp | 6 ++++- .../test_structured_logging_json/test.py | 27 +++---------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/src/Loggers/OwnJSONPatternFormatter.cpp b/src/Loggers/OwnJSONPatternFormatter.cpp index 4ab2066b548..d12506e2451 100644 --- a/src/Loggers/OwnJSONPatternFormatter.cpp +++ b/src/Loggers/OwnJSONPatternFormatter.cpp @@ -82,7 +82,11 @@ void OwnJSONPatternFormatter::formatExtended(const DB::ExtendedLogMessage & msg_ writeJSONString("source_file", wb, settings); DB::writeChar(':', wb); - writeJSONString(msg.getSourceFile(), wb, settings); + const char * source_file = msg.getSourceFile(); + if (source_file != nullptr) + writeJSONString(source_file, wb, settings); + else + writeJSONString("", wb, settings); DB::writeChar(',', wb); writeJSONString("source_line", wb, settings); diff --git a/tests/integration/test_structured_logging_json/test.py b/tests/integration/test_structured_logging_json/test.py index fdbf3651a0a..fff9365b45e 100644 --- a/tests/integration/test_structured_logging_json/test.py +++ b/tests/integration/test_structured_logging_json/test.py @@ -5,7 +5,6 @@ import json cluster = ClickHouseCluster(__file__) node = cluster.add_instance("node", main_configs=["configs/config_json.xml"]) - @pytest.fixture(scope="module") def start_cluster(): try: @@ -14,18 +13,6 @@ def start_cluster(): finally: cluster.shutdown() - -def get_log_array(logs): - log_array = [] - temp_log = "" - for i in range(0, len(logs)): - temp_log += logs[i] - if logs[i] == "}": - log_array.append(temp_log) - temp_log = "" - return log_array - - def is_json(log_json): try: json.loads(log_json) @@ -37,13 +24,7 @@ def is_json(log_json): def test_structured_logging_json_format(start_cluster): node.query("SELECT 1") - logs = node.grep_in_log(" ") - log_array = get_log_array(logs) - result = True - for i in range(0, len(log_array)): - temporary_result = is_json(log_array[i]) - result &= temporary_result - # we will test maximum 5 logs - if i >= min(4, len(log_array) - 1): - break - assert result + logs = node.grep_in_log("").split('\n') + length = min(10, len(logs)) + for i in range(0, length): + assert is_json(logs[i]) From b98e645ff7316af83457aa5df3dee8ca660c3cef Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Tue, 2 Aug 2022 11:33:45 +0800 Subject: [PATCH 241/672] Revert "test what will be wrong if state returns norm type" This reverts commit 55802099bcf42ccca359a1ddc462b20ab72123df. --- src/AggregateFunctions/AggregateFunctionState.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AggregateFunctions/AggregateFunctionState.h b/src/AggregateFunctions/AggregateFunctionState.h index a31c96ceaf5..a598e4838cc 100644 --- a/src/AggregateFunctions/AggregateFunctionState.h +++ b/src/AggregateFunctions/AggregateFunctionState.h @@ -34,7 +34,7 @@ public: DataTypePtr getReturnType() const override { - return getNormalizedStateType(); + return getStateType(); } const IAggregateFunction & getBaseAggregateFunctionWithSameStateRepresentation() const override From 495df1da07d662904f1541d58a13da479e31b080 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 1 Aug 2022 20:48:57 -0700 Subject: [PATCH 242/672] reformat test.py to avoid style-check error --- tests/integration/test_structured_logging_json/test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_structured_logging_json/test.py b/tests/integration/test_structured_logging_json/test.py index fff9365b45e..d56e9a41968 100644 --- a/tests/integration/test_structured_logging_json/test.py +++ b/tests/integration/test_structured_logging_json/test.py @@ -5,6 +5,7 @@ import json cluster = ClickHouseCluster(__file__) node = cluster.add_instance("node", main_configs=["configs/config_json.xml"]) + @pytest.fixture(scope="module") def start_cluster(): try: @@ -13,6 +14,7 @@ def start_cluster(): finally: cluster.shutdown() + def is_json(log_json): try: json.loads(log_json) @@ -24,7 +26,7 @@ def is_json(log_json): def test_structured_logging_json_format(start_cluster): node.query("SELECT 1") - logs = node.grep_in_log("").split('\n') + logs = node.grep_in_log("").split("\n") length = min(10, len(logs)) for i in range(0, length): assert is_json(logs[i]) From b3b3c371f068d390a7fed7623197e7122929f204 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 2 Aug 2022 09:20:02 +0200 Subject: [PATCH 243/672] Update KeeperStorage.h --- src/Coordination/KeeperStorage.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index a96387c5def..3e7d27fc1b4 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -73,11 +73,11 @@ public: enum DigestVersion : uint8_t { NO_DIGEST = 0, - V0 = 1, - V1 = 2 // added system nodes that modify the digest on startup so digest from V0 is invalid + V1 = 1, + V2 = 2 // added system nodes that modify the digest on startup so digest from V0 is invalid }; - static constexpr auto CURRENT_DIGEST_VERSION = DigestVersion::V1; + static constexpr auto CURRENT_DIGEST_VERSION = DigestVersion::V2; struct ResponseForSession { From 8d5279d9bcbbe0ab44e8d6231f93e7ad90aee865 Mon Sep 17 00:00:00 2001 From: avogar Date: Tue, 2 Aug 2022 07:58:13 +0000 Subject: [PATCH 244/672] Fix style --- tests/queries/0_stateless/02375_pretty_formats.sql.j2 | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 tests/queries/0_stateless/02375_pretty_formats.sql.j2 diff --git a/tests/queries/0_stateless/02375_pretty_formats.sql.j2 b/tests/queries/0_stateless/02375_pretty_formats.sql.j2 old mode 100755 new mode 100644 From 6976c690bda83689766c6152ffd6c33c43c9fdbf Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Tue, 2 Aug 2022 10:09:18 +0200 Subject: [PATCH 245/672] Revert "Revert "Update arrow to fix possible data race"" --- contrib/arrow | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/arrow b/contrib/arrow index efdcd015cfd..3e03c6de41a 160000 --- a/contrib/arrow +++ b/contrib/arrow @@ -1 +1 @@ -Subproject commit efdcd015cfdee1b6aa349c9ca227ca12c3d697f5 +Subproject commit 3e03c6de41a86df2fc54a61e9be1abaefeff6b0e From 91e3e2f18bdf70d8d3cc66d16074605d9743a40c Mon Sep 17 00:00:00 2001 From: Wangyang Guo Date: Tue, 2 Aug 2022 15:38:27 +0800 Subject: [PATCH 246/672] KeyCondition: optimize applyFunction in multi-thread scenario Construct and deconstruct args (ColumnsWithTypeAndName) will inc/dec ref_count (actually this is a atomic lock inc/dec operation) to share_ptr, which may share the same DataTypePtr among different threads. This will have a lock contention issue in large parallel situation. The patch try to minimize `args` scope and reduce unnecessary construct/destory of instances. It will improve the performance in multi-thread cases. --- src/Storages/MergeTree/KeyCondition.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index daf31698aad..362c01d87d6 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -607,9 +607,9 @@ static FieldRef applyFunction(const FunctionBasePtr & func, const DataTypePtr & result_idx = i; } - ColumnsWithTypeAndName args{(*columns)[field.column_idx]}; if (result_idx == columns->size()) { + ColumnsWithTypeAndName args{(*columns)[field.column_idx]}; field.columns->emplace_back(ColumnWithTypeAndName {nullptr, func->getResultType(), result_name}); (*columns)[result_idx].column = func->execute(args, (*columns)[result_idx].type, columns->front().column->size()); } From 3be13e4f925fdca311e32830e4bd569e63dada7b Mon Sep 17 00:00:00 2001 From: avogar Date: Tue, 2 Aug 2022 08:25:46 +0000 Subject: [PATCH 247/672] Try fix build under ppc64 --- contrib/arrow | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/arrow b/contrib/arrow index 3e03c6de41a..611e4a63107 160000 --- a/contrib/arrow +++ b/contrib/arrow @@ -1 +1 @@ -Subproject commit 3e03c6de41a86df2fc54a61e9be1abaefeff6b0e +Subproject commit 611e4a631072d822074f6ea119a2b8d20c8760ca From e832153e93561fb7e075e1aa423ce2c933f77cec Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Tue, 2 Aug 2022 08:37:58 +0000 Subject: [PATCH 248/672] Typos --- docker/packager/packager | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/packager/packager b/docker/packager/packager index 3b9d181d3e0..cf0e555d57c 100755 --- a/docker/packager/packager +++ b/docker/packager/packager @@ -215,7 +215,7 @@ def parse_env_variables( cmake_flags.append(f"-DCMAKE_C_COMPILER={cc}") cmake_flags.append(f"-DCMAKE_CXX_COMPILER={cxx}") - # Create combined output archive for split build and for performance tests. + # Create combined output archive for shared library build and for performance tests. if package_type == "coverity": result.append("COMBINED_OUTPUT=coverity") result.append('COVERITY_TOKEN="$COVERITY_TOKEN"') @@ -269,8 +269,8 @@ def parse_env_variables( "-DUSE_STATIC_LIBRARIES=0 -DSPLIT_SHARED_LIBRARIES=1" ) # We can't always build utils because it requires too much space, but - # we have to build them at least in some way in CI. The split build is - # probably the least heavy disk-wise. + # we have to build them at least in some way in CI. The shared library + # build is probably the least heavy disk-wise. cmake_flags.append("-DENABLE_UTILS=1") # utils are not included into clickhouse-bundle, so build everything build_target = "all" From c5eab9c760c8f33752978f4f90bd33f65a60604c Mon Sep 17 00:00:00 2001 From: avogar Date: Tue, 2 Aug 2022 08:38:15 +0000 Subject: [PATCH 249/672] Delete test for s3 --- .../02367_cancel_write_into_s3.reference | 0 .../0_stateless/02367_cancel_write_into_s3.sh | 25 ------------------- 2 files changed, 25 deletions(-) delete mode 100644 tests/queries/0_stateless/02367_cancel_write_into_s3.reference delete mode 100755 tests/queries/0_stateless/02367_cancel_write_into_s3.sh diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.reference b/tests/queries/0_stateless/02367_cancel_write_into_s3.reference deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh b/tests/queries/0_stateless/02367_cancel_write_into_s3.sh deleted file mode 100755 index 54f6d2a941b..00000000000 --- a/tests/queries/0_stateless/02367_cancel_write_into_s3.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# Tags: no-fasttest - -CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -# shellcheck source=../shell_config.sh -. "$CURDIR"/../shell_config.sh - - -for i in $(seq 1 10); -do - $CLICKHOUSE_CLIENT --query_id="02367_$i" -q "insert into function s3('http://localhost:11111/test/02367_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings s3_truncate_on_insert=1, output_format_parallel_formatting=1" 2> /dev/null & -done - -sleep 2 - -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null 2>&1 - -for i in $(seq 1 10); -do - $CLICKHOUSE_CLIENT --query_id="02367_$i" -q "insert into function s3('http://localhost:11111/test/02367_data_$i.jsonl') select range(number % 1000) from numbers(100000) settings s3_truncate_on_insert=1, output_format_parallel_formatting=0" 2> /dev/null & -done - -sleep 2 - -$CLICKHOUSE_CLIENT -q "kill query where startsWith(query_id, '02367_') sync" > /dev/null 2>&1 From e5c47cb26f3d9cd15ebbdb1383865469aca81dc0 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 2 Aug 2022 12:10:53 +0300 Subject: [PATCH 250/672] Update run.sh --- docker/test/stress/run.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index 42493627934..577fb0a659a 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -218,6 +218,10 @@ clickhouse-client --query "SELECT 'Server successfully started', 'OK'" >> /test_ || (echo -e 'Server failed to start (see application_errors.txt and clickhouse-server.clean.log)\tFAIL' >> /test_output/test_results.tsv \ && grep -a ".*Application" /var/log/clickhouse-server/clickhouse-server.log > /test_output/application_errors.txt) +echo "Get previous release tag" +previous_release_tag=$(clickhouse-client --query="SELECT version()" | get_previous_release_tag) +echo $previous_release_tag + stop [ -f /var/log/clickhouse-server/clickhouse-server.log ] || echo -e "Server log does not exist\tFAIL" @@ -267,10 +271,6 @@ zgrep -Fa " received signal " /test_output/gdb.log > /dev/null \ echo -e "Backward compatibility check\n" -echo "Get previous release tag" -previous_release_tag=$(clickhouse-client --query="SELECT version()" | get_previous_release_tag) -echo $previous_release_tag - echo "Clone previous release repository" git clone https://github.com/ClickHouse/ClickHouse.git --no-tags --progress --branch=$previous_release_tag --no-recurse-submodules --depth=1 previous_release_repository From 094b28b869766ca43e2ac5b427e7d220b889a572 Mon Sep 17 00:00:00 2001 From: qianmoQ Date: Tue, 2 Aug 2022 17:17:08 +0800 Subject: [PATCH 251/672] Support cte statement for antlr4 syntax file #39810 --- utils/antlr/ClickHouseParser.g4 | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/utils/antlr/ClickHouseParser.g4 b/utils/antlr/ClickHouseParser.g4 index 24db6478aa0..13194a8c2d2 100644 --- a/utils/antlr/ClickHouseParser.g4 +++ b/utils/antlr/ClickHouseParser.g4 @@ -26,6 +26,20 @@ query | truncateStmt // DDL | useStmt | watchStmt + | ctes? selectStmt + ; + +// CTE statement +ctes + : WITH namedQuery (',' namedQuery)* + ; + +namedQuery + : name=identifier (columnAliases)? AS '(' query ')' + ; + +columnAliases + : '(' identifier (',' identifier)* ')' ; // ALTER statement From 5050e0aca52189cbe3bc07c10dfd2a40e0180107 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 2 Aug 2022 12:48:31 +0300 Subject: [PATCH 252/672] Revert "Limit number of analyze for one query" --- src/Core/Settings.h | 2 +- src/Interpreters/Context.h | 21 ------------------- src/Interpreters/InterpreterSelectQuery.cpp | 11 ---------- .../02337_join_analyze_stuck.reference | 0 .../0_stateless/02337_join_analyze_stuck.sql | 15 ------------- 5 files changed, 1 insertion(+), 48 deletions(-) delete mode 100644 tests/queries/0_stateless/02337_join_analyze_stuck.reference delete mode 100644 tests/queries/0_stateless/02337_join_analyze_stuck.sql diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 4e88a46dc53..51197022908 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -344,7 +344,7 @@ static constexpr UInt64 operator""_GiB(unsigned long long value) M(UInt64, max_temporary_non_const_columns, 0, "", 0) \ \ M(UInt64, max_subquery_depth, 100, "", 0) \ - M(UInt64, max_pipeline_depth, 10000, "", 0) \ + M(UInt64, max_pipeline_depth, 1000, "", 0) \ M(UInt64, max_ast_depth, 1000, "Maximum depth of query syntax tree. Checked after parsing.", 0) \ M(UInt64, max_ast_elements, 50000, "Maximum size of query syntax tree in number of nodes. Checked after parsing.", 0) \ M(UInt64, max_expanded_ast_elements, 500000, "Maximum size of query syntax tree in number of nodes after expansion of aliases and the asterisk.", 0) \ diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index 68947d140ff..cf508c7bfdb 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -367,27 +367,6 @@ public: // Top-level OpenTelemetry trace context for the query. Makes sense only for a query context. OpenTelemetryTraceContext query_trace_context; - /// Some counters for current query execution. - /// Most of them are workarounds and should be removed in the future. - struct KitchenSink - { - std::atomic analyze_counter = 0; - - KitchenSink() = default; - - KitchenSink(const KitchenSink & rhs) - : analyze_counter(rhs.analyze_counter.load()) - {} - - KitchenSink & operator=(const KitchenSink & rhs) - { - analyze_counter = rhs.analyze_counter.load(); - return *this; - } - }; - - KitchenSink kitchen_sink; - private: using SampleBlockCache = std::unordered_map; mutable SampleBlockCache sample_block_cache; diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 7a217a061e1..3af95fd77b8 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -98,7 +98,6 @@ namespace ErrorCodes extern const int SAMPLING_NOT_SUPPORTED; extern const int ILLEGAL_FINAL; extern const int ILLEGAL_PREWHERE; - extern const int TOO_DEEP_PIPELINE; extern const int TOO_MANY_COLUMNS; extern const int LOGICAL_ERROR; extern const int NOT_IMPLEMENTED; @@ -499,14 +498,6 @@ InterpreterSelectQuery::InterpreterSelectQuery( auto analyze = [&] (bool try_move_to_prewhere) { - if (context->hasQueryContext()) - { - std::atomic & current_query_analyze_count = context->getQueryContext()->kitchen_sink.analyze_counter; - ++current_query_analyze_count; - if (settings.max_pipeline_depth && current_query_analyze_count >= settings.max_pipeline_depth) - throw DB::Exception(ErrorCodes::TOO_DEEP_PIPELINE, "Query analyze overflow. Try to increase `max_pipeline_depth` or simplify the query"); - } - /// Allow push down and other optimizations for VIEW: replace with subquery and rewrite it. ASTPtr view_table; if (view) @@ -645,7 +636,6 @@ InterpreterSelectQuery::InterpreterSelectQuery( analyze(shouldMoveToPrewhere()); bool need_analyze_again = false; - if (analysis_result.prewhere_constant_filter_description.always_false || analysis_result.prewhere_constant_filter_description.always_true) { if (analysis_result.prewhere_constant_filter_description.always_true) @@ -654,7 +644,6 @@ InterpreterSelectQuery::InterpreterSelectQuery( query.setExpression(ASTSelectQuery::Expression::PREWHERE, std::make_shared(0u)); need_analyze_again = true; } - if (analysis_result.where_constant_filter_description.always_false || analysis_result.where_constant_filter_description.always_true) { if (analysis_result.where_constant_filter_description.always_true) diff --git a/tests/queries/0_stateless/02337_join_analyze_stuck.reference b/tests/queries/0_stateless/02337_join_analyze_stuck.reference deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/queries/0_stateless/02337_join_analyze_stuck.sql b/tests/queries/0_stateless/02337_join_analyze_stuck.sql deleted file mode 100644 index 9bdc418f028..00000000000 --- a/tests/queries/0_stateless/02337_join_analyze_stuck.sql +++ /dev/null @@ -1,15 +0,0 @@ --- Tags: long - --- https://github.com/ClickHouse/ClickHouse/issues/21557 - -SET max_pipeline_depth = 1000; - -EXPLAIN SYNTAX -WITH - x AS ( SELECT number FROM numbers(10) ), - cross_sales AS ( - SELECT 1 AS xx - FROM x, x AS d1, x AS d2, x AS d3, x AS d4, x AS d5, x AS d6, x AS d7, x AS d8, x AS d9 - WHERE x.number = d9.number - ) -SELECT xx FROM cross_sales WHERE xx = 2000; -- { serverError TOO_DEEP_PIPELINE } From ad55c2f55a1f85368061bdb9483b4b057c172859 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 2 Aug 2022 12:49:29 +0300 Subject: [PATCH 253/672] Revert "Revert "tests: enable back 02232_dist_insert_send_logs_level_hung"" --- .../02232_dist_insert_send_logs_level_hung.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.sh b/tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.sh index a8dce5cb516..322e7e73991 100755 --- a/tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.sh +++ b/tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: disabled +# Tags: long, no-parallel # Tag: no-parallel - to heavy # Tag: long - to heavy @@ -33,11 +33,14 @@ $CLICKHOUSE_CLIENT "${client_opts[@]}" -nm -q " create materialized view mv_02232 to out_02232 as select * from in_02232; " +# 600 is the default timeout of clickhouse-test, and 30 is just a safe padding, +# to avoid hung query check triggering +insert_timeout=$((600-30)) +# Increase timeouts to avoid timeout during trying to send Log packet to +# the remote side, when the socket is full. insert_client_opts=( - # Increase timeouts to avoid timeout during trying to send Log packet to - # the remote side, when the socket is full. - --send_timeout 86400 - --receive_timeout 86400 + --send_timeout "$insert_timeout" + --receive_timeout "$insert_timeout" ) # 250 seconds is enough to trigger the query hung (even in debug build) # From 26ef951fdea1d7a71515ba475f3cc59c9bda9129 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Tue, 2 Aug 2022 12:04:26 +0200 Subject: [PATCH 254/672] Add DNS trick to the dockerhub proxy too --- tests/ci/worker/dockerhub_proxy_template.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/ci/worker/dockerhub_proxy_template.sh b/tests/ci/worker/dockerhub_proxy_template.sh index add15158d94..38f2bc6486a 100644 --- a/tests/ci/worker/dockerhub_proxy_template.sh +++ b/tests/ci/worker/dockerhub_proxy_template.sh @@ -1,6 +1,20 @@ #!/usr/bin/env bash set -xeuo pipefail +# Add cloudflare DNS as a fallback +# Get default gateway interface +IFACE=$(ip --json route list | jq '.[]|select(.dst == "default").dev' --raw-output) +# `Link 2 (eth0): 172.31.0.2` +ETH_DNS=$(resolvectl dns "$IFACE") || : +CLOUDFLARE_NS=1.1.1.1 +if [[ "$ETH_DNS" ]] && [[ "${ETH_DNS#*: }" != *"$CLOUDFLARE_NS"* ]]; then + # Cut the leading legend + ETH_DNS=${ETH_DNS#*: } + # shellcheck disable=SC2206 + new_dns=(${ETH_DNS} "$CLOUDFLARE_NS") + resolvectl dns "$IFACE" "${new_dns[@]}" +fi + mkdir /home/ubuntu/registrystorage sed -i 's/preserve_hostname: false/preserve_hostname: true/g' /etc/cloud/cloud.cfg From ce70f3dacb4b197e7b96617b2d9de742fd477f20 Mon Sep 17 00:00:00 2001 From: Anton Kozlov Date: Thu, 14 Jul 2022 15:07:47 +0000 Subject: [PATCH 255/672] fixed 02303_query_kind test; added logging in 02352_grouby_shadows_arg test --- src/Interpreters/FunctionMaskingArgumentCheckVisitor.h | 3 ++- src/Interpreters/TreeOptimizer.cpp | 6 +++--- tests/queries/0_stateless/02303_query_kind.reference | 8 ++++---- .../0_stateless/02352_grouby_shadows_arg.reference | 5 +++++ tests/queries/0_stateless/02352_grouby_shadows_arg.sql | 3 ++- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Interpreters/FunctionMaskingArgumentCheckVisitor.h b/src/Interpreters/FunctionMaskingArgumentCheckVisitor.h index b455dd6ac5a..1f3c44f8087 100644 --- a/src/Interpreters/FunctionMaskingArgumentCheckVisitor.h +++ b/src/Interpreters/FunctionMaskingArgumentCheckVisitor.h @@ -10,7 +10,8 @@ namespace DB { /// Checks from bottom to top if a function's alias shadows the name -/// of one of it's arguments +/// of one of it's arguments, e.g. +/// SELECT toString(dummy) as dummy FROM system.one GROUP BY dummy; class FunctionMaskingArgumentCheckMatcher { public: diff --git a/src/Interpreters/TreeOptimizer.cpp b/src/Interpreters/TreeOptimizer.cpp index 1d0b41e796a..2623904d817 100644 --- a/src/Interpreters/TreeOptimizer.cpp +++ b/src/Interpreters/TreeOptimizer.cpp @@ -154,9 +154,9 @@ void optimizeGroupBy(ASTSelectQuery * select_query, ContextPtr context) continue; } } - /// don't optimise functions that shadow any of it's arguments: - /// https://github.com/ClickHouse/ClickHouse/issues/37032 - else if (!function->alias.empty()) + /// don't optimise functions that shadow any of it's arguments, e.g.: + /// SELECT toString(dummy) as dummy FROM system.one GROUP BY dummy; + if (!function->alias.empty()) { FunctionMaskingArgumentCheckVisitor::Data data{.alias=function->alias}; FunctionMaskingArgumentCheckVisitor(data).visit(function->arguments); diff --git a/tests/queries/0_stateless/02303_query_kind.reference b/tests/queries/0_stateless/02303_query_kind.reference index b899b9f5b45..163f8b0ed5e 100644 --- a/tests/queries/0_stateless/02303_query_kind.reference +++ b/tests/queries/0_stateless/02303_query_kind.reference @@ -20,17 +20,17 @@ clickhouse-client --query_kind initial_query -q explain plan header=1 select toS Expression ((Projection + Before ORDER BY)) Header: dummy String Aggregating - Header: dummy UInt8 + Header: toString(dummy) String Expression (Before GROUP BY) - Header: dummy UInt8 + Header: toString(dummy) String ReadFromStorage (SystemOne) Header: dummy UInt8 clickhouse-local --query_kind initial_query -q explain plan header=1 select toString(dummy) as dummy from system.one group by dummy Expression ((Projection + Before ORDER BY)) Header: dummy String Aggregating - Header: dummy UInt8 + Header: toString(dummy) String Expression (Before GROUP BY) - Header: dummy UInt8 + Header: toString(dummy) String ReadFromStorage (SystemOne) Header: dummy UInt8 diff --git a/tests/queries/0_stateless/02352_grouby_shadows_arg.reference b/tests/queries/0_stateless/02352_grouby_shadows_arg.reference index c6ed459bf52..d67f6f6df02 100644 --- a/tests/queries/0_stateless/02352_grouby_shadows_arg.reference +++ b/tests/queries/0_stateless/02352_grouby_shadows_arg.reference @@ -1,6 +1,11 @@ +-- { echoOn } +SELECT toString(dummy) as dummy FROM remote('127.{1,1}', 'system.one') GROUP BY dummy; 0 +SELECT toString(dummy+1) as dummy FROM remote('127.{1,1}', 'system.one') GROUP BY dummy; 1 +SELECT toString((toInt8(dummy)+2) * (toInt8(dummy)+2)) as dummy FROM remote('127.{1,1}', system.one) GROUP BY dummy; 4 +SELECT round(number % 3) AS number FROM remote('127.{1,1}', numbers(20)) GROUP BY number ORDER BY number ASC; 0 1 2 diff --git a/tests/queries/0_stateless/02352_grouby_shadows_arg.sql b/tests/queries/0_stateless/02352_grouby_shadows_arg.sql index c293b250f86..34076057a4b 100644 --- a/tests/queries/0_stateless/02352_grouby_shadows_arg.sql +++ b/tests/queries/0_stateless/02352_grouby_shadows_arg.sql @@ -1,5 +1,6 @@ SET prefer_localhost_replica=0; +-- { echoOn } SELECT toString(dummy) as dummy FROM remote('127.{1,1}', 'system.one') GROUP BY dummy; SELECT toString(dummy+1) as dummy FROM remote('127.{1,1}', 'system.one') GROUP BY dummy; SELECT toString((toInt8(dummy)+2) * (toInt8(dummy)+2)) as dummy FROM remote('127.{1,1}', system.one) GROUP BY dummy; -SELECT round(number % 3) AS number FROM remote('127.{1,1}', numbers(20)) GROUP BY number ORDER BY number ASC; \ No newline at end of file +SELECT round(number % 3) AS number FROM remote('127.{1,1}', numbers(20)) GROUP BY number ORDER BY number ASC; From 691526b7de8f43c930b920cdb298fbcf28231fa5 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 2 Aug 2022 12:51:00 +0200 Subject: [PATCH 256/672] ignore unknown hosts when looking for current host --- src/Interpreters/DDLTask.cpp | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/Interpreters/DDLTask.cpp b/src/Interpreters/DDLTask.cpp index 26343f7a8bb..880a624376d 100644 --- a/src/Interpreters/DDLTask.cpp +++ b/src/Interpreters/DDLTask.cpp @@ -25,6 +25,7 @@ namespace ErrorCodes extern const int UNKNOWN_TYPE_OF_QUERY; extern const int INCONSISTENT_CLUSTER_DEFINITION; extern const int LOGICAL_ERROR; + extern const int DNS_ERROR; } HostID HostID::fromString(const String & host_port_str) @@ -169,17 +170,33 @@ ContextMutablePtr DDLTaskBase::makeQueryContext(ContextPtr from_context, const Z bool DDLTask::findCurrentHostID(ContextPtr global_context, Poco::Logger * log) { bool host_in_hostlist = false; + std::exception_ptr first_exception = nullptr; for (const HostID & host : entry.hosts) { auto maybe_secure_port = global_context->getTCPPortSecure(); - /// The port is considered local if it matches TCP or TCP secure port that the server is listening. - bool is_local_port = (maybe_secure_port && host.isLocalAddress(*maybe_secure_port)) - || host.isLocalAddress(global_context->getTCPPort()); + try + { + /// The port is considered local if it matches TCP or TCP secure port that the server is listening. + bool is_local_port + = (maybe_secure_port && host.isLocalAddress(*maybe_secure_port)) || host.isLocalAddress(global_context->getTCPPort()); - if (!is_local_port) + if (!is_local_port) + continue; + } + catch (const Exception & e) + { + if (e.code() != ErrorCodes::DNS_ERROR) + throw; + + if (!first_exception) + first_exception = std::current_exception(); + + /// Ignore unknown hosts (in case DNS record was removed) + /// We will rethrow exception if we don't find local host in the list. continue; + } if (host_in_hostlist) { @@ -195,6 +212,12 @@ bool DDLTask::findCurrentHostID(ContextPtr global_context, Poco::Logger * log) } } + if (!host_in_hostlist && first_exception) + { + /// We don't know for sure if we should process task or not + std::rethrow_exception(first_exception); + } + return host_in_hostlist; } From e9b124b4bcced6ebc5a7a77aa54aca2ddb48db15 Mon Sep 17 00:00:00 2001 From: Arthur Passos Date: Tue, 2 Aug 2022 09:17:53 -0300 Subject: [PATCH 257/672] Don't use default implementation for LC columns in Scalar functions --- src/Functions/IFunction.cpp | 2 -- src/Functions/getScalar.cpp | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Functions/IFunction.cpp b/src/Functions/IFunction.cpp index 4e73f7588c5..5be2ea3c5e3 100644 --- a/src/Functions/IFunction.cpp +++ b/src/Functions/IFunction.cpp @@ -285,8 +285,6 @@ ColumnPtr IExecutableFunction::executeWithoutSparseColumns(const ColumnsWithType ? res->cloneResized(1)->convertToFullColumnIfConst() : res; - keys = keys->convertToFullColumnIfLowCardinality(); - auto res_mut_dictionary = DataTypeLowCardinality::createColumnUnique(*res_low_cardinality_type->getDictionaryType()); ColumnPtr res_indexes = res_mut_dictionary->uniqueInsertRangeFrom(*keys, 0, keys->size()); ColumnUniquePtr res_dictionary = std::move(res_mut_dictionary); diff --git a/src/Functions/getScalar.cpp b/src/Functions/getScalar.cpp index c165ef26ffa..aeb68c3825e 100644 --- a/src/Functions/getScalar.cpp +++ b/src/Functions/getScalar.cpp @@ -42,6 +42,11 @@ public: return 1; } + bool useDefaultImplementationForLowCardinalityColumns() const override + { + return false; + } + bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; } DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override From 5f7848ffd48f40a34e72d22182571fe31bfa666d Mon Sep 17 00:00:00 2001 From: Arthur Passos Date: Tue, 2 Aug 2022 09:30:54 -0300 Subject: [PATCH 258/672] Replace LC CTE scalar integ tests by stateless tests --- tests/integration/test_cte_lc/__init__.py | 0 tests/integration/test_cte_lc/test.py | 26 ------------------- .../0_stateless/02375_scalar_lc_cte.reference | 1 + .../0_stateless/02375_scalar_lc_cte.sql | 1 + 4 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 tests/integration/test_cte_lc/__init__.py delete mode 100644 tests/integration/test_cte_lc/test.py create mode 100644 tests/queries/0_stateless/02375_scalar_lc_cte.reference create mode 100644 tests/queries/0_stateless/02375_scalar_lc_cte.sql diff --git a/tests/integration/test_cte_lc/__init__.py b/tests/integration/test_cte_lc/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/integration/test_cte_lc/test.py b/tests/integration/test_cte_lc/test.py deleted file mode 100644 index 36244446fff..00000000000 --- a/tests/integration/test_cte_lc/test.py +++ /dev/null @@ -1,26 +0,0 @@ -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_lc_of_string(start_cluster): - result = node.query("WITH ( SELECT toLowCardinality('a') ) AS bar SELECT bar") - - assert result == "a\n" - - -def test_lc_of_int(start_cluster): - result = node.query("WITH ( SELECT toLowCardinality(1) ) AS bar SELECT bar") - - assert result == "1\n" diff --git a/tests/queries/0_stateless/02375_scalar_lc_cte.reference b/tests/queries/0_stateless/02375_scalar_lc_cte.reference new file mode 100644 index 00000000000..2e65efe2a14 --- /dev/null +++ b/tests/queries/0_stateless/02375_scalar_lc_cte.reference @@ -0,0 +1 @@ +a \ No newline at end of file diff --git a/tests/queries/0_stateless/02375_scalar_lc_cte.sql b/tests/queries/0_stateless/02375_scalar_lc_cte.sql new file mode 100644 index 00000000000..80018333829 --- /dev/null +++ b/tests/queries/0_stateless/02375_scalar_lc_cte.sql @@ -0,0 +1 @@ +WITH ( SELECT toLowCardinality('a') ) AS bar SELECT bar \ No newline at end of file From e292d830f57540e20b1f1f021f86d13eb38ca4a2 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 2 Aug 2022 15:37:02 +0300 Subject: [PATCH 259/672] Update Dockerfile --- docker/packager/binary/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/packager/binary/Dockerfile b/docker/packager/binary/Dockerfile index 9e7a5cfdfa4..cd7797752ca 100644 --- a/docker/packager/binary/Dockerfile +++ b/docker/packager/binary/Dockerfile @@ -125,7 +125,7 @@ WORKDIR /workdir # https://github.com/google/sanitizers/issues/1552 RUN export CODENAME="$(lsb_release --codename --short | tr 'A-Z' 'a-z')" \ - && echo "deb [trusted=yes] https://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME} main" >> \ + && echo "deb [trusted=yes] https://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-15 main" >> \ /etc/apt/sources.list.d/clang.list \ && apt-get update \ && apt-get install \ From 0fad007220b48958dccc79d071e247622eb67661 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 2 Aug 2022 06:42:50 -0700 Subject: [PATCH 260/672] changing OwnPatternFormatter.cpp to its original version --- src/Loggers/OwnPatternFormatter.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Loggers/OwnPatternFormatter.cpp b/src/Loggers/OwnPatternFormatter.cpp index 67e038f4ea1..02a2c2e510b 100644 --- a/src/Loggers/OwnPatternFormatter.cpp +++ b/src/Loggers/OwnPatternFormatter.cpp @@ -3,16 +3,18 @@ #include #include #include -#include -#include -#include #include +#include +#include +#include -OwnPatternFormatter::OwnPatternFormatter(bool color_) : Poco::PatternFormatter(""), color(color_) +OwnPatternFormatter::OwnPatternFormatter(bool color_) + : Poco::PatternFormatter(""), color(color_) { } + void OwnPatternFormatter::formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text) const { DB::WriteBufferFromString wb(text); From b386db02e14caf5cc4eb283c5d05b822dbb01e0e Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Tue, 2 Aug 2022 15:51:57 +0200 Subject: [PATCH 261/672] Fix test --- tests/queries/0_stateless/02375_scalar_lc_cte.reference | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02375_scalar_lc_cte.reference b/tests/queries/0_stateless/02375_scalar_lc_cte.reference index 2e65efe2a14..78981922613 100644 --- a/tests/queries/0_stateless/02375_scalar_lc_cte.reference +++ b/tests/queries/0_stateless/02375_scalar_lc_cte.reference @@ -1 +1 @@ -a \ No newline at end of file +a From 469b7e7668cd976b058079c4c0e8a3e1c8769f53 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Tue, 2 Aug 2022 18:44:49 +0200 Subject: [PATCH 262/672] Add notes about _is_cache_updated logic --- tests/ci/github_helper.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/ci/github_helper.py b/tests/ci/github_helper.py index 15fcf88aa40..685d9f2c841 100644 --- a/tests/ci/github_helper.py +++ b/tests/ci/github_helper.py @@ -176,12 +176,15 @@ class GitHub(github.Github): self, cache_file: Path, obj_updated_at: Optional[datetime] ) -> Tuple[bool, object]: cached_obj = self._get_cached(cache_file) - cache_updated = cached_obj.updated_at + # We don't want the cache_updated being always old, + # for example in cases when the user is not updated for ages + cache_updated = max( + datetime.fromtimestamp(cache_file.stat().st_mtime), cached_obj.updated_at + ) if obj_updated_at is None: + # When we don't know about the object is updated or not, + # we update it once per hour obj_updated_at = datetime.now() - timedelta(hours=1) - cache_updated = max( - datetime.fromtimestamp(cache_file.stat().st_mtime), cache_updated - ) if obj_updated_at <= cache_updated: return True, cached_obj return False, cached_obj From 8a351724decfdaa00a06a304875955c98039a2a5 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Tue, 2 Aug 2022 19:31:20 +0200 Subject: [PATCH 263/672] Update settings changes history --- src/Core/SettingsChangesHistory.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/SettingsChangesHistory.h b/src/Core/SettingsChangesHistory.h index ba60fb99308..8d0e69f4b29 100644 --- a/src/Core/SettingsChangesHistory.h +++ b/src/Core/SettingsChangesHistory.h @@ -81,7 +81,8 @@ static std::map sett {"22.7", {{"cross_to_inner_join_rewrite", 1, 2, "Force rewrite comma join to inner"}, {"enable_positional_arguments", false, true, "Enable positional arguments feature by default"}, {"format_csv_allow_single_quotes", true, false, "Most tools don't treat single quote in CSV specially, don't do it by default too"}}}, - {"22.6", {{"output_format_json_named_tuples_as_objects", false, true, "Allow to serialize named tuples as JSON objects in JSON formats by default"}}}, + {"22.6", {{"output_format_json_named_tuples_as_objects", false, true, "Allow to serialize named tuples as JSON objects in JSON formats by default"}, + {"input_format_skip_unknown_fields", false, true, "Optimize reading subset of columns for some input formats"}}}, {"22.5", {{"memory_overcommit_ratio_denominator", 0, 1073741824, "Enable memory overcommit feature by default"}, {"memory_overcommit_ratio_denominator_for_user", 0, 1073741824, "Enable memory overcommit feature by default"}}}, {"22.4", {{"allow_settings_after_format_in_insert", true, false, "Do not allow SETTINGS after FORMAT for INSERT queries because ClickHouse interpret SETTINGS as some values, which is misleading"}}}, From 30782a2b0580400440c977c9d97a5287cd3ca714 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Tue, 2 Aug 2022 17:44:43 +0000 Subject: [PATCH 264/672] Test: distinct sorted is not used on const column --- .../Transforms/DistinctSortedTransform.cpp | 1 + ...distinct_in_order_optimization_explain.reference | 13 ++++++++----- .../02317_distinct_in_order_optimization_explain.sh | 13 ++++++++----- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Processors/Transforms/DistinctSortedTransform.cpp b/src/Processors/Transforms/DistinctSortedTransform.cpp index 12f8da67025..4c6ca03950c 100644 --- a/src/Processors/Transforms/DistinctSortedTransform.cpp +++ b/src/Processors/Transforms/DistinctSortedTransform.cpp @@ -179,6 +179,7 @@ void DistinctSortedTransform::transform(Chunk & chunk) chunk = std::move(res_chunk); } + template bool DistinctSortedTransform::buildFilter( Method & method, diff --git a/tests/queries/0_stateless/02317_distinct_in_order_optimization_explain.reference b/tests/queries/0_stateless/02317_distinct_in_order_optimization_explain.reference index 3e57d4de586..01e0c397dc7 100644 --- a/tests/queries/0_stateless/02317_distinct_in_order_optimization_explain.reference +++ b/tests/queries/0_stateless/02317_distinct_in_order_optimization_explain.reference @@ -9,24 +9,27 @@ DistinctSortedChunkTransform -- distinct with primary key prefix -> pre-distinct optimization only DistinctTransform DistinctSortedChunkTransform --- distinct with primary key prefix and order by on column in distinct -> pre-distinct and final distinct optimization +-- distinct with primary key prefix and order by column in distinct -> pre-distinct and final distinct optimization DistinctSortedTransform DistinctSortedChunkTransform --- distinct with primary key prefix and order by on the same columns -> pre-distinct and final distinct optimization +-- distinct with primary key prefix and order by the same columns -> pre-distinct and final distinct optimization DistinctSortedStreamTransform DistinctSortedChunkTransform -- distinct with primary key prefix and order by column in distinct but non-primary key prefix -> pre-distinct and final distinct optimization DistinctSortedTransform DistinctSortedChunkTransform --- distinct with primary key prefix and order by on column _not_ in distinct -> pre-distinct optimization only +-- distinct with primary key prefix and order by column _not_ in distinct -> pre-distinct optimization only DistinctTransform DistinctSortedChunkTransform -- distinct with non-primary key prefix -> ordinary distinct DistinctTransform DistinctTransform --- distinct with non-primary key prefix and order by on column in distinct -> final distinct optimization only +-- distinct with non-primary key prefix and order by column in distinct -> final distinct optimization only DistinctSortedTransform DistinctTransform --- distinct with non-primary key prefix and order by on column _not_ in distinct -> ordinary distinct +-- distinct with non-primary key prefix and order by column _not_ in distinct -> ordinary distinct +DistinctTransform +DistinctTransform +-- distinct with non-primary key prefix and order by _const_ column in distinct -> ordinary distinct DistinctTransform DistinctTransform diff --git a/tests/queries/0_stateless/02317_distinct_in_order_optimization_explain.sh b/tests/queries/0_stateless/02317_distinct_in_order_optimization_explain.sh index 903f3bb9e11..fcffb974433 100755 --- a/tests/queries/0_stateless/02317_distinct_in_order_optimization_explain.sh +++ b/tests/queries/0_stateless/02317_distinct_in_order_optimization_explain.sh @@ -27,25 +27,28 @@ $CLICKHOUSE_CLIENT -nq "$ENABLE_OPTIMIZATION;explain pipeline select distinct * $CLICKHOUSE_CLIENT -q "select '-- distinct with primary key prefix -> pre-distinct optimization only'" $CLICKHOUSE_CLIENT -nq "$ENABLE_OPTIMIZATION;explain pipeline select distinct a, c from distinct_in_order_explain" | eval $FIND_DISTINCT -$CLICKHOUSE_CLIENT -q "select '-- distinct with primary key prefix and order by on column in distinct -> pre-distinct and final distinct optimization'" +$CLICKHOUSE_CLIENT -q "select '-- distinct with primary key prefix and order by column in distinct -> pre-distinct and final distinct optimization'" $CLICKHOUSE_CLIENT -nq "$ENABLE_OPTIMIZATION;explain pipeline select distinct a, c from distinct_in_order_explain order by c" | eval $FIND_DISTINCT -$CLICKHOUSE_CLIENT -q "select '-- distinct with primary key prefix and order by on the same columns -> pre-distinct and final distinct optimization'" +$CLICKHOUSE_CLIENT -q "select '-- distinct with primary key prefix and order by the same columns -> pre-distinct and final distinct optimization'" $CLICKHOUSE_CLIENT -nq "$ENABLE_OPTIMIZATION;explain pipeline select distinct a, b from distinct_in_order_explain order by a, b" | eval $FIND_DISTINCT $CLICKHOUSE_CLIENT -q "select '-- distinct with primary key prefix and order by column in distinct but non-primary key prefix -> pre-distinct and final distinct optimization'" $CLICKHOUSE_CLIENT -nq "$ENABLE_OPTIMIZATION;explain pipeline select distinct a, b, c from distinct_in_order_explain order by c" | eval $FIND_DISTINCT -$CLICKHOUSE_CLIENT -q "select '-- distinct with primary key prefix and order by on column _not_ in distinct -> pre-distinct optimization only'" +$CLICKHOUSE_CLIENT -q "select '-- distinct with primary key prefix and order by column _not_ in distinct -> pre-distinct optimization only'" $CLICKHOUSE_CLIENT -nq "$ENABLE_OPTIMIZATION;explain pipeline select distinct a, c from distinct_in_order_explain order by b" | eval $FIND_DISTINCT $CLICKHOUSE_CLIENT -q "select '-- distinct with non-primary key prefix -> ordinary distinct'" $CLICKHOUSE_CLIENT -nq "$ENABLE_OPTIMIZATION;explain pipeline select distinct b, c from distinct_in_order_explain" | eval $FIND_DISTINCT -$CLICKHOUSE_CLIENT -q "select '-- distinct with non-primary key prefix and order by on column in distinct -> final distinct optimization only'" +$CLICKHOUSE_CLIENT -q "select '-- distinct with non-primary key prefix and order by column in distinct -> final distinct optimization only'" $CLICKHOUSE_CLIENT -nq "$ENABLE_OPTIMIZATION;explain pipeline select distinct b, c from distinct_in_order_explain order by b" | eval $FIND_DISTINCT -$CLICKHOUSE_CLIENT -q "select '-- distinct with non-primary key prefix and order by on column _not_ in distinct -> ordinary distinct'" +$CLICKHOUSE_CLIENT -q "select '-- distinct with non-primary key prefix and order by column _not_ in distinct -> ordinary distinct'" $CLICKHOUSE_CLIENT -nq "$ENABLE_OPTIMIZATION;explain pipeline select distinct b, c from distinct_in_order_explain order by a" | eval $FIND_DISTINCT +$CLICKHOUSE_CLIENT -q "select '-- distinct with non-primary key prefix and order by _const_ column in distinct -> ordinary distinct'" +$CLICKHOUSE_CLIENT -nq "$ENABLE_OPTIMIZATION;explain pipeline select distinct b, 1 as x from distinct_in_order_explain order by x" | eval $FIND_DISTINCT + $CLICKHOUSE_CLIENT -q "drop table if exists distinct_in_order_explain sync" From 504180d7d6576467f25c61518ca4742dfd45335f Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy <99031427+yakov-olkhovskiy@users.noreply.github.com> Date: Tue, 2 Aug 2022 15:39:11 -0400 Subject: [PATCH 265/672] stat is different for macos --- utils/self-extracting-executable/post_build.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/self-extracting-executable/post_build.sh b/utils/self-extracting-executable/post_build.sh index 397de0074ef..ca595dafabf 100755 --- a/utils/self-extracting-executable/post_build.sh +++ b/utils/self-extracting-executable/post_build.sh @@ -1,3 +1,7 @@ padding=" " -sz="$(stat -c %s 'decompressor')" +if [[ $OSTYPE == 'darwin'* ]]; then + sz="$(stat -f %z 'decompressor')" +else + sz="$(stat -c %s 'decompressor')" +fi printf "%s%s" "${padding:${#sz}}" $sz From 57e31cadfae61d3504018431dbb4b3fbc0ff9a37 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 3 Aug 2022 09:08:51 +0300 Subject: [PATCH 266/672] Update unaligned.h --- base/base/unaligned.h | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/base/base/unaligned.h b/base/base/unaligned.h index 7fff0ddb20c..c469972b4b6 100644 --- a/base/base/unaligned.h +++ b/base/base/unaligned.h @@ -4,14 +4,20 @@ #include #include -inline void reverseMemcpy(void *dst, const void *src, int length) -{ - uint8_t *d = reinterpret_cast(dst); - const uint8_t *s = reinterpret_cast(src); - d += length; - while (length--) - *--d = *s++; +inline void reverseMemcpy(void * dst, const void * src, size_t size) +{ + uint8_t * uint_dst = reinterpret_cast(dst); + const uint8_t * uint_src = reinterpret_cast(src); + + uint_dst += length; + while (length) + { + --uint_dst; + *uint_dst = *uint_src; + ++uint_src; + --length; + } } template @@ -25,6 +31,7 @@ inline T unalignedLoadLE(const void * address) return res; } + template inline void unalignedStoreLE(void * address, const typename std::enable_if::type & src) From 0b82fb78164ea08d5ee4e8f89055bc7285a82b52 Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Wed, 3 Aug 2022 12:52:06 +0530 Subject: [PATCH 267/672] Extra semicolon removed from the TTL example This PR removes an extra semicolon from the TTL example. --- docs/en/sql-reference/statements/alter/ttl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/sql-reference/statements/alter/ttl.md b/docs/en/sql-reference/statements/alter/ttl.md index 37a171d4969..2682279d1f7 100644 --- a/docs/en/sql-reference/statements/alter/ttl.md +++ b/docs/en/sql-reference/statements/alter/ttl.md @@ -34,7 +34,7 @@ CREATE TABLE table_with_ttl ) ENGINE MergeTree() ORDER BY tuple() -TTL event_time + INTERVAL 3 MONTH; +TTL event_time + INTERVAL 3 MONTH SETTINGS min_bytes_for_wide_part = 0; INSERT INTO table_with_ttl VALUES (now(), 1, 'username1'); From 5a5d028154d9204cdd3bb3f7b33ca3b161731253 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy <99031427+yakov-olkhovskiy@users.noreply.github.com> Date: Wed, 3 Aug 2022 03:34:51 -0400 Subject: [PATCH 268/672] remove global variables --- utils/self-extracting-executable/decompressor.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/utils/self-extracting-executable/decompressor.cpp b/utils/self-extracting-executable/decompressor.cpp index 5615216e262..45da2189713 100644 --- a/utils/self-extracting-executable/decompressor.cpp +++ b/utils/self-extracting-executable/decompressor.cpp @@ -28,9 +28,6 @@ #include "types.h" -char decompressed_suffix[7] = {0}; -uint64_t decompressed_umask = 0; - /// decompress part int doDecompress(char * input, char * output, off_t & in_offset, off_t & out_offset, off_t input_size, off_t output_size, ZSTD_DCtx* dctx) @@ -166,7 +163,7 @@ int decompress(char * input, char * output, off_t start, off_t end, size_t max_n /// Read data about files and decomrpess them. -int decompressFiles(int input_fd, char * path, char * name, bool & have_compressed_analoge) +int decompressFiles(int input_fd, char * path, char * name, bool & have_compressed_analoge, char * decompressed_suffix, uint64_t * decompressed_umask) { /// Read data about output file. /// Compressed data will replace data in file @@ -249,7 +246,7 @@ int decompressFiles(int input_fd, char * path, char * name, bool & have_compress } close(fd); strncpy(decompressed_suffix, file_name + strlen(file_name) - 6, 6); - decompressed_umask = le64toh(file_info.umask); + *decompressed_umask = le64toh(file_info.umask); have_compressed_analoge = true; } @@ -334,9 +331,11 @@ int main(int/* argc*/, char* argv[]) } bool have_compressed_analoge = false; + char decompressed_suffix[7] = {0}; + uint64_t decompressed_umask = 0; /// Decompress all files - if (0 != decompressFiles(input_fd, path, name, have_compressed_analoge)) + if (0 != decompressFiles(input_fd, path, name, have_compressed_analoge, decompressed_suffix, &decompressed_umask)) { printf("Error happened during decompression.\n"); if (0 != close(input_fd)) From 724d3d0359fc6a9f2f87d8b38e1617e19a05c0b7 Mon Sep 17 00:00:00 2001 From: SkyhotQin Date: Wed, 3 Aug 2022 17:09:10 +0800 Subject: [PATCH 269/672] Update dbms-naming.md delete the `h` and some spaces --- docs/zh/faq/general/dbms-naming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/faq/general/dbms-naming.md b/docs/zh/faq/general/dbms-naming.md index 72b96886924..6725524d58a 100644 --- a/docs/zh/faq/general/dbms-naming.md +++ b/docs/zh/faq/general/dbms-naming.md @@ -10,7 +10,7 @@ sidebar_position: 10 这个由两部分组成的意思有两个结果: -- 唯一正确的写“Click**H** house”的方式是用大写H。 +- 唯一正确的写“Click**H**ouse”的方式是用大写H。 - 如果需要缩写,请使用“**CH**”。由于一些历史原因,缩写CK在中国也很流行,主要是因为中文中最早的一个关于ClickHouse的演讲使用了这种形式。 !!! info “有趣的事实” From 3e6b663020833e26dfa2a08af28e47ea35453280 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Wed, 3 Aug 2022 11:25:45 +0200 Subject: [PATCH 270/672] ASTSelectQuery added hasJoin method --- src/Parsers/ASTSelectQuery.cpp | 19 +++++++++++++++++++ src/Parsers/ASTSelectQuery.h | 1 + src/Storages/MergeTree/MergeTreeData.cpp | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Parsers/ASTSelectQuery.cpp b/src/Parsers/ASTSelectQuery.cpp index 53c820a180d..4e84e1d1e20 100644 --- a/src/Parsers/ASTSelectQuery.cpp +++ b/src/Parsers/ASTSelectQuery.cpp @@ -344,6 +344,25 @@ const ASTTablesInSelectQueryElement * ASTSelectQuery::join() const return getFirstTableJoin(*this); } +bool ASTSelectQuery::hasJoin() const +{ + if (!tables()) + return false; + + const auto & tables_in_select_query = tables()->as(); + if (tables_in_select_query.children.empty()) + return false; + + for (const auto & child : tables_in_select_query.children) + { + const auto & tables_element = child->as(); + if (tables_element.table_join) + return true; + } + + return false; +} + static String getTableExpressionAlias(const ASTTableExpression * table_expression) { if (table_expression->subquery) diff --git a/src/Parsers/ASTSelectQuery.h b/src/Parsers/ASTSelectQuery.h index 1a02717db8d..5e3af545f12 100644 --- a/src/Parsers/ASTSelectQuery.h +++ b/src/Parsers/ASTSelectQuery.h @@ -131,6 +131,7 @@ public: std::pair arrayJoinExpressionList() const; const ASTTablesInSelectQueryElement * join() const; + bool hasJoin() const; bool final() const; bool withFill() const; void replaceDatabaseAndTable(const String & database_name, const String & table_name); diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 8705b9c4969..4fc8f77b5b6 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -5489,7 +5489,7 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg // In order to properly analyze joins, aliases should be recognized. However, aliases get lost during projection analysis. // Let's disable projection if there are any JOIN clauses. // TODO: We need a better identifier resolution mechanism for projection analysis. - if (select_query->join()) + if (select_query->hasJoin()) return std::nullopt; // INTERPOLATE expressions may include aliases, so aliases should be preserved From 08474cf869078120e5e32e83b3f3eb8cca676de2 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 3 Aug 2022 11:43:47 +0200 Subject: [PATCH 271/672] Update tweak on version part update --- tests/ci/version_helper.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/ci/version_helper.py b/tests/ci/version_helper.py index 08ee052cf1a..de98b8431de 100755 --- a/tests/ci/version_helper.py +++ b/tests/ci/version_helper.py @@ -65,14 +65,20 @@ class ClickHouseVersion: return method() def major_update(self) -> "ClickHouseVersion": + if self._git is not None: + self._git.update() return ClickHouseVersion(self.major + 1, 1, 1, self.revision + 1, self._git) def minor_update(self) -> "ClickHouseVersion": + if self._git is not None: + self._git.update() return ClickHouseVersion( self.major, self.minor + 1, 1, self.revision + 1, self._git ) def patch_update(self) -> "ClickHouseVersion": + if self._git is not None: + self._git.update() return ClickHouseVersion( self.major, self.minor, self.patch + 1, self.revision, self._git ) From da655fbfcfeee1dc0c39e2e7ec80eb1057ecb0f2 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 3 Aug 2022 09:57:02 +0000 Subject: [PATCH 272/672] Update version_date.tsv and changelogs after v22.7.2.15-stable --- docs/changelogs/v22.7.2.15-stable.md | 24 ++++++++++++++++++++++++ utils/list-versions/version_date.tsv | 1 + 2 files changed, 25 insertions(+) create mode 100644 docs/changelogs/v22.7.2.15-stable.md diff --git a/docs/changelogs/v22.7.2.15-stable.md b/docs/changelogs/v22.7.2.15-stable.md new file mode 100644 index 00000000000..e60c3e6fd2f --- /dev/null +++ b/docs/changelogs/v22.7.2.15-stable.md @@ -0,0 +1,24 @@ +--- +sidebar_position: 1 +sidebar_label: 2022 +--- + +# 2022 Changelog + +### ClickHouse release v22.7.2.15-stable FIXME as compared to v22.7.1.2484-stable + +#### Bug Fix +* Backported in [#39750](https://github.com/ClickHouse/ClickHouse/issues/39750): Fix seeking while reading from encrypted disk. This PR fixes [#38381](https://github.com/ClickHouse/ClickHouse/issues/38381). [#39687](https://github.com/ClickHouse/ClickHouse/pull/39687) ([Vitaly Baranov](https://github.com/vitlibar)). + +#### Bug Fix (user-visible misbehavior in official stable or prestable release) + +* Backported in [#39591](https://github.com/ClickHouse/ClickHouse/issues/39591): Fix data race and possible heap-buffer-overflow in Avro format. Closes [#39094](https://github.com/ClickHouse/ClickHouse/issues/39094) Closes [#33652](https://github.com/ClickHouse/ClickHouse/issues/33652). [#39498](https://github.com/ClickHouse/ClickHouse/pull/39498) ([Kruglov Pavel](https://github.com/Avogar)). +* Backported in [#39613](https://github.com/ClickHouse/ClickHouse/issues/39613): Fix bug with maxsplit argument for splitByChar, which was not working correctly. [#39552](https://github.com/ClickHouse/ClickHouse/pull/39552) ([filimonov](https://github.com/filimonov)). +* Backported in [#39792](https://github.com/ClickHouse/ClickHouse/issues/39792): Fix wrong index analysis with tuples and operator `IN`, which could lead to wrong query result. [#39752](https://github.com/ClickHouse/ClickHouse/pull/39752) ([Anton Popov](https://github.com/CurtizJ)). +* Backported in [#39837](https://github.com/ClickHouse/ClickHouse/issues/39837): Fix `CANNOT_READ_ALL_DATA` exception with `local_filesystem_read_method=pread_threadpool`. This bug affected only Linux kernel version 5.9 and 5.10 according to [man](https://manpages.debian.org/testing/manpages-dev/preadv2.2.en.html#BUGS). [#39800](https://github.com/ClickHouse/ClickHouse/pull/39800) ([Anton Popov](https://github.com/CurtizJ)). + +#### NOT FOR CHANGELOG / INSIGNIFICANT + +* Replace MemoryTrackerBlockerInThread to LockMemoryExceptionInThread [#39619](https://github.com/ClickHouse/ClickHouse/pull/39619) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). +* Change mysql-odbc url [#39702](https://github.com/ClickHouse/ClickHouse/pull/39702) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + diff --git a/utils/list-versions/version_date.tsv b/utils/list-versions/version_date.tsv index 777fd424321..e829e7b2bc0 100644 --- a/utils/list-versions/version_date.tsv +++ b/utils/list-versions/version_date.tsv @@ -1,3 +1,4 @@ +v22.7.2.15-stable 2022-08-03 v22.7.1.2484-stable 2022-07-21 v22.6.4.35-stable 2022-07-25 v22.6.3.35-stable 2022-07-06 From 49b1f62abd91af876190b4ebb4b343500f541105 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 3 Aug 2022 12:06:45 +0200 Subject: [PATCH 273/672] Update SECURITY.md --- SECURITY.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 81d2fd18fb2..4bb6d9d0b3b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,9 +10,10 @@ The following versions of ClickHouse server are currently being supported with s | Version | Supported | |:-|:-| +| 22.7 | ✔️ | | 22.6 | ✔️ | | 22.5 | ✔️ | -| 22.4 | ✔️ | +| 22.4 | ❌ | | 22.3 | ✔️ | | 22.2 | ❌ | | 22.1 | ❌ | @@ -57,5 +58,5 @@ As the security issue moves from triage, to identified fix, to release planning ## Public Disclosure Timing -A public disclosure date is negotiated by the ClickHouse maintainers and the bug submitter. We prefer to fully disclose the bug as soon as possible once a user mitigation is available. It is reasonable to delay disclosure when the bug or the fix is not yet fully understood, the solution is not well-tested, or for vendor coordination. The timeframe for disclosure is from immediate (especially if it's already publicly known) to 90 days. For a vulnerability with a straightforward mitigation, we expect report date to disclosure date to be on the order of 7 days. +A public disclosure date is negotiated by the ClickHouse maintainers and the bug submitter. We prefer to fully disclose the bug as soon as possible once a user mitigation is available. It is reasonable to delay disclosure when the bug or the fix is not yet fully understood, the solution is not well-tested, or for vendor coordination. The timeframe for disclosure is from immediate (especially if it's already publicly known) to 90 days. For a vulnerability with a straightforward mitigation, we expect the report date to disclosure date to be on the order of 7 days. From e78a176b0a63bc9333e3481c06f42c5e905e0b53 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 3 Aug 2022 12:09:29 +0200 Subject: [PATCH 274/672] Regenerate changelog with the recent script --- docs/changelogs/v22.7.2.15-stable.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelogs/v22.7.2.15-stable.md b/docs/changelogs/v22.7.2.15-stable.md index e60c3e6fd2f..a1c270041a4 100644 --- a/docs/changelogs/v22.7.2.15-stable.md +++ b/docs/changelogs/v22.7.2.15-stable.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.7.2.15-stable FIXME as compared to v22.7.1.2484-stable +### ClickHouse release v22.7.2.15-stable (f843089624e) FIXME as compared to v22.7.1.2484-stable (f4f05ec786a) #### Bug Fix * Backported in [#39750](https://github.com/ClickHouse/ClickHouse/issues/39750): Fix seeking while reading from encrypted disk. This PR fixes [#38381](https://github.com/ClickHouse/ClickHouse/issues/38381). [#39687](https://github.com/ClickHouse/ClickHouse/pull/39687) ([Vitaly Baranov](https://github.com/vitlibar)). From da49a0cc0206f50a62e823bc8b4b68680a230b41 Mon Sep 17 00:00:00 2001 From: kssenii Date: Wed, 3 Aug 2022 12:32:55 +0200 Subject: [PATCH 275/672] Fix build --- contrib/azure-cmake/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/azure-cmake/CMakeLists.txt b/contrib/azure-cmake/CMakeLists.txt index 19f2940cbf0..367131daf0b 100644 --- a/contrib/azure-cmake/CMakeLists.txt +++ b/contrib/azure-cmake/CMakeLists.txt @@ -1,6 +1,6 @@ option (ENABLE_AZURE_BLOB_STORAGE "Enable Azure blob storage" ${ENABLE_LIBRARIES}) -if (NOT ENABLE_AZURE_BLOB_STORAGE) +if (NOT ENABLE_AZURE_BLOB_STORAGE OR BUILD_STANDALONE_KEEPER) message(STATUS "Not using Azure blob storage") return() endif() From 62a05dc10df37725b58e9e222c633addc801bacb Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 3 Aug 2022 13:40:31 +0200 Subject: [PATCH 276/672] Add instructions for github-cli installation --- tests/ci/release.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/ci/release.py b/tests/ci/release.py index b07deffa1fb..f982c7c6f53 100755 --- a/tests/ci/release.py +++ b/tests/ci/release.py @@ -96,10 +96,17 @@ class Release: def check_prerequisites(self): """ - Check tooling installed in the system + Check tooling installed in the system, `git` is checked by Git() init """ - self.run("gh auth status") - self.run("git status") + try: + self.run("gh auth status") + except subprocess.SubprocessError: + logging.error( + "The github-cli either not installed or not setup, please follow " + "the instructions on https://github.com/cli/cli#installation and " + "https://cli.github.com/manual/" + ) + raise def do(self, check_dirty: bool, check_branch: bool, with_release_branch: bool): self.check_prerequisites() From 71e1b1916c2489f8a486d7b33463b8f4e9f242ab Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 3 Aug 2022 11:43:30 +0000 Subject: [PATCH 277/672] Randomize snapshot on exit in tests --- tests/config/config.d/keeper_port.xml | 2 ++ tests/config/install.sh | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/tests/config/config.d/keeper_port.xml b/tests/config/config.d/keeper_port.xml index 8cea9044dd0..cffd325e968 100644 --- a/tests/config/config.d/keeper_port.xml +++ b/tests/config/config.d/keeper_port.xml @@ -3,6 +3,8 @@ 9181 1 + true + 10000 100000 diff --git a/tests/config/install.sh b/tests/config/install.sh index a9e66ebb633..46009ce671e 100755 --- a/tests/config/install.sh +++ b/tests/config/install.sh @@ -80,6 +80,10 @@ ln -sf $SRC_PATH/dhparam.pem $DEST_SERVER_PATH/ ln -sf --backup=simple --suffix=_original.xml \ $SRC_PATH/config.d/query_masking_rules.xml $DEST_SERVER_PATH/config.d/ +# We randomize creating the snapshot on exit for Keeper to test out using older snapshots +create_snapshot_on_exit=$(($RANDOM % 2)) +cat $DEST_SERVER_PATH/config.d/keeper_port.xml | sed "s|true|$create_snapshot_on_exit|" > $DEST_SERVER_PATH/config.d/keeper_port.xml + if [[ -n "$USE_POLYMORPHIC_PARTS" ]] && [[ "$USE_POLYMORPHIC_PARTS" -eq 1 ]]; then ln -sf $SRC_PATH/config.d/polymorphic_parts.xml $DEST_SERVER_PATH/config.d/ fi From 05a74850b31c29bea08d71f9a1604ec0fa615e38 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 3 Aug 2022 13:57:20 +0200 Subject: [PATCH 278/672] Update tests/config/install.sh Co-authored-by: Alexander Tokmakov --- tests/config/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/config/install.sh b/tests/config/install.sh index 46009ce671e..478601620e1 100755 --- a/tests/config/install.sh +++ b/tests/config/install.sh @@ -82,7 +82,7 @@ ln -sf --backup=simple --suffix=_original.xml \ # We randomize creating the snapshot on exit for Keeper to test out using older snapshots create_snapshot_on_exit=$(($RANDOM % 2)) -cat $DEST_SERVER_PATH/config.d/keeper_port.xml | sed "s|true|$create_snapshot_on_exit|" > $DEST_SERVER_PATH/config.d/keeper_port.xml +sed --follow-symlinks -i "s|true|$create_snapshot_on_exit|" $DEST_SERVER_PATH/config.d/keeper_port.xml if [[ -n "$USE_POLYMORPHIC_PARTS" ]] && [[ "$USE_POLYMORPHIC_PARTS" -eq 1 ]]; then ln -sf $SRC_PATH/config.d/polymorphic_parts.xml $DEST_SERVER_PATH/config.d/ From 1a059035b8345bf8ac707ac208fd8d855fa2e5f3 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 3 Aug 2022 14:13:01 +0200 Subject: [PATCH 279/672] fix logical error on connection loss --- src/Databases/DatabaseReplicated.cpp | 16 +++++++++------ src/Interpreters/DDLTask.h | 10 ++++++++++ src/Interpreters/InterpreterCreateQuery.cpp | 22 ++++++--------------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/Databases/DatabaseReplicated.cpp b/src/Databases/DatabaseReplicated.cpp index c94ab7279e4..cb205d6d63a 100644 --- a/src/Databases/DatabaseReplicated.cpp +++ b/src/Databases/DatabaseReplicated.cpp @@ -979,7 +979,7 @@ void DatabaseReplicated::dropTable(ContextPtr local_context, const String & tabl { auto txn = local_context->getZooKeeperMetadataTransaction(); assert(!ddl_worker->isCurrentlyActive() || txn || startsWith(table_name, ".inner_id.")); - if (txn && txn->isInitialQuery()) + if (txn && txn->isInitialQuery() && !txn->isCreateOrReplaceQuery()) { String metadata_zk_path = zookeeper_path + "/metadata/" + escapeForFileName(table_name); txn->addOp(zkutil::makeRemoveRequest(metadata_zk_path, -1)); @@ -995,7 +995,7 @@ void DatabaseReplicated::dropTable(ContextPtr local_context, const String & tabl std::lock_guard lock{metadata_mutex}; UInt64 new_digest = tables_metadata_digest; new_digest -= getMetadataHash(table_name); - if (txn) + if (txn && !txn->isCreateOrReplaceQuery()) txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1)); DatabaseAtomic::dropTableImpl(local_context, table_name, sync); @@ -1028,11 +1028,14 @@ void DatabaseReplicated::renameTable(ContextPtr local_context, const String & ta { String metadata_zk_path = zookeeper_path + "/metadata/" + escapeForFileName(table_name); String metadata_zk_path_to = zookeeper_path + "/metadata/" + escapeForFileName(to_table_name); - txn->addOp(zkutil::makeRemoveRequest(metadata_zk_path, -1)); + if (!txn->isCreateOrReplaceQuery()) + txn->addOp(zkutil::makeRemoveRequest(metadata_zk_path, -1)); + if (exchange) { txn->addOp(zkutil::makeRemoveRequest(metadata_zk_path_to, -1)); - txn->addOp(zkutil::makeCreateRequest(metadata_zk_path, statement_to, zkutil::CreateMode::Persistent)); + if (!txn->isCreateOrReplaceQuery()) + txn->addOp(zkutil::makeCreateRequest(metadata_zk_path, statement_to, zkutil::CreateMode::Persistent)); } txn->addOp(zkutil::makeCreateRequest(metadata_zk_path_to, statement, zkutil::CreateMode::Persistent)); } @@ -1062,7 +1065,7 @@ void DatabaseReplicated::commitCreateTable(const ASTCreateQuery & query, const S assert(!ddl_worker->isCurrentlyActive() || txn); String statement = getObjectDefinitionFromCreateQuery(query.clone()); - if (txn && txn->isInitialQuery()) + if (txn && txn->isInitialQuery() && !txn->isCreateOrReplaceQuery()) { String metadata_zk_path = zookeeper_path + "/metadata/" + escapeForFileName(query.getTable()); /// zk::multi(...) will throw if `metadata_zk_path` exists @@ -1072,7 +1075,7 @@ void DatabaseReplicated::commitCreateTable(const ASTCreateQuery & query, const S std::lock_guard lock{metadata_mutex}; UInt64 new_digest = tables_metadata_digest; new_digest += DB::getMetadataHash(query.getTable(), statement); - if (txn) + if (txn && !txn->isCreateOrReplaceQuery()) txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1)); DatabaseAtomic::commitCreateTable(query, table, table_metadata_tmp_path, table_metadata_path, query_context); @@ -1085,6 +1088,7 @@ void DatabaseReplicated::commitAlterTable(const StorageID & table_id, const String & statement, ContextPtr query_context) { auto txn = query_context->getZooKeeperMetadataTransaction(); + assert(!ddl_worker->isCurrentlyActive() || txn); if (txn && txn->isInitialQuery()) { String metadata_zk_path = zookeeper_path + "/metadata/" + escapeForFileName(table_id.table_name); diff --git a/src/Interpreters/DDLTask.h b/src/Interpreters/DDLTask.h index 50cfb4e2281..d5990edd43f 100644 --- a/src/Interpreters/DDLTask.h +++ b/src/Interpreters/DDLTask.h @@ -182,6 +182,12 @@ class ZooKeeperMetadataTransaction String task_path; Coordination::Requests ops; + /// CREATE OR REPLACE is special query that consists of 3 separate DDL queries (CREATE, RENAME, DROP) + /// and not all changes should be applied to metadata in ZooKeeper + /// (otherwise we may get partially applied changes on connection loss). + /// So we need this flag to avoid doing unnecessary operations with metadata. + bool is_create_or_replace_query = false; + public: ZooKeeperMetadataTransaction(const ZooKeeperPtr & current_zookeeper_, const String & zookeeper_path_, bool is_initial_query_, const String & task_path_) : current_zookeeper(current_zookeeper_) @@ -201,6 +207,10 @@ public: ZooKeeperPtr getZooKeeper() const { return current_zookeeper; } + void setIsCreateOrReplaceQuery() { is_create_or_replace_query = true; } + + bool isCreateOrReplaceQuery() const { return is_create_or_replace_query; } + void addOp(Coordination::RequestPtr && op) { if (isExecuted()) diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index 1fb5e386a58..e66fe543ab0 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -1366,25 +1366,15 @@ BlockIO InterpreterCreateQuery::doCreateOrReplaceTable(ASTCreateQuery & create, { /// Replicated database requires separate contexts for each DDL query ContextPtr current_context = getContext(); + if (auto txn = current_context->getZooKeeperMetadataTransaction()) + txn->setIsCreateOrReplaceQuery(); ContextMutablePtr create_context = Context::createCopy(current_context); create_context->setQueryContext(std::const_pointer_cast(current_context)); - auto make_drop_context = [&](bool on_error) -> ContextMutablePtr + auto make_drop_context = [&]() -> ContextMutablePtr { ContextMutablePtr drop_context = Context::createCopy(current_context); - drop_context->makeQueryContext(); - if (on_error) - return drop_context; - - if (auto txn = current_context->getZooKeeperMetadataTransaction()) - { - /// Execute drop as separate query, because [CREATE OR] REPLACE query can be considered as - /// successfully executed after RENAME/EXCHANGE query. - drop_context->resetZooKeeperMetadataTransaction(); - auto drop_txn = std::make_shared(txn->getZooKeeper(), txn->getDatabaseZooKeeperPath(), - txn->isInitialQuery(), txn->getTaskZooKeeperPath()); - drop_context->initZooKeeperMetadataTransaction(drop_txn); - } + drop_context->setQueryContext(std::const_pointer_cast(current_context)); return drop_context; }; @@ -1460,7 +1450,7 @@ BlockIO InterpreterCreateQuery::doCreateOrReplaceTable(ASTCreateQuery & create, if (!interpreter_rename.renamedInsteadOfExchange()) { /// Target table was replaced with new one, drop old table - auto drop_context = make_drop_context(false); + auto drop_context = make_drop_context(); InterpreterDropQuery(ast_drop, drop_context).execute(); } @@ -1473,7 +1463,7 @@ BlockIO InterpreterCreateQuery::doCreateOrReplaceTable(ASTCreateQuery & create, /// Drop temporary table if it was successfully created, but was not renamed to target name if (created && !renamed) { - auto drop_context = make_drop_context(true); + auto drop_context = make_drop_context(); InterpreterDropQuery(ast_drop, drop_context).execute(); } throw; From 56a4d26e87b5100f59b54a4cf2bccbcb15fdcebd Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 3 Aug 2022 14:15:45 +0200 Subject: [PATCH 280/672] Better total part size calculation on mutation --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 3 +-- src/Storages/MergeTree/MutateTask.cpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 60941108f00..f1bf78409c7 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -946,7 +946,6 @@ void IMergeTreeDataPart::appendFilesOfPartitionAndMinMaxIndex(Strings & files) c void IMergeTreeDataPart::loadChecksums(bool require) { - //const String path = fs::path(getRelativePath()) / "checksums.txt"; bool exists = metadata_manager->exists("checksums.txt"); if (exists) { @@ -957,7 +956,7 @@ void IMergeTreeDataPart::loadChecksums(bool require) bytes_on_disk = checksums.getTotalSizeOnDisk(); } else - bytes_on_disk = data_part_storage->calculateTotalSizeOnDisk(); //calculateTotalSizeOnDisk(volume->getDisk(), getRelativePath()); + bytes_on_disk = data_part_storage->calculateTotalSizeOnDisk(); } else { diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 3a5aa2f8860..53e437ae7dc 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -589,7 +589,7 @@ void finalizeMutatedPart( { auto out = data_part_storage_builder->writeFile(IMergeTreeDataPart::DEFAULT_COMPRESSION_CODEC_FILE_NAME, 4096, context->getWriteSettings()); DB::writeText(queryToString(codec->getFullCodecDesc()), *out); - } + } /// close fd { /// Write a file with a description of columns. @@ -603,9 +603,14 @@ void finalizeMutatedPart( new_data_part->minmax_idx = source_part->minmax_idx; new_data_part->modification_time = time(nullptr); new_data_part->loadProjections(false, false); - new_data_part->setBytesOnDisk(new_data_part->data_part_storage->calculateTotalSizeOnDisk()); - new_data_part->default_codec = codec; + + /// All information about sizes is stored in checksums. + /// It doesn't make sense to touch filesystem for sizes. + new_data_part->setBytesOnDisk(new_data_part->checksums->getTotalSizeOnDisk()); + /// Also use information from checksums new_data_part->calculateColumnsAndSecondaryIndicesSizesOnDisk(); + + new_data_part->default_codec = codec; } } From f608e62c27bd5c830b87d646758081def912224f Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 3 Aug 2022 14:17:31 +0200 Subject: [PATCH 281/672] Fix call --- src/Storages/MergeTree/MutateTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 53e437ae7dc..bf1ee037257 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -606,7 +606,7 @@ void finalizeMutatedPart( /// All information about sizes is stored in checksums. /// It doesn't make sense to touch filesystem for sizes. - new_data_part->setBytesOnDisk(new_data_part->checksums->getTotalSizeOnDisk()); + new_data_part->setBytesOnDisk(new_data_part->checksums.getTotalSizeOnDisk()); /// Also use information from checksums new_data_part->calculateColumnsAndSecondaryIndicesSizesOnDisk(); From dc25f18f132006b6b194788bf9769b412c4f8e59 Mon Sep 17 00:00:00 2001 From: Vitaly Baranov Date: Wed, 3 Aug 2022 14:04:18 +0200 Subject: [PATCH 282/672] Fix flaky integration test test_async_backups_to_same_destination. --- tests/integration/helpers/cluster.py | 98 +++++++++++++------ .../test_backup_restore_new/test.py | 46 ++++++--- .../test_backup_restore_on_cluster/test.py | 61 +++++++----- 3 files changed, 139 insertions(+), 66 deletions(-) diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 7700fc2dffd..ccb0501a99b 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -3121,7 +3121,69 @@ class ClickHouseInstance: params=None, user=None, password=None, - expect_fail_and_get_error=False, + port=8123, + timeout=None, + retry_strategy=None, + ): + output, error = self.http_query_and_get_answer_with_error( + sql, + data=data, + method=method, + params=params, + user=user, + password=password, + port=port, + timeout=timeout, + retry_strategy=retry_strategy, + ) + + if error: + raise Exception("ClickHouse HTTP server returned " + error) + + return output + + # Connects to the instance via HTTP interface, sends a query, expects an error and return the error message + def http_query_and_get_error( + self, + sql, + data=None, + method=None, + params=None, + user=None, + password=None, + port=8123, + timeout=None, + retry_strategy=None, + ): + output, error = self.http_query_and_get_answer_with_error( + sql, + data=data, + method=method, + params=params, + user=user, + password=password, + port=port, + timeout=timeout, + retry_strategy=retry_strategy, + ) + + if not error: + raise Exception( + "ClickHouse HTTP server is expected to fail, but succeeded: " + output + ) + + return error + + # Connects to the instance via HTTP interface, sends a query and returns both the answer and the error message + # as a tuple (output, error). + def http_query_and_get_answer_with_error( + self, + sql, + data=None, + method=None, + params=None, + user=None, + password=None, port=8123, timeout=None, retry_strategy=None, @@ -3155,23 +3217,11 @@ class ClickHouseInstance: r = requester.request(method, url, data=data, auth=auth, timeout=timeout) - def http_code_and_message(): - code = r.status_code - return str(code) + " " + http.client.responses[code] + ": " + r.text + if r.ok: + return (r.text, None) - if expect_fail_and_get_error: - if r.ok: - raise Exception( - "ClickHouse HTTP server is expected to fail, but succeeded: " - + r.text - ) - return http_code_and_message() - else: - if not r.ok: - raise Exception( - "ClickHouse HTTP server returned " + http_code_and_message() - ) - return r.text + code = r.status_code + return (None, str(code) + " " + http.client.responses[code] + ": " + r.text) # Connects to the instance via HTTP interface, sends a query and returns the answer def http_request(self, url, method="GET", params=None, data=None, headers=None): @@ -3181,20 +3231,6 @@ class ClickHouseInstance: method=method, url=url, params=params, data=data, headers=headers ) - # Connects to the instance via HTTP interface, sends a query, expects an error and return the error message - def http_query_and_get_error( - self, sql, data=None, params=None, user=None, password=None - ): - logging.debug(f"Executing query {sql} on {self.name} via HTTP interface") - return self.http_query( - sql=sql, - data=data, - params=params, - user=user, - password=password, - expect_fail_and_get_error=True, - ) - def stop_clickhouse(self, stop_wait_sec=30, kill=False): if not self.stay_alive: raise Exception( diff --git a/tests/integration/test_backup_restore_new/test.py b/tests/integration/test_backup_restore_new/test.py index fc8ca6ab0b7..fc44bf5ee13 100644 --- a/tests/integration/test_backup_restore_new/test.py +++ b/tests/integration/test_backup_restore_new/test.py @@ -335,31 +335,51 @@ def test_async_backups_to_same_destination(interface): create_and_fill_table() backup_name = new_backup_name() - ids = [] - for _ in range(2): - if interface == "http": - res = instance.http_query(f"BACKUP TABLE test.table TO {backup_name} ASYNC") - else: - res = instance.query(f"BACKUP TABLE test.table TO {backup_name} ASYNC") - ids.append(res.split("\t")[0]) + # The first backup. + if interface == "http": + res = instance.http_query(f"BACKUP TABLE test.table TO {backup_name} ASYNC") + else: + res = instance.query(f"BACKUP TABLE test.table TO {backup_name} ASYNC") + id1 = res.split("\t")[0] - [id1, id2] = ids + # The second backup to the same destination. + if interface == "http": + res, err = instance.http_query_and_get_answer_with_error( + f"BACKUP TABLE test.table TO {backup_name} ASYNC" + ) + else: + res, err = instance.query_and_get_answer_with_error( + f"BACKUP TABLE test.table TO {backup_name} ASYNC" + ) + + # The second backup to the same destination is expected to fail. It can either fail immediately or after a while. + # If it fails immediately we won't even get its ID. + id2 = None if err else res.split("\t")[0] + + ids = [id1] + if id2: + ids.append(id2) + ids_for_query = "[" + ", ".join(f"'{id}'" for id in ids) + "]" assert_eq_with_retry( instance, - f"SELECT status FROM system.backups WHERE id IN ['{id1}', '{id2}'] AND status == 'CREATING_BACKUP'", + f"SELECT status FROM system.backups WHERE id IN {ids_for_query} AND status == 'CREATING_BACKUP'", "", ) + # The first backup should succeed. assert instance.query( f"SELECT status, error FROM system.backups WHERE id='{id1}'" ) == TSV([["BACKUP_CREATED", ""]]) - assert ( - instance.query(f"SELECT status FROM system.backups WHERE id='{id2}'") - == "BACKUP_FAILED\n" - ) + if id2: + # The second backup should fail. + assert ( + instance.query(f"SELECT status FROM system.backups WHERE id='{id2}'") + == "BACKUP_FAILED\n" + ) + # Check that the first backup is all right. instance.query("DROP TABLE test.table") instance.query(f"RESTORE TABLE test.table FROM {backup_name}") assert instance.query("SELECT count(), sum(x) FROM test.table") == "100\t4950\n" diff --git a/tests/integration/test_backup_restore_on_cluster/test.py b/tests/integration/test_backup_restore_on_cluster/test.py index ecf713f0f6f..09915c8e789 100644 --- a/tests/integration/test_backup_restore_on_cluster/test.py +++ b/tests/integration/test_backup_restore_on_cluster/test.py @@ -439,56 +439,73 @@ def test_async_backups_to_same_destination(interface, on_cluster): "ORDER BY x" ) + nodes = [node1, node2] node1.query("INSERT INTO tbl VALUES (1)") backup_name = new_backup_name() - - ids = [] - nodes = [node1, node2] on_cluster_part = "ON CLUSTER 'cluster'" if on_cluster else "" + + # Multiple backups to the same destination. + ids = [] for node in nodes: if interface == "http": - res = node.http_query( + res, err = node.http_query_and_get_answer_with_error( f"BACKUP TABLE tbl {on_cluster_part} TO {backup_name} ASYNC" ) else: - res = node.query( + res, err = node.query_and_get_answer_with_error( f"BACKUP TABLE tbl {on_cluster_part} TO {backup_name} ASYNC" ) - ids.append(res.split("\t")[0]) - [id1, id2] = ids + # The second backup to the same destination is expected to fail. It can either fail immediately or after a while. + # If it fails immediately we won't even get its ID. + if not err: + ids.append(res.split("\t")[0]) - for i in range(len(nodes)): + ids_for_query = "[" + ", ".join(f"'{id}'" for id in ids) + "]" + + for node in nodes: assert_eq_with_retry( - nodes[i], - f"SELECT status FROM system.backups WHERE id='{ids[i]}' AND status == 'CREATING_BACKUP'", + node, + f"SELECT status FROM system.backups WHERE id IN {ids_for_query} AND status == 'CREATING_BACKUP'", "", ) - num_completed_backups = sum( + num_created_backups = sum( [ int( - nodes[i] - .query( - f"SELECT count() FROM system.backups WHERE id='{ids[i]}' AND status == 'BACKUP_CREATED'" - ) - .strip() + node.query( + f"SELECT count() FROM system.backups WHERE id IN {ids_for_query} AND status == 'BACKUP_CREATED'" + ).strip() ) - for i in range(len(nodes)) + for node in nodes ] ) - if num_completed_backups != 1: - for i in range(len(nodes)): + num_failed_backups = sum( + [ + int( + node.query( + f"SELECT count() FROM system.backups WHERE id IN {ids_for_query} AND status == 'BACKUP_FAILED'" + ).strip() + ) + for node in nodes + ] + ) + + # Only one backup should succeed. + if (num_created_backups != 1) or (num_failed_backups != len(ids) - 1): + for node in nodes: print( - nodes[i].query( - f"SELECT status, error FROM system.backups WHERE id='{ids[i]}'" + node.query( + f"SELECT status, error FROM system.backups WHERE id IN {ids_for_query}" ) ) - assert num_completed_backups == 1 + assert num_created_backups == 1 + assert num_failed_backups == len(ids) - 1 + # Check that the succeeded backup is all right. node1.query("DROP TABLE tbl ON CLUSTER 'cluster' NO DELAY") node1.query(f"RESTORE TABLE tbl FROM {backup_name}") assert node1.query("SELECT * FROM tbl") == "1\n" From eeaf08525fd167b9241ced37840fd007d1157579 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Fri, 29 Jul 2022 14:31:53 +0200 Subject: [PATCH 283/672] Use test-util as source for base-test, fasttest and package builder --- docker/images.json | 1 + docker/packager/binary/Dockerfile | 59 +--------------------- docker/test/base/Dockerfile | 49 +------------------ docker/test/fasttest/Dockerfile | 62 +---------------------- docker/test/util/Dockerfile | 81 ++++++++++++++++++++++++++++++- 5 files changed, 84 insertions(+), 168 deletions(-) diff --git a/docker/images.json b/docker/images.json index 181452f17bc..8339205b52f 100644 --- a/docker/images.json +++ b/docker/images.json @@ -29,6 +29,7 @@ "docker/test/util": { "name": "clickhouse/test-util", "dependent": [ + "docker/packager/binary", "docker/test/base", "docker/test/fasttest" ] diff --git a/docker/packager/binary/Dockerfile b/docker/packager/binary/Dockerfile index 91d1354a10c..02e7ee13452 100644 --- a/docker/packager/binary/Dockerfile +++ b/docker/packager/binary/Dockerfile @@ -1,62 +1,7 @@ # rebuild in #33610 # docker build -t clickhouse/binary-builder . -FROM ubuntu:20.04 - -# ARG for quick switch to a given ubuntu mirror -ARG apt_archive="http://archive.ubuntu.com" -RUN sed -i "s|http://archive.ubuntu.com|$apt_archive|g" /etc/apt/sources.list - -ENV DEBIAN_FRONTEND=noninteractive LLVM_VERSION=14 - -RUN apt-get update \ - && apt-get install \ - apt-transport-https \ - apt-utils \ - ca-certificates \ - dnsutils \ - gnupg \ - iputils-ping \ - lsb-release \ - wget \ - --yes --no-install-recommends --verbose-versions \ - && export LLVM_PUBKEY_HASH="bda960a8da687a275a2078d43c111d66b1c6a893a3275271beedf266c1ff4a0cdecb429c7a5cccf9f486ea7aa43fd27f" \ - && wget -nv -O /tmp/llvm-snapshot.gpg.key https://apt.llvm.org/llvm-snapshot.gpg.key \ - && echo "${LLVM_PUBKEY_HASH} /tmp/llvm-snapshot.gpg.key" | sha384sum -c \ - && apt-key add /tmp/llvm-snapshot.gpg.key \ - && export CODENAME="$(lsb_release --codename --short | tr 'A-Z' 'a-z')" \ - && echo "deb [trusted=yes] https://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-${LLVM_VERSION} main" >> \ - /etc/apt/sources.list \ - && apt-get clean - -# initial packages -RUN apt-get update \ - && apt-get install \ - bash \ - build-essential \ - ccache \ - clang-${LLVM_VERSION} \ - clang-tidy-${LLVM_VERSION} \ - cmake \ - curl \ - fakeroot \ - gdb \ - git \ - gperf \ - lld-${LLVM_VERSION} \ - llvm-${LLVM_VERSION} \ - llvm-${LLVM_VERSION}-dev \ - moreutils \ - ninja-build \ - pigz \ - rename \ - software-properties-common \ - tzdata \ - nasm \ - --yes --no-install-recommends \ - && apt-get clean - -# This symlink required by gcc to find lld compiler -RUN ln -s /usr/bin/lld-${LLVM_VERSION} /usr/bin/ld.lld +ARG FROM_TAG=latest +FROM clickhouse/test-util:$FROM_TAG ENV CC=clang-${LLVM_VERSION} ENV CXX=clang++-${LLVM_VERSION} diff --git a/docker/test/base/Dockerfile b/docker/test/base/Dockerfile index a1ae77343cb..43cfca1fdfc 100644 --- a/docker/test/base/Dockerfile +++ b/docker/test/base/Dockerfile @@ -3,59 +3,12 @@ ARG FROM_TAG=latest FROM clickhouse/test-util:$FROM_TAG -# ARG for quick switch to a given ubuntu mirror -ARG apt_archive="http://archive.ubuntu.com" -RUN sed -i "s|http://archive.ubuntu.com|$apt_archive|g" /etc/apt/sources.list - -ENV DEBIAN_FRONTEND=noninteractive LLVM_VERSION=14 - -RUN apt-get update \ - && apt-get install ca-certificates lsb-release wget gnupg apt-transport-https \ - --yes --no-install-recommends --verbose-versions \ - && export LLVM_PUBKEY_HASH="bda960a8da687a275a2078d43c111d66b1c6a893a3275271beedf266c1ff4a0cdecb429c7a5cccf9f486ea7aa43fd27f" \ - && wget -nv -O /tmp/llvm-snapshot.gpg.key https://apt.llvm.org/llvm-snapshot.gpg.key \ - && echo "${LLVM_PUBKEY_HASH} /tmp/llvm-snapshot.gpg.key" | sha384sum -c \ - && apt-key add /tmp/llvm-snapshot.gpg.key \ - && export CODENAME="$(lsb_release --codename --short | tr 'A-Z' 'a-z')" \ - && echo "deb [trusted=yes] http://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-${LLVM_VERSION} main" >> \ - /etc/apt/sources.list - -# initial packages RUN apt-get update \ && apt-get install \ - bash \ - fakeroot \ - ccache \ - curl \ - software-properties-common \ - --yes --no-install-recommends - -# Architecture of the image when BuildKit/buildx is used -ARG TARGETARCH - -# Special dpkg-deb (https://github.com/ClickHouse-Extras/dpkg) version which is able -# to compress files using pigz (https://zlib.net/pigz/) instead of gzip. -# Significantly increase deb packaging speed and compatible with old systems -RUN arch=${TARGETARCH:-amd64} \ - && curl -Lo /usr/bin/dpkg-deb https://github.com/ClickHouse-Extras/dpkg/releases/download/1.21.1-clickhouse/dpkg-deb-${arch} - -RUN apt-get update \ - && apt-get install \ - clang-${LLVM_VERSION} \ - debhelper \ - devscripts \ - gdb \ - git \ - gperf \ lcov \ - llvm-${LLVM_VERSION} \ - moreutils \ + netbase \ perl \ - pigz \ - pkg-config \ - tzdata \ pv \ - nasm \ --yes --no-install-recommends # Sanitizer options for services (clickhouse-server) diff --git a/docker/test/fasttest/Dockerfile b/docker/test/fasttest/Dockerfile index 699e2c7ceb9..d74f99cf54b 100644 --- a/docker/test/fasttest/Dockerfile +++ b/docker/test/fasttest/Dockerfile @@ -3,83 +3,23 @@ ARG FROM_TAG=latest FROM clickhouse/test-util:$FROM_TAG -# ARG for quick switch to a given ubuntu mirror -ARG apt_archive="http://archive.ubuntu.com" -RUN sed -i "s|http://archive.ubuntu.com|$apt_archive|g" /etc/apt/sources.list - -ENV DEBIAN_FRONTEND=noninteractive LLVM_VERSION=14 - -RUN apt-get update \ - && apt-get install ca-certificates lsb-release wget gnupg apt-transport-https \ - --yes --no-install-recommends --verbose-versions \ - && export LLVM_PUBKEY_HASH="bda960a8da687a275a2078d43c111d66b1c6a893a3275271beedf266c1ff4a0cdecb429c7a5cccf9f486ea7aa43fd27f" \ - && wget -nv -O /tmp/llvm-snapshot.gpg.key https://apt.llvm.org/llvm-snapshot.gpg.key \ - && echo "${LLVM_PUBKEY_HASH} /tmp/llvm-snapshot.gpg.key" | sha384sum -c \ - && apt-key add /tmp/llvm-snapshot.gpg.key \ - && export CODENAME="$(lsb_release --codename --short | tr 'A-Z' 'a-z')" \ - && echo "deb [trusted=yes] https://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-${LLVM_VERSION} main" >> \ - /etc/apt/sources.list - -# initial packages RUN apt-get update \ && apt-get install \ - bash \ - fakeroot \ - ccache \ - curl \ - software-properties-common \ - --yes --no-install-recommends - -# Architecture of the image when BuildKit/buildx is used -ARG TARGETARCH - -# Special dpkg-deb (https://github.com/ClickHouse-Extras/dpkg) version which is able -# to compress files using pigz (https://zlib.net/pigz/) instead of gzip. -# Significantly increase deb packaging speed and compatible with old systems -RUN arch=${TARGETARCH:-amd64} \ - && curl -Lo /usr/bin/dpkg-deb https://github.com/ClickHouse-Extras/dpkg/releases/download/1.21.1-clickhouse/dpkg-deb-${arch} - -RUN apt-get update \ - && apt-get install \ - apt-transport-https \ - bash \ brotli \ - build-essential \ - ca-certificates \ - ccache \ - clang-${LLVM_VERSION} \ - clang-tidy-${LLVM_VERSION} \ - cmake \ - curl \ expect \ - fakeroot \ - gdb \ - git \ - gperf \ - lld-${LLVM_VERSION} \ - llvm-${LLVM_VERSION} \ + file \ lsof \ - moreutils \ - ninja-build \ psmisc \ python3 \ python3-lxml \ python3-pip \ python3-requests \ python3-termcolor \ - rename \ - software-properties-common \ - tzdata \ unixodbc \ - file \ - nasm \ --yes --no-install-recommends RUN pip3 install numpy scipy pandas Jinja2 -# This symlink required by gcc to find lld compiler -RUN ln -s /usr/bin/lld-${LLVM_VERSION} /usr/bin/ld.lld - ARG odbc_driver_url="https://github.com/ClickHouse/clickhouse-odbc/releases/download/v1.1.4.20200302/clickhouse-odbc-1.1.4-Linux.tar.gz" RUN mkdir -p /tmp/clickhouse-odbc-tmp \ diff --git a/docker/test/util/Dockerfile b/docker/test/util/Dockerfile index d9827260acb..b891b71492c 100644 --- a/docker/test/util/Dockerfile +++ b/docker/test/util/Dockerfile @@ -1,5 +1,82 @@ -# rebuild in #33610 # docker build -t clickhouse/test-util . - FROM ubuntu:20.04 + +# ARG for quick switch to a given ubuntu mirror +ARG apt_archive="http://archive.ubuntu.com" +RUN sed -i "s|http://archive.ubuntu.com|$apt_archive|g" /etc/apt/sources.list + +ENV DEBIAN_FRONTEND=noninteractive LLVM_VERSION=14 + +RUN apt-get update \ + && apt-get install \ + apt-transport-https \ + apt-utils \ + ca-certificates \ + dnsutils \ + gnupg \ + iputils-ping \ + lsb-release \ + wget \ + --yes --no-install-recommends --verbose-versions \ + && export LLVM_PUBKEY_HASH="bda960a8da687a275a2078d43c111d66b1c6a893a3275271beedf266c1ff4a0cdecb429c7a5cccf9f486ea7aa43fd27f" \ + && wget -nv -O /tmp/llvm-snapshot.gpg.key https://apt.llvm.org/llvm-snapshot.gpg.key \ + && echo "${LLVM_PUBKEY_HASH} /tmp/llvm-snapshot.gpg.key" | sha384sum -c \ + && apt-key add /tmp/llvm-snapshot.gpg.key \ + && export CODENAME="$(lsb_release --codename --short | tr 'A-Z' 'a-z')" \ + && echo "deb [trusted=yes] https://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-${LLVM_VERSION} main" >> \ + /etc/apt/sources.list \ + && apt-get clean + +# initial packages +RUN apt-get update \ + && apt-get install \ + bash \ + bsdmainutils \ + build-essential \ + clang-${LLVM_VERSION} \ + clang-tidy-${LLVM_VERSION} \ + cmake \ + curl \ + fakeroot \ + gdb \ + git \ + gperf \ + lld-${LLVM_VERSION} \ + llvm-${LLVM_VERSION} \ + llvm-${LLVM_VERSION}-dev \ + moreutils \ + nasm \ + ninja-build \ + pigz \ + rename \ + software-properties-common \ + tzdata \ + --yes --no-install-recommends \ + && apt-get clean + +# This symlink required by gcc to find lld compiler +RUN ln -s /usr/bin/lld-${LLVM_VERSION} /usr/bin/ld.lld + +ARG CCACHE_VERSION=4.6.1 +RUN mkdir /tmp/ccache \ + && cd /tmp/ccache \ + && curl -L \ + -O https://github.com/ccache/ccache/releases/download/v$CCACHE_VERSION/ccache-$CCACHE_VERSION.tar.xz \ + -O https://github.com/ccache/ccache/releases/download/v$CCACHE_VERSION/ccache-$CCACHE_VERSION.tar.xz.asc \ + && gpg --recv-keys --keyserver hkps://keyserver.ubuntu.com 5A939A71A46792CF57866A51996DDA075594ADB8 \ + && gpg --verify ccache-4.6.1.tar.xz.asc \ + && tar xf ccache-$CCACHE_VERSION.tar.xz \ + && cd /tmp/ccache/ccache-$CCACHE_VERSION \ + && cmake -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_BUILD_TYPE=None \ + -DZSTD_FROM_INTERNET=ON \ + -DREDIS_STORAGE_BACKEND=OFF \ + -Wno-dev \ + -B build \ + -S . \ + && make VERBOSE=1 -C build \ + && make install -C build \ + && cd / \ + && rm -rf /tmp/ccache + COPY process_functional_tests_result.py / From 8533769132027c4bd8fee5386dc1a3837704e470 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Sat, 30 Jul 2022 00:47:12 +0200 Subject: [PATCH 284/672] Use compression and cleanup with the recent version ccache --- docker/packager/binary/build.sh | 9 +++++---- docker/packager/packager | 2 +- docker/test/fasttest/run.sh | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docker/packager/binary/build.sh b/docker/packager/binary/build.sh index b5db874f33c..fb2b08a2633 100755 --- a/docker/packager/binary/build.sh +++ b/docker/packager/binary/build.sh @@ -3,7 +3,7 @@ set -x -e exec &> >(ts) -cache_status () { +ccache_status () { ccache --show-config ||: ccache --show-stats ||: } @@ -48,7 +48,7 @@ if [ -n "$MAKE_DEB" ]; then fi -cache_status +ccache_status # clear cache stats ccache --zero-stats ||: @@ -92,7 +92,7 @@ $SCAN_WRAPPER ninja $NINJA_FLAGS $BUILD_TARGET ls -la ./programs -cache_status +ccache_status if [ -n "$MAKE_DEB" ]; then # No quotes because I want it to expand to nothing if empty. @@ -178,7 +178,8 @@ then mv "coverity-scan.tgz" /output fi -cache_status +ccache_status +ccache --evict-older-than 1d if [ "${CCACHE_DEBUG:-}" == "1" ] then diff --git a/docker/packager/packager b/docker/packager/packager index cf0e555d57c..65bdd95b0cc 100755 --- a/docker/packager/packager +++ b/docker/packager/packager @@ -234,6 +234,7 @@ def parse_env_variables( if cache: result.append("CCACHE_DIR=/ccache") + result.append("CCACHE_COMPRESSLEVEL=5") result.append("CCACHE_BASEDIR=/build") result.append("CCACHE_NOHASHDIR=true") result.append("CCACHE_COMPILERCHECK=content") @@ -242,7 +243,6 @@ def parse_env_variables( # 15G is not enough for tidy build cache_maxsize = "25G" result.append(f"CCACHE_MAXSIZE={cache_maxsize}") - # result.append("CCACHE_UMASK=777") if distcc_hosts: hosts_with_params = [f"{host}/24,lzo" for host in distcc_hosts] + [ diff --git a/docker/test/fasttest/run.sh b/docker/test/fasttest/run.sh index 6b8109a15b2..377b816b2b6 100755 --- a/docker/test/fasttest/run.sh +++ b/docker/test/fasttest/run.sh @@ -160,9 +160,8 @@ function run_cmake "-DENABLE_REPLXX=1" ) - # TODO remove this? we don't use ccache anyway. An option would be to download it - # from S3 simultaneously with cloning. export CCACHE_DIR="$FASTTEST_WORKSPACE/ccache" + export CCACHE_COMPRESSLEVEL=5 export CCACHE_BASEDIR="$FASTTEST_SOURCE" export CCACHE_NOHASHDIR=true export CCACHE_COMPILERCHECK=content @@ -191,6 +190,7 @@ function build gzip "$FASTTEST_OUTPUT/clickhouse-stripped" fi ccache --show-stats ||: + ccache --evict-older-than 1d ||: ) } From fda8b113dc88100ff80dfd778ed1e0bcd740d4d2 Mon Sep 17 00:00:00 2001 From: nathanbegbie Date: Wed, 3 Aug 2022 15:54:58 +0300 Subject: [PATCH 285/672] typo: PostgerSQL -> PostgreSQL --- .../en/engines/database-engines/postgresql.md | 76 +++++++-------- .../ru/engines/database-engines/postgresql.md | 75 +++++++-------- .../zh/engines/database-engines/postgresql.md | 93 ++++++++++--------- 3 files changed, 123 insertions(+), 121 deletions(-) diff --git a/docs/en/engines/database-engines/postgresql.md b/docs/en/engines/database-engines/postgresql.md index 969a326b701..9eea920d8cb 100644 --- a/docs/en/engines/database-engines/postgresql.md +++ b/docs/en/engines/database-engines/postgresql.md @@ -13,53 +13,52 @@ Supports table structure modifications (`ALTER TABLE ... ADD|DROP COLUMN`). If ` ## Creating a Database {#creating-a-database} -``` sql +```sql CREATE DATABASE test_database ENGINE = PostgreSQL('host:port', 'database', 'user', 'password'[, `schema`, `use_table_cache`]); ``` **Engine Parameters** -- `host:port` — PostgreSQL server address. -- `database` — Remote database name. -- `user` — PostgreSQL user. -- `password` — User password. -- `schema` — PostgreSQL schema. -- `use_table_cache` — Defines if the database table structure is cached or not. Optional. Default value: `0`. +- `host:port` — PostgreSQL server address. +- `database` — Remote database name. +- `user` — PostgreSQL user. +- `password` — User password. +- `schema` — PostgreSQL schema. +- `use_table_cache` — Defines if the database table structure is cached or not. Optional. Default value: `0`. ## Data Types Support {#data_types-support} -| PostgerSQL | ClickHouse | -|------------------|--------------------------------------------------------------| -| DATE | [Date](../../sql-reference/data-types/date.md) | -| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | -| REAL | [Float32](../../sql-reference/data-types/float.md) | -| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | -| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | -| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | -| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | -| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | -| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | -| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | -| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | -| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md))| -| ARRAY | [Array](../../sql-reference/data-types/array.md) | - +| PostgreSQL | ClickHouse | +| ---------------- | ------------------------------------------------------------- | +| DATE | [Date](../../sql-reference/data-types/date.md) | +| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | +| REAL | [Float32](../../sql-reference/data-types/float.md) | +| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | +| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | +| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | +| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | +| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | +| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | +| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | +| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | +| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md)) | +| ARRAY | [Array](../../sql-reference/data-types/array.md) | ## Examples of Use {#examples-of-use} Database in ClickHouse, exchanging data with the PostgreSQL server: -``` sql +```sql CREATE DATABASE test_database ENGINE = PostgreSQL('postgres1:5432', 'test_database', 'postgres', 'mysecretpassword', 'schema_name',1); ``` -``` sql +```sql SHOW DATABASES; ``` -``` text +```text ┌─name──────────┐ │ default │ │ test_database │ @@ -67,11 +66,11 @@ SHOW DATABASES; └───────────────┘ ``` -``` sql +```sql SHOW TABLES FROM test_database; ``` -``` text +```text ┌─name───────┐ │ test_table │ └────────────┘ @@ -79,11 +78,11 @@ SHOW TABLES FROM test_database; Reading data from the PostgreSQL table: -``` sql +```sql SELECT * FROM test_database.test_table; ``` -``` text +```text ┌─id─┬─value─┐ │ 1 │ 2 │ └────┴───────┘ @@ -91,12 +90,12 @@ SELECT * FROM test_database.test_table; Writing data to the PostgreSQL table: -``` sql +```sql INSERT INTO test_database.test_table VALUES (3,4); SELECT * FROM test_database.test_table; ``` -``` text +```text ┌─int_id─┬─value─┐ │ 1 │ 2 │ │ 3 │ 4 │ @@ -105,16 +104,17 @@ SELECT * FROM test_database.test_table; Consider the table structure was modified in PostgreSQL: -``` sql +```sql postgre> ALTER TABLE test_table ADD COLUMN data Text ``` As the `use_table_cache` parameter was set to `1` when the database was created, the table structure in ClickHouse was cached and therefore not modified: -``` sql +```sql DESCRIBE TABLE test_database.test_table; ``` -``` text + +```text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ @@ -123,16 +123,16 @@ DESCRIBE TABLE test_database.test_table; After detaching the table and attaching it again, the structure was updated: -``` sql +```sql DETACH TABLE test_database.test_table; ATTACH TABLE test_database.test_table; DESCRIBE TABLE test_database.test_table; ``` -``` text + +```text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ │ data │ Nullable(String) │ └────────┴───────────────────┘ ``` - diff --git a/docs/ru/engines/database-engines/postgresql.md b/docs/ru/engines/database-engines/postgresql.md index 521a3689a7d..00957194d04 100644 --- a/docs/ru/engines/database-engines/postgresql.md +++ b/docs/ru/engines/database-engines/postgresql.md @@ -13,53 +13,52 @@ sidebar_label: PostgreSQL ## Создание БД {#creating-a-database} -``` sql +```sql CREATE DATABASE test_database ENGINE = PostgreSQL('host:port', 'database', 'user', 'password'[, `schema`, `use_table_cache`]); ``` **Параметры движка** -- `host:port` — адрес сервера PostgreSQL. -- `database` — имя удаленной БД. -- `user` — пользователь PostgreSQL. -- `password` — пароль пользователя. - - `schema` — схема PostgreSQL. -- `use_table_cache` — определяет кеширование структуры таблиц БД. Необязательный параметр. Значение по умолчанию: `0`. +- `host:port` — адрес сервера PostgreSQL. +- `database` — имя удаленной БД. +- `user` — пользователь PostgreSQL. +- `password` — пароль пользователя. +- `schema` — схема PostgreSQL. +- `use_table_cache` — определяет кеширование структуры таблиц БД. Необязательный параметр. Значение по умолчанию: `0`. ## Поддерживаемые типы данных {#data_types-support} -| PostgerSQL | ClickHouse | -|------------------|--------------------------------------------------------------| -| DATE | [Date](../../sql-reference/data-types/date.md) | -| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | -| REAL | [Float32](../../sql-reference/data-types/float.md) | -| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | -| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | -| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | -| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | -| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | -| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | -| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | -| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | -| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md))| -| ARRAY | [Array](../../sql-reference/data-types/array.md) | - +| PostgreSQL | ClickHouse | +| ---------------- | ------------------------------------------------------------- | +| DATE | [Date](../../sql-reference/data-types/date.md) | +| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | +| REAL | [Float32](../../sql-reference/data-types/float.md) | +| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | +| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | +| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | +| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | +| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | +| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | +| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | +| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | +| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md)) | +| ARRAY | [Array](../../sql-reference/data-types/array.md) | ## Примеры использования {#examples-of-use} Обмен данными между БД ClickHouse и сервером PostgreSQL: -``` sql +```sql CREATE DATABASE test_database ENGINE = PostgreSQL('postgres1:5432', 'test_database', 'postgres', 'mysecretpassword', 1); ``` -``` sql +```sql SHOW DATABASES; ``` -``` text +```text ┌─name──────────┐ │ default │ │ test_database │ @@ -67,11 +66,11 @@ SHOW DATABASES; └───────────────┘ ``` -``` sql +```sql SHOW TABLES FROM test_database; ``` -``` text +```text ┌─name───────┐ │ test_table │ └────────────┘ @@ -79,11 +78,11 @@ SHOW TABLES FROM test_database; Чтение данных из таблицы PostgreSQL: -``` sql +```sql SELECT * FROM test_database.test_table; ``` -``` text +```text ┌─id─┬─value─┐ │ 1 │ 2 │ └────┴───────┘ @@ -91,12 +90,12 @@ SELECT * FROM test_database.test_table; Запись данных в таблицу PostgreSQL: -``` sql +```sql INSERT INTO test_database.test_table VALUES (3,4); SELECT * FROM test_database.test_table; ``` -``` text +```text ┌─int_id─┬─value─┐ │ 1 │ 2 │ │ 3 │ 4 │ @@ -105,16 +104,17 @@ SELECT * FROM test_database.test_table; Пусть структура таблицы была изменена в PostgreSQL: -``` sql +```sql postgre> ALTER TABLE test_table ADD COLUMN data Text ``` Поскольку при создании БД параметр `use_table_cache` был установлен в значение `1`, структура таблицы в ClickHouse была кеширована и поэтому не изменилась: -``` sql +```sql DESCRIBE TABLE test_database.test_table; ``` -``` text + +```text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ @@ -123,12 +123,13 @@ DESCRIBE TABLE test_database.test_table; После того как таблицу «отцепили» и затем снова «прицепили», структура обновилась: -``` sql +```sql DETACH TABLE test_database.test_table; ATTACH TABLE test_database.test_table; DESCRIBE TABLE test_database.test_table; ``` -``` text + +```text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ diff --git a/docs/zh/engines/database-engines/postgresql.md b/docs/zh/engines/database-engines/postgresql.md index 6afefa09cd5..076189dfe3d 100644 --- a/docs/zh/engines/database-engines/postgresql.md +++ b/docs/zh/engines/database-engines/postgresql.md @@ -5,61 +5,60 @@ sidebar_label: PostgreSQL # PostgreSQL {#postgresql} -允许连接到远程[PostgreSQL](https://www.postgresql.org)服务。支持读写操作(`SELECT`和`INSERT`查询),以在ClickHouse和PostgreSQL之间交换数据。 +允许连接到远程[PostgreSQL](https://www.postgresql.org)服务。支持读写操作(`SELECT`和`INSERT`查询),以在 ClickHouse 和 PostgreSQL 之间交换数据。 -在`SHOW TABLES`和`DESCRIBE TABLE`查询的帮助下,从远程PostgreSQL实时访问表列表和表结构。 +在`SHOW TABLES`和`DESCRIBE TABLE`查询的帮助下,从远程 PostgreSQL 实时访问表列表和表结构。 支持表结构修改(`ALTER TABLE ... ADD|DROP COLUMN`)。如果`use_table_cache`参数(参见下面的引擎参数)设置为`1`,则会缓存表结构,不会检查是否被修改,但可以用`DETACH`和`ATTACH`查询进行更新。 ## 创建数据库 {#creating-a-database} -``` sql -CREATE DATABASE test_database +```sql +CREATE DATABASE test_database ENGINE = PostgreSQL('host:port', 'database', 'user', 'password'[, `use_table_cache`]); ``` **引擎参数** -- `host:port` — PostgreSQL服务地址 -- `database` — 远程数据库名次 -- `user` — PostgreSQL用户名称 -- `password` — PostgreSQL用户密码 -- `schema` - PostgreSQL 模式 -- `use_table_cache` — 定义数据库表结构是否已缓存或不进行。可选的。默认值: `0`. +- `host:port` — PostgreSQL 服务地址 +- `database` — 远程数据库名次 +- `user` — PostgreSQL 用户名称 +- `password` — PostgreSQL 用户密码 +- `schema` - PostgreSQL 模式 +- `use_table_cache` — 定义数据库表结构是否已缓存或不进行。可选的。默认值: `0`. ## 支持的数据类型 {#data_types-support} -| PostgerSQL | ClickHouse | -|------------------|--------------------------------------------------------------| -| DATE | [Date](../../sql-reference/data-types/date.md) | -| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | -| REAL | [Float32](../../sql-reference/data-types/float.md) | -| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | -| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | -| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | -| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | -| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | -| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | -| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | -| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | -| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md))| -| ARRAY | [Array](../../sql-reference/data-types/array.md) | - +| PostgreSQL | ClickHouse | +| ---------------- | ------------------------------------------------------------- | +| DATE | [Date](../../sql-reference/data-types/date.md) | +| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | +| REAL | [Float32](../../sql-reference/data-types/float.md) | +| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | +| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | +| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | +| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | +| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | +| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | +| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | +| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | +| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md)) | +| ARRAY | [Array](../../sql-reference/data-types/array.md) | ## 使用示例 {#examples-of-use} -ClickHouse中的数据库,与PostgreSQL服务器交换数据: +ClickHouse 中的数据库,与 PostgreSQL 服务器交换数据: -``` sql -CREATE DATABASE test_database +```sql +CREATE DATABASE test_database ENGINE = PostgreSQL('postgres1:5432', 'test_database', 'postgres', 'mysecretpassword', 1); ``` -``` sql +```sql SHOW DATABASES; ``` -``` text +```text ┌─name──────────┐ │ default │ │ test_database │ @@ -67,54 +66,55 @@ SHOW DATABASES; └───────────────┘ ``` -``` sql +```sql SHOW TABLES FROM test_database; ``` -``` text +```text ┌─name───────┐ │ test_table │ └────────────┘ ``` -从PostgreSQL表中读取数据: +从 PostgreSQL 表中读取数据: -``` sql +```sql SELECT * FROM test_database.test_table; ``` -``` text +```text ┌─id─┬─value─┐ │ 1 │ 2 │ └────┴───────┘ ``` -将数据写入PostgreSQL表: +将数据写入 PostgreSQL 表: -``` sql +```sql INSERT INTO test_database.test_table VALUES (3,4); SELECT * FROM test_database.test_table; ``` -``` text +```text ┌─int_id─┬─value─┐ │ 1 │ 2 │ │ 3 │ 4 │ └────────┴───────┘ ``` -在PostgreSQL中修改了表结构: +在 PostgreSQL 中修改了表结构: -``` sql +```sql postgre> ALTER TABLE test_table ADD COLUMN data Text ``` -当创建数据库时,参数`use_table_cache`被设置为`1`,ClickHouse中的表结构被缓存,因此没有被修改: +当创建数据库时,参数`use_table_cache`被设置为`1`,ClickHouse 中的表结构被缓存,因此没有被修改: -``` sql +```sql DESCRIBE TABLE test_database.test_table; ``` -``` text + +```text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ @@ -123,12 +123,13 @@ DESCRIBE TABLE test_database.test_table; 分离表并再次附加它之后,结构被更新了: -``` sql +```sql DETACH TABLE test_database.test_table; ATTACH TABLE test_database.test_table; DESCRIBE TABLE test_database.test_table; ``` -``` text + +```text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ From de91875b5e8e0ad425d7110229d3861509a24beb Mon Sep 17 00:00:00 2001 From: nathanbegbie Date: Wed, 3 Aug 2022 15:59:36 +0300 Subject: [PATCH 286/672] Revert "typo: PostgerSQL -> PostgreSQL" This reverts commit fda8b113dc88100ff80dfd778ed1e0bcd740d4d2. --- .../en/engines/database-engines/postgresql.md | 76 +++++++-------- .../ru/engines/database-engines/postgresql.md | 75 ++++++++------- .../zh/engines/database-engines/postgresql.md | 93 +++++++++---------- 3 files changed, 121 insertions(+), 123 deletions(-) diff --git a/docs/en/engines/database-engines/postgresql.md b/docs/en/engines/database-engines/postgresql.md index 9eea920d8cb..969a326b701 100644 --- a/docs/en/engines/database-engines/postgresql.md +++ b/docs/en/engines/database-engines/postgresql.md @@ -13,52 +13,53 @@ Supports table structure modifications (`ALTER TABLE ... ADD|DROP COLUMN`). If ` ## Creating a Database {#creating-a-database} -```sql +``` sql CREATE DATABASE test_database ENGINE = PostgreSQL('host:port', 'database', 'user', 'password'[, `schema`, `use_table_cache`]); ``` **Engine Parameters** -- `host:port` — PostgreSQL server address. -- `database` — Remote database name. -- `user` — PostgreSQL user. -- `password` — User password. -- `schema` — PostgreSQL schema. -- `use_table_cache` — Defines if the database table structure is cached or not. Optional. Default value: `0`. +- `host:port` — PostgreSQL server address. +- `database` — Remote database name. +- `user` — PostgreSQL user. +- `password` — User password. +- `schema` — PostgreSQL schema. +- `use_table_cache` — Defines if the database table structure is cached or not. Optional. Default value: `0`. ## Data Types Support {#data_types-support} -| PostgreSQL | ClickHouse | -| ---------------- | ------------------------------------------------------------- | -| DATE | [Date](../../sql-reference/data-types/date.md) | -| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | -| REAL | [Float32](../../sql-reference/data-types/float.md) | -| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | -| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | -| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | -| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | -| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | -| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | -| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | -| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | -| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md)) | -| ARRAY | [Array](../../sql-reference/data-types/array.md) | +| PostgerSQL | ClickHouse | +|------------------|--------------------------------------------------------------| +| DATE | [Date](../../sql-reference/data-types/date.md) | +| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | +| REAL | [Float32](../../sql-reference/data-types/float.md) | +| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | +| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | +| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | +| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | +| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | +| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | +| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | +| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | +| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md))| +| ARRAY | [Array](../../sql-reference/data-types/array.md) | + ## Examples of Use {#examples-of-use} Database in ClickHouse, exchanging data with the PostgreSQL server: -```sql +``` sql CREATE DATABASE test_database ENGINE = PostgreSQL('postgres1:5432', 'test_database', 'postgres', 'mysecretpassword', 'schema_name',1); ``` -```sql +``` sql SHOW DATABASES; ``` -```text +``` text ┌─name──────────┐ │ default │ │ test_database │ @@ -66,11 +67,11 @@ SHOW DATABASES; └───────────────┘ ``` -```sql +``` sql SHOW TABLES FROM test_database; ``` -```text +``` text ┌─name───────┐ │ test_table │ └────────────┘ @@ -78,11 +79,11 @@ SHOW TABLES FROM test_database; Reading data from the PostgreSQL table: -```sql +``` sql SELECT * FROM test_database.test_table; ``` -```text +``` text ┌─id─┬─value─┐ │ 1 │ 2 │ └────┴───────┘ @@ -90,12 +91,12 @@ SELECT * FROM test_database.test_table; Writing data to the PostgreSQL table: -```sql +``` sql INSERT INTO test_database.test_table VALUES (3,4); SELECT * FROM test_database.test_table; ``` -```text +``` text ┌─int_id─┬─value─┐ │ 1 │ 2 │ │ 3 │ 4 │ @@ -104,17 +105,16 @@ SELECT * FROM test_database.test_table; Consider the table structure was modified in PostgreSQL: -```sql +``` sql postgre> ALTER TABLE test_table ADD COLUMN data Text ``` As the `use_table_cache` parameter was set to `1` when the database was created, the table structure in ClickHouse was cached and therefore not modified: -```sql +``` sql DESCRIBE TABLE test_database.test_table; ``` - -```text +``` text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ @@ -123,16 +123,16 @@ DESCRIBE TABLE test_database.test_table; After detaching the table and attaching it again, the structure was updated: -```sql +``` sql DETACH TABLE test_database.test_table; ATTACH TABLE test_database.test_table; DESCRIBE TABLE test_database.test_table; ``` - -```text +``` text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ │ data │ Nullable(String) │ └────────┴───────────────────┘ ``` + diff --git a/docs/ru/engines/database-engines/postgresql.md b/docs/ru/engines/database-engines/postgresql.md index 00957194d04..521a3689a7d 100644 --- a/docs/ru/engines/database-engines/postgresql.md +++ b/docs/ru/engines/database-engines/postgresql.md @@ -13,52 +13,53 @@ sidebar_label: PostgreSQL ## Создание БД {#creating-a-database} -```sql +``` sql CREATE DATABASE test_database ENGINE = PostgreSQL('host:port', 'database', 'user', 'password'[, `schema`, `use_table_cache`]); ``` **Параметры движка** -- `host:port` — адрес сервера PostgreSQL. -- `database` — имя удаленной БД. -- `user` — пользователь PostgreSQL. -- `password` — пароль пользователя. -- `schema` — схема PostgreSQL. -- `use_table_cache` — определяет кеширование структуры таблиц БД. Необязательный параметр. Значение по умолчанию: `0`. +- `host:port` — адрес сервера PostgreSQL. +- `database` — имя удаленной БД. +- `user` — пользователь PostgreSQL. +- `password` — пароль пользователя. + - `schema` — схема PostgreSQL. +- `use_table_cache` — определяет кеширование структуры таблиц БД. Необязательный параметр. Значение по умолчанию: `0`. ## Поддерживаемые типы данных {#data_types-support} -| PostgreSQL | ClickHouse | -| ---------------- | ------------------------------------------------------------- | -| DATE | [Date](../../sql-reference/data-types/date.md) | -| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | -| REAL | [Float32](../../sql-reference/data-types/float.md) | -| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | -| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | -| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | -| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | -| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | -| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | -| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | -| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | -| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md)) | -| ARRAY | [Array](../../sql-reference/data-types/array.md) | +| PostgerSQL | ClickHouse | +|------------------|--------------------------------------------------------------| +| DATE | [Date](../../sql-reference/data-types/date.md) | +| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | +| REAL | [Float32](../../sql-reference/data-types/float.md) | +| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | +| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | +| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | +| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | +| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | +| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | +| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | +| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | +| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md))| +| ARRAY | [Array](../../sql-reference/data-types/array.md) | + ## Примеры использования {#examples-of-use} Обмен данными между БД ClickHouse и сервером PostgreSQL: -```sql +``` sql CREATE DATABASE test_database ENGINE = PostgreSQL('postgres1:5432', 'test_database', 'postgres', 'mysecretpassword', 1); ``` -```sql +``` sql SHOW DATABASES; ``` -```text +``` text ┌─name──────────┐ │ default │ │ test_database │ @@ -66,11 +67,11 @@ SHOW DATABASES; └───────────────┘ ``` -```sql +``` sql SHOW TABLES FROM test_database; ``` -```text +``` text ┌─name───────┐ │ test_table │ └────────────┘ @@ -78,11 +79,11 @@ SHOW TABLES FROM test_database; Чтение данных из таблицы PostgreSQL: -```sql +``` sql SELECT * FROM test_database.test_table; ``` -```text +``` text ┌─id─┬─value─┐ │ 1 │ 2 │ └────┴───────┘ @@ -90,12 +91,12 @@ SELECT * FROM test_database.test_table; Запись данных в таблицу PostgreSQL: -```sql +``` sql INSERT INTO test_database.test_table VALUES (3,4); SELECT * FROM test_database.test_table; ``` -```text +``` text ┌─int_id─┬─value─┐ │ 1 │ 2 │ │ 3 │ 4 │ @@ -104,17 +105,16 @@ SELECT * FROM test_database.test_table; Пусть структура таблицы была изменена в PostgreSQL: -```sql +``` sql postgre> ALTER TABLE test_table ADD COLUMN data Text ``` Поскольку при создании БД параметр `use_table_cache` был установлен в значение `1`, структура таблицы в ClickHouse была кеширована и поэтому не изменилась: -```sql +``` sql DESCRIBE TABLE test_database.test_table; ``` - -```text +``` text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ @@ -123,13 +123,12 @@ DESCRIBE TABLE test_database.test_table; После того как таблицу «отцепили» и затем снова «прицепили», структура обновилась: -```sql +``` sql DETACH TABLE test_database.test_table; ATTACH TABLE test_database.test_table; DESCRIBE TABLE test_database.test_table; ``` - -```text +``` text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ diff --git a/docs/zh/engines/database-engines/postgresql.md b/docs/zh/engines/database-engines/postgresql.md index 076189dfe3d..6afefa09cd5 100644 --- a/docs/zh/engines/database-engines/postgresql.md +++ b/docs/zh/engines/database-engines/postgresql.md @@ -5,60 +5,61 @@ sidebar_label: PostgreSQL # PostgreSQL {#postgresql} -允许连接到远程[PostgreSQL](https://www.postgresql.org)服务。支持读写操作(`SELECT`和`INSERT`查询),以在 ClickHouse 和 PostgreSQL 之间交换数据。 +允许连接到远程[PostgreSQL](https://www.postgresql.org)服务。支持读写操作(`SELECT`和`INSERT`查询),以在ClickHouse和PostgreSQL之间交换数据。 -在`SHOW TABLES`和`DESCRIBE TABLE`查询的帮助下,从远程 PostgreSQL 实时访问表列表和表结构。 +在`SHOW TABLES`和`DESCRIBE TABLE`查询的帮助下,从远程PostgreSQL实时访问表列表和表结构。 支持表结构修改(`ALTER TABLE ... ADD|DROP COLUMN`)。如果`use_table_cache`参数(参见下面的引擎参数)设置为`1`,则会缓存表结构,不会检查是否被修改,但可以用`DETACH`和`ATTACH`查询进行更新。 ## 创建数据库 {#creating-a-database} -```sql -CREATE DATABASE test_database +``` sql +CREATE DATABASE test_database ENGINE = PostgreSQL('host:port', 'database', 'user', 'password'[, `use_table_cache`]); ``` **引擎参数** -- `host:port` — PostgreSQL 服务地址 -- `database` — 远程数据库名次 -- `user` — PostgreSQL 用户名称 -- `password` — PostgreSQL 用户密码 -- `schema` - PostgreSQL 模式 -- `use_table_cache` — 定义数据库表结构是否已缓存或不进行。可选的。默认值: `0`. +- `host:port` — PostgreSQL服务地址 +- `database` — 远程数据库名次 +- `user` — PostgreSQL用户名称 +- `password` — PostgreSQL用户密码 +- `schema` - PostgreSQL 模式 +- `use_table_cache` — 定义数据库表结构是否已缓存或不进行。可选的。默认值: `0`. ## 支持的数据类型 {#data_types-support} -| PostgreSQL | ClickHouse | -| ---------------- | ------------------------------------------------------------- | -| DATE | [Date](../../sql-reference/data-types/date.md) | -| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | -| REAL | [Float32](../../sql-reference/data-types/float.md) | -| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | -| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | -| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | -| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | -| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | -| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | -| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | -| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | -| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md)) | -| ARRAY | [Array](../../sql-reference/data-types/array.md) | +| PostgerSQL | ClickHouse | +|------------------|--------------------------------------------------------------| +| DATE | [Date](../../sql-reference/data-types/date.md) | +| TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | +| REAL | [Float32](../../sql-reference/data-types/float.md) | +| DOUBLE | [Float64](../../sql-reference/data-types/float.md) | +| DECIMAL, NUMERIC | [Decimal](../../sql-reference/data-types/decimal.md) | +| SMALLINT | [Int16](../../sql-reference/data-types/int-uint.md) | +| INTEGER | [Int32](../../sql-reference/data-types/int-uint.md) | +| BIGINT | [Int64](../../sql-reference/data-types/int-uint.md) | +| SERIAL | [UInt32](../../sql-reference/data-types/int-uint.md) | +| BIGSERIAL | [UInt64](../../sql-reference/data-types/int-uint.md) | +| TEXT, CHAR | [String](../../sql-reference/data-types/string.md) | +| INTEGER | Nullable([Int32](../../sql-reference/data-types/int-uint.md))| +| ARRAY | [Array](../../sql-reference/data-types/array.md) | + ## 使用示例 {#examples-of-use} -ClickHouse 中的数据库,与 PostgreSQL 服务器交换数据: +ClickHouse中的数据库,与PostgreSQL服务器交换数据: -```sql -CREATE DATABASE test_database +``` sql +CREATE DATABASE test_database ENGINE = PostgreSQL('postgres1:5432', 'test_database', 'postgres', 'mysecretpassword', 1); ``` -```sql +``` sql SHOW DATABASES; ``` -```text +``` text ┌─name──────────┐ │ default │ │ test_database │ @@ -66,55 +67,54 @@ SHOW DATABASES; └───────────────┘ ``` -```sql +``` sql SHOW TABLES FROM test_database; ``` -```text +``` text ┌─name───────┐ │ test_table │ └────────────┘ ``` -从 PostgreSQL 表中读取数据: +从PostgreSQL表中读取数据: -```sql +``` sql SELECT * FROM test_database.test_table; ``` -```text +``` text ┌─id─┬─value─┐ │ 1 │ 2 │ └────┴───────┘ ``` -将数据写入 PostgreSQL 表: +将数据写入PostgreSQL表: -```sql +``` sql INSERT INTO test_database.test_table VALUES (3,4); SELECT * FROM test_database.test_table; ``` -```text +``` text ┌─int_id─┬─value─┐ │ 1 │ 2 │ │ 3 │ 4 │ └────────┴───────┘ ``` -在 PostgreSQL 中修改了表结构: +在PostgreSQL中修改了表结构: -```sql +``` sql postgre> ALTER TABLE test_table ADD COLUMN data Text ``` -当创建数据库时,参数`use_table_cache`被设置为`1`,ClickHouse 中的表结构被缓存,因此没有被修改: +当创建数据库时,参数`use_table_cache`被设置为`1`,ClickHouse中的表结构被缓存,因此没有被修改: -```sql +``` sql DESCRIBE TABLE test_database.test_table; ``` - -```text +``` text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ @@ -123,13 +123,12 @@ DESCRIBE TABLE test_database.test_table; 分离表并再次附加它之后,结构被更新了: -```sql +``` sql DETACH TABLE test_database.test_table; ATTACH TABLE test_database.test_table; DESCRIBE TABLE test_database.test_table; ``` - -```text +``` text ┌─name───┬─type──────────────┐ │ id │ Nullable(Integer) │ │ value │ Nullable(Integer) │ From b4c3ff0cef0a95063cef4f9c6ea93517bff6c002 Mon Sep 17 00:00:00 2001 From: nathanbegbie Date: Wed, 3 Aug 2022 16:01:24 +0300 Subject: [PATCH 287/672] typo: PostgerSQL -> PostgreSQL --- docs/en/engines/database-engines/postgresql.md | 2 +- docs/ru/engines/database-engines/postgresql.md | 2 +- docs/zh/engines/database-engines/postgresql.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/engines/database-engines/postgresql.md b/docs/en/engines/database-engines/postgresql.md index 969a326b701..5a430565d54 100644 --- a/docs/en/engines/database-engines/postgresql.md +++ b/docs/en/engines/database-engines/postgresql.md @@ -29,7 +29,7 @@ ENGINE = PostgreSQL('host:port', 'database', 'user', 'password'[, `schema`, `use ## Data Types Support {#data_types-support} -| PostgerSQL | ClickHouse | +| PostgreSQL | ClickHouse | |------------------|--------------------------------------------------------------| | DATE | [Date](../../sql-reference/data-types/date.md) | | TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | diff --git a/docs/ru/engines/database-engines/postgresql.md b/docs/ru/engines/database-engines/postgresql.md index 521a3689a7d..324b39117d5 100644 --- a/docs/ru/engines/database-engines/postgresql.md +++ b/docs/ru/engines/database-engines/postgresql.md @@ -29,7 +29,7 @@ ENGINE = PostgreSQL('host:port', 'database', 'user', 'password'[, `schema`, `use ## Поддерживаемые типы данных {#data_types-support} -| PostgerSQL | ClickHouse | +| PostgreSQL | ClickHouse | |------------------|--------------------------------------------------------------| | DATE | [Date](../../sql-reference/data-types/date.md) | | TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | diff --git a/docs/zh/engines/database-engines/postgresql.md b/docs/zh/engines/database-engines/postgresql.md index 6afefa09cd5..0f2901e6389 100644 --- a/docs/zh/engines/database-engines/postgresql.md +++ b/docs/zh/engines/database-engines/postgresql.md @@ -29,7 +29,7 @@ ENGINE = PostgreSQL('host:port', 'database', 'user', 'password'[, `use_table_cac ## 支持的数据类型 {#data_types-support} -| PostgerSQL | ClickHouse | +| PostgreSQL | ClickHouse | |------------------|--------------------------------------------------------------| | DATE | [Date](../../sql-reference/data-types/date.md) | | TIMESTAMP | [DateTime](../../sql-reference/data-types/datetime.md) | From 149581e319ac2b78bdd65cfd78b27519ca16eb8e Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Wed, 3 Aug 2022 13:15:16 +0000 Subject: [PATCH 288/672] Remove prefer_localhost_replica Test queries failed before fix #39103 regardless the setting value The setting is randomized --- tests/queries/0_stateless/02352_grouby_shadows_arg.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/queries/0_stateless/02352_grouby_shadows_arg.sql b/tests/queries/0_stateless/02352_grouby_shadows_arg.sql index 34076057a4b..953da2382ef 100644 --- a/tests/queries/0_stateless/02352_grouby_shadows_arg.sql +++ b/tests/queries/0_stateless/02352_grouby_shadows_arg.sql @@ -1,4 +1,3 @@ -SET prefer_localhost_replica=0; -- { echoOn } SELECT toString(dummy) as dummy FROM remote('127.{1,1}', 'system.one') GROUP BY dummy; SELECT toString(dummy+1) as dummy FROM remote('127.{1,1}', 'system.one') GROUP BY dummy; From f144eae388b93c65aedf9237cf3a3f2dd8856c31 Mon Sep 17 00:00:00 2001 From: Nikita Mikhaylov Date: Wed, 3 Aug 2022 15:23:07 +0200 Subject: [PATCH 289/672] Fix typo and extra dots in exception messages from OverCommitTracker (#39858) --- src/Common/MemoryTracker.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Common/MemoryTracker.cpp b/src/Common/MemoryTracker.cpp index 5546dc6b7b3..f467fccc514 100644 --- a/src/Common/MemoryTracker.cpp +++ b/src/Common/MemoryTracker.cpp @@ -57,17 +57,17 @@ inline std::string_view toDescription(OvercommitResult result) switch (result) { case OvercommitResult::NONE: - return "Memory overcommit isn't used. OvercommitTracker isn't set."; + return "Memory overcommit isn't used. OvercommitTracker isn't set"; case OvercommitResult::DISABLED: - return "Memory overcommit isn't used. Waiting time or orvercommit denominator are set to zero."; + return "Memory overcommit isn't used. Waiting time or overcommit denominator are set to zero"; case OvercommitResult::MEMORY_FREED: throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "OvercommitResult::MEMORY_FREED shouldn't be asked for description"); case OvercommitResult::SELECTED: - return "Query was selected to stop by OvercommitTracker."; + return "Query was selected to stop by OvercommitTracker"; case OvercommitResult::TIMEOUTED: - return "Waiting timeout for memory to be freed is reached."; + return "Waiting timeout for memory to be freed is reached"; case OvercommitResult::NOT_ENOUGH_FREED: - return "Memory overcommit has freed not enough memory."; + return "Memory overcommit has freed not enough memory"; } } From d58ae7871290f6d429dead539b7a00ab0b915a0b Mon Sep 17 00:00:00 2001 From: root Date: Wed, 3 Aug 2022 06:30:48 -0700 Subject: [PATCH 290/672] removed default color enable in OwnPatternFormatter constructor calling --- src/Daemon/BaseDaemon.cpp | 2 +- src/Loggers/Loggers.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index 61ad1785b2d..d9a12b1640d 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -1017,7 +1017,7 @@ void BaseDaemon::setupWatchdog() if (config().getString("logger.formatting", "") == "json") pf = new OwnJSONPatternFormatter; else - pf = new OwnPatternFormatter(true); + pf = new OwnPatternFormatter; Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr)); logger().setChannel(log); } diff --git a/src/Loggers/Loggers.cpp b/src/Loggers/Loggers.cpp index 35415fb91ba..dec6bcd51c7 100644 --- a/src/Loggers/Loggers.cpp +++ b/src/Loggers/Loggers.cpp @@ -102,7 +102,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log if (config.getString("logger.formatting", "") == "json") pf = new OwnJSONPatternFormatter; else - pf = new OwnPatternFormatter(true); + pf = new OwnPatternFormatter; Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, log_file); log->setLevel(log_level); @@ -143,7 +143,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log if (config.getString("logger.formatting", "") == "json") pf = new OwnJSONPatternFormatter; else - pf = new OwnPatternFormatter(true); + pf = new OwnPatternFormatter; Poco::AutoPtr errorlog = new DB::OwnFormattingChannel(pf, error_log_file); errorlog->setLevel(errorlog_level); @@ -187,7 +187,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log if (config.getString("logger.formatting", "") == "json") pf = new OwnJSONPatternFormatter; else - pf = new OwnPatternFormatter(true); + pf = new OwnPatternFormatter; Poco::AutoPtr log = new DB::OwnFormattingChannel(pf, syslog_channel); log->setLevel(syslog_level); From 624ccd5206a7be7c8a56f2f7590e13d57a074008 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 3 Aug 2022 15:33:55 +0200 Subject: [PATCH 291/672] fix --- src/Databases/DatabaseReplicated.cpp | 36 ++++++++++++---------- src/Databases/DatabaseReplicated.h | 8 +++-- src/Databases/DatabaseReplicatedWorker.cpp | 26 ++++++++++++---- src/Databases/LoadingStrictnessLevel.h | 5 +++ 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/Databases/DatabaseReplicated.cpp b/src/Databases/DatabaseReplicated.cpp index cb205d6d63a..21e7eb05d34 100644 --- a/src/Databases/DatabaseReplicated.cpp +++ b/src/Databases/DatabaseReplicated.cpp @@ -89,6 +89,7 @@ DatabaseReplicated::DatabaseReplicated( , shard_name(shard_name_) , replica_name(replica_name_) , db_settings(std::move(db_settings_)) + , tables_metadata_digest(0) { if (zookeeper_path.empty() || shard_name.empty() || replica_name.empty()) throw Exception("ZooKeeper path, shard and replica names must be non-empty", ErrorCodes::BAD_ARGUMENTS); @@ -450,8 +451,8 @@ void DatabaseReplicated::startupTables(ThreadPool & thread_pool, LoadingStrictne digest += getMetadataHash(table.first); LOG_DEBUG(log, "Calculated metadata digest of {} tables: {}", TSA_SUPPRESS_WARNING_FOR_READ(tables).size(), digest); - chassert(!tables_metadata_digest); - tables_metadata_digest = digest; + chassert(!TSA_SUPPRESS_WARNING_FOR_READ(tables_metadata_digest)); + TSA_SUPPRESS_WARNING_FOR_WRITE(tables_metadata_digest) = digest; ddl_worker = std::make_unique(this, getContext()); if (is_probably_dropped) @@ -459,11 +460,14 @@ void DatabaseReplicated::startupTables(ThreadPool & thread_pool, LoadingStrictne ddl_worker->startup(); } -bool DatabaseReplicated::debugCheckDigest(const ContextPtr & local_context) const +bool DatabaseReplicated::checkDigestValid(const ContextPtr & local_context, bool debug_check /* = true */) const { - /// Reduce number of debug checks - //if (thread_local_rng() % 16) - // return true; + if (debug_check) + { + /// Reduce number of debug checks + if (thread_local_rng() % 16) + return true; + } LOG_TEST(log, "Current in-memory metadata digest: {}", tables_metadata_digest); @@ -773,7 +777,7 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep new_digest -= getMetadataHash(broken_table_name); DatabaseAtomic::renameTable(make_query_context(), broken_table_name, *to_db_ptr, to_name, /* exchange */ false, /* dictionary */ false); tables_metadata_digest = new_digest; - assert(debugCheckDigest(getContext())); + assert(checkDigestValid(getContext())); ++moved_tables; }; @@ -799,7 +803,7 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep new_digest -= getMetadataHash(table_name); DatabaseAtomic::dropTableImpl(make_query_context(), table_name, /* sync */ true); tables_metadata_digest = new_digest; - assert(debugCheckDigest(getContext())); + assert(checkDigestValid(getContext())); } else if (!table->supportsReplication()) { @@ -833,7 +837,7 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep new_digest += DB::getMetadataHash(to, statement); DatabaseAtomic::renameTable(make_query_context(), from, *this, to, false, false); tables_metadata_digest = new_digest; - assert(debugCheckDigest(getContext())); + assert(checkDigestValid(getContext())); } for (const auto & id : dropped_tables) @@ -870,7 +874,7 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep } std::lock_guard lock{metadata_mutex}; - chassert(debugCheckDigest(getContext())); + chassert(checkDigestValid(getContext())); current_zookeeper->set(replica_path + "/digest", toString(tables_metadata_digest)); } @@ -1001,7 +1005,7 @@ void DatabaseReplicated::dropTable(ContextPtr local_context, const String & tabl DatabaseAtomic::dropTableImpl(local_context, table_name, sync); tables_metadata_digest = new_digest; - assert(debugCheckDigest(local_context)); + assert(checkDigestValid(local_context)); } void DatabaseReplicated::renameTable(ContextPtr local_context, const String & table_name, IDatabase & to_database, @@ -1054,7 +1058,7 @@ void DatabaseReplicated::renameTable(ContextPtr local_context, const String & ta DatabaseAtomic::renameTable(local_context, table_name, to_database, to_table_name, exchange, dictionary); tables_metadata_digest = new_digest; - assert(debugCheckDigest(local_context)); + assert(checkDigestValid(local_context)); } void DatabaseReplicated::commitCreateTable(const ASTCreateQuery & query, const StoragePtr & table, @@ -1080,7 +1084,7 @@ void DatabaseReplicated::commitCreateTable(const ASTCreateQuery & query, const S DatabaseAtomic::commitCreateTable(query, table, table_metadata_tmp_path, table_metadata_path, query_context); tables_metadata_digest = new_digest; - assert(debugCheckDigest(query_context)); + assert(checkDigestValid(query_context)); } void DatabaseReplicated::commitAlterTable(const StorageID & table_id, @@ -1104,7 +1108,7 @@ void DatabaseReplicated::commitAlterTable(const StorageID & table_id, DatabaseAtomic::commitAlterTable(table_id, table_metadata_tmp_path, table_metadata_path, statement, query_context); tables_metadata_digest = new_digest; - assert(debugCheckDigest(query_context)); + assert(checkDigestValid(query_context)); } void DatabaseReplicated::detachTablePermanently(ContextPtr local_context, const String & table_name) @@ -1127,7 +1131,7 @@ void DatabaseReplicated::detachTablePermanently(ContextPtr local_context, const DatabaseAtomic::detachTablePermanently(local_context, table_name); tables_metadata_digest = new_digest; - assert(debugCheckDigest(local_context)); + assert(checkDigestValid(local_context)); } void DatabaseReplicated::removeDetachedPermanentlyFlag(ContextPtr local_context, const String & table_name, const String & table_metadata_path, bool attach) @@ -1152,7 +1156,7 @@ void DatabaseReplicated::removeDetachedPermanentlyFlag(ContextPtr local_context, DatabaseAtomic::removeDetachedPermanentlyFlag(local_context, table_name, table_metadata_path, attach); tables_metadata_digest = new_digest; - assert(debugCheckDigest(local_context)); + assert(checkDigestValid(local_context)); } diff --git a/src/Databases/DatabaseReplicated.h b/src/Databases/DatabaseReplicated.h index 8256d54a796..56689ed94bf 100644 --- a/src/Databases/DatabaseReplicated.h +++ b/src/Databases/DatabaseReplicated.h @@ -112,7 +112,7 @@ private: } UInt64 getMetadataHash(const String & table_name) const; - bool debugCheckDigest(const ContextPtr & local_context) const; + bool checkDigestValid(const ContextPtr & local_context, bool debug_check = true) const TSA_REQUIRES(metadata_mutex); String zookeeper_path; String shard_name; @@ -131,7 +131,11 @@ private: /// Usually operation with metadata are single-threaded because of the way replication works, /// but StorageReplicatedMergeTree may call alterTable outside from DatabaseReplicatedDDLWorker causing race conditions. std::mutex metadata_mutex; - UInt64 tables_metadata_digest = 0; + + /// Sum of hashes of pairs (table_name, table_create_statement). + /// We calculate this sum from local metadata files and compare it will value in ZooKeeper. + /// It allows to detect if metadata is broken and recover replica. + UInt64 tables_metadata_digest TSA_GUARDED_BY(metadata_mutex); mutable ClusterPtr cluster; }; diff --git a/src/Databases/DatabaseReplicatedWorker.cpp b/src/Databases/DatabaseReplicatedWorker.cpp index 161e262a574..63d5af8da3d 100644 --- a/src/Databases/DatabaseReplicatedWorker.cpp +++ b/src/Databases/DatabaseReplicatedWorker.cpp @@ -67,23 +67,33 @@ void DatabaseReplicatedDDLWorker::initializeReplication() UInt32 max_log_ptr = parse(zookeeper->get(database->zookeeper_path + "/max_log_ptr")); logs_to_keep = parse(zookeeper->get(database->zookeeper_path + "/logs_to_keep")); - String digest; - if (!zookeeper->tryGet(database->replica_path + "/digest", digest)) + UInt64 digest; + String digest_str; + UInt64 local_digest; + if (zookeeper->tryGet(database->replica_path + "/digest", digest_str)) + { + digest = parse(digest_str); + std::lock_guard lock{database->metadata_mutex}; + local_digest = database->tables_metadata_digest; + } + else { /// Database was created by old ClickHouse versions, let's create the node - digest = toString(database->tables_metadata_digest); - zookeeper->create(database->replica_path + "/digest", digest, zkutil::CreateMode::Persistent); + std::lock_guard lock{database->metadata_mutex}; + digest = local_digest = database->tables_metadata_digest; + digest_str = toString(digest); + zookeeper->create(database->replica_path + "/digest", digest_str, zkutil::CreateMode::Persistent); } bool is_new_replica = our_log_ptr == 0; bool lost_according_to_log_ptr = our_log_ptr + logs_to_keep < max_log_ptr; - bool lost_according_to_digest = database->db_settings.check_consistency && database->tables_metadata_digest != parse(digest); + bool lost_according_to_digest = database->db_settings.check_consistency && local_digest != digest; if (is_new_replica || lost_according_to_log_ptr || lost_according_to_digest) { if (!is_new_replica) LOG_WARNING(log, "Replica seems to be lost: our_log_ptr={}, max_log_ptr={}, local_digest={}, zk_digest={}", - our_log_ptr, max_log_ptr, database->tables_metadata_digest, digest); + our_log_ptr, max_log_ptr, local_digest, digest); database->recoverLostReplica(zookeeper, our_log_ptr, max_log_ptr); zookeeper->set(database->replica_path + "/log_ptr", toString(max_log_ptr)); initializeLogPointer(DDLTaskBase::getLogEntryName(max_log_ptr)); @@ -94,6 +104,10 @@ void DatabaseReplicatedDDLWorker::initializeReplication() last_skipped_entry_name.emplace(log_entry_name); initializeLogPointer(log_entry_name); } + + std::lock_guard lock{database->metadata_mutex}; + if (!database->checkDigestValid(context)) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Inconsistent database metadata after reconnection to ZooKeeper"); } String DatabaseReplicatedDDLWorker::enqueueQuery(DDLLogEntry & entry) diff --git a/src/Databases/LoadingStrictnessLevel.h b/src/Databases/LoadingStrictnessLevel.h index 4c566d3ac72..b6449a0a9fd 100644 --- a/src/Databases/LoadingStrictnessLevel.h +++ b/src/Databases/LoadingStrictnessLevel.h @@ -3,11 +3,16 @@ namespace DB { +/// Strictness mode for loading a table or database enum class LoadingStrictnessLevel { + /// Do all possible sanity checks CREATE = 0, + /// Expect existing paths on FS and in ZK for ATTACH query ATTACH = 1, + /// We ignore some error on server startup FORCE_ATTACH = 2, + /// Skip all sanity checks (if force_restore_data flag exists) FORCE_RESTORE = 3, }; From 56ca93ca3f5325bd5712e9589d6ee84492b6103c Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 3 Aug 2022 14:14:57 +0000 Subject: [PATCH 292/672] Block memory tracker in Keeper during commit --- contrib/NuRaft | 2 +- src/Coordination/KeeperServer.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/contrib/NuRaft b/contrib/NuRaft index 2ef198694e1..d73e12adf75 160000 --- a/contrib/NuRaft +++ b/contrib/NuRaft @@ -1 +1 @@ -Subproject commit 2ef198694e10c86175ee6ead389346d199060437 +Subproject commit d73e12adf7557a0ebca7f7ecde68c064dee22fa0 diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 7238c86cc50..a7c9b0836f1 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -173,6 +174,12 @@ struct KeeperServer::KeeperRaftServer : public nuraft::raft_server reconfigure(new_config); } + void commit_in_bg() override + { + MemoryTrackerBlockerInThread blocker; + nuraft::raft_server::commit_in_bg(); + } + using nuraft::raft_server::raft_server; // peers are initially marked as responding because at least one cycle From 58fc49df6665cc939032a59d58798e45e2d06780 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 3 Aug 2022 14:53:22 +0000 Subject: [PATCH 293/672] Update version_date.tsv after v22.3.10.22-lts --- utils/list-versions/version_date.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/list-versions/version_date.tsv b/utils/list-versions/version_date.tsv index e829e7b2bc0..7d18c567d26 100644 --- a/utils/list-versions/version_date.tsv +++ b/utils/list-versions/version_date.tsv @@ -12,6 +12,7 @@ v22.4.5.9-stable 2022-05-06 v22.4.4.7-stable 2022-04-29 v22.4.3.3-stable 2022-04-26 v22.4.2.1-stable 2022-04-22 +v22.3.10.22-lts 2022-08-03 v22.3.9.19-lts 2022-07-25 v22.3.8.39-lts 2022-07-07 v22.3.7.28-lts 2022-06-20 From 5da32fafb3aed6417c10156ad67174924afeee45 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 3 Aug 2022 15:08:38 +0000 Subject: [PATCH 294/672] fix ALTER MODIFY COLUMN from nested --- src/Storages/MergeTree/MutateTask.cpp | 83 ++++++++++--------- .../02377_modify_column_from_lc.reference | 1 + .../02377_modify_column_from_lc.sql | 43 ++++++++++ .../02377_modify_column_from_nested.reference | 6 ++ .../02377_modify_column_from_nested.sql | 21 +++++ 5 files changed, 115 insertions(+), 39 deletions(-) create mode 100644 tests/queries/0_stateless/02377_modify_column_from_lc.reference create mode 100644 tests/queries/0_stateless/02377_modify_column_from_lc.sql create mode 100644 tests/queries/0_stateless/02377_modify_column_from_nested.reference create mode 100644 tests/queries/0_stateless/02377_modify_column_from_nested.sql diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index ab441fd8b77..4a68c9917fa 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -434,11 +434,34 @@ std::set getProjectionsToRecalculate( return projections_to_recalc; } +static std::unordered_map getStreamCounts( + const MergeTreeDataPartPtr & data_part, const Names & column_names) +{ + std::unordered_map stream_counts; + + for (const auto & column_name : column_names) + { + if (auto serialization = data_part->getSerialization(column_name)) + { + auto callback = [&](const ISerialization::SubstreamPath & substream_path) + { + auto stream_name = ISerialization::getFileNameForStream(column_name, substream_path); + ++stream_counts[stream_name]; + }; + + serialization->enumerateStreams(callback); + } + } + + return stream_counts; +} + /// Files, that we don't need to remove and don't need to hardlink, for example columns.txt and checksums.txt. /// Because we will generate new versions of them after we perform mutation. -NameSet collectFilesToSkip( +static NameSet collectFilesToSkip( const MergeTreeDataPartPtr & source_part, + const MergeTreeDataPartPtr & new_part, const Block & updated_header, const std::set & indices_to_recalc, const String & mrk_extension, @@ -446,24 +469,31 @@ NameSet collectFilesToSkip( { NameSet files_to_skip = source_part->getFileNamesWithoutChecksums(); + auto new_stream_counts = getStreamCounts(new_part, new_part->getColumns().getNames()); + auto source_updated_stream_counts = getStreamCounts(source_part, updated_header.getNames()); + auto new_updated_stream_counts = getStreamCounts(new_part, updated_header.getNames()); + /// Skip updated files - for (const auto & entry : updated_header) + for (const auto & [stream_name, _] : source_updated_stream_counts) { - ISerialization::StreamCallback callback = [&](const ISerialization::SubstreamPath & substream_path) + /// If we read shared stream and do not write it + /// (e.g. while ALTER MODIFY COLUMN from array of Nested type to String), + /// we need to hardlink its files, because they will be lost otherwise. + bool need_hardlink = new_updated_stream_counts[stream_name] == 0 && new_stream_counts[stream_name] != 0; + + if (!need_hardlink) { - String stream_name = ISerialization::getFileNameForStream(entry.name, substream_path); files_to_skip.insert(stream_name + ".bin"); files_to_skip.insert(stream_name + mrk_extension); - }; - - if (auto serialization = source_part->tryGetSerialization(entry.name)) - serialization->enumerateStreams(callback); + } } + for (const auto & index : indices_to_recalc) { files_to_skip.insert(index->getFileName() + ".idx"); files_to_skip.insert(index->getFileName() + mrk_extension); } + for (const auto & projection : projections_to_recalc) files_to_skip.insert(projection->getDirectoryName()); @@ -482,19 +512,7 @@ static NameToNameVector collectFilesForRenames( const String & mrk_extension) { /// Collect counts for shared streams of different columns. As an example, Nested columns have shared stream with array sizes. - std::unordered_map stream_counts; - for (const auto & column : source_part->getColumns()) - { - if (auto serialization = source_part->tryGetSerialization(column.name)) - { - serialization->enumerateStreams( - [&](const ISerialization::SubstreamPath & substream_path) - { - ++stream_counts[ISerialization::getFileNameForStream(column, substream_path)]; - }); - } - } - + auto stream_counts = getStreamCounts(source_part, source_part->getColumns().getNames()); NameToNameVector rename_vector; /// Remove old data @@ -560,26 +578,12 @@ static NameToNameVector collectFilesForRenames( /// but were removed in new_part by MODIFY COLUMN from /// type with higher number of streams (e.g. LowCardinality -> String). - auto collect_stream_names = [&](const auto & data_part) - { - NameSet res; - if (auto serialization = data_part->tryGetSerialization(command.column_name)) - { - serialization->enumerateStreams( - [&](const ISerialization::SubstreamPath & substream_path) - { - res.insert(ISerialization::getFileNameForStream(command.column_name, substream_path)); - }); - } - return res; - }; + auto old_streams = getStreamCounts(source_part, source_part->getColumns().getNames()); + auto new_streams = getStreamCounts(new_part, source_part->getColumns().getNames()); - auto old_streams = collect_stream_names(source_part); - auto new_streams = collect_stream_names(new_part); - - for (const auto & old_stream : old_streams) + for (const auto & [old_stream, _] : old_streams) { - if (!new_streams.contains(old_stream)) + 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, ""); @@ -1580,6 +1584,7 @@ bool MutateTask::prepare() ctx->files_to_skip = MutationHelpers::collectFilesToSkip( ctx->source_part, + ctx->new_data_part, ctx->updated_header, ctx->indices_to_recalc, ctx->mrk_extension, diff --git a/tests/queries/0_stateless/02377_modify_column_from_lc.reference b/tests/queries/0_stateless/02377_modify_column_from_lc.reference new file mode 100644 index 00000000000..556d825db42 --- /dev/null +++ b/tests/queries/0_stateless/02377_modify_column_from_lc.reference @@ -0,0 +1 @@ +2 1 diff --git a/tests/queries/0_stateless/02377_modify_column_from_lc.sql b/tests/queries/0_stateless/02377_modify_column_from_lc.sql new file mode 100644 index 00000000000..a578e7cb03a --- /dev/null +++ b/tests/queries/0_stateless/02377_modify_column_from_lc.sql @@ -0,0 +1,43 @@ +DROP TABLE IF EXISTS t_modify_from_lc_1; +DROP TABLE IF EXISTS t_modify_from_lc_2; + +SET allow_suspicious_low_cardinality_types = 1; + +CREATE TABLE t_modify_from_lc_1 +( + id UInt64, + a LowCardinality(UInt32) CODEC(NONE) +) +ENGINE = MergeTree ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0; + +CREATE TABLE t_modify_from_lc_2 +( + id UInt64, + a LowCardinality(UInt32) CODEC(NONE) +) +ENGINE = MergeTree ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0; + +INSERT INTO t_modify_from_lc_1 SELECT number, number FROM numbers(100000); +INSERT INTO t_modify_from_lc_2 SELECT number, number FROM numbers(100000); + +OPTIMIZE TABLE t_modify_from_lc_1 FINAL; +OPTIMIZE TABLE t_modify_from_lc_2 FINAL; + +ALTER TABLE t_modify_from_lc_1 MODIFY COLUMN a UInt32; + +-- Check that dictionary of LowCardinality is actually +-- dropped and total size on disk is reduced. +WITH groupArray((table, bytes))::Map(String, UInt64) AS stats +SELECT + length(stats), stats['t_modify_from_lc_1'] < stats['t_modify_from_lc_2'] +FROM +( + SELECT table, sum(bytes_on_disk) AS bytes FROM system.parts + WHERE database = currentDatabase() AND table LIKE 't_modify_from_lc%' AND active + GROUP BY table +); + +DROP TABLE IF EXISTS t_modify_from_lc_1; +DROP TABLE IF EXISTS t_modify_from_lc_2; diff --git a/tests/queries/0_stateless/02377_modify_column_from_nested.reference b/tests/queries/0_stateless/02377_modify_column_from_nested.reference new file mode 100644 index 00000000000..0c66773ab43 --- /dev/null +++ b/tests/queries/0_stateless/02377_modify_column_from_nested.reference @@ -0,0 +1,6 @@ +1 [2] ['aa'] Array(String) +2 [44,55] ['bb','cc'] Array(String) +1 [2] [\'aa\'] String +2 [44,55] [\'bb\',\'cc\'] String +1 [2] [\'aa\'] String +2 [44,55] [\'bb\',\'cc\'] String diff --git a/tests/queries/0_stateless/02377_modify_column_from_nested.sql b/tests/queries/0_stateless/02377_modify_column_from_nested.sql new file mode 100644 index 00000000000..8270cce6278 --- /dev/null +++ b/tests/queries/0_stateless/02377_modify_column_from_nested.sql @@ -0,0 +1,21 @@ +DROP TABLE IF EXISTS t_nested_modify; + +CREATE TABLE t_nested_modify (id UInt64, `n.a` Array(UInt32), `n.b` Array(String)) +ENGINE = MergeTree ORDER BY id +SETTINGS min_bytes_for_wide_part = 0; + +INSERT INTO t_nested_modify VALUES (1, [2], ['aa']); +INSERT INTO t_nested_modify VALUES (2, [44, 55], ['bb', 'cc']); + +SELECT id, `n.a`, `n.b`, toTypeName(`n.b`) FROM t_nested_modify ORDER BY id; + +ALTER TABLE t_nested_modify MODIFY COLUMN `n.b` String; + +SELECT id, `n.a`, `n.b`, toTypeName(`n.b`) FROM t_nested_modify ORDER BY id; + +DETACH TABLE t_nested_modify; +ATTACH TABLE t_nested_modify; + +SELECT id, `n.a`, `n.b`, toTypeName(`n.b`) FROM t_nested_modify ORDER BY id; + +DROP TABLE t_nested_modify; From 4943202921001add4b363fd1cd770db898079877 Mon Sep 17 00:00:00 2001 From: Nikita Taranov Date: Wed, 3 Aug 2022 17:56:59 +0200 Subject: [PATCH 295/672] Improve memory usage during memory efficient merging of aggregation results (#39429) --- src/Interpreters/Aggregator.cpp | 61 ++++++++++++------- src/Interpreters/Aggregator.h | 12 +++- ...gingAggregatedMemoryEfficientTransform.cpp | 15 +++-- .../00284_external_aggregation.reference | 20 ++++++ .../00284_external_aggregation.sql | 15 +++++ ...xternal_aggregation_memory_usage.reference | 1 + ...with_external_aggregation_memory_usage.sql | 23 +++++++ 7 files changed, 117 insertions(+), 30 deletions(-) create mode 100644 tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.reference create mode 100644 tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql diff --git a/src/Interpreters/Aggregator.cpp b/src/Interpreters/Aggregator.cpp index a99ecee43bf..fc5774735a0 100644 --- a/src/Interpreters/Aggregator.cpp +++ b/src/Interpreters/Aggregator.cpp @@ -911,7 +911,7 @@ void Aggregator::mergeOnBlockSmall( mergeStreamsImpl(result.aggregates_pool, *result.NAME, result.NAME->data, \ result.without_key, /* no_more_keys= */ false, \ row_begin, row_end, \ - aggregate_columns_data, key_columns); + aggregate_columns_data, key_columns, result.aggregates_pool); APPLY_FOR_AGGREGATED_VARIANTS(M) #undef M @@ -2647,19 +2647,23 @@ void NO_INLINE Aggregator::mergeStreamsImplCase( size_t row_begin, size_t row_end, const AggregateColumnsConstData & aggregate_columns_data, - const ColumnRawPtrs & key_columns) const + const ColumnRawPtrs & key_columns, + Arena * arena_for_keys) const { typename Method::State state(key_columns, key_sizes, aggregation_state_cache); std::unique_ptr places(new AggregateDataPtr[row_end]); + if (!arena_for_keys) + arena_for_keys = aggregates_pool; + for (size_t i = row_begin; i < row_end; ++i) { AggregateDataPtr aggregate_data = nullptr; if (!no_more_keys) { - auto emplace_result = state.emplaceKey(data, i, *aggregates_pool); + auto emplace_result = state.emplaceKey(data, i, *arena_for_keys); // NOLINT if (emplace_result.isInserted()) { emplace_result.setMapped(nullptr); @@ -2674,7 +2678,7 @@ void NO_INLINE Aggregator::mergeStreamsImplCase( } else { - auto find_result = state.findKey(data, i, *aggregates_pool); + auto find_result = state.findKey(data, i, *arena_for_keys); if (find_result.isFound()) aggregate_data = find_result.getMapped(); } @@ -2703,21 +2707,14 @@ void NO_INLINE Aggregator::mergeStreamsImpl( Method & method, Table & data, AggregateDataPtr overflow_row, - bool no_more_keys) const + bool no_more_keys, + Arena * arena_for_keys) const { const AggregateColumnsConstData & aggregate_columns_data = params.makeAggregateColumnsData(block); const ColumnRawPtrs & key_columns = params.makeRawKeyColumns(block); mergeStreamsImpl( - aggregates_pool, - method, - data, - overflow_row, - no_more_keys, - 0, - block.rows(), - aggregate_columns_data, - key_columns); + aggregates_pool, method, data, overflow_row, no_more_keys, 0, block.rows(), aggregate_columns_data, key_columns, arena_for_keys); } template @@ -2730,12 +2727,15 @@ void NO_INLINE Aggregator::mergeStreamsImpl( size_t row_begin, size_t row_end, const AggregateColumnsConstData & aggregate_columns_data, - const ColumnRawPtrs & key_columns) const + const ColumnRawPtrs & key_columns, + Arena * arena_for_keys) const { if (!no_more_keys) - mergeStreamsImplCase(aggregates_pool, method, data, overflow_row, row_begin, row_end, aggregate_columns_data, key_columns); + mergeStreamsImplCase( + aggregates_pool, method, data, overflow_row, row_begin, row_end, aggregate_columns_data, key_columns, arena_for_keys); else - mergeStreamsImplCase(aggregates_pool, method, data, overflow_row, row_begin, row_end, aggregate_columns_data, key_columns); + mergeStreamsImplCase( + aggregates_pool, method, data, overflow_row, row_begin, row_end, aggregate_columns_data, key_columns, arena_for_keys); } @@ -3015,17 +3015,26 @@ Block Aggregator::mergeBlocks(BlocksList & blocks, bool final) result.keys_size = params.keys_size; result.key_sizes = key_sizes; + size_t source_rows = 0; + + /// In some aggregation methods (e.g. serialized) aggregates pools are used also to store serialized aggregation keys. + /// Memory occupied by them will have the same lifetime as aggregate function states, while it is not actually necessary and leads to excessive memory consumption. + /// To avoid this we use a separate arena to allocate memory for aggregation keys. Its memory will be freed at this function return. + auto arena_for_keys = std::make_shared(); + for (Block & block : blocks) { + source_rows += block.rows(); + if (bucket_num >= 0 && block.info.bucket_num != bucket_num) bucket_num = -1; if (result.type == AggregatedDataVariants::Type::without_key || is_overflows) mergeBlockWithoutKeyStreamsImpl(std::move(block), result); - #define M(NAME, IS_TWO_LEVEL) \ - else if (result.type == AggregatedDataVariants::Type::NAME) \ - mergeStreamsImpl(std::move(block), result.aggregates_pool, *result.NAME, result.NAME->data, nullptr, false); +#define M(NAME, IS_TWO_LEVEL) \ + else if (result.type == AggregatedDataVariants::Type::NAME) \ + mergeStreamsImpl(std::move(block), result.aggregates_pool, *result.NAME, result.NAME->data, nullptr, false, arena_for_keys.get()); APPLY_FOR_AGGREGATED_VARIANTS(M) #undef M @@ -3049,9 +3058,15 @@ Block Aggregator::mergeBlocks(BlocksList & blocks, bool final) size_t rows = block.rows(); size_t bytes = block.bytes(); double elapsed_seconds = watch.elapsedSeconds(); - LOG_DEBUG(log, "Merged partially aggregated blocks. {} rows, {}. in {} sec. ({:.3f} rows/sec., {}/sec.)", - rows, ReadableSize(bytes), - elapsed_seconds, rows / elapsed_seconds, + LOG_DEBUG( + log, + "Merged partially aggregated blocks for bucket #{}. Got {} rows, {} from {} source rows in {} sec. ({:.3f} rows/sec., {}/sec.)", + bucket_num, + rows, + ReadableSize(bytes), + source_rows, + elapsed_seconds, + rows / elapsed_seconds, ReadableSize(bytes / elapsed_seconds)); block.info.bucket_num = bucket_num; diff --git a/src/Interpreters/Aggregator.h b/src/Interpreters/Aggregator.h index 716849465de..3e8b25c1a8c 100644 --- a/src/Interpreters/Aggregator.h +++ b/src/Interpreters/Aggregator.h @@ -1348,8 +1348,11 @@ private: size_t row_begin, size_t row_end, const AggregateColumnsConstData & aggregate_columns_data, - const ColumnRawPtrs & key_columns) const; + const ColumnRawPtrs & key_columns, + Arena * arena_for_keys) const; + /// `arena_for_keys` used to store serialized aggregation keys (in methods like `serialized`) to save some space. + /// If not provided, aggregates_pool is used instead. Refer to mergeBlocks() for an usage example. template void mergeStreamsImpl( Block block, @@ -1357,7 +1360,9 @@ private: Method & method, Table & data, AggregateDataPtr overflow_row, - bool no_more_keys) const; + bool no_more_keys, + Arena * arena_for_keys = nullptr) const; + template void mergeStreamsImpl( Arena * aggregates_pool, @@ -1368,7 +1373,8 @@ private: size_t row_begin, size_t row_end, const AggregateColumnsConstData & aggregate_columns_data, - const ColumnRawPtrs & key_columns) const; + const ColumnRawPtrs & key_columns, + Arena * arena_for_keys) const; void mergeBlockWithoutKeyStreamsImpl( Block block, diff --git a/src/Processors/Transforms/MergingAggregatedMemoryEfficientTransform.cpp b/src/Processors/Transforms/MergingAggregatedMemoryEfficientTransform.cpp index 905620d39f9..8471139d9dc 100644 --- a/src/Processors/Transforms/MergingAggregatedMemoryEfficientTransform.cpp +++ b/src/Processors/Transforms/MergingAggregatedMemoryEfficientTransform.cpp @@ -1,9 +1,10 @@ -#include +#include +#include #include #include #include +#include #include -#include namespace DB { @@ -367,7 +368,7 @@ SortingAggregatedTransform::SortingAggregatedTransform(size_t num_inputs_, Aggre : IProcessor(InputPorts(num_inputs_, params_->getHeader()), {params_->getHeader()}) , num_inputs(num_inputs_) , params(std::move(params_)) - , last_bucket_number(num_inputs, -1) + , last_bucket_number(num_inputs, std::numeric_limits::min()) , is_input_finished(num_inputs, false) { } @@ -462,7 +463,13 @@ IProcessor::Status SortingAggregatedTransform::prepare() continue; } - //all_finished = false; + /// We want to keep not more than `num_inputs` buckets in memory (and there will be only a single chunk with the given `bucket_id`). + const bool bucket_from_this_input_still_in_memory = chunks.contains(last_bucket_number[input_num]); + if (bucket_from_this_input_still_in_memory) + { + all_finished = false; + continue; + } in->setNeeded(); diff --git a/tests/queries/0_stateless/00284_external_aggregation.reference b/tests/queries/0_stateless/00284_external_aggregation.reference index 48e30e781e0..be0db217a97 100644 --- a/tests/queries/0_stateless/00284_external_aggregation.reference +++ b/tests/queries/0_stateless/00284_external_aggregation.reference @@ -1,2 +1,22 @@ 49999995000000 10000000 499999500000 1000000 15 +100033 2 +100034 2 +100035 2 +100036 2 +100037 2 +100038 2 +100039 2 +10004 2 +100040 2 +100041 2 +100033 2 +100034 2 +100035 2 +100036 2 +100037 2 +100038 2 +100039 2 +10004 2 +100040 2 +100041 2 diff --git a/tests/queries/0_stateless/00284_external_aggregation.sql b/tests/queries/0_stateless/00284_external_aggregation.sql index 057cb749521..a42dd91b6a5 100644 --- a/tests/queries/0_stateless/00284_external_aggregation.sql +++ b/tests/queries/0_stateless/00284_external_aggregation.sql @@ -7,3 +7,18 @@ SET group_by_two_level_threshold_bytes = 50000000; SELECT sum(k), sum(c) FROM (SELECT number AS k, count() AS c FROM (SELECT * FROM system.numbers LIMIT 10000000) GROUP BY k); SELECT sum(k), sum(c), max(u) FROM (SELECT number AS k, count() AS c, uniqArray(range(number % 16)) AS u FROM (SELECT * FROM system.numbers LIMIT 1000000) GROUP BY k); + +SET group_by_two_level_threshold = 100000; +SET max_bytes_before_external_group_by = '1Mi'; + +-- method: key_string & key_string_two_level +CREATE TABLE t_00284_str(s String) ENGINE = MergeTree() ORDER BY tuple(); +INSERT INTO t_00284_str SELECT toString(number) FROM numbers_mt(1e6); +INSERT INTO t_00284_str SELECT toString(number) FROM numbers_mt(1e6); +SELECT s, count() FROM t_00284_str GROUP BY s ORDER BY s LIMIT 10 OFFSET 42; + +-- method: low_cardinality_key_string & low_cardinality_key_string_two_level +CREATE TABLE t_00284_lc_str(s LowCardinality(String)) ENGINE = MergeTree() ORDER BY tuple(); +INSERT INTO t_00284_lc_str SELECT toString(number) FROM numbers_mt(1e6); +INSERT INTO t_00284_lc_str SELECT toString(number) FROM numbers_mt(1e6); +SELECT s, count() FROM t_00284_lc_str GROUP BY s ORDER BY s LIMIT 10 OFFSET 42; diff --git a/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.reference b/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.reference new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.reference @@ -0,0 +1 @@ +1 diff --git a/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql b/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql new file mode 100644 index 00000000000..340eee038ba --- /dev/null +++ b/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql @@ -0,0 +1,23 @@ +-- Tags: long, no-tsan, no-msan, no-asan, no-ubsan + +create table t_2354_dist_with_external_aggr(a UInt64, b String, c FixedString(100)) engine = MergeTree order by tuple(); + +insert into t_2354_dist_with_external_aggr select number, toString(number) as s, toFixedString(s, 100) from numbers_mt(5e7); + +set max_bytes_before_external_group_by = '2G', + max_threads = 16, + aggregation_memory_efficient_merge_threads = 16, + distributed_aggregation_memory_efficient = 1, + prefer_localhost_replica = 1, + group_by_two_level_threshold = 100000; + +select a, b, c, sum(a) as s +from remote('127.0.0.{1,2}', currentDatabase(), t_2354_dist_with_external_aggr) +group by a, b, c +format Null; + +system flush logs; + +select memory_usage < 4 * 1024 * 1024 * 1024 -- whole aggregation state of local aggregation uncompressed is 5.8G +from system.query_log +where event_time >= now() - interval '15 minute' and type = 'QueryFinish' and is_initial_query and query like '%t_2354_dist_with_external_aggr%group_by%' and current_database = currentDatabase(); From 53530a5fa4b4e34f11cc8cd42bd095172ce39ee6 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Wed, 3 Aug 2022 13:11:11 -0400 Subject: [PATCH 296/672] deadcode/clang-tidy fight solution --- utils/self-extracting-executable/decompressor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/self-extracting-executable/decompressor.cpp b/utils/self-extracting-executable/decompressor.cpp index 5615216e262..8fc9092932e 100644 --- a/utils/self-extracting-executable/decompressor.cpp +++ b/utils/self-extracting-executable/decompressor.cpp @@ -360,7 +360,7 @@ int main(int/* argc*/, char* argv[]) const char * const decompressed_name_fmt = "%s.decompressed.%s"; int decompressed_name_len = snprintf(nullptr, 0, decompressed_name_fmt, argv[0], decompressed_suffix); char decompressed_name[decompressed_name_len + 1]; - decompressed_name_len = snprintf(decompressed_name, decompressed_name_len + 1, decompressed_name_fmt, argv[0], decompressed_suffix); + (void)snprintf(decompressed_name, decompressed_name_len + 1, decompressed_name_fmt, argv[0], decompressed_suffix); std::error_code ec; std::filesystem::copy_file(static_cast(decompressed_name), argv[0], ec); From 61fd28e19f5bef14595cfb2fde3ef42b5627b503 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 3 Aug 2022 18:11:17 +0200 Subject: [PATCH 297/672] Get api url from event, not from const/ENV --- tests/ci/workflow_approve_rerun_lambda/app.py | 63 +++++++++++-------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/tests/ci/workflow_approve_rerun_lambda/app.py b/tests/ci/workflow_approve_rerun_lambda/app.py index ef0759be6e1..28056fc51a8 100644 --- a/tests/ci/workflow_approve_rerun_lambda/app.py +++ b/tests/ci/workflow_approve_rerun_lambda/app.py @@ -3,15 +3,12 @@ from collections import namedtuple import fnmatch import json -import os import time import jwt import requests # type: ignore import boto3 # type: ignore -API_URL = os.getenv("API_URL", "https://api.github.com/repos/ClickHouse/ClickHouse") - SUSPICIOUS_CHANGED_FILES_NUMBER = 200 SUSPICIOUS_PATTERNS = [ @@ -27,7 +24,7 @@ SUSPICIOUS_PATTERNS = [ MAX_RETRY = 5 # Number of times a check can re-run as a whole. -# It is needed, because we are using AWS "spot" instances, that are terminated very frequently. +# It is needed, because we are using AWS "spot" instances, that are terminated often MAX_WORKFLOW_RERUN = 20 WorkflowDescription = namedtuple( @@ -46,6 +43,7 @@ WorkflowDescription = namedtuple( "rerun_url", "jobs_url", "attempt", + "repo_url", "url", ], ) @@ -228,8 +226,8 @@ def _exec_post_with_retry(url, token, data=None): raise Exception("Cannot execute POST request with retry") -def _get_pull_requests_from(owner, branch): - url = f"{API_URL}/pulls?head={owner}:{branch}" +def _get_pull_requests_from(repo_url, owner, branch): + url = f"{repo_url}/pulls?head={owner}:{branch}" return _exec_get_with_retry(url) @@ -248,6 +246,7 @@ def get_workflow_description_from_event(event): rerun_url = event["workflow_run"]["rerun_url"] url = event["workflow_run"]["html_url"] api_url = event["workflow_run"]["url"] + repo_url = event["repository"]["url"] return WorkflowDescription( name=name, action=action, @@ -262,6 +261,7 @@ def get_workflow_description_from_event(event): jobs_url=jobs_url, rerun_url=rerun_url, url=url, + repo_url=repo_url, api_url=api_url, ) @@ -273,13 +273,12 @@ def get_pr_author_and_orgs(pull_request): def get_changed_files_for_pull_request(pull_request): - number = pull_request["number"] + url = pull_request["url"] changed_files = set([]) for i in range(1, 31): print("Requesting changed files page", i) - url = f"{API_URL}/pulls/{number}/files?page={i}&per_page=100" - data = _exec_get_with_retry(url) + data = _exec_get_with_retry(f"{url}/files?page={i}&per_page=100") print(f"Got {len(data)} changed files") if len(data) == 0: print("No more changed files") @@ -317,14 +316,13 @@ def check_suspicious_changed_files(changed_files): return False -def approve_run(run_id, token): - url = f"{API_URL}/actions/runs/{run_id}/approve" +def approve_run(workflow_description: WorkflowDescription, token): + url = f"{workflow_description.api_url}/approve" _exec_post_with_retry(url, token) def label_manual_approve(pull_request, token): - number = pull_request["number"] - url = f"{API_URL}/issues/{number}/labels" + url = f"{pull_request['url']}/labels" data = {"labels": "manual approve"} _exec_post_with_retry(url, token, data) @@ -395,13 +393,9 @@ def rerun_workflow(workflow_description, token): _exec_post_with_retry(workflow_description.rerun_url, token) -def main(event): - token = get_token_from_aws() - event_data = json.loads(event["body"]) - print("The body received:", event["body"]) - workflow_description = get_workflow_description_from_event(event_data) - - print("Got workflow description", workflow_description) +def check_workflow_completed( + event_data, workflow_description: WorkflowDescription, token: str +) -> bool: if workflow_description.action == "completed": attempt = 0 # Nice and reliable GH API sends from time to time such events, e.g: @@ -421,7 +415,7 @@ def main(event): "Workflow finished with status " f"{workflow_description.conclusion}, exiting" ) - return + return True print( "Workflow", @@ -435,11 +429,24 @@ def main(event): workflow_description.name, "not in list of rerunable workflows", ) - return + return True if check_need_to_rerun(workflow_description): rerun_workflow(workflow_description, token) - return + return True + + return False + + +def main(event): + token = get_token_from_aws() + event_data = json.loads(event["body"]) + print("The body received:", event["body"]) + workflow_description = get_workflow_description_from_event(event_data) + + print("Got workflow description", workflow_description) + if check_workflow_completed(event_data, workflow_description, token): + return if workflow_description.action != "requested": print("Exiting, event action is", workflow_description.action) @@ -447,11 +454,13 @@ def main(event): if workflow_description.workflow_id in TRUSTED_WORKFLOW_IDS: print("Workflow in trusted list, approving run") - approve_run(workflow_description.run_id, token) + approve_run(workflow_description, token) return pull_requests = _get_pull_requests_from( - workflow_description.fork_owner_login, workflow_description.fork_branch + workflow_description.repo_url, + workflow_description.fork_owner_login, + workflow_description.fork_branch, ) print("Got pull requests for workflow", len(pull_requests)) @@ -465,7 +474,7 @@ def main(event): author, author_orgs = get_pr_author_and_orgs(pull_request) if is_trusted_contributor(author, author_orgs): print("Contributor is trusted, approving run") - approve_run(workflow_description.run_id, token) + approve_run(workflow_description, token) return changed_files = get_changed_files_for_pull_request(pull_request) @@ -478,7 +487,7 @@ def main(event): label_manual_approve(pull_request, token) else: print(f"Pull Request {pull_request['number']} has no suspicious changes") - approve_run(workflow_description.run_id, token) + approve_run(workflow_description, token) def handler(event, _): From c6948c3e821e8dbc3f1d313ecc0bcc11be4f8abf Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 3 Aug 2022 18:35:57 +0200 Subject: [PATCH 298/672] Use token for all requests --- tests/ci/workflow_approve_rerun_lambda/app.py | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tests/ci/workflow_approve_rerun_lambda/app.py b/tests/ci/workflow_approve_rerun_lambda/app.py index 28056fc51a8..29f3271a34c 100644 --- a/tests/ci/workflow_approve_rerun_lambda/app.py +++ b/tests/ci/workflow_approve_rerun_lambda/app.py @@ -187,10 +187,11 @@ def is_trusted_contributor(pr_user_login, pr_user_orgs): return False -def _exec_get_with_retry(url): +def _exec_get_with_retry(url, token): + headers = {"Authorization": f"token {token}"} for i in range(MAX_RETRY): try: - response = requests.get(url) + response = requests.get(url, headers=headers) response.raise_for_status() return response.json() except Exception as ex: @@ -226,9 +227,9 @@ def _exec_post_with_retry(url, token, data=None): raise Exception("Cannot execute POST request with retry") -def _get_pull_requests_from(repo_url, owner, branch): +def _get_pull_requests_from(repo_url, owner, branch, token): url = f"{repo_url}/pulls?head={owner}:{branch}" - return _exec_get_with_retry(url) + return _exec_get_with_retry(url, token) def get_workflow_description_from_event(event): @@ -266,19 +267,19 @@ def get_workflow_description_from_event(event): ) -def get_pr_author_and_orgs(pull_request): +def get_pr_author_and_orgs(pull_request, token): author = pull_request["user"]["login"] - orgs = _exec_get_with_retry(pull_request["user"]["organizations_url"]) + orgs = _exec_get_with_retry(pull_request["user"]["organizations_url"], token) return author, [org["id"] for org in orgs] -def get_changed_files_for_pull_request(pull_request): +def get_changed_files_for_pull_request(pull_request, token): url = pull_request["url"] changed_files = set([]) for i in range(1, 31): print("Requesting changed files page", i) - data = _exec_get_with_retry(f"{url}/files?page={i}&per_page=100") + data = _exec_get_with_retry(f"{url}/files?page={i}&per_page=100", token) print(f"Got {len(data)} changed files") if len(data) == 0: print("No more changed files") @@ -341,14 +342,14 @@ def get_token_from_aws(): return get_access_token(encoded_jwt, installation_id) -def get_workflow_jobs(workflow_description): +def get_workflow_jobs(workflow_description, token): jobs_url = ( workflow_description.api_url + f"/attempts/{workflow_description.attempt}/jobs" ) jobs = [] i = 1 while True: - got_jobs = _exec_get_with_retry(jobs_url + f"?page={i}") + got_jobs = _exec_get_with_retry(jobs_url + f"?page={i}", token) if len(got_jobs["jobs"]) == 0: break @@ -358,7 +359,7 @@ def get_workflow_jobs(workflow_description): return jobs -def check_need_to_rerun(workflow_description): +def check_need_to_rerun(workflow_description, token): if workflow_description.attempt >= MAX_WORKFLOW_RERUN: print( "Not going to rerun workflow because it's already tried more than two times" @@ -366,7 +367,7 @@ def check_need_to_rerun(workflow_description): return False print("Going to check jobs") - jobs = get_workflow_jobs(workflow_description) + jobs = get_workflow_jobs(workflow_description, token) print("Got jobs", len(jobs)) for job in jobs: if job["conclusion"] not in ("success", "skipped"): @@ -405,7 +406,7 @@ def check_workflow_completed( progressive_sleep = 3 * sum(i + 1 for i in range(attempt)) time.sleep(progressive_sleep) event_data["workflow_run"] = _exec_get_with_retry( - workflow_description.api_url + workflow_description.api_url, token ) workflow_description = get_workflow_description_from_event(event_data) attempt += 1 @@ -431,7 +432,7 @@ def check_workflow_completed( ) return True - if check_need_to_rerun(workflow_description): + if check_need_to_rerun(workflow_description, token): rerun_workflow(workflow_description, token) return True @@ -461,6 +462,7 @@ def main(event): workflow_description.repo_url, workflow_description.fork_owner_login, workflow_description.fork_branch, + token, ) print("Got pull requests for workflow", len(pull_requests)) @@ -471,13 +473,13 @@ def main(event): pull_request = pull_requests[0] print("Pull request for workflow number", pull_request["number"]) - author, author_orgs = get_pr_author_and_orgs(pull_request) + author, author_orgs = get_pr_author_and_orgs(pull_request, token) if is_trusted_contributor(author, author_orgs): print("Contributor is trusted, approving run") approve_run(workflow_description, token) return - changed_files = get_changed_files_for_pull_request(pull_request) + changed_files = get_changed_files_for_pull_request(pull_request, token) print(f"Totally have {len(changed_files)} changed files in PR:", changed_files) if check_suspicious_changed_files(changed_files): print( From 04f6a850706f935c71f5d4d913175d333a59c070 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 3 Aug 2022 17:34:46 +0000 Subject: [PATCH 299/672] fix mutations --- src/Storages/MergeTree/MutateTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 4a68c9917fa..a72b6af9c65 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -441,7 +441,7 @@ static std::unordered_map getStreamCounts( for (const auto & column_name : column_names) { - if (auto serialization = data_part->getSerialization(column_name)) + if (auto serialization = data_part->tryGetSerialization(column_name)) { auto callback = [&](const ISerialization::SubstreamPath & substream_path) { From b8ffaabdd8b849ba3a947363990e3b356cfbb7e7 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 3 Aug 2022 17:43:14 +0200 Subject: [PATCH 300/672] add chmod and stat to IDisk --- src/Disks/DiskDecorator.h | 6 ++++++ src/Disks/DiskLocal.cpp | 20 +++++++++++++++++++ src/Disks/DiskLocal.h | 6 ++++++ src/Disks/FakeDiskTransaction.h | 5 +++++ src/Disks/IDisk.h | 6 ++++++ src/Disks/IDiskTransaction.h | 3 +++ .../ObjectStorages/DiskObjectStorage.cpp | 12 +++++++++++ src/Disks/ObjectStorages/DiskObjectStorage.h | 6 ++++++ .../DiskObjectStorageTransaction.cpp | 9 +++++++++ .../DiskObjectStorageTransaction.h | 1 + .../FakeMetadataStorageFromDisk.h | 10 ++++++++++ src/Disks/ObjectStorages/IMetadataStorage.h | 8 ++++++++ .../MetadataStorageFromDisk.cpp | 5 +++++ .../ObjectStorages/MetadataStorageFromDisk.h | 10 ++++++++++ ...taStorageFromDiskTransactionOperations.cpp | 18 +++++++++++++++++ ...dataStorageFromDiskTransactionOperations.h | 15 ++++++++++++++ 16 files changed, 140 insertions(+) diff --git a/src/Disks/DiskDecorator.h b/src/Disks/DiskDecorator.h index 1eac37e13f3..0110154ff78 100644 --- a/src/Disks/DiskDecorator.h +++ b/src/Disks/DiskDecorator.h @@ -99,6 +99,12 @@ public: void syncRevision(UInt64 revision) override { delegate->syncRevision(revision); } UInt64 getRevision() const override { return delegate->getRevision(); } + bool supportsStat() const override { return delegate->supportsStat(); } + struct stat stat(const String & path) const override { return delegate->stat(path); } + + bool supportsChmod() const override { return delegate->supportsChmod(); } + void chmod(const String & path, mode_t mode) override { delegate->chmod(path, mode); } + protected: Executor & getExecutor() override; diff --git a/src/Disks/DiskLocal.cpp b/src/Disks/DiskLocal.cpp index e793f4dfb5a..b224d202541 100644 --- a/src/Disks/DiskLocal.cpp +++ b/src/Disks/DiskLocal.cpp @@ -11,6 +11,8 @@ #include #include +#include +#include #include #include @@ -39,6 +41,7 @@ namespace ErrorCodes extern const int CANNOT_UNLINK; extern const int CANNOT_RMDIR; extern const int BAD_ARGUMENTS; + extern const int CANNOT_STAT; } std::mutex DiskLocal::reservation_mutex; @@ -671,6 +674,23 @@ bool DiskLocal::setup() return true; } +struct stat DiskLocal::stat(const String & path) const +{ + struct stat st; + auto full_path = fs::path(disk_path) / path; + if (::stat(full_path.string().c_str(), &st) == 0) + return st; + DB::throwFromErrnoWithPath("Cannot stat file: " + path, path, DB::ErrorCodes::CANNOT_STAT); +} + +void DiskLocal::chmod(const String & path, mode_t mode) +{ + auto full_path = fs::path(disk_path) / path; + if (::chmod(full_path.string().c_str(), mode) == 0) + return; + DB::throwFromErrnoWithPath("Cannot chmod file: " + path, path, DB::ErrorCodes::PATH_ACCESS_DENIED); +} + void registerDiskLocal(DiskFactory & factory) { auto creator = [](const String & name, diff --git a/src/Disks/DiskLocal.h b/src/Disks/DiskLocal.h index 0db9d8d467c..2919063c979 100644 --- a/src/Disks/DiskLocal.h +++ b/src/Disks/DiskLocal.h @@ -122,6 +122,12 @@ public: bool canRead() const noexcept; bool canWrite() const noexcept; + bool supportsStat() const override { return true; } + struct stat stat(const String & path) const override; + + bool supportsChmod() const override { return true; } + void chmod(const String & path, mode_t mode) override; + private: std::optional tryReserve(UInt64 bytes); diff --git a/src/Disks/FakeDiskTransaction.h b/src/Disks/FakeDiskTransaction.h index 6d61ac752f2..e80b45a94ec 100644 --- a/src/Disks/FakeDiskTransaction.h +++ b/src/Disks/FakeDiskTransaction.h @@ -112,6 +112,11 @@ public: disk.setLastModified(path, timestamp); } + void chmod(const String & path, mode_t mode) override + { + disk.chmod(path, mode); + } + void setReadOnly(const std::string & path) override { disk.setReadOnly(path); diff --git a/src/Disks/IDisk.h b/src/Disks/IDisk.h index 2337fa00af5..816db57b016 100644 --- a/src/Disks/IDisk.h +++ b/src/Disks/IDisk.h @@ -351,6 +351,12 @@ public: getType()); } + virtual bool supportsStat() const { return false; } + virtual struct stat stat(const String & /*path*/) const { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Disk does not support stat"); } + + virtual bool supportsChmod() const { return false; } + virtual void chmod(const String & /*path*/, mode_t /*mode*/) { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Disk does not support chmod"); } + protected: friend class DiskDecorator; diff --git a/src/Disks/IDiskTransaction.h b/src/Disks/IDiskTransaction.h index 74fbe8919fe..572d86dcfdb 100644 --- a/src/Disks/IDiskTransaction.h +++ b/src/Disks/IDiskTransaction.h @@ -103,6 +103,9 @@ public: /// Set last modified time to file or directory at `path`. virtual void setLastModified(const std::string & path, const Poco::Timestamp & timestamp) = 0; + /// Just chmod. + virtual void chmod(const String & path, mode_t mode) = 0; + /// Set file at `path` as read-only. virtual void setReadOnly(const std::string & path) = 0; diff --git a/src/Disks/ObjectStorages/DiskObjectStorage.cpp b/src/Disks/ObjectStorages/DiskObjectStorage.cpp index 0b7d16bd895..c534e59c710 100644 --- a/src/Disks/ObjectStorages/DiskObjectStorage.cpp +++ b/src/Disks/ObjectStorages/DiskObjectStorage.cpp @@ -367,6 +367,18 @@ time_t DiskObjectStorage::getLastChanged(const String & path) const return metadata_storage->getLastChanged(path); } +struct stat DiskObjectStorage::stat(const String & path) const +{ + return metadata_storage->stat(path); +} + +void DiskObjectStorage::chmod(const String & path, mode_t mode) +{ + auto transaction = createObjectStorageTransaction(); + transaction->chmod(path, mode); + transaction->commit(); +} + void DiskObjectStorage::shutdown() { LOG_INFO(log, "Shutting down disk {}", name); diff --git a/src/Disks/ObjectStorages/DiskObjectStorage.h b/src/Disks/ObjectStorages/DiskObjectStorage.h index 9494f421e6a..ad9f744f92e 100644 --- a/src/Disks/ObjectStorages/DiskObjectStorage.h +++ b/src/Disks/ObjectStorages/DiskObjectStorage.h @@ -168,6 +168,12 @@ public: bool supportsCache() const override; + bool supportsStat() const override { return metadata_storage->supportsStat(); } + struct stat stat(const String & path) const override; + + bool supportsChmod() const override { return metadata_storage->supportsChmod(); } + void chmod(const String & path, mode_t mode) override; + private: /// Create actual disk object storage transaction for operations diff --git a/src/Disks/ObjectStorages/DiskObjectStorageTransaction.cpp b/src/Disks/ObjectStorages/DiskObjectStorageTransaction.cpp index 6e807747478..7fc3532ed4a 100644 --- a/src/Disks/ObjectStorages/DiskObjectStorageTransaction.cpp +++ b/src/Disks/ObjectStorages/DiskObjectStorageTransaction.cpp @@ -613,6 +613,15 @@ void DiskObjectStorageTransaction::setLastModified(const std::string & path, con })); } +void DiskObjectStorageTransaction::chmod(const String & path, mode_t mode) +{ + operations_to_execute.emplace_back( + std::make_unique(object_storage, metadata_storage, [path, mode](MetadataTransactionPtr tx) + { + tx->chmod(path, mode); + })); +} + void DiskObjectStorageTransaction::createFile(const std::string & path) { operations_to_execute.emplace_back( diff --git a/src/Disks/ObjectStorages/DiskObjectStorageTransaction.h b/src/Disks/ObjectStorages/DiskObjectStorageTransaction.h index ceed79a23b7..9c42203b613 100644 --- a/src/Disks/ObjectStorages/DiskObjectStorageTransaction.h +++ b/src/Disks/ObjectStorages/DiskObjectStorageTransaction.h @@ -109,6 +109,7 @@ public: void removeSharedFiles(const RemoveBatchRequest & files, bool keep_all_batch_data, const NameSet & file_names_remove_metadata_only) override; void setLastModified(const std::string & path, const Poco::Timestamp & timestamp) override; + void chmod(const String & path, mode_t mode) override; void setReadOnly(const std::string & path) override; void createHardLink(const std::string & src_path, const std::string & dst_path) override; }; diff --git a/src/Disks/ObjectStorages/FakeMetadataStorageFromDisk.h b/src/Disks/ObjectStorages/FakeMetadataStorageFromDisk.h index 6d5ae12a157..b1c8340ef1b 100644 --- a/src/Disks/ObjectStorages/FakeMetadataStorageFromDisk.h +++ b/src/Disks/ObjectStorages/FakeMetadataStorageFromDisk.h @@ -42,6 +42,12 @@ public: time_t getLastChanged(const std::string & path) const override; + bool supportsChmod() const override { return disk->supportsChmod(); } + + bool supportsStat() const override { return disk->supportsStat(); } + + struct stat stat(const String & path) const override { return disk->stat(path); } + std::vector listDirectory(const std::string & path) const override; DirectoryIteratorPtr iterateDirectory(const std::string & path) const override; @@ -89,6 +95,10 @@ public: void setLastModified(const std::string & path, const Poco::Timestamp & timestamp) override; + bool supportsChmod() const override { return disk->supportsChmod(); } + + void chmod(const String & path, mode_t mode) override { disk->chmod(path, mode); } + void setReadOnly(const std::string & path) override; void unlinkFile(const std::string & path) override; diff --git a/src/Disks/ObjectStorages/IMetadataStorage.h b/src/Disks/ObjectStorages/IMetadataStorage.h index a941f1ca514..948bfba2615 100644 --- a/src/Disks/ObjectStorages/IMetadataStorage.h +++ b/src/Disks/ObjectStorages/IMetadataStorage.h @@ -37,6 +37,9 @@ public: virtual void setLastModified(const std::string & path, const Poco::Timestamp & timestamp) = 0; + virtual bool supportsChmod() const = 0; + virtual void chmod(const String & path, mode_t mode) = 0; + virtual void setReadOnly(const std::string & path) = 0; virtual void unlinkFile(const std::string & path) = 0; @@ -107,6 +110,11 @@ public: virtual time_t getLastChanged(const std::string & path) const = 0; + virtual bool supportsChmod() const = 0; + + virtual bool supportsStat() const = 0; + virtual struct stat stat(const String & path) const = 0; + virtual std::vector listDirectory(const std::string & path) const = 0; virtual DirectoryIteratorPtr iterateDirectory(const std::string & path) const = 0; diff --git a/src/Disks/ObjectStorages/MetadataStorageFromDisk.cpp b/src/Disks/ObjectStorages/MetadataStorageFromDisk.cpp index 489772647d1..cdd0fdc8457 100644 --- a/src/Disks/ObjectStorages/MetadataStorageFromDisk.cpp +++ b/src/Disks/ObjectStorages/MetadataStorageFromDisk.cpp @@ -250,6 +250,11 @@ void MetadataStorageFromDiskTransaction::setLastModified(const std::string & pat addOperation(std::make_unique(path, timestamp, *metadata_storage.getDisk())); } +void MetadataStorageFromDiskTransaction::chmod(const String & path, mode_t mode) +{ + addOperation(std::make_unique(path, mode, *metadata_storage.getDisk())); +} + void MetadataStorageFromDiskTransaction::unlinkFile(const std::string & path) { addOperation(std::make_unique(path, *metadata_storage.getDisk())); diff --git a/src/Disks/ObjectStorages/MetadataStorageFromDisk.h b/src/Disks/ObjectStorages/MetadataStorageFromDisk.h index 104e9d54bff..8c1fb6edd14 100644 --- a/src/Disks/ObjectStorages/MetadataStorageFromDisk.h +++ b/src/Disks/ObjectStorages/MetadataStorageFromDisk.h @@ -39,6 +39,12 @@ public: time_t getLastChanged(const std::string & path) const override; + bool supportsChmod() const override { return disk->supportsChmod(); } + + bool supportsStat() const override { return disk->supportsStat(); } + + struct stat stat(const String & path) const override { return disk->stat(path); } + std::vector listDirectory(const std::string & path) const override; DirectoryIteratorPtr iterateDirectory(const std::string & path) const override; @@ -94,6 +100,10 @@ public: void setLastModified(const std::string & path, const Poco::Timestamp & timestamp) override; + bool supportsChmod() const override { return metadata_storage.supportsChmod(); } + + void chmod(const String & path, mode_t mode) override; + void setReadOnly(const std::string & path) override; void unlinkFile(const std::string & path) override; diff --git a/src/Disks/ObjectStorages/MetadataStorageFromDiskTransactionOperations.cpp b/src/Disks/ObjectStorages/MetadataStorageFromDiskTransactionOperations.cpp index 72da240cf8a..4a3f76cb2aa 100644 --- a/src/Disks/ObjectStorages/MetadataStorageFromDiskTransactionOperations.cpp +++ b/src/Disks/ObjectStorages/MetadataStorageFromDiskTransactionOperations.cpp @@ -36,6 +36,24 @@ void SetLastModifiedOperation::undo() disk.setLastModified(path, old_timestamp); } +ChmodOperation::ChmodOperation(const std::string & path_, mode_t mode_, IDisk & disk_) + : path(path_) + , mode(mode_) + , disk(disk_) +{ +} + +void ChmodOperation::execute(std::unique_lock &) +{ + old_mode = disk.stat(path).st_mode; + disk.chmod(path, mode); +} + +void ChmodOperation::undo() +{ + disk.chmod(path, old_mode); +} + UnlinkFileOperation::UnlinkFileOperation(const std::string & path_, IDisk & disk_) : path(path_) , disk(disk_) diff --git a/src/Disks/ObjectStorages/MetadataStorageFromDiskTransactionOperations.h b/src/Disks/ObjectStorages/MetadataStorageFromDiskTransactionOperations.h index 5f8e772ebc7..0bce6141301 100644 --- a/src/Disks/ObjectStorages/MetadataStorageFromDiskTransactionOperations.h +++ b/src/Disks/ObjectStorages/MetadataStorageFromDiskTransactionOperations.h @@ -37,6 +37,21 @@ private: IDisk & disk; }; +struct ChmodOperation final : public IMetadataOperation +{ + ChmodOperation(const std::string & path_, mode_t mode_, IDisk & disk_); + + void execute(std::unique_lock & metadata_lock) override; + + void undo() override; + +private: + std::string path; + mode_t mode; + mode_t old_mode; + IDisk & disk; +}; + struct UnlinkFileOperation final : public IMetadataOperation { From a269cf29e4ebfebd9034997de2b877be85e9d8ce Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 3 Aug 2022 19:49:16 +0200 Subject: [PATCH 301/672] remove unused dirs from store on all disks --- src/Interpreters/DatabaseCatalog.cpp | 199 +++++++++---------- src/Interpreters/DatabaseCatalog.h | 4 +- tests/integration/test_merge_tree_s3/test.py | 25 +++ 3 files changed, 125 insertions(+), 103 deletions(-) diff --git a/src/Interpreters/DatabaseCatalog.cpp b/src/Interpreters/DatabaseCatalog.cpp index 02d3e5eac32..a92a82ea821 100644 --- a/src/Interpreters/DatabaseCatalog.cpp +++ b/src/Interpreters/DatabaseCatalog.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -19,10 +20,6 @@ #include #include -#include -#include -#include - #include "config_core.h" #if USE_MYSQL @@ -894,7 +891,7 @@ void DatabaseCatalog::enqueueDroppedTableCleanup(StorageID table_id, StoragePtr create->setTable(table_id.table_name); try { - table = createTableFromAST(*create, table_id.getDatabaseName(), data_path, getContext(), false).second; + table = createTableFromAST(*create, table_id.getDatabaseName(), data_path, getContext(), true).second; table->is_dropped = true; } catch (...) @@ -979,7 +976,6 @@ void DatabaseCatalog::dropTableDataTask() if (table.table_id) { - try { dropTableFinally(table); @@ -1019,13 +1015,15 @@ void DatabaseCatalog::dropTableFinally(const TableMarkedAsDropped & table) table.table->drop(); } - /// Even if table is not loaded, try remove its data from disk. - /// TODO remove data from all volumes - fs::path data_path = fs::path(getContext()->getPath()) / "store" / getPathForUUID(table.table_id.uuid); - if (fs::exists(data_path)) + /// Even if table is not loaded, try remove its data from disks. + for (const auto & [disk_name, disk] : getContext()->getDisksMap()) { - LOG_INFO(log, "Removing data directory {} of dropped table {}", data_path.string(), table.table_id.getNameForLogs()); - fs::remove_all(data_path); + String data_path = "store/" + getPathForUUID(table.table_id.uuid); + if (!disk->exists(data_path)) + continue; + + LOG_INFO(log, "Removing data directory {} of dropped table {} from disk {}", data_path, table.table_id.getNameForLogs(), disk_name); + disk->removeRecursive(data_path); } LOG_INFO(log, "Removing metadata {} of dropped table {}", table.metadata_path, table.table_id.getNameForLogs()); @@ -1169,121 +1167,118 @@ void DatabaseCatalog::updateLoadingDependencies(const StorageID & table_id, Tabl void DatabaseCatalog::cleanupStoreDirectoryTask() { - fs::path store_path = fs::path(getContext()->getPath()) / "store"; - size_t affected_dirs = 0; - for (const auto & prefix_dir : fs::directory_iterator{store_path}) + for (const auto & [disk_name, disk] : getContext()->getDisksMap()) { - String prefix = prefix_dir.path().filename(); - bool expected_prefix_dir = prefix_dir.is_directory() && - prefix.size() == 3 && - isHexDigit(prefix[0]) && - isHexDigit(prefix[1]) && - isHexDigit(prefix[2]); - - if (!expected_prefix_dir) - { - LOG_WARNING(log, "Found invalid directory {}, will try to remove it", prefix_dir.path().string()); - affected_dirs += maybeRemoveDirectory(prefix_dir.path()); + if (!disk->supportsStat() || !disk->supportsChmod()) continue; - } - for (const auto & uuid_dir : fs::directory_iterator{prefix_dir.path()}) + size_t affected_dirs = 0; + for (auto it = disk->iterateDirectory("store"); it->isValid(); it->next()) { - String uuid_str = uuid_dir.path().filename(); - UUID uuid; - bool parsed = tryParse(uuid, uuid_str); + String prefix = it->name(); + bool expected_prefix_dir = disk->isDirectory(it->path()) && prefix.size() == 3 && isHexDigit(prefix[0]) && isHexDigit(prefix[1]) + && isHexDigit(prefix[2]); - bool expected_dir = uuid_dir.is_directory() && - parsed && - uuid != UUIDHelpers::Nil && - uuid_str.starts_with(prefix); - - if (!expected_dir) + if (!expected_prefix_dir) { - LOG_WARNING(log, "Found invalid directory {}, will try to remove it", uuid_dir.path().string()); - affected_dirs += maybeRemoveDirectory(uuid_dir.path()); + LOG_WARNING(log, "Found invalid directory {} on disk {}, will try to remove it", it->path(), disk_name); + affected_dirs += maybeRemoveDirectory(disk_name, disk, it->path()); continue; } - /// Order is important - if (!hasUUIDMapping(uuid)) + for (auto jt = disk->iterateDirectory(it->path()); jt->isValid(); jt->next()) { - /// We load uuids even for detached and permanently detached tables, - /// so it looks safe enough to remove directory if we don't have uuid mapping for it. - /// No table or database using this directory should concurrently appear, - /// because creation of new table would fail with "directory already exists". - affected_dirs += maybeRemoveDirectory(uuid_dir.path()); + String uuid_str = jt->name(); + UUID uuid; + bool parsed = tryParse(uuid, uuid_str); + + bool expected_dir = disk->isDirectory(jt->path()) && parsed && uuid != UUIDHelpers::Nil && uuid_str.starts_with(prefix); + + if (!expected_dir) + { + LOG_WARNING(log, "Found invalid directory {} on disk {}, will try to remove it", jt->path(), disk_name); + affected_dirs += maybeRemoveDirectory(disk_name, disk, jt->path()); + continue; + } + + /// Order is important + if (!hasUUIDMapping(uuid)) + { + /// We load uuids even for detached and permanently detached tables, + /// so it looks safe enough to remove directory if we don't have uuid mapping for it. + /// No table or database using this directory should concurrently appear, + /// because creation of new table would fail with "directory already exists". + affected_dirs += maybeRemoveDirectory(disk_name, disk, jt->path()); + } } } - } - if (affected_dirs) - LOG_INFO(log, "Cleaned up {} directories from store/", affected_dirs); + if (affected_dirs) + LOG_INFO(log, "Cleaned up {} directories from store/ on disk {}", affected_dirs, disk_name); + } (*cleanup_task)->scheduleAfter(unused_dir_cleanup_period_sec * 1000); } -bool DatabaseCatalog::maybeRemoveDirectory(const fs::path & unused_dir) +bool DatabaseCatalog::maybeRemoveDirectory(const String & disk_name, const DiskPtr & disk, const String & unused_dir) { /// "Safe" automatic removal of some directory. /// At first we do not remove anything and only revoke all access right. /// And remove only if nobody noticed it after, for example, one month. - struct stat st; - if (stat(unused_dir.string().c_str(), &st)) + try { - LOG_ERROR(log, "Failed to stat {}, errno: {}", unused_dir.string(), errno); + struct stat st = disk->stat(unused_dir); + + if (st.st_uid != geteuid()) + { + /// Directory is not owned by clickhouse, it's weird, let's ignore it (chmod will likely fail anyway). + LOG_WARNING(log, "Found directory {} with unexpected owner (uid={}) on disk {}", unused_dir, st.st_uid, disk_name); + return false; + } + + time_t max_modification_time = std::max(st.st_atime, std::max(st.st_mtime, st.st_ctime)); + time_t current_time = time(nullptr); + if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) + { + if (current_time <= max_modification_time + unused_dir_hide_timeout_sec) + return false; + + LOG_INFO(log, "Removing access rights for unused directory {} from disk {} (will remove it when timeout exceed)", unused_dir, disk_name); + + /// Explicitly update modification time just in case + + disk->setLastModified(unused_dir, Poco::Timestamp::fromEpochTime(current_time)); + + /// Remove all access right + disk->chmod(unused_dir, 0); + + return true; + } + else + { + if (!unused_dir_rm_timeout_sec) + return false; + + if (current_time <= max_modification_time + unused_dir_rm_timeout_sec) + return false; + + LOG_INFO(log, "Removing unused directory {} from disk {}", unused_dir, disk_name); + + /// We have to set these access rights to make recursive removal work + disk->chmod(unused_dir, S_IRWXU); + + disk->removeRecursive(unused_dir); + + return true; + } + } + catch (...) + { + tryLogCurrentException(log, fmt::format("Failed to remove unused directory {} from disk {} ({})", + unused_dir, disk->getName(), disk->getPath())); return false; } - - if (st.st_uid != geteuid()) - { - /// Directory is not owned by clickhouse, it's weird, let's ignore it (chmod will likely fail anyway). - LOG_WARNING(log, "Found directory {} with unexpected owner (uid={})", unused_dir.string(), st.st_uid); - return false; - } - - time_t max_modification_time = std::max(st.st_atime, std::max(st.st_mtime, st.st_ctime)); - time_t current_time = time(nullptr); - if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) - { - if (current_time <= max_modification_time + unused_dir_hide_timeout_sec) - return false; - - LOG_INFO(log, "Removing access rights for unused directory {} (will remove it when timeout exceed)", unused_dir.string()); - - /// Explicitly update modification time just in case - - struct utimbuf tb; - tb.actime = current_time; - tb.modtime = current_time; - if (utime(unused_dir.string().c_str(), &tb) != 0) - LOG_ERROR(log, "Failed to utime {}, errno: {}", unused_dir.string(), errno); - - /// Remove all access right - if (chmod(unused_dir.string().c_str(), 0)) - LOG_ERROR(log, "Failed to chmod {}, errno: {}", unused_dir.string(), errno); - - return true; - } - else - { - if (!unused_dir_rm_timeout_sec) - return false; - - if (current_time <= max_modification_time + unused_dir_rm_timeout_sec) - return false; - - LOG_INFO(log, "Removing unused directory {}", unused_dir.string()); - - /// We have to set these access rights to make recursive removal work - if (chmod(unused_dir.string().c_str(), S_IRWXU)) - LOG_ERROR(log, "Failed to chmod {}, errno: {}", unused_dir.string(), errno); - - fs::remove_all(unused_dir); - - return true; - } } static void maybeUnlockUUID(UUID uuid) diff --git a/src/Interpreters/DatabaseCatalog.h b/src/Interpreters/DatabaseCatalog.h index d82ad56eadd..0b3daefb258 100644 --- a/src/Interpreters/DatabaseCatalog.h +++ b/src/Interpreters/DatabaseCatalog.h @@ -31,10 +31,12 @@ class IDatabase; class Exception; class ColumnsDescription; struct ConstraintsDescription; +class IDisk; using DatabasePtr = std::shared_ptr; using DatabaseAndTable = std::pair; using Databases = std::map>; +using DiskPtr = std::shared_ptr; /// Table -> set of table-views that make SELECT from it. using ViewDependencies = std::map>; @@ -271,7 +273,7 @@ private: void dropTableFinally(const TableMarkedAsDropped & table); void cleanupStoreDirectoryTask(); - bool maybeRemoveDirectory(const fs::path & unused_dir); + 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; diff --git a/tests/integration/test_merge_tree_s3/test.py b/tests/integration/test_merge_tree_s3/test.py index 3ce2a08ae74..ab380d31494 100644 --- a/tests/integration/test_merge_tree_s3/test.py +++ b/tests/integration/test_merge_tree_s3/test.py @@ -24,6 +24,7 @@ def cluster(): "configs/config.d/storage_conf.xml", "configs/config.d/bg_processing_pool_conf.xml", ], + stay_alive=True, with_minio=True, ) @@ -709,3 +710,27 @@ def test_cache_with_full_disk_space(cluster, node_name): "Insert into cache is skipped due to insufficient disk space" ) node.query("DROP TABLE IF EXISTS s3_test NO DELAY") + + +@pytest.mark.parametrize("node_name", ["node"]) +def test_store_cleanup_disk_s3(cluster, node_name): + node = cluster.instances[node_name] + node.query("DROP TABLE IF EXISTS store_cleanup SYNC") + node.query( + "CREATE TABLE store_cleanup UUID '00000000-1000-4000-8000-000000000001' (n UInt64) Engine=MergeTree() ORDER BY n SETTINGS storage_policy='s3';" + ) + node.query("INSERT INTO store_cleanup SELECT 1") + + node.stop_clickhouse(kill=True) + path_to_data = "/var/lib/clickhouse/" + node.exec_in_container(["rm", f"{path_to_data}/metadata/default/store_cleanup.sql"]) + node.start_clickhouse() + + node.wait_for_log_line( + "Removing unused directory", timeout=90, look_behind_lines=1000 + ) + node.wait_for_log_line("directories from store") + node.query( + "CREATE TABLE store_cleanup UUID '00000000-1000-4000-8000-000000000001' (n UInt64) Engine=MergeTree() ORDER BY n SETTINGS storage_policy='s3';" + ) + node.query("INSERT INTO store_cleanup SELECT 1") From ab455f3767a55d55d22dfbfe0419509277b707f9 Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Wed, 3 Aug 2022 14:47:31 -0400 Subject: [PATCH 302/672] moving PR from clickhouse-docs --- .../_category_.yml | 8 + .../skipping-indexes.md | 167 +++ .../sparse-primary-indexes.md | 1172 +++++++++++++++++ 3 files changed, 1347 insertions(+) create mode 100644 docs/zh/guides/imporoviing-query-performance/_category_.yml create mode 100644 docs/zh/guides/imporoviing-query-performance/skipping-indexes.md create mode 100644 docs/zh/guides/imporoviing-query-performance/sparse-primary-indexes.md diff --git a/docs/zh/guides/imporoviing-query-performance/_category_.yml b/docs/zh/guides/imporoviing-query-performance/_category_.yml new file mode 100644 index 00000000000..62885213bfc --- /dev/null +++ b/docs/zh/guides/imporoviing-query-performance/_category_.yml @@ -0,0 +1,8 @@ +position: 10 +label: '优化查询性能' +collapsible: true +collapsed: true +link: + type: generated-index + title: Improving Query Performance + slug: /zh/guides/improving-query-performance diff --git a/docs/zh/guides/imporoviing-query-performance/skipping-indexes.md b/docs/zh/guides/imporoviing-query-performance/skipping-indexes.md new file mode 100644 index 00000000000..b3cb82bf769 --- /dev/null +++ b/docs/zh/guides/imporoviing-query-performance/skipping-indexes.md @@ -0,0 +1,167 @@ +--- +sidebar_label: Data Skipping Indexes +sidebar_position: 2 +--- + +# 深入理解ClickHouse跳数索引 + +### 跳数索引 + +影响ClickHouse查询性能的因素很多。在大多数场景中,关键因素是ClickHouse在计算查询WHERE子句条件时是否可以使用主键。因此,选择适用于最常见查询模式的主键对于表的设计至关重要。 + +然而,无论如何仔细地调优主键,不可避免地会出现不能有效使用它的查询用例。用户通常依赖于ClickHouse获得时间序列类型的数据,但他们通常希望根据其他业务维度(如客户id、网站URL或产品编号)分析同一批数据。在这种情况下,查询性能可能会相当差,因为应用WHERE子句条件可能需要对每个列值进行完整扫描。虽然ClickHouse在这些情况下仍然相对较快,但计算数百万或数十亿个单独的值将导致“非索引”查询的执行速度比基于主键的查询慢得多。 + +在传统的关系数据库中,解决这个问题的一种方法是将一个或多个“二级”索引附加到表上。这是一个b-树结构,允许数据库在O(log(n))时间内找到磁盘上所有匹配的行,而不是O(n)时间(一次表扫描),其中n是行数。但是,这种类型的二级索引不适用于ClickHouse(或其他面向列的数据库),因为磁盘上没有单独的行可以添加到索引中。 + +相反,ClickHouse提供了一种不同类型的索引,在特定情况下可以显著提高查询速度。这些结构被标记为跳数索引,因为它们使ClickHouse能够跳过保证没有匹配值的数据块。 +### 基本操作 + +用户只能在MergeTree表引擎上使用数据跳数索引。每个跳数索引都有四个主要参数: + +- 索引名称。索引名用于在每个分区中创建索引文件。此外,在删除或具体化索引时需要将其作为参数。 +- 索引的表达式。索引表达式用于计算存储在索引中的值集。它可以是列、简单操作符、函数的子集的组合。 +- 类型。索引的类型控制计算,该计算决定是否可以跳过读取和计算每个索引块。 +- GRANULARITY。每个索引块由颗粒(granule)组成。例如,如果主表索引粒度为8192行,GRANULARITY为4,则每个索引“块”将为32768行。 + +当用户创建数据跳数索引时,表的每个数据部分目录中将有两个额外的文件。 + +- skp_idx_{index_name}.idx:包含排序的表达式值。 +- skp_idx_{index_name}.mrk2:包含关联数据列文件中的相应偏移量。 + +如果在执行查询并读取相关列文件时,WHERE子句过滤条件的某些部分与跳数索引表达式匹配,ClickHouse将使用索引文件数据来确定每个相关的数据块是必须被处理还是可以被绕过(假设块还没有通过应用主键索引被排除)。这里用一个非常简单的示例:考虑以下加载了可预测数据的表。 + +``` +CREATE TABLE skip_table +( + my_key UInt64, + my_value UInt64 +) +ENGINE MergeTree primary key my_key +SETTINGS index_granularity=8192; + +INSERT INTO skip_table SELECT number, intDiv(number,4096) FROM numbers(100000000); +``` + +当执行一个不使用主键的简单查询时,将扫描my_value列所有的一亿条记录: + +``` +SELECT * FROM skip_table WHERE my_value IN (125, 700) + +┌─my_key─┬─my_value─┐ +│ 512000 │ 125 │ +│ 512001 │ 125 │ +│ ... | ... | +└────────┴──────────┘ + +8192 rows in set. Elapsed: 0.079 sec. Processed 100.00 million rows, 800.10 MB (1.26 billion rows/s., 10.10 GB/s. +``` + +增加一个基本的跳数索引: + +``` +ALTER TABLE skip_table ADD INDEX vix my_value TYPE set(100) GRANULARITY 2; +``` + +通常,跳数索引只应用于新插入的数据,所以仅仅添加索引不会影响上述查询。 + +要使已经存在的数据生效,那执行: + +``` +ALTER TABLE skip_table MATERIALIZE INDEX vix; +``` + +重跑SQL: + +``` +SELECT * FROM skip_table WHERE my_value IN (125, 700) + +┌─my_key─┬─my_value─┐ +│ 512000 │ 125 │ +│ 512001 │ 125 │ +│ ... | ... | +└────────┴──────────┘ + +8192 rows in set. Elapsed: 0.051 sec. Processed 32.77 thousand rows, 360.45 KB (643.75 thousand rows/s., 7.08 MB/s.) +``` + +这次没有再去处理1亿行800MB的数据,ClickHouse只读取和分析32768行360KB的数据—4个granule的数据。 + +下图是更直观的展示,这就是如何读取和选择my_value为125的4096行,以及如何跳过以下行而不从磁盘读取: + +![Simple Skip](../../../en/guides/improving-query-performance/images/simple_skip.svg) + +通过在执行查询时启用跟踪,用户可以看到关于跳数索引使用情况的详细信息。在clickhouse-client中设置send_logs_level: + +``` +SET send_logs_level='trace'; +``` +这将在尝试调优查询SQL和表索引时提供有用的调试信息。上面的例子中,调试日志显示跳数索引过滤了大部分granule,只读取了两个: + +``` + default.skip_table (933d4b2c-8cea-4bf9-8c93-c56e900eefd1) (SelectExecutor): Index `vix` has dropped 6102/6104 granules. +``` +### 跳数索引类型 + +#### minmax + +这种轻量级索引类型不需要参数。它存储每个块的索引表达式的最小值和最大值(如果表达式是一个元组,它分别存储元组元素的每个成员的值)。对于倾向于按值松散排序的列,这种类型非常理想。在查询处理期间,这种索引类型的开销通常是最小的。 + +这种类型的索引只适用于标量或元组表达式——索引永远不适用于返回数组或map数据类型的表达式。 + +#### set + +这种轻量级索引类型接受单个参数max_size,即每个块的值集(0允许无限数量的离散值)。这个集合包含块中的所有值(如果值的数量超过max_size则为空)。这种索引类型适用于每组颗粒中基数较低(本质上是“聚集在一起”)但总体基数较高的列。 + +该索引的成本、性能和有效性取决于块中的基数。如果每个块包含大量惟一值,那么针对大型索引集计算查询条件将非常昂贵,或者由于索引超过max_size而为空,因此索引将不应用。 + +#### Bloom Filter Types + +Bloom filter是一种数据结构,它允许对集合成员进行高效的是否存在测试,但代价是有轻微的误报。在跳数索引的使用场景,假阳性不是一个大问题,因为惟一的问题只是读取一些不必要的块。潜在的假阳性意味着索引表达式应该为真,否则有效的数据可能会被跳过。 + +因为Bloom filter可以更有效地处理大量离散值的测试,所以它们可以适用于大量条件表达式判断的场景。特别的是Bloom filter索引可以应用于数组,数组中的每个值都被测试,也可以应用于map,通过使用mapKeys或mapValues函数将键或值转换为数组。 + +有三种基于Bloom过滤器的数据跳数索引类型: + +* 基本的**bloom_filter**接受一个可选参数,该参数表示在0到1之间允许的“假阳性”率(如果未指定,则使用.025)。 + +* 更专业的**tokenbf_v1**。需要三个参数,用来优化布隆过滤器:(1)过滤器的大小字节(大过滤器有更少的假阳性,有更高的存储成本),(2)哈希函数的个数(更多的散列函数可以减少假阳性)。(3)布隆过滤器哈希函数的种子。有关这些参数如何影响布隆过滤器功能的更多细节,请参阅 [这里](https://hur.st/bloomfilter/) 。此索引仅适用于String、FixedString和Map类型的数据。输入表达式被分割为由非字母数字字符分隔的字符序列。例如,列值`This is a candidate for a "full text" search`将被分割为`This` `is` `a` `candidate` `for` `full` `text` `search`。它用于LIKE、EQUALS、in、hasToken()和类似的长字符串中单词和其他值的搜索。例如,一种可能的用途是在非结构的应用程序日志行列中搜索少量的类名或行号。 + +* 更专业的**ngrambf_v1**。该索引的功能与tokenbf_v1相同。在Bloom filter设置之前需要一个额外的参数,即要索引的ngram的大小。一个ngram是长度为n的任何字符串,比如如果n是4,`A short string`会被分割为`A sh`` sho`, `shor`, `hort`, `ort s`, `or st`, `r str`, ` stri`, `trin`, `ring`。这个索引对于文本搜索也很有用,特别是没有单词间断的语言,比如中文。 + +### 跳数索引函数 + +跳数索引核心目的是限制流行查询分析的数据量。鉴于ClickHouse数据的分析特性,这些查询的模式在大多数情况下都包含函数表达式。因此,跳数索引必须与常用函数正确交互才能提高效率。这种情况可能发生在: +* 插入数据并将索引定义为一个函数表达式(表达式的结果存储在索引文件中)或者 +* 处理查询,并将表达式应用于存储的索引值,以确定是否排除数据块。 + +每种类型的跳数索引支持的函数列表可以查看 [这里](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree/#functions-support) 。通常,集合索引和基于Bloom filter的索引(另一种类型的集合索引)都是无序的,因此不能用于范围。相反,最大最小值索引在范围中工作得特别好,因为确定范围是否相交非常快。部分匹配函数LIKE、startsWith、endsWith和hasToken的有效性取决于使用的索引类型、索引表达式和数据的特定形状。 + +### 跳数索引的配置 + +有两个可用的设置可应用于跳数索引。 + +* **use_skip_indexes** (0或1,默认为1)。不是所有查询都可以有效地使用跳过索引。如果一个特定的过滤条件可能包含很多颗粒,那么应用数据跳过索引将导致不必要的、有时甚至是非常大的成本。对于不太可能从任何跳过索引中获益的查询,将该值设置为0。 +* **force_data_skipping_indexes** (以逗号分隔的索引名列表)。此设置可用于防止某些类型的低效查询。在某些情况下,除非使用跳过索引,否则查询表的开销太大,如果将此设置与一个或多个索引名一起使用,则对于任何没有使用所列索引的查询将返回一个异常。这将防止编写糟糕的查询消耗服务器资源。 + +### 最佳实践 + +跳数索引并不直观,特别是对于来自RDMS领域并且习惯二级行索引或来自文档存储的反向索引的用户来说。要获得任何优化,应用ClickHouse数据跳数索引必须避免足够多的颗粒读取,以抵消计算索引的成本。关键是,如果一个值在一个索引块中只出现一次,就意味着整个块必须读入内存并计算,而索引开销是不必要的。 + +考虑以下数据分布: + +![Bad Skip!](../../../en/guides/improving-query-performance/images/bad_skip_1.svg) + + +假设主键/顺序是时间戳,并且在visitor_id上有一个索引。考虑下面的查询: + + `SELECT timestamp, url FROM table WHERE visitor_id = 1001` + +对于这种数据分布,传统的二级索引非常有利。不是读取所有的32678行来查找具有请求的visitor_id的5行,而是二级索引只包含5行位置,并且只从磁盘读取这5行。ClickHouse数据跳过索引的情况正好相反。无论跳转索引的类型是什么,visitor_id列中的所有32678值都将被测试。 + +因此,试图通过简单地向键列添加索引来加速ClickHouse查询的冲动通常是不正确的。只有在研究了其他替代方法之后,才应该使用此高级功能,例如修改主键(查看 [如何选择主键](../improving-query-performance/sparse-primary-indexes.md))、使用投影或使用实体化视图。即使跳数索引是合适的,也经常需要对索引和表进行仔细的调优。 + +在大多数情况下,一个有用的跳数索引需要主键和目标的非主列/表达式之间具有很强的相关性。如果没有相关性(如上图所示),那么在包含数千个值的块中,至少有一行满足过滤条件的可能性很高,并且只有几个块会被跳过。相反,如果主键的值范围(如一天中的时间)与潜在索引列中的值强相关(如电视观众年龄),则最小值类型的索引可能是有益的。注意,在插入数据时,可以增加这种相关性,方法是在sort /ORDER by键中包含额外的列,或者以在插入时对与主键关联的值进行分组的方式对插入进行批处理。例如,即使主键是一个包含大量站点事件的时间戳,特定site_id的所有事件也都可以被分组并由写入进程插入到一起,这将导致许多只包含少量站点id的颗粒,因此当根据特定的site_id值搜索时,可以跳过许多块。 + +跳数索引的另一个候选者是高基数表达式,其中任何一个值在数据中都相对稀疏。一个可能的例子是跟踪API请求中的错误代码的可观察性平台。某些错误代码虽然在数据中很少出现,但对搜索来说可能特别重要。error_code列上的set skip索引将允许绕过绝大多数不包含错误的块,从而显著改善针对错误的查询。 + +最后,关键的最佳实践是测试、测试、再测试。同样,与用于搜索文档的b-树二级索引或倒排索引不同,跳数索引行为是不容易预测的。将它们添加到表中会在数据摄取和查询方面产生很大的成本,这些查询由于各种原因不能从索引中受益。它们应该总是在真实世界的数据类型上进行测试,测试应该包括类型、粒度大小和其他参数的变化。测试通常会暴露仅仅通过思考不能发现的陷阱。 diff --git a/docs/zh/guides/imporoviing-query-performance/sparse-primary-indexes.md b/docs/zh/guides/imporoviing-query-performance/sparse-primary-indexes.md new file mode 100644 index 00000000000..12b9c7c598d --- /dev/null +++ b/docs/zh/guides/imporoviing-query-performance/sparse-primary-indexes.md @@ -0,0 +1,1172 @@ +--- +sidebar_label: Sparse Primary Indexes +sidebar_position: 20 +--- + + + +# ClickHouse主键索引最佳实践 + +在本文中,我们将深入研究ClickHouse索引。我们将对此进行详细说明和讨论: +- ClickHouse的索引与传统的关系数据库有何不同 +- ClickHouse是怎样构建和使用主键稀疏索引的 +- ClickHouse索引的最佳实践 + +您可以选择在自己的机器上执行本文给出的所有Clickhouse SQL语句和查询。 +如何安装和搭建ClickHouse请查看快速上手 + +:::note +这篇文章主要关注稀疏索引。 + +如果想了解二级跳数索引,请查看[教程](./skipping-indexes.md). + +::: + + +## 数据集 + +在本文中,我们将使用一个匿名的web流量数据集。 + +- 我们将使用样本数据集中的887万行(事件)的子集。 +- 未压缩的数据大小为887万个事件和大约700mb。当存储在ClickHouse时,压缩为200mb。 +- 在我们的子集中,每行包含三列,表示在特定时间(EventTime列)单击URL (URL列)的互联网用户(UserID列)。 + +通过这三个列,我们已经可以制定一些典型的web分析查询,如: + +- 某个用户点击次数最多的前10个url是什么? +- 点击某个URL次数最多的前10名用户是谁? +- 用户点击特定URL的最频繁时间(比如一周中的几天)是什么? + +## 测试环境 + +本文档中给出的所有运行时数据都是在带有Apple M1 Pro芯片和16GB RAM的MacBook Pro上本地运行ClickHouse 22.2.1。 + +## 全表扫描 + +为了了解在没有主键的情况下如何对数据集执行查询,我们通过执行以下SQL DDL语句(使用MergeTree表引擎)创建了一个表: + +```sql +CREATE TABLE hits_NoPrimaryKey +( + `UserID` UInt32, + `URL` String, + `EventTime` DateTime +) +ENGINE = MergeTree +PRIMARY KEY tuple(); +``` + + + +接下来,使用以下插入SQL将命中数据集的一个子集插入到表中。这个SQL使用URL表函数类型推断从clickhouse.com加载一个数据集的一部分数据: + +```sql +INSERT INTO hits_NoPrimaryKey SELECT + intHash32(c11::UInt64) AS UserID, + c15 AS URL, + c5 AS EventTime +FROM url('https://datasets.clickhouse.com/hits/tsv/hits_v1.tsv.xz') +WHERE URL != ''; +``` +结果: +```response +Ok. + +0 rows in set. Elapsed: 145.993 sec. Processed 8.87 million rows, 18.40 GB (60.78 thousand rows/s., 126.06 MB/s.) +``` + + +ClickHouse客户端输出了执行结果,插入了887万行数据。 + + +最后,为了简化本文后面的讨论,并使图表和结果可重现,我们使用FINAL关键字optimize该表: + +```sql +OPTIMIZE TABLE hits_NoPrimaryKey FINAL; +``` + +:::note +一般来说,不需要也不建议在加载数据后立即执行optimize。对于这个示例,为什么需要这样做是很明显的。 +::: + + +现在我们执行第一个web分析查询。以下是用户id为749927693的互联网用户点击次数最多的前10个url: + +```sql +SELECT URL, count(URL) as Count +FROM hits_NoPrimaryKey +WHERE UserID = 749927693 +GROUP BY URL +ORDER BY Count DESC +LIMIT 10; +``` +结果: +```response +┌─URL────────────────────────────┬─Count─┐ +│ http://auto.ru/chatay-barana.. │ 170 │ +│ http://auto.ru/chatay-id=371...│ 52 │ +│ http://public_search │ 45 │ +│ http://kovrik-medvedevushku-...│ 36 │ +│ http://forumal │ 33 │ +│ http://korablitz.ru/L_1OFFER...│ 14 │ +│ http://auto.ru/chatay-id=371...│ 14 │ +│ http://auto.ru/chatay-john-D...│ 13 │ +│ http://auto.ru/chatay-john-D...│ 10 │ +│ http://wot/html?page/23600_m...│ 9 │ +└────────────────────────────────┴───────┘ + +10 rows in set. Elapsed: 0.022 sec. +// highlight-next-line +Processed 8.87 million rows, +70.45 MB (398.53 million rows/s., 3.17 GB/s.) +``` + + +ClickHouse客户端输出表明,ClickHouse执行了一个完整的表扫描!我们的表的887万行中的每一行都被加载到ClickHouse中,这不是可扩展的。 + +为了使这种(方式)更有效和更快,我们需要使用一个具有适当主键的表。这将允许ClickHouse自动(基于主键的列)创建一个稀疏的主索引,然后可以用于显著加快我们示例查询的执行。 + + + +## 包含主键的表 + +创建一个包含联合主键UserID和URL列的表: + +```sql +CREATE TABLE hits_UserID_URL +( + `UserID` UInt32, + `URL` String, + `EventTime` DateTime +) +ENGINE = MergeTree +// highlight-next-line +PRIMARY KEY (UserID, URL) +ORDER BY (UserID, URL, EventTime) +SETTINGS index_granularity = 8192, index_granularity_bytes = 0; +``` + +[//]: # (

) +
+ + DDL详情 + +

+ +为了简化本文后面的讨论,并使图和结果可重现,使用DDL语句有如下说明: +

    +
  • 通过ORDER BY子句指定表的复合排序键
  • +
    +
  • 通过设置配置控制主索引有多少索引项:
  • +
    +
      +
    • index_granularity: 显式设置为其默认值8192。这意味着对于每一组8192行,主索引将有一个索引条目,例如,如果表包含16384行,那么索引将有两个索引条目。 +
    • +
      +
    • index_granularity_bytes: 设置为0表示禁止字适应索引粒度。自适应索引粒度意味着ClickHouse自动为一组n行创建一个索引条目 +
        +
      • 如果n小于8192,但n行的合并行数据大小大于或等于10MB (index_granularity_bytes的默认值)或
      • +
      • n达到8192
      • +
      +
    • +
    +
+

+
+ + +上面DDL语句中的主键会基于两个指定的键列创建主索引。 + +
+插入数据: + +```sql +INSERT INTO hits_UserID_URL SELECT + intHash32(c11::UInt64) AS UserID, + c15 AS URL, + c5 AS EventTime +FROM url('https://datasets.clickhouse.com/hits/tsv/hits_v1.tsv.xz') +WHERE URL != ''; +``` +结果: +```response +0 rows in set. Elapsed: 149.432 sec. Processed 8.87 million rows, 18.40 GB (59.38 thousand rows/s., 123.16 MB/s.) +``` + + +
+optimize表: + +```sql +OPTIMIZE TABLE hits_UserID_URL FINAL; +``` + +
+我们可以使用下面的查询来获取关于表的元数据: + +```sql +SELECT + part_type, + path, + formatReadableQuantity(rows) AS rows, + formatReadableSize(data_uncompressed_bytes) AS data_uncompressed_bytes, + formatReadableSize(data_compressed_bytes) AS data_compressed_bytes, + formatReadableSize(primary_key_bytes_in_memory) AS primary_key_bytes_in_memory, + marks, + formatReadableSize(bytes_on_disk) AS bytes_on_disk +FROM system.parts +WHERE (table = 'hits_UserID_URL') AND (active = 1) +FORMAT Vertical; +``` + +结果: + +```response +part_type: Wide +path: ./store/d9f/d9f36a1a-d2e6-46d4-8fb5-ffe9ad0d5aed/all_1_9_2/ +rows: 8.87 million +data_uncompressed_bytes: 733.28 MiB +data_compressed_bytes: 206.94 MiB +primary_key_bytes_in_memory: 96.93 KiB +marks: 1083 +bytes_on_disk: 207.07 MiB + + +1 rows in set. Elapsed: 0.003 sec. +``` + +客户端输出表明: + +- 表数据以wide format存储在一个特定目录,每个列有一个数据文件和mark文件。 +- 表有887万行数据。 +- 未压缩的数据有733.28 MB。 +- 压缩之后的数据有206.94 MB。 +- 有1083个主键索引条目,大小是96.93 KB。 +- 在磁盘上,表的数据、标记文件和主索引文件总共占用207.07 MB。 + + +## 针对海量数据规模的索引设计 + +在传统的关系数据库管理系统中,每个表行包含一个主索引。对于我们的数据集,这将导致主索引——通常是一个B(+)-Tree的数据结构——包含887万个条目。 + +这样的索引允许快速定位特定的行,从而提高查找点查和更新的效率。在B(+)-Tree数据结构中搜索一个条目的平均时间复杂度为O(log2n)。对于一个有887万行的表,这意味着需要23步来定位任何索引条目。 + +这种能力是有代价的:额外的磁盘和内存开销,以及向表中添加新行和向索引中添加条目时更高的插入成本(有时还需要重新平衡B-Tree)。 + +考虑到与B-Tee索引相关的挑战,ClickHouse中的表引擎使用了一种不同的方法。ClickHouseMergeTree Engine引擎系列被设计和优化用来处理大量数据。 + +这些表被设计为每秒接收数百万行插入,并存储非常大(100 pb)的数据量。 + +数据被一批一批的快速写入表中,并在后台应用合并规则。 + +在ClickHouse中,每个数据部分(data part)都有自己的主索引。当他们被合并时,合并部分的主索引也被合并。 + +在大规模中情况下,磁盘和内存的效率是非常重要的。因此,不是为每一行创建索引,而是为一组数据行(称为颗粒(granule))构建一个索引条目。 + +之所以可以使用这种稀疏索引,是因为ClickHouse会按照主键列的顺序将一组行存储在磁盘上。 + +与直接定位单个行(如基于B-Tree的索引)不同,稀疏主索引允许它快速(通过对索引项进行二分查找)识别可能匹配查询的行组。 + +然后潜在的匹配行组(颗粒)以并行的方式被加载到ClickHouse引擎中,以便找到匹配的行。 + +这种索引设计允许主索引很小(它可以而且必须完全适合主内存),同时仍然显著加快查询执行时间:特别是对于数据分析用例中常见的范围查询。 + +下面详细说明了ClickHouse是如何构建和使用其稀疏主索引的。在本文后面,我们将讨论如何选择、移除和排序用于构建索引的表列(主键列)的一些最佳实践。 + + + +## 数据按照主键排序存储在磁盘上 + +上面创建的表有: +- 联合主键 (UserID, URL) +- 联合排序键 (UserID, URL, EventTime)。 + +:::note +- 如果我们只指定了排序键,那么主键将隐式定义为排序键。 + +- 为了提高内存效率,我们显式地指定了一个主键,只包含查询过滤的列。基于主键的主索引被完全加载到主内存中。 + +- 为了上下文的一致性和最大的压缩比例,我们单独定义了排序键,排序键包含当前表所有的列(和压缩算法有关,一般排序之后又更好的压缩率)。 + +- 如果同时指定了主键和排序键,则主键必须是排序键的前缀。 +::: + + +插入的行按照主键列(以及排序键的附加EventTime列)的字典序(从小到大)存储在磁盘上。 + +:::note +ClickHouse允许插入具有相同主键列的多行数据。在这种情况下(参见下图中的第1行和第2行),最终的顺序是由指定的排序键决定的,这里是EventTime列的值。 +::: + + +如下图所示:ClickHouse是列存数据库。 +- 在磁盘上,每个表都有一个数据文件(*.bin),该列的所有值都以压缩格式存储,并且 +- 在这个例子中,这887万行按主键列(以及附加的排序键列)的字典升序存储在磁盘上 + - UserID第一位, + - 然后是URL, + - 最后是EventTime: + + +UserID.bin,URL.bin,和EventTime.bin是UserIDURL,和EventTime列的数据文件。 + +
+
+ + +:::note +- 因为主键定义了磁盘上行的字典顺序,所以一个表只能有一个主键。 + +- 我们从0开始对行进行编号,以便与ClickHouse内部行编号方案对齐,该方案也用于记录消息。 +::: + + + +## 数据被组织成颗粒以进行并行数据处理 + +出于数据处理的目的,表的列值在逻辑上被划分为多个颗粒。颗粒是流进ClickHouse进行数据处理的最小的不可分割数据集。这意味着,ClickHouse不是读取单独的行,而是始终读取(以流方式并并行地)整个行组(颗粒)。 +:::note +列值并不物理地存储在颗粒中,颗粒只是用于查询处理的列值的逻辑组织方式。 +::: + +下图显示了如何将表中的887万行(列值)组织成1083个颗粒,这是表的DDL语句包含设置index_granularity(设置为默认值8192)的结果。 + + + +第一个(根据磁盘上的物理顺序)8192行(它们的列值)在逻辑上属于颗粒0,然后下一个8192行(它们的列值)属于颗粒1,以此类推。 + +:::note +- 最后一个颗粒(1082颗粒)是少于8192行的。 + +- 我们将主键列(UserID, URL)中的一些列值标记为橙色。 + + 这些橙色标记的列值是每个颗粒中每个主键列的最小值。这里的例外是最后一个颗粒(上图中的颗粒1082),最后一个颗粒我们标记的是最大的值。 + + 正如我们将在下面看到的,这些橙色标记的列值将是表主索引中的条目。 + +- 我们从0开始对行进行编号,以便与ClickHouse内部行编号方案对齐,该方案也用于记录消息。 +::: + + + +## 每个颗粒对应主索引的一个条目 + +主索引是基于上图中显示的颗粒创建的。这个索引是一个未压缩的扁平数组文件(primary.idx),包含从0开始的所谓的数字索引标记。 + +下面的图显示了索引存储了每个颗粒的最小主键列值(在上面的图中用橙色标记的值)。 +例如: +- 第一个索引条目(下图中的“mark 0”)存储上图中颗粒0的主键列的最小值, +- 第二个索引条目(下图中的“mark 1”)存储上图中颗粒1的主键列的最小值,以此类推。 + + + +在我们的表中,索引总共有1083个条目,887万行数据和1083个颗粒: + + + +:::note +- 最后一个索引条目(上图中的“mark 1082”)存储了上图中颗粒1082的主键列的最大值。 + +- 索引条目(索引标记)不是基于表中的特定行,而是基于颗粒。例如,对于上图中的索引条目‘mark 0’,在我们的表中没有UserID为240.923且URL为“goal://metry=10000467796a411…”的行,相反,对于该表,有一个颗粒0,在该颗粒中,最小UserID值是240.923,最小URL值是“goal://metry=10000467796a411…”,这两个值来自不同的行。 + +- 主索引文件完全加载到主内存中。如果文件大于可用的空闲内存空间,则ClickHouse将发生错误。 +::: + + +主键条目称为索引标记,因为每个索引条目都标志着特定数据范围的开始。对于示例表: +- UserID index marks:
+ 主索引中存储的UserID值按升序排序。
+ 上图中的‘mark 1’指示颗粒1中所有表行的UserID值,以及随后所有颗粒中的UserID值,都保证大于或等于4.073.710。 + + [正如我们稍后将看到的](#query-on-userid-fast), 当查询对主键的第一列进行过滤时,此全局有序使ClickHouse能够对第一个键列的索引标记使用二分查找算法。 + +- URL index marks:
+ 主键列UserIDURL有相同的基数,这意味着第一列之后的所有主键列的索引标记通常只表示每个颗粒的数据范围。
+ 例如,‘mark 0’中的URL列所有的值都大于等于goal://metry=10000467796a411..., 然后颗粒1中的URL并不是如此,这是因为‘mark 1‘与‘mark 0‘具有不同的UserID列值。 + + 稍后我们将更详细地讨论这对查询执行性能的影响。 + +## 主索引被用来选择颗粒 + +现在,我们可以在主索引的支持下执行查询。 + + +下面计算UserID 749927693点击次数最多的10个url。 + +```sql +SELECT URL, count(URL) AS Count +FROM hits_UserID_URL +WHERE UserID = 749927693 +GROUP BY URL +ORDER BY Count DESC +LIMIT 10; +``` + +结果: + + +```response +┌─URL────────────────────────────┬─Count─┐ +│ http://auto.ru/chatay-barana.. │ 170 │ +│ http://auto.ru/chatay-id=371...│ 52 │ +│ http://public_search │ 45 │ +│ http://kovrik-medvedevushku-...│ 36 │ +│ http://forumal │ 33 │ +│ http://korablitz.ru/L_1OFFER...│ 14 │ +│ http://auto.ru/chatay-id=371...│ 14 │ +│ http://auto.ru/chatay-john-D...│ 13 │ +│ http://auto.ru/chatay-john-D...│ 10 │ +│ http://wot/html?page/23600_m...│ 9 │ +└────────────────────────────────┴───────┘ + +10 rows in set. Elapsed: 0.005 sec. +// highlight-next-line +Processed 8.19 thousand rows, +740.18 KB (1.53 million rows/s., 138.59 MB/s.) +``` + +ClickHouse客户端的输出显示,没有进行全表扫描,只有8.19万行流到ClickHouse。 + + +如果trace logging打开了,那ClickHouse服务端日志会显示ClickHouse正在对1083个UserID索引标记执行二分查找以便识别可能包含UserID列值为749927693的行的颗粒。这需要19个步骤,平均时间复杂度为O(log2 n): +```response +...Executor): Key condition: (column 0 in [749927693, 749927693]) +// highlight-next-line +...Executor): Running binary search on index range for part all_1_9_2 (1083 marks) +...Executor): Found (LEFT) boundary mark: 176 +...Executor): Found (RIGHT) boundary mark: 177 +...Executor): Found continuous range in 19 steps +...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, +// highlight-next-line + 1/1083 marks by primary key, 1 marks to read from 1 ranges +...Reading ...approx. 8192 rows starting from 1441792 +``` + + +我们可以在上面的跟踪日志中看到,1083个现有标记中有一个满足查询。 + +
+ + Trace Log详情 + +

+ +Mark 176 was identified (the 'found left boundary mark' is inclusive, the 'found right boundary mark' is exclusive), and therefore all 8192 rows from granule 176 (which starts at row 1.441.792 - we will see that later on in this article) are then streamed into ClickHouse in order to find the actual rows with a UserID column value of 749927693. +

+
+ +我们也可以通过使用EXPLAIN来重现这个结果: +```sql +EXPLAIN indexes = 1 +SELECT URL, count(URL) AS Count +FROM hits_UserID_URL +WHERE UserID = 749927693 +GROUP BY URL +ORDER BY Count DESC +LIMIT 10; +``` + +结果如下: + +```response +┌─explain───────────────────────────────────────────────────────────────────────────────┐ +│ Expression (Projection) │ +│ Limit (preliminary LIMIT (without OFFSET)) │ +│ Sorting (Sorting for ORDER BY) │ +│ Expression (Before ORDER BY) │ +│ Aggregating │ +│ Expression (Before GROUP BY) │ +│ Filter (WHERE) │ +│ SettingQuotaAndLimits (Set limits and quota after reading from storage) │ +│ ReadFromMergeTree │ +│ Indexes: │ +│ PrimaryKey │ +│ Keys: │ +│ UserID │ +│ Condition: (UserID in [749927693, 749927693]) │ +│ Parts: 1/1 │ +// highlight-next-line +│ Granules: 1/1083 │ +└───────────────────────────────────────────────────────────────────────────────────────┘ + +16 rows in set. Elapsed: 0.003 sec. +``` +客户端输出显示,在1083个颗粒中选择了一个可能包含UserID列值为749927693的行。 + + +:::note Conclusion +当查询对联合主键的一部分并且是第一个主键进行过滤时,ClickHouse将主键索引标记运行二分查找算法。 +::: + +
+ + +正如上面所讨论的,ClickHouse使用它的稀疏主索引来快速(通过二分查找算法)选择可能包含匹配查询的行的颗粒。 + +这是ClickHouse查询执行的**第一阶段(颗粒选择)**。 + +在**第二阶段(数据读取中)**, ClickHouse定位所选的颗粒,以便将它们的所有行流到ClickHouse引擎中,以便找到实际匹配查询的行。 + +我们将在下一节更详细地讨论第二阶段。 + + + +## 标记文件用来定位颗粒 + +下图描述了上表主索引文件的一部分。 + + + +如上所述,通过对索引的1083个UserID标记进行二分搜索,确定了第176个标记。因此,它对应的颗粒176可能包含UserID列值为749.927.693的行。 + +
+ + 颗粒选择的具体过程 + +

+ +上图显示,标记176是第一个UserID值小于749.927.693的索引条目,并且下一个标记(标记177)的颗粒177的最小UserID值大于该值的索引条目。因此,只有标记176对应的颗粒176可能包含UserID列值为749.927.693的行。 +

+
+ +为了确认(或排除)颗粒176中的某些行包含UserID列值为749.927.693,需要将属于此颗粒的所有8192行读取到ClickHouse。 + +为了读取这部分数据,ClickHouse需要知道颗粒176的物理地址。 + +在ClickHouse中,我们表的所有颗粒的物理位置都存储在标记文件中。与数据文件类似,每个表的列有一个标记文件。 + +下图显示了三个标记文件UserID.mrk、URL.mrk、EventTime.mrk,为表的UserID、URL和EventTime列存储颗粒的物理位置。 + + + +我们已经讨论了主索引是一个扁平的未压缩数组文件(primary.idx),其中包含从0开始编号的索引标记。 + +类似地,标记文件也是一个扁平的未压缩数组文件(*.mrk),其中包含从0开始编号的标记。 + +一旦ClickHouse确定并选择了可能包含查询所需的匹配行的颗粒的索引标记,就可以在标记文件数组中查找,以获得颗粒的物理位置。 + +每个特定列的标记文件条目以偏移量的形式存储两个位置: + +- 第一个偏移量(上图中的'block_offset')是在包含所选颗粒的压缩版本的压缩列数据文件中定位块。这个压缩块可能包含几个压缩的颗粒。所定位的压缩文件块在读取时被解压到内存中。 + +- 标记文件的第二个偏移量(上图中的“granule_offset”)提供了颗粒在解压数据块中的位置。 + +定位到的颗粒中的所有8192行数据都会被ClickHouse加载然后进一步处理。 + + +:::note 为什么需要mark文件 + +为什么主索引不直接包含与索引标记相对应的颗粒的物理位置? + +因为ClickHouse设计的场景就是超大规模数据,非常高效地使用磁盘和内存非常重要。 + +主索引文件需要放入内存中。 + +对于我们的示例查询,ClickHouse使用了主索引,并选择了可能包含与查询匹配的行的单个颗粒。只有对于这一个颗粒,ClickHouse才需定位物理位置,以便将相应的行组读取以进一步的处理。 + +而且,只有UserID和URL列需要这个偏移量信息。 + +对于查询中不使用的列,例如EventTime,不需要偏移量信息。 + +对于我们的示例查询,Clickhouse只需要UserID数据文件(UserID.bin)中176颗粒的两个物理位置偏移,以及URL数据文件(URL.data)中176颗粒的两个物理位置偏移。 + +由mark文件提供的间接方法避免了直接在主索引中存储所有三个列的所有1083个颗粒的物理位置的条目:因此避免了在主内存中有不必要的(可能未使用的)数据。 + +::: + +下面的图表和文本说明了我们的查询示例,ClickHouse如何在UserID.bin数据文件中定位176颗粒。 + + + +我们在本文前面讨论过,ClickHouse选择了主索引标记176,因此176颗粒可能包含查询所需的匹配行。 + +ClickHouse现在使用从索引中选择的标记号(176)在UserID.mark中进行位置数组查找,以获得两个偏移量,用于定位颗粒176。 + +如图所示,第一个偏移量是定位UserID.bin数据文件中的压缩文件块,该数据文件包含颗粒176的压缩数据。 + +一旦所定位的文件块被解压缩到主内存中,就可以使用标记文件的第二个偏移量在未压缩的数据中定位颗粒176。 + +ClickHouse需要从UserID.bin数据文件和URL.bin数据文件中定位(读取)颗粒176,以便执行我们的示例查询(UserID为749.927.693的互联网用户点击次数最多的10个url)。 + +上图显示了ClickHouse如何定位UserID.bin数据文件的颗粒。 + +同时,ClickHouse对URL.bin数据文件的颗粒176执行相同的操作。这两个不同的颗粒被对齐并加载到ClickHouse引擎以进行进一步的处理,即聚合并计算UserID为749.927.693的所有行的每组URL值,最后以计数降序输出10个最大的URL组。 + + + + +## 查询使用第二位主键的性能问题 + + +当查询对复合键的一部分并且是第一个主键列进行过滤时,ClickHouse将对主键列的索引标记运行二分查找。 + +但是,当查询对联合主键的一部分但不是第一个键列进行过滤时,会发生什么情况? + +:::note +我们讨论了这样一种场景:查询不是显式地对第一个主键列进行过滤,而是对第一个主键列之后的任何键列进行过滤。 + +当查询同时对第一个主键列和第一个主键列之后的任何键列进行过滤时,ClickHouse将对第一个主键列的索引标记运行二分查找。 +::: + +
+
+ + +我们使用一个查询来计算最点击"http://public_search"的最多的前10名用户: + +```sql +SELECT UserID, count(UserID) AS Count +FROM hits_UserID_URL +WHERE URL = 'http://public_search' +GROUP BY UserID +ORDER BY Count DESC +LIMIT 10; +``` + +结果是: +```response +┌─────UserID─┬─Count─┐ +│ 2459550954 │ 3741 │ +│ 1084649151 │ 2484 │ +│ 723361875 │ 729 │ +│ 3087145896 │ 695 │ +│ 2754931092 │ 672 │ +│ 1509037307 │ 582 │ +│ 3085460200 │ 573 │ +│ 2454360090 │ 556 │ +│ 3884990840 │ 539 │ +│ 765730816 │ 536 │ +└────────────┴───────┘ + +10 rows in set. Elapsed: 0.086 sec. +// highlight-next-line +Processed 8.81 million rows, +799.69 MB (102.11 million rows/s., 9.27 GB/s.) +``` + +客户端输出表明,尽管URL列是联合主键的一部分,ClickHouse几乎执行了一一次全表扫描!ClickHouse从表的887万行中读取881万行。 + +如果启用了trace日志,那么ClickHouse服务日志文件显示,ClickHouse在1083个URL索引标记上使用了通用的排除搜索,以便识别那些可能包含URL列值为"http://public_search"的行。 +```response +...Executor): Key condition: (column 1 in ['http://public_search', + 'http://public_search']) +// highlight-next-line +...Executor): Used generic exclusion search over index for part all_1_9_2 + with 1537 steps +...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, +// highlight-next-line + 1076/1083 marks by primary key, 1076 marks to read from 5 ranges +...Executor): Reading approx. 8814592 rows with 10 streams +``` +我们可以在上面的跟踪日志示例中看到,1083个颗粒中有1076个(通过标记)被选中,因为可能包含具有匹配URL值的行。 + +这将导致881万行被读取到ClickHouse引擎中(通过使用10个流并行地读取),以便识别实际包含URL值"http://public_search"的行。 + +然而,[稍后](#query-on-url-fast)仅仅39个颗粒包含匹配的行。 + +虽然基于联合主键(UserID, URL)的主索引对于加快过滤具有特定UserID值的行的查询非常有用,但对于过滤具有特定URL值的行的查询,索引并没有提供显著的帮助。 + +原因是URL列不是第一个主键列,因此ClickHouse是使用一个通用的排除搜索算法(而不是二分查找)查找URL列的索引标志,和UserID主键列不同,它的算法的有效性依赖于URL列的基数。 + +为了说明,我们给出通用的排除搜索算法的工作原理: + +
+ + 通用排除搜索算法 + +

+ + + + +下面将演示当通过第一个列之后的任何列选择颗粒时,当前一个键列具有或高或低的基数时,ClickHouse通用排除搜索算法 是如何工作的。 + +作为这两种情况的例子,我们将假设: +- 搜索URL值为"W3"的行。 +- 点击表抽象简化为只有简单值的UserID和UserID。 +- 相同联合主键(UserID、URL)。这意味着行首先按UserID值排序,具有相同UserID值的行然后再按URL排序。 +- 颗粒大小为2,即每个颗粒包含两行。 + +在下面的图表中,我们用橙色标注了每个颗粒的最小键列值。 + +**前缀主键低基数** + +假设UserID具有较低的基数。在这种情况下,相同的UserID值很可能分布在多个表行和颗粒上,从而分布在索引标记上。对于具有相同UserID的索引标记,索引标记的URL值按升序排序(因为表行首先按UserID排序,然后按URL排序)。这使得有效的过滤如下所述: + + + +在上图中,我们的抽象样本数据的颗粒选择过程有三种不同的场景: + + +1. 如果索引标记0的(最小)URL值小于W3,并且紧接索引标记的URL值也小于W3,则可以排除索引标记0,因为标记0、标记1和标记2具有相同的UserID值。注意,这个排除前提条件确保颗粒0和下一个颗粒1完全由U1 UserID值组成,这样ClickHouse就可以假设颗粒0中的最大URL值也小于W3并排除该颗粒。 + +2. 如果索引标记1的URL值小于(或等于)W3,并且后续索引标记的URL值大于(或等于)W3,则选择索引标记1,因为这意味着粒度1可能包含URL为W3的行)。 + +3. 可以排除URL值大于W3的索引标记2和3,因为主索引的索引标记存储了每个颗粒的最小键列值,因此颗粒2和3不可能包含URL值W3。 + + + +**前缀主键高基数** + +当UserID具有较高的基数时,相同的UserID值不太可能分布在多个表行和颗粒上。这意味着索引标记的URL值不是单调递增的: + + + + +正如在上面的图表中所看到的,所有URL值小于W3的标记都被选中,以便将其关联的颗粒的行加载到ClickHouse引擎中。 + +这是因为虽然图中的所有索引标记都属于上面描述的场景1,但它们不满足前面提到的排除前提条件,即两个直接随后的索引标记都具有与当前标记相同的UserID值,因此不能被排除。 + +例如,考虑索引标记0,其URL值小于W3,并且其直接后续索引标记的URL值也小于W3。这不能排除,因为两个直接随后的索引标记1和2与当前标记0没有相同的UserID值。 + +请注意,随后的两个索引标记需要具有相同的UserID值。这确保了当前和下一个标记的颗粒完全由U1 UserID值组成。如果仅仅是下一个标记具有相同的UserID,那么下一个标记的URL值可能来自具有不同UserID的表行——当您查看上面的图表时,确实是这样的情况,即W2来自U2而不是U1的行。 + +这最终阻止了ClickHouse对颗粒0中的最大URL值进行假设。相反,它必须假设颗粒0可能包含URL值为W3的行,并被迫选择标记0。 + +
+同样的情况也适用于标记1、2和3。 + +

+
+ +:::note 结论 +当查询对联合主键的一部分列(但不是第一个键列)进行过滤时,ClickHouse使用的通用排除搜索算法(而不是二分查找)在前一个键列基数较低时最有效。 +::: + +在我们的示例数据集中,两个键列(UserID、URL)都具有类似的高基数,并且,如前所述,当URL列的前一个键列具有较高基数时,通用排除搜索算法不是很有效。 + +:::note 看下跳数索引 +因为UserID和URL具有较高的基数,[根据URL过滤数据](#query-on-url)不是特别有效,对URL列创建[二级跳数索引](./skipping-indexes.md)同样也不会有太多改善。 + +例如,这两个语句在我们的表的URL列上创建并填充一个minmax跳数索引。 +```sql +ALTER TABLE hits_UserID_URL ADD INDEX url_skipping_index URL TYPE minmax GRANULARITY 4; +ALTER TABLE hits_UserID_URL MATERIALIZE INDEX url_skipping_index; +``` +ClickHouse现在创建了一个额外的索引来存储—每组4个连续的颗粒(注意上面ALTER TABLE语句中的GRANULARITY 4子句)—最小和最大的URL值: + + + +第一个索引条目(上图中的mark 0)存储属于表的前4个颗粒的行的最小和最大URL值。 + +第二个索引条目(mark 1)存储属于表中下一个4个颗粒的行的最小和最大URL值,依此类推。 + +(ClickHouse还为跳数索引创建了一个特殊的标记文件,用于定位与索引标记相关联的颗粒组。) + +由于UserID和URL的基数相似,在执行对URL的查询过滤时,这个二级跳数索引不能帮助排除选择的颗粒。 + +正在寻找的特定URL值('http://public_search')很可能是索引为每组颗粒存储的最小值和最大值之间的值,导致ClickHouse被迫选择这组颗粒(因为它们可能包含匹配查询的行)。 + + + + +::: + + +因此,如果我们想显著提高过滤具有特定URL的行的示例查询的速度,那么我们需要使用针对该查询优化的主索引。 + +此外,如果我们想保持过滤具有特定UserID的行的示例查询的良好性能,那么我们需要使用多个主索引。 + +下面是实现这一目标的方法。 + + + +## 使用多个主键索引进行调优 + + +如果我们想显著加快我们的两个示例查询——一个过滤具有特定UserID的行,一个过滤具有特定URL的行——那么我们需要使用多个主索引,通过使用这三个方法中的一个: + +- 新建一个不同主键的新表。 +- 创建一个雾化视图。 +- 增加projection。 + +这三个方法都会有效地将示例数据复制到另一个表中,以便重新组织表的主索引和行排序顺序。 + +然而,这三个选项的不同之处在于,附加表对于查询和插入语句的路由对用户的透明程度。 + +当创建有不同主键的第二个表时,查询必须显式地发送给最适合查询的表版本,并且必须显式地插入新数据到两个表中,以保持表的同步: + + + + +在物化视图中,额外的表被隐藏,数据自动在两个表之间保持同步: + + + +projection方式是最透明的选项,因为除了自动保持隐藏的附加表与数据变化同步外,ClickHouse还会自动选择最有效的表版本进行查询: + + +下面我们使用真实的例子详细讨论下这三种方式。 + + + +## 通过辅助表使用联合主键索引 + + +我们创建一个新的附加表,其中我们在主键中切换键列的顺序(与原始表相比): + +```sql +CREATE TABLE hits_URL_UserID +( + `UserID` UInt32, + `URL` String, + `EventTime` DateTime +) +ENGINE = MergeTree +// highlight-next-line +PRIMARY KEY (URL, UserID) +ORDER BY (URL, UserID, EventTime) +SETTINGS index_granularity = 8192, index_granularity_bytes = 0; +``` + +写入887万行源表数据: + +```sql +INSERT INTO hits_URL_UserID +SELECT * from hits_UserID_URL; +``` + +结果: + +```response +Ok. + +0 rows in set. Elapsed: 2.898 sec. Processed 8.87 million rows, 838.84 MB (3.06 million rows/s., 289.46 MB/s.) +``` + +最后optimize下: +```sql +OPTIMIZE TABLE hits_URL_UserID FINAL; +``` + +因为我们切换了主键中列的顺序,插入的行现在以不同的字典顺序存储在磁盘上(与我们的原始表相比),因此该表的1083个颗粒也包含了与以前不同的值: + + + +主键索引如下: + + +现在计算最频繁点击URL"http://public_search"的前10名用户,这时候的查询速度是明显加快的: +```sql +SELECT UserID, count(UserID) AS Count +// highlight-next-line +FROM hits_URL_UserID +WHERE URL = 'http://public_search' +GROUP BY UserID +ORDER BY Count DESC +LIMIT 10; +``` + +结果: + + +```response +┌─────UserID─┬─Count─┐ +│ 2459550954 │ 3741 │ +│ 1084649151 │ 2484 │ +│ 723361875 │ 729 │ +│ 3087145896 │ 695 │ +│ 2754931092 │ 672 │ +│ 1509037307 │ 582 │ +│ 3085460200 │ 573 │ +│ 2454360090 │ 556 │ +│ 3884990840 │ 539 │ +│ 765730816 │ 536 │ +└────────────┴───────┘ + +10 rows in set. Elapsed: 0.017 sec. +// highlight-next-line +Processed 319.49 thousand rows, +11.38 MB (18.41 million rows/s., 655.75 MB/s.) +``` + +现在没有全表扫描了,ClickHouse执行高效了很多。 + +对于原始表中的主索引(其中UserID是第一个键列,URL是第二个键列),ClickHouse在索引标记上使用了通用排除搜索来执行该查询,但这不是很有效,因为UserID和URL的基数同样很高。 + +将URL作为主索引的第一列,ClickHouse现在对索引标记运行二分搜索。ClickHouse服务器日志文件中对应的跟踪日志: + +```response +...Executor): Key condition: (column 0 in ['http://public_search', + 'http://public_search']) +// highlight-next-line +...Executor): Running binary search on index range for part all_1_9_2 (1083 marks) +...Executor): Found (LEFT) boundary mark: 644 +...Executor): Found (RIGHT) boundary mark: 683 +...Executor): Found continuous range in 19 steps +...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, +// highlight-next-line + 39/1083 marks by primary key, 39 marks to read from 1 ranges +...Executor): Reading approx. 319488 rows with 2 streams +``` +ClickHouse只选择了39个索引标记,而不是使用通用排除搜索时的1076个。 + +请注意,辅助表经过了优化,以加快对url的示例查询过滤的执行。 + +像之前我们查询过滤URL一样,如果我们现在对辅助表查询过滤UserID,性能同样会比较差,因为现在UserID是第二主索引键列,所以ClickHouse将使用通用排除搜索算法查找颗粒,这对于类似高基数的UserID和URL来说不是很有效。 + +点击下面了解详情: +
+ + 对UserID的查询过滤性能较差 + +

+ +```sql +SELECT URL, count(URL) AS Count +FROM hits_URL_UserID +WHERE UserID = 749927693 +GROUP BY URL +ORDER BY Count DESC +LIMIT 10; +``` + +结果 + +```response +┌─URL────────────────────────────┬─Count─┐ +│ http://auto.ru/chatay-barana.. │ 170 │ +│ http://auto.ru/chatay-id=371...│ 52 │ +│ http://public_search │ 45 │ +│ http://kovrik-medvedevushku-...│ 36 │ +│ http://forumal │ 33 │ +│ http://korablitz.ru/L_1OFFER...│ 14 │ +│ http://auto.ru/chatay-id=371...│ 14 │ +│ http://auto.ru/chatay-john-D...│ 13 │ +│ http://auto.ru/chatay-john-D...│ 10 │ +│ http://wot/html?page/23600_m...│ 9 │ +└────────────────────────────────┴───────┘ + +10 rows in set. Elapsed: 0.024 sec. +// highlight-next-line +Processed 8.02 million rows, +73.04 MB (340.26 million rows/s., 3.10 GB/s.) +``` + +服务端日志: +```response +...Executor): Key condition: (column 1 in [749927693, 749927693]) +// highlight-next-line +...Executor): Used generic exclusion search over index for part all_1_9_2 + with 1453 steps +...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, +// highlight-next-line + 980/1083 marks by primary key, 980 marks to read from 23 ranges +...Executor): Reading approx. 8028160 rows with 10 streams +``` +

+
+ + + +现在我们有了两张表。优化了对UserID和URL的查询过滤,分别: + + + + + + + + + + +## 通过物化视图使用联合主键 + +在原表上创建物化视图: +```sql +CREATE MATERIALIZED VIEW mv_hits_URL_UserID +ENGINE = MergeTree() +PRIMARY KEY (URL, UserID) +ORDER BY (URL, UserID, EventTime) +POPULATE +AS SELECT * FROM hits_UserID_URL; +``` + +结果: + +```response +Ok. + +0 rows in set. Elapsed: 2.935 sec. Processed 8.87 million rows, 838.84 MB (3.02 million rows/s., 285.84 MB/s.) +``` + +:::note +- 我们在视图的主键中切换键列的顺序(与原始表相比) +- 雾化视图由一个隐藏表支持,该表的行顺序和主索引基于给定的主键定义 +- 我们使用POPULATE关键字,以便用源表hits_UserID_URL中的所有887万行立即导入新的物化视图 +- 如果在源表hits_UserID_URL中插入了新行,那么这些行也会自动插入到隐藏表中 +- 实际上,隐式创建的隐藏表的行顺序和主索引与我们上面显式创建的辅助表相同: + + + + + + + +ClickHouse将隐藏表的列数据文件(.bin)、标记文件(.mrk2)和主索引(primary.idx)存储在ClickHouse服务器的数据目录的一个特殊文件夹中: + + + + +::: + + +物化视图背后的隐藏表(和它的主索引)现在可以用来显著加快我们在URL列上查询过滤的执行速度: +```sql +SELECT UserID, count(UserID) AS Count +// highlight-next-line +FROM mv_hits_URL_UserID +WHERE URL = 'http://public_search' +GROUP BY UserID +ORDER BY Count DESC +LIMIT 10; +``` + +结果: + +```response +┌─────UserID─┬─Count─┐ +│ 2459550954 │ 3741 │ +│ 1084649151 │ 2484 │ +│ 723361875 │ 729 │ +│ 3087145896 │ 695 │ +│ 2754931092 │ 672 │ +│ 1509037307 │ 582 │ +│ 3085460200 │ 573 │ +│ 2454360090 │ 556 │ +│ 3884990840 │ 539 │ +│ 765730816 │ 536 │ +└────────────┴───────┘ + +10 rows in set. Elapsed: 0.026 sec. +// highlight-next-line +Processed 335.87 thousand rows, +13.54 MB (12.91 million rows/s., 520.38 MB/s.) +``` + +物化视图背后隐藏表(及其主索引)实际上与我们显式创建的辅助表是相同的,所以查询的执行方式与显式创建的表相同。 + +ClickHouse服务器日志文件中相应的跟踪日志确认了ClickHouse正在对索引标记运行二分搜索: + +```response +...Executor): Key condition: (column 0 in ['http://public_search', + 'http://public_search']) +// highlight-next-line +...Executor): Running binary search on index range ... +... +...Executor): Selected 4/4 parts by partition key, 4 parts by primary key, +// highlight-next-line + 41/1083 marks by primary key, 41 marks to read from 4 ranges +...Executor): Reading approx. 335872 rows with 4 streams +``` + + + +## 通过projections使用联合主键索引 + + +Projections目前是一个实验性的功能,因此我们需要告诉ClickHouse: + +```sql +SET allow_experimental_projection_optimization = 1; +``` + + +在原表上创建projection: +```sql +ALTER TABLE hits_UserID_URL + ADD PROJECTION prj_url_userid + ( + SELECT * + ORDER BY (URL, UserID) + ); +``` + +雾化projection: +```sql +ALTER TABLE hits_UserID_URL + MATERIALIZE PROJECTION prj_url_userid; +``` + +:::note +- 该projection正在创建一个隐藏表,该表的行顺序和主索引基于该projection的给定order BY子句 +- 我们使用MATERIALIZE关键字,以便立即用源表hits_UserID_URL的所有887万行导入隐藏表 +- 如果在源表hits_UserID_URL中插入了新行,那么这些行也会自动插入到隐藏表中 +- 查询总是(从语法上)针对源表hits_UserID_URL,但是如果隐藏表的行顺序和主索引允许更有效地执行查询,那么将使用该隐藏表 +- 实际上,隐式创建的隐藏表的行顺序和主索引与我们显式创建的辅助表相同: + + + +ClickHouse将隐藏表的列数据文件(.bin)、标记文件(.mrk2)和主索引(primary.idx)存储在一个特殊的文件夹中(在下面的截图中用橙色标记),紧挨着源表的数据文件、标记文件和主索引文件: + + +::: + +由投影创建的隐藏表(以及它的主索引)现在可以(隐式地)用于显著加快URL列上查询过滤的执行。注意,查询在语法上针对投影的源表。 + +```sql +SELECT UserID, count(UserID) AS Count +// highlight-next-line +FROM hits_UserID_URL +WHERE URL = 'http://public_search' +GROUP BY UserID +ORDER BY Count DESC +LIMIT 10; +``` + +结果: + +```response +┌─────UserID─┬─Count─┐ +│ 2459550954 │ 3741 │ +│ 1084649151 │ 2484 │ +│ 723361875 │ 729 │ +│ 3087145896 │ 695 │ +│ 2754931092 │ 672 │ +│ 1509037307 │ 582 │ +│ 3085460200 │ 573 │ +│ 2454360090 │ 556 │ +│ 3884990840 │ 539 │ +│ 765730816 │ 536 │ +└────────────┴───────┘ + +10 rows in set. Elapsed: 0.029 sec. +// highlight-next-line +Processed 319.49 thousand rows, 1 +1.38 MB (11.05 million rows/s., 393.58 MB/s.) +``` + +因为由投影创建的隐藏表(及其主索引)实际上与我们显式创建的辅助表相同,所以查询的执行方式与显式创建的表相同。 + +ClickHouse服务器日志文件中跟踪日志确认了ClickHouse正在对索引标记运行二分搜索: + + +```response +...Executor): Key condition: (column 0 in ['http://public_search', + 'http://public_search']) +// highlight-next-line +...Executor): Running binary search on index range for part prj_url_userid (1083 marks) +...Executor): ... +// highlight-next-line +...Executor): Choose complete Normal projection prj_url_userid +...Executor): projection required columns: URL, UserID +...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, +// highlight-next-line + 39/1083 marks by primary key, 39 marks to read from 1 ranges +...Executor): Reading approx. 319488 rows with 2 streams +``` + + +## 移除无效的主键列 + + +带有联合主键(UserID, URL)的表的主索引对于加快UserID的查询过滤非常有用。但是,尽管URL列是联合主键的一部分,但该索引在加速URL查询过滤方面并没有提供显著的帮助。 + +反之亦然:具有复合主键(URL, UserID)的表的主索引加快了URL上的查询过滤,但没有为UserID上的查询过滤提供太多支持。 + +由于主键列UserID和URL的基数同样很高,过滤第二个键列的查询不会因为第二个键列位于索引中而受益太多。 + +因此,从主索引中删除第二个键列(从而减少索引的内存消耗)并使用多个主索引是有意义的。 + +但是,如果复合主键中的键列在基数上有很大的差异,那么查询按基数升序对主键列进行排序是有益的。 + +主键键列之间的基数差越大,主键键列的顺序越重要。我们将在以后的文章中对此进行演示。请继续关注。 From 2e34b384c14f5bb3eeb0f19490bdc90048c68fe4 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Wed, 3 Aug 2022 15:44:08 -0400 Subject: [PATCH 303/672] update tcp protocol, add quota_key --- programs/benchmark/Benchmark.cpp | 11 +++++++++-- programs/copier/ClusterCopier.cpp | 2 +- src/Client/Connection.cpp | 13 ++++++++++++- src/Client/Connection.h | 3 +++ src/Client/ConnectionParameters.cpp | 1 + src/Client/ConnectionParameters.h | 1 + src/Client/ConnectionPool.cpp | 4 +++- src/Client/ConnectionPool.h | 7 ++++++- src/Core/ProtocolDefines.h | 4 +++- src/Dictionaries/ClickHouseDictionarySource.cpp | 6 +++++- src/Dictionaries/ClickHouseDictionarySource.h | 1 + src/Interpreters/Cluster.cpp | 7 ++++--- src/Interpreters/Cluster.h | 1 + src/Server/TCPHandler.cpp | 9 +++++++++ src/Server/TCPHandler.h | 2 ++ src/Storages/Distributed/DirectoryMonitor.cpp | 1 + src/Storages/ExternalDataSourceConfiguration.cpp | 9 ++++++++- src/Storages/ExternalDataSourceConfiguration.h | 1 + src/Storages/HDFS/StorageHDFSCluster.cpp | 2 +- src/Storages/StorageS3Cluster.cpp | 2 +- 20 files changed, 73 insertions(+), 14 deletions(-) diff --git a/programs/benchmark/Benchmark.cpp b/programs/benchmark/Benchmark.cpp index 5b77883c7b0..5d82386e2c8 100644 --- a/programs/benchmark/Benchmark.cpp +++ b/programs/benchmark/Benchmark.cpp @@ -61,7 +61,7 @@ public: Benchmark(unsigned concurrency_, double delay_, Strings && hosts_, Ports && ports_, bool round_robin_, bool cumulative_, bool secure_, const String & default_database_, - const String & user_, const String & password_, const String & stage, + const String & user_, const String & password_, const String & quota_key_, const String & stage, bool randomize_, size_t max_iterations_, double max_time_, const String & json_path_, size_t confidence_, const String & query_id_, const String & query_to_execute_, bool continue_on_errors_, @@ -90,7 +90,7 @@ public: connections.emplace_back(std::make_unique( concurrency, cur_host, cur_port, - default_database_, user_, password_, + default_database_, user_, password_, quota_key_, /* cluster_= */ "", /* cluster_secret_= */ "", /* client_name_= */ "benchmark", @@ -607,6 +607,7 @@ int mainEntryClickHouseBenchmark(int argc, char ** argv) /// So we copy the results to std::string. std::optional env_user_str; std::optional env_password_str; + std::optional env_quota_key_str; const char * env_user = getenv("CLICKHOUSE_USER"); if (env_user != nullptr) @@ -616,6 +617,10 @@ int mainEntryClickHouseBenchmark(int argc, char ** argv) if (env_password != nullptr) env_password_str.emplace(std::string(env_password)); + const char * env_quota_key = getenv("CLICKHOUSE_QUOTA_KEY"); + if (env_quota_key != nullptr) + env_quota_key_str.emplace(std::string(env_quota_key)); + boost::program_options::options_description desc = createOptionsDescription("Allowed options", getTerminalWidth()); desc.add_options() ("help", "produce help message") @@ -634,6 +639,7 @@ int mainEntryClickHouseBenchmark(int argc, char ** argv) ("secure,s", "Use TLS connection") ("user,u", value()->default_value(env_user_str.value_or("default")), "") ("password", value()->default_value(env_password_str.value_or("")), "") + ("quota_key", value()->default_value(env_quota_key_str.value_or("")), "") ("database", value()->default_value("default"), "") ("stacktrace", "print stack traces of exceptions") ("confidence", value()->default_value(5), "set the level of confidence for T-test [0=80%, 1=90%, 2=95%, 3=98%, 4=99%, 5=99.5%(default)") @@ -682,6 +688,7 @@ int mainEntryClickHouseBenchmark(int argc, char ** argv) options["database"].as(), options["user"].as(), options["password"].as(), + options["quota_key"].as(), options["stage"].as(), options["randomize"].as(), options["iterations"].as(), diff --git a/programs/copier/ClusterCopier.cpp b/programs/copier/ClusterCopier.cpp index f3609902fcb..2e7d81617fa 100644 --- a/programs/copier/ClusterCopier.cpp +++ b/programs/copier/ClusterCopier.cpp @@ -2010,7 +2010,7 @@ UInt64 ClusterCopier::executeQueryOnCluster( { connections.emplace_back(std::make_shared( node.host_name, node.port, node.default_database, - node.user, node.password, node.cluster, node.cluster_secret, + node.user, node.password, node.quota_key, node.cluster, node.cluster_secret, "ClusterCopier", node.compression, node.secure )); diff --git a/src/Client/Connection.cpp b/src/Client/Connection.cpp index df37d1c98a4..602f2d812c4 100644 --- a/src/Client/Connection.cpp +++ b/src/Client/Connection.cpp @@ -64,6 +64,7 @@ Connection::~Connection() = default; Connection::Connection(const String & host_, UInt16 port_, const String & default_database_, const String & user_, const String & password_, + const String & quota_key_, const String & cluster_, const String & cluster_secret_, const String & client_name_, @@ -71,7 +72,7 @@ Connection::Connection(const String & host_, UInt16 port_, Protocol::Secure secure_, Poco::Timespan sync_request_timeout_) : host(host_), port(port_), default_database(default_database_) - , user(user_), password(password_) + , user(user_), password(password_), quota_key(quota_key_) , cluster(cluster_) , cluster_secret(cluster_secret_) , client_name(client_name_) @@ -169,6 +170,8 @@ void Connection::connect(const ConnectionTimeouts & timeouts) sendHello(); receiveHello(); + if (server_revision >= DBMS_MIN_PROTOCOL_VERSION_WITH_QUOTA_KEY) + sendAddendum(); LOG_TRACE(log_wrapper.get(), "Connected to {} server version {}.{}.{}.", server_name, server_version_major, server_version_minor, server_version_patch); @@ -266,6 +269,13 @@ void Connection::sendHello() } +void Connection::sendAddendum() +{ + writeStringBinary(quota_key, *out); + out->next(); +} + + void Connection::receiveHello() { /// Receive hello packet. @@ -1083,6 +1093,7 @@ ServerConnectionPtr Connection::createConnection(const ConnectionParameters & pa parameters.default_database, parameters.user, parameters.password, + parameters.quota_key, "", /* cluster */ "", /* cluster_secret */ "client", diff --git a/src/Client/Connection.h b/src/Client/Connection.h index d00a5760a8d..c712fd730dd 100644 --- a/src/Client/Connection.h +++ b/src/Client/Connection.h @@ -51,6 +51,7 @@ public: Connection(const String & host_, UInt16 port_, const String & default_database_, const String & user_, const String & password_, + const String & quota_key_, const String & cluster_, const String & cluster_secret_, const String & client_name_, @@ -159,6 +160,7 @@ private: String default_database; String user; String password; + String quota_key; /// For inter-server authorization String cluster; @@ -245,6 +247,7 @@ private: void connect(const ConnectionTimeouts & timeouts); void sendHello(); + void sendAddendum(); void receiveHello(); #if USE_SSL diff --git a/src/Client/ConnectionParameters.cpp b/src/Client/ConnectionParameters.cpp index 3101176101b..f6720405eb0 100644 --- a/src/Client/ConnectionParameters.cpp +++ b/src/Client/ConnectionParameters.cpp @@ -58,6 +58,7 @@ ConnectionParameters::ConnectionParameters(const Poco::Util::AbstractConfigurati if (auto * result = readpassphrase(prompt.c_str(), buf, sizeof(buf), 0)) password = result; } + quota_key = config.getString("quota_key", ""); /// By default compression is disabled if address looks like localhost. compression = config.getBool("compression", !isLocalAddress(DNSResolver::instance().resolveHost(host))) diff --git a/src/Client/ConnectionParameters.h b/src/Client/ConnectionParameters.h index ae204296fbf..0ccd6b92290 100644 --- a/src/Client/ConnectionParameters.h +++ b/src/Client/ConnectionParameters.h @@ -18,6 +18,7 @@ struct ConnectionParameters std::string default_database; std::string user; std::string password; + std::string quota_key; Protocol::Secure security = Protocol::Secure::Disable; Protocol::Compression compression = Protocol::Compression::Enable; ConnectionTimeouts timeouts; diff --git a/src/Client/ConnectionPool.cpp b/src/Client/ConnectionPool.cpp index 4ec87127318..8433b0833fa 100644 --- a/src/Client/ConnectionPool.cpp +++ b/src/Client/ConnectionPool.cpp @@ -12,6 +12,7 @@ ConnectionPoolPtr ConnectionPoolFactory::get( String default_database, String user, String password, + String quota_key, String cluster, String cluster_secret, String client_name, @@ -20,7 +21,7 @@ ConnectionPoolPtr ConnectionPoolFactory::get( Int64 priority) { Key key{ - max_connections, host, port, default_database, user, password, cluster, cluster_secret, client_name, compression, secure, priority}; + max_connections, host, port, default_database, user, password, quota_key, cluster, cluster_secret, client_name, compression, secure, priority}; std::lock_guard lock(mutex); auto [it, inserted] = pools.emplace(key, ConnectionPoolPtr{}); @@ -37,6 +38,7 @@ ConnectionPoolPtr ConnectionPoolFactory::get( default_database, user, password, + quota_key, cluster, cluster_secret, client_name, diff --git a/src/Client/ConnectionPool.h b/src/Client/ConnectionPool.h index d1ee844358b..c3d0955019e 100644 --- a/src/Client/ConnectionPool.h +++ b/src/Client/ConnectionPool.h @@ -54,6 +54,7 @@ public: const String & default_database_, const String & user_, const String & password_, + const String & quota_key_, const String & cluster_, const String & cluster_secret_, const String & client_name_, @@ -67,6 +68,7 @@ public: default_database(default_database_), user(user_), password(password_), + quota_key(quota_key_), cluster(cluster_), cluster_secret(cluster_secret_), client_name(client_name_), @@ -112,7 +114,7 @@ protected: { return std::make_shared( host, port, - default_database, user, password, + default_database, user, password, quota_key, cluster, cluster_secret, client_name, compression, secure); } @@ -123,6 +125,7 @@ private: String default_database; String user; String password; + String quota_key; /// For inter-server authorization String cluster; @@ -149,6 +152,7 @@ public: String default_database; String user; String password; + String quota_key; String cluster; String cluster_secret; String client_name; @@ -171,6 +175,7 @@ public: String default_database, String user, String password, + String quota_key, String cluster, String cluster_secret, String client_name, diff --git a/src/Core/ProtocolDefines.h b/src/Core/ProtocolDefines.h index 584720694d7..5794cf5f6d6 100644 --- a/src/Core/ProtocolDefines.h +++ b/src/Core/ProtocolDefines.h @@ -52,10 +52,12 @@ /// NOTE: DBMS_TCP_PROTOCOL_VERSION has nothing common with VERSION_REVISION, /// later is just a number for server version (one number instead of commit SHA) /// for simplicity (sometimes it may be more convenient in some use cases). -#define DBMS_TCP_PROTOCOL_VERSION 54457 +#define DBMS_TCP_PROTOCOL_VERSION 54458 #define DBMS_MIN_PROTOCOL_VERSION_WITH_INITIAL_QUERY_START_TIME 54449 #define DBMS_MIN_PROTOCOL_VERSION_WITH_PROFILE_EVENTS_IN_INSERT 54456 #define DBMS_MIN_PROTOCOL_VERSION_WITH_VIEW_IF_PERMITTED 54457 + +#define DBMS_MIN_PROTOCOL_VERSION_WITH_QUOTA_KEY 54458 diff --git a/src/Dictionaries/ClickHouseDictionarySource.cpp b/src/Dictionaries/ClickHouseDictionarySource.cpp index 1a3f3f0edc4..b88fa413cf8 100644 --- a/src/Dictionaries/ClickHouseDictionarySource.cpp +++ b/src/Dictionaries/ClickHouseDictionarySource.cpp @@ -29,7 +29,7 @@ namespace ErrorCodes } static const std::unordered_set dictionary_allowed_keys = { - "host", "port", "user", "password", "db", "database", "table", + "host", "port", "user", "password", "quota_key", "db", "database", "table", "update_field", "update_lag", "invalidate_query", "query", "where", "name", "secure"}; namespace @@ -54,6 +54,7 @@ namespace configuration.db, configuration.user, configuration.password, + configuration.quota_key, "", /* cluster */ "", /* cluster_secret */ "ClickHouseDictionarySource", @@ -237,6 +238,7 @@ void registerDictionarySourceClickHouse(DictionarySourceFactory & factory) std::string host = config.getString(settings_config_prefix + ".host", "localhost"); std::string user = config.getString(settings_config_prefix + ".user", "default"); std::string password = config.getString(settings_config_prefix + ".password", ""); + std::string quota_key = config.getString(settings_config_prefix + ".quota_key", ""); std::string db = config.getString(settings_config_prefix + ".db", default_database); std::string table = config.getString(settings_config_prefix + ".table", ""); UInt16 port = static_cast(config.getUInt(settings_config_prefix + ".port", default_port)); @@ -252,6 +254,7 @@ void registerDictionarySourceClickHouse(DictionarySourceFactory & factory) host = configuration.host; user = configuration.username; password = configuration.password; + quota_key = configuration.quota_key; db = configuration.database; table = configuration.table; port = configuration.port; @@ -261,6 +264,7 @@ void registerDictionarySourceClickHouse(DictionarySourceFactory & factory) .host = host, .user = user, .password = password, + .quota_key = quota_key, .db = db, .table = table, .query = config.getString(settings_config_prefix + ".query", ""), diff --git a/src/Dictionaries/ClickHouseDictionarySource.h b/src/Dictionaries/ClickHouseDictionarySource.h index 007e3e8b29d..7bf6b805c03 100644 --- a/src/Dictionaries/ClickHouseDictionarySource.h +++ b/src/Dictionaries/ClickHouseDictionarySource.h @@ -23,6 +23,7 @@ public: const std::string host; const std::string user; const std::string password; + const std::string quota_key; const std::string db; const std::string table; const std::string query; diff --git a/src/Interpreters/Cluster.cpp b/src/Interpreters/Cluster.cpp index ddf3b46f102..6877c0ece06 100644 --- a/src/Interpreters/Cluster.cpp +++ b/src/Interpreters/Cluster.cpp @@ -425,7 +425,7 @@ Cluster::Cluster(const Poco::Util::AbstractConfiguration & config, auto pool = ConnectionPoolFactory::instance().get( settings.distributed_connections_pool_size, address.host_name, address.port, - address.default_database, address.user, address.password, + address.default_database, address.user, address.password, address.quota_key, address.cluster, address.cluster_secret, "server", address.compression, address.secure, address.priority); @@ -499,7 +499,7 @@ Cluster::Cluster(const Poco::Util::AbstractConfiguration & config, auto replica_pool = ConnectionPoolFactory::instance().get( settings.distributed_connections_pool_size, replica.host_name, replica.port, - replica.default_database, replica.user, replica.password, + replica.default_database, replica.user, replica.password, replica.quota_key, replica.cluster, replica.cluster_secret, "server", replica.compression, replica.secure, replica.priority); @@ -587,7 +587,7 @@ Cluster::Cluster( auto replica_pool = ConnectionPoolFactory::instance().get( settings.distributed_connections_pool_size, replica.host_name, replica.port, - replica.default_database, replica.user, replica.password, + replica.default_database, replica.user, replica.password, replica.quota_key, replica.cluster, replica.cluster_secret, "server", replica.compression, replica.secure, replica.priority); all_replicas.emplace_back(replica_pool); @@ -699,6 +699,7 @@ Cluster::Cluster(Cluster::ReplicasAsShardsTag, const Cluster & from, const Setti address.default_database, address.user, address.password, + address.quota_key, address.cluster, address.cluster_secret, "server", diff --git a/src/Interpreters/Cluster.h b/src/Interpreters/Cluster.h index 72958703d0e..ada04aa1cae 100644 --- a/src/Interpreters/Cluster.h +++ b/src/Interpreters/Cluster.h @@ -93,6 +93,7 @@ public: UInt16 port{0}; String user; String password; + String quota_key; /// For inter-server authorization String cluster; diff --git a/src/Server/TCPHandler.cpp b/src/Server/TCPHandler.cpp index 05565063893..0076ee5eb0c 100644 --- a/src/Server/TCPHandler.cpp +++ b/src/Server/TCPHandler.cpp @@ -134,6 +134,8 @@ void TCPHandler::runImpl() { receiveHello(); sendHello(); + if (client_tcp_protocol_version >= DBMS_MIN_PROTOCOL_VERSION_WITH_QUOTA_KEY) + receiveAddendum(); if (!is_interserver_mode) /// In interserver mode queries are executed without a session context. { @@ -1019,6 +1021,7 @@ std::unique_ptr TCPHandler::makeSession() client_info.connection_client_version_patch = client_version_patch; client_info.connection_tcp_protocol_version = client_tcp_protocol_version; + client_info.quota_key = quota_key; client_info.interface = interface; return res; @@ -1077,6 +1080,12 @@ void TCPHandler::receiveHello() session->authenticate(user, password, socket().peerAddress()); } +void TCPHandler::receiveAddendum() +{ + readStringBinary(quota_key, *in); + session->getClientInfo().quota_key = quota_key; +} + void TCPHandler::receiveUnexpectedHello() { diff --git a/src/Server/TCPHandler.h b/src/Server/TCPHandler.h index bea00c815c8..cee3cf448d6 100644 --- a/src/Server/TCPHandler.h +++ b/src/Server/TCPHandler.h @@ -155,6 +155,7 @@ private: UInt64 client_version_minor = 0; UInt64 client_version_patch = 0; UInt64 client_tcp_protocol_version = 0; + String quota_key; /// Connection settings, which are extracted from a context. bool send_exception_with_stack_trace = true; @@ -211,6 +212,7 @@ private: bool receiveProxyHeader(); void receiveHello(); + void receiveAddendum(); bool receivePacket(); void receiveQuery(); void receiveIgnoredPartUUIDs(); diff --git a/src/Storages/Distributed/DirectoryMonitor.cpp b/src/Storages/Distributed/DirectoryMonitor.cpp index 21b4e20a26f..ff5a38fcc52 100644 --- a/src/Storages/Distributed/DirectoryMonitor.cpp +++ b/src/Storages/Distributed/DirectoryMonitor.cpp @@ -538,6 +538,7 @@ ConnectionPoolPtr StorageDistributedDirectoryMonitor::createPool(const std::stri address.default_database, address.user, address.password, + address.quota_key, address.cluster, address.cluster_secret, storage.getName() + '_' + address.user, /* client */ diff --git a/src/Storages/ExternalDataSourceConfiguration.cpp b/src/Storages/ExternalDataSourceConfiguration.cpp index 0d6beb1733b..5710aa6cd6a 100644 --- a/src/Storages/ExternalDataSourceConfiguration.cpp +++ b/src/Storages/ExternalDataSourceConfiguration.cpp @@ -35,7 +35,7 @@ namespace ErrorCodes IMPLEMENT_SETTINGS_TRAITS(EmptySettingsTraits, EMPTY_SETTINGS) static const std::unordered_set dictionary_allowed_keys = { - "host", "port", "user", "password", "db", + "host", "port", "user", "password", "quota_key", "db", "database", "table", "schema", "replica", "update_field", "update_lag", "invalidate_query", "query", "where", "name", "secure", "uri", "collection"}; @@ -84,6 +84,7 @@ void ExternalDataSourceConfiguration::set(const ExternalDataSourceConfiguration port = conf.port; username = conf.username; password = conf.password; + quota_key = conf.quota_key; database = conf.database; table = conf.table; schema = conf.schema; @@ -123,6 +124,7 @@ std::optional getExternalDataSourceConfiguration( configuration.port = config.getInt(collection_prefix + ".port", 0); configuration.username = config.getString(collection_prefix + ".user", ""); configuration.password = config.getString(collection_prefix + ".password", ""); + configuration.quota_key = config.getString(collection_prefix + ".quota_key", ""); configuration.database = config.getString(collection_prefix + ".database", ""); configuration.table = config.getString(collection_prefix + ".table", config.getString(collection_prefix + ".collection", "")); configuration.schema = config.getString(collection_prefix + ".schema", ""); @@ -169,6 +171,8 @@ std::optional getExternalDataSourceConfiguration( configuration.username = arg_value.safeGet(); else if (arg_name == "password") configuration.password = arg_value.safeGet(); + else if (arg_name == "quota_key") + configuration.quota_key = arg_value.safeGet(); else if (arg_name == "database") configuration.database = arg_value.safeGet(); else if (arg_name == "table") @@ -236,6 +240,7 @@ std::optional getExternalDataSourceConfiguration( configuration.port = dict_config.getInt(dict_config_prefix + ".port", config.getUInt(collection_prefix + ".port", 0)); configuration.username = dict_config.getString(dict_config_prefix + ".user", config.getString(collection_prefix + ".user", "")); configuration.password = dict_config.getString(dict_config_prefix + ".password", config.getString(collection_prefix + ".password", "")); + configuration.quota_key = dict_config.getString(dict_config_prefix + ".quota_key", config.getString(collection_prefix + ".quota_key", "")); configuration.database = dict_config.getString(dict_config_prefix + ".db", config.getString(dict_config_prefix + ".database", config.getString(collection_prefix + ".db", config.getString(collection_prefix + ".database", "")))); configuration.table = dict_config.getString(dict_config_prefix + ".table", config.getString(collection_prefix + ".table", "")); @@ -328,6 +333,7 @@ ExternalDataSourcesByPriority getExternalDataSourceConfigurationByPriority( common_configuration.port = dict_config.getUInt(dict_config_prefix + ".port", 0); common_configuration.username = dict_config.getString(dict_config_prefix + ".user", ""); common_configuration.password = dict_config.getString(dict_config_prefix + ".password", ""); + common_configuration.quota_key = dict_config.getString(dict_config_prefix + ".quota_key", ""); common_configuration.database = dict_config.getString(dict_config_prefix + ".db", dict_config.getString(dict_config_prefix + ".database", "")); common_configuration.table = dict_config.getString(fmt::format("{}.table", dict_config_prefix), ""); common_configuration.schema = dict_config.getString(fmt::format("{}.schema", dict_config_prefix), ""); @@ -359,6 +365,7 @@ ExternalDataSourcesByPriority getExternalDataSourceConfigurationByPriority( replica_configuration.port = dict_config.getUInt(replica_name + ".port", common_configuration.port); replica_configuration.username = dict_config.getString(replica_name + ".user", common_configuration.username); replica_configuration.password = dict_config.getString(replica_name + ".password", common_configuration.password); + replica_configuration.quota_key = dict_config.getString(replica_name + ".quota_key", common_configuration.quota_key); if (replica_configuration.host.empty() || replica_configuration.port == 0 || replica_configuration.username.empty() || replica_configuration.password.empty()) diff --git a/src/Storages/ExternalDataSourceConfiguration.h b/src/Storages/ExternalDataSourceConfiguration.h index 4ed46e1b26c..719fceb7df1 100644 --- a/src/Storages/ExternalDataSourceConfiguration.h +++ b/src/Storages/ExternalDataSourceConfiguration.h @@ -19,6 +19,7 @@ struct ExternalDataSourceConfiguration UInt16 port = 0; String username = "default"; String password; + String quota_key; String database; String table; String schema; diff --git a/src/Storages/HDFS/StorageHDFSCluster.cpp b/src/Storages/HDFS/StorageHDFSCluster.cpp index 08dabfccf55..47a6fbf5eaa 100644 --- a/src/Storages/HDFS/StorageHDFSCluster.cpp +++ b/src/Storages/HDFS/StorageHDFSCluster.cpp @@ -99,7 +99,7 @@ Pipe StorageHDFSCluster::read( { auto connection = std::make_shared( node.host_name, node.port, context->getGlobalContext()->getCurrentDatabase(), - node.user, node.password, node.cluster, node.cluster_secret, + node.user, node.password, node.quota_key, node.cluster, node.cluster_secret, "HDFSClusterInititiator", node.compression, node.secure diff --git a/src/Storages/StorageS3Cluster.cpp b/src/Storages/StorageS3Cluster.cpp index 297f806d086..a3f368effa7 100644 --- a/src/Storages/StorageS3Cluster.cpp +++ b/src/Storages/StorageS3Cluster.cpp @@ -129,7 +129,7 @@ Pipe StorageS3Cluster::read( { auto connection = std::make_shared( node.host_name, node.port, context->getGlobalContext()->getCurrentDatabase(), - node.user, node.password, node.cluster, node.cluster_secret, + node.user, node.password, node.quota_key, node.cluster, node.cluster_secret, "S3ClusterInititiator", node.compression, node.secure From d0c3de9da9e5df877d9034fbe9fd873d9ead4d74 Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Wed, 3 Aug 2022 15:55:12 -0400 Subject: [PATCH 304/672] wrong dir --- .../_category_.yml | 8 + .../skipping-indexes.md | 167 +++ .../sparse-primary-indexes.md | 1172 +++++++++++++++++ 3 files changed, 1347 insertions(+) create mode 100644 docs/zh/guides/improving-query-performance/_category_.yml create mode 100644 docs/zh/guides/improving-query-performance/skipping-indexes.md create mode 100644 docs/zh/guides/improving-query-performance/sparse-primary-indexes.md diff --git a/docs/zh/guides/improving-query-performance/_category_.yml b/docs/zh/guides/improving-query-performance/_category_.yml new file mode 100644 index 00000000000..62885213bfc --- /dev/null +++ b/docs/zh/guides/improving-query-performance/_category_.yml @@ -0,0 +1,8 @@ +position: 10 +label: '优化查询性能' +collapsible: true +collapsed: true +link: + type: generated-index + title: Improving Query Performance + slug: /zh/guides/improving-query-performance diff --git a/docs/zh/guides/improving-query-performance/skipping-indexes.md b/docs/zh/guides/improving-query-performance/skipping-indexes.md new file mode 100644 index 00000000000..b3cb82bf769 --- /dev/null +++ b/docs/zh/guides/improving-query-performance/skipping-indexes.md @@ -0,0 +1,167 @@ +--- +sidebar_label: Data Skipping Indexes +sidebar_position: 2 +--- + +# 深入理解ClickHouse跳数索引 + +### 跳数索引 + +影响ClickHouse查询性能的因素很多。在大多数场景中,关键因素是ClickHouse在计算查询WHERE子句条件时是否可以使用主键。因此,选择适用于最常见查询模式的主键对于表的设计至关重要。 + +然而,无论如何仔细地调优主键,不可避免地会出现不能有效使用它的查询用例。用户通常依赖于ClickHouse获得时间序列类型的数据,但他们通常希望根据其他业务维度(如客户id、网站URL或产品编号)分析同一批数据。在这种情况下,查询性能可能会相当差,因为应用WHERE子句条件可能需要对每个列值进行完整扫描。虽然ClickHouse在这些情况下仍然相对较快,但计算数百万或数十亿个单独的值将导致“非索引”查询的执行速度比基于主键的查询慢得多。 + +在传统的关系数据库中,解决这个问题的一种方法是将一个或多个“二级”索引附加到表上。这是一个b-树结构,允许数据库在O(log(n))时间内找到磁盘上所有匹配的行,而不是O(n)时间(一次表扫描),其中n是行数。但是,这种类型的二级索引不适用于ClickHouse(或其他面向列的数据库),因为磁盘上没有单独的行可以添加到索引中。 + +相反,ClickHouse提供了一种不同类型的索引,在特定情况下可以显著提高查询速度。这些结构被标记为跳数索引,因为它们使ClickHouse能够跳过保证没有匹配值的数据块。 +### 基本操作 + +用户只能在MergeTree表引擎上使用数据跳数索引。每个跳数索引都有四个主要参数: + +- 索引名称。索引名用于在每个分区中创建索引文件。此外,在删除或具体化索引时需要将其作为参数。 +- 索引的表达式。索引表达式用于计算存储在索引中的值集。它可以是列、简单操作符、函数的子集的组合。 +- 类型。索引的类型控制计算,该计算决定是否可以跳过读取和计算每个索引块。 +- GRANULARITY。每个索引块由颗粒(granule)组成。例如,如果主表索引粒度为8192行,GRANULARITY为4,则每个索引“块”将为32768行。 + +当用户创建数据跳数索引时,表的每个数据部分目录中将有两个额外的文件。 + +- skp_idx_{index_name}.idx:包含排序的表达式值。 +- skp_idx_{index_name}.mrk2:包含关联数据列文件中的相应偏移量。 + +如果在执行查询并读取相关列文件时,WHERE子句过滤条件的某些部分与跳数索引表达式匹配,ClickHouse将使用索引文件数据来确定每个相关的数据块是必须被处理还是可以被绕过(假设块还没有通过应用主键索引被排除)。这里用一个非常简单的示例:考虑以下加载了可预测数据的表。 + +``` +CREATE TABLE skip_table +( + my_key UInt64, + my_value UInt64 +) +ENGINE MergeTree primary key my_key +SETTINGS index_granularity=8192; + +INSERT INTO skip_table SELECT number, intDiv(number,4096) FROM numbers(100000000); +``` + +当执行一个不使用主键的简单查询时,将扫描my_value列所有的一亿条记录: + +``` +SELECT * FROM skip_table WHERE my_value IN (125, 700) + +┌─my_key─┬─my_value─┐ +│ 512000 │ 125 │ +│ 512001 │ 125 │ +│ ... | ... | +└────────┴──────────┘ + +8192 rows in set. Elapsed: 0.079 sec. Processed 100.00 million rows, 800.10 MB (1.26 billion rows/s., 10.10 GB/s. +``` + +增加一个基本的跳数索引: + +``` +ALTER TABLE skip_table ADD INDEX vix my_value TYPE set(100) GRANULARITY 2; +``` + +通常,跳数索引只应用于新插入的数据,所以仅仅添加索引不会影响上述查询。 + +要使已经存在的数据生效,那执行: + +``` +ALTER TABLE skip_table MATERIALIZE INDEX vix; +``` + +重跑SQL: + +``` +SELECT * FROM skip_table WHERE my_value IN (125, 700) + +┌─my_key─┬─my_value─┐ +│ 512000 │ 125 │ +│ 512001 │ 125 │ +│ ... | ... | +└────────┴──────────┘ + +8192 rows in set. Elapsed: 0.051 sec. Processed 32.77 thousand rows, 360.45 KB (643.75 thousand rows/s., 7.08 MB/s.) +``` + +这次没有再去处理1亿行800MB的数据,ClickHouse只读取和分析32768行360KB的数据—4个granule的数据。 + +下图是更直观的展示,这就是如何读取和选择my_value为125的4096行,以及如何跳过以下行而不从磁盘读取: + +![Simple Skip](../../../en/guides/improving-query-performance/images/simple_skip.svg) + +通过在执行查询时启用跟踪,用户可以看到关于跳数索引使用情况的详细信息。在clickhouse-client中设置send_logs_level: + +``` +SET send_logs_level='trace'; +``` +这将在尝试调优查询SQL和表索引时提供有用的调试信息。上面的例子中,调试日志显示跳数索引过滤了大部分granule,只读取了两个: + +``` + default.skip_table (933d4b2c-8cea-4bf9-8c93-c56e900eefd1) (SelectExecutor): Index `vix` has dropped 6102/6104 granules. +``` +### 跳数索引类型 + +#### minmax + +这种轻量级索引类型不需要参数。它存储每个块的索引表达式的最小值和最大值(如果表达式是一个元组,它分别存储元组元素的每个成员的值)。对于倾向于按值松散排序的列,这种类型非常理想。在查询处理期间,这种索引类型的开销通常是最小的。 + +这种类型的索引只适用于标量或元组表达式——索引永远不适用于返回数组或map数据类型的表达式。 + +#### set + +这种轻量级索引类型接受单个参数max_size,即每个块的值集(0允许无限数量的离散值)。这个集合包含块中的所有值(如果值的数量超过max_size则为空)。这种索引类型适用于每组颗粒中基数较低(本质上是“聚集在一起”)但总体基数较高的列。 + +该索引的成本、性能和有效性取决于块中的基数。如果每个块包含大量惟一值,那么针对大型索引集计算查询条件将非常昂贵,或者由于索引超过max_size而为空,因此索引将不应用。 + +#### Bloom Filter Types + +Bloom filter是一种数据结构,它允许对集合成员进行高效的是否存在测试,但代价是有轻微的误报。在跳数索引的使用场景,假阳性不是一个大问题,因为惟一的问题只是读取一些不必要的块。潜在的假阳性意味着索引表达式应该为真,否则有效的数据可能会被跳过。 + +因为Bloom filter可以更有效地处理大量离散值的测试,所以它们可以适用于大量条件表达式判断的场景。特别的是Bloom filter索引可以应用于数组,数组中的每个值都被测试,也可以应用于map,通过使用mapKeys或mapValues函数将键或值转换为数组。 + +有三种基于Bloom过滤器的数据跳数索引类型: + +* 基本的**bloom_filter**接受一个可选参数,该参数表示在0到1之间允许的“假阳性”率(如果未指定,则使用.025)。 + +* 更专业的**tokenbf_v1**。需要三个参数,用来优化布隆过滤器:(1)过滤器的大小字节(大过滤器有更少的假阳性,有更高的存储成本),(2)哈希函数的个数(更多的散列函数可以减少假阳性)。(3)布隆过滤器哈希函数的种子。有关这些参数如何影响布隆过滤器功能的更多细节,请参阅 [这里](https://hur.st/bloomfilter/) 。此索引仅适用于String、FixedString和Map类型的数据。输入表达式被分割为由非字母数字字符分隔的字符序列。例如,列值`This is a candidate for a "full text" search`将被分割为`This` `is` `a` `candidate` `for` `full` `text` `search`。它用于LIKE、EQUALS、in、hasToken()和类似的长字符串中单词和其他值的搜索。例如,一种可能的用途是在非结构的应用程序日志行列中搜索少量的类名或行号。 + +* 更专业的**ngrambf_v1**。该索引的功能与tokenbf_v1相同。在Bloom filter设置之前需要一个额外的参数,即要索引的ngram的大小。一个ngram是长度为n的任何字符串,比如如果n是4,`A short string`会被分割为`A sh`` sho`, `shor`, `hort`, `ort s`, `or st`, `r str`, ` stri`, `trin`, `ring`。这个索引对于文本搜索也很有用,特别是没有单词间断的语言,比如中文。 + +### 跳数索引函数 + +跳数索引核心目的是限制流行查询分析的数据量。鉴于ClickHouse数据的分析特性,这些查询的模式在大多数情况下都包含函数表达式。因此,跳数索引必须与常用函数正确交互才能提高效率。这种情况可能发生在: +* 插入数据并将索引定义为一个函数表达式(表达式的结果存储在索引文件中)或者 +* 处理查询,并将表达式应用于存储的索引值,以确定是否排除数据块。 + +每种类型的跳数索引支持的函数列表可以查看 [这里](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree/#functions-support) 。通常,集合索引和基于Bloom filter的索引(另一种类型的集合索引)都是无序的,因此不能用于范围。相反,最大最小值索引在范围中工作得特别好,因为确定范围是否相交非常快。部分匹配函数LIKE、startsWith、endsWith和hasToken的有效性取决于使用的索引类型、索引表达式和数据的特定形状。 + +### 跳数索引的配置 + +有两个可用的设置可应用于跳数索引。 + +* **use_skip_indexes** (0或1,默认为1)。不是所有查询都可以有效地使用跳过索引。如果一个特定的过滤条件可能包含很多颗粒,那么应用数据跳过索引将导致不必要的、有时甚至是非常大的成本。对于不太可能从任何跳过索引中获益的查询,将该值设置为0。 +* **force_data_skipping_indexes** (以逗号分隔的索引名列表)。此设置可用于防止某些类型的低效查询。在某些情况下,除非使用跳过索引,否则查询表的开销太大,如果将此设置与一个或多个索引名一起使用,则对于任何没有使用所列索引的查询将返回一个异常。这将防止编写糟糕的查询消耗服务器资源。 + +### 最佳实践 + +跳数索引并不直观,特别是对于来自RDMS领域并且习惯二级行索引或来自文档存储的反向索引的用户来说。要获得任何优化,应用ClickHouse数据跳数索引必须避免足够多的颗粒读取,以抵消计算索引的成本。关键是,如果一个值在一个索引块中只出现一次,就意味着整个块必须读入内存并计算,而索引开销是不必要的。 + +考虑以下数据分布: + +![Bad Skip!](../../../en/guides/improving-query-performance/images/bad_skip_1.svg) + + +假设主键/顺序是时间戳,并且在visitor_id上有一个索引。考虑下面的查询: + + `SELECT timestamp, url FROM table WHERE visitor_id = 1001` + +对于这种数据分布,传统的二级索引非常有利。不是读取所有的32678行来查找具有请求的visitor_id的5行,而是二级索引只包含5行位置,并且只从磁盘读取这5行。ClickHouse数据跳过索引的情况正好相反。无论跳转索引的类型是什么,visitor_id列中的所有32678值都将被测试。 + +因此,试图通过简单地向键列添加索引来加速ClickHouse查询的冲动通常是不正确的。只有在研究了其他替代方法之后,才应该使用此高级功能,例如修改主键(查看 [如何选择主键](../improving-query-performance/sparse-primary-indexes.md))、使用投影或使用实体化视图。即使跳数索引是合适的,也经常需要对索引和表进行仔细的调优。 + +在大多数情况下,一个有用的跳数索引需要主键和目标的非主列/表达式之间具有很强的相关性。如果没有相关性(如上图所示),那么在包含数千个值的块中,至少有一行满足过滤条件的可能性很高,并且只有几个块会被跳过。相反,如果主键的值范围(如一天中的时间)与潜在索引列中的值强相关(如电视观众年龄),则最小值类型的索引可能是有益的。注意,在插入数据时,可以增加这种相关性,方法是在sort /ORDER by键中包含额外的列,或者以在插入时对与主键关联的值进行分组的方式对插入进行批处理。例如,即使主键是一个包含大量站点事件的时间戳,特定site_id的所有事件也都可以被分组并由写入进程插入到一起,这将导致许多只包含少量站点id的颗粒,因此当根据特定的site_id值搜索时,可以跳过许多块。 + +跳数索引的另一个候选者是高基数表达式,其中任何一个值在数据中都相对稀疏。一个可能的例子是跟踪API请求中的错误代码的可观察性平台。某些错误代码虽然在数据中很少出现,但对搜索来说可能特别重要。error_code列上的set skip索引将允许绕过绝大多数不包含错误的块,从而显著改善针对错误的查询。 + +最后,关键的最佳实践是测试、测试、再测试。同样,与用于搜索文档的b-树二级索引或倒排索引不同,跳数索引行为是不容易预测的。将它们添加到表中会在数据摄取和查询方面产生很大的成本,这些查询由于各种原因不能从索引中受益。它们应该总是在真实世界的数据类型上进行测试,测试应该包括类型、粒度大小和其他参数的变化。测试通常会暴露仅仅通过思考不能发现的陷阱。 diff --git a/docs/zh/guides/improving-query-performance/sparse-primary-indexes.md b/docs/zh/guides/improving-query-performance/sparse-primary-indexes.md new file mode 100644 index 00000000000..a25293fae24 --- /dev/null +++ b/docs/zh/guides/improving-query-performance/sparse-primary-indexes.md @@ -0,0 +1,1172 @@ +--- +sidebar_label: Sparse Primary Indexes +sidebar_position: 20 +--- + + + +# ClickHouse主键索引最佳实践 + +在本文中,我们将深入研究ClickHouse索引。我们将对此进行详细说明和讨论: +- ClickHouse的索引与传统的关系数据库有何不同 +- ClickHouse是怎样构建和使用主键稀疏索引的 +- ClickHouse索引的最佳实践 + +您可以选择在自己的机器上执行本文给出的所有Clickhouse SQL语句和查询。 +如何安装和搭建ClickHouse请查看快速上手 + +:::note +这篇文章主要关注稀疏索引。 + +如果想了解二级跳数索引,请查看[教程](./skipping-indexes.md). + +::: + + +## 数据集 + +在本文中,我们将使用一个匿名的web流量数据集。 + +- 我们将使用样本数据集中的887万行(事件)的子集。 +- 未压缩的数据大小为887万个事件和大约700mb。当存储在ClickHouse时,压缩为200mb。 +- 在我们的子集中,每行包含三列,表示在特定时间(EventTime列)单击URL (URL列)的互联网用户(UserID列)。 + +通过这三个列,我们已经可以制定一些典型的web分析查询,如: + +- 某个用户点击次数最多的前10个url是什么? +- 点击某个URL次数最多的前10名用户是谁? +- 用户点击特定URL的最频繁时间(比如一周中的几天)是什么? + +## 测试环境 + +本文档中给出的所有运行时数据都是在带有Apple M1 Pro芯片和16GB RAM的MacBook Pro上本地运行ClickHouse 22.2.1。 + +## 全表扫描 + +为了了解在没有主键的情况下如何对数据集执行查询,我们通过执行以下SQL DDL语句(使用MergeTree表引擎)创建了一个表: + +```sql +CREATE TABLE hits_NoPrimaryKey +( + `UserID` UInt32, + `URL` String, + `EventTime` DateTime +) +ENGINE = MergeTree +PRIMARY KEY tuple(); +``` + + + +接下来,使用以下插入SQL将命中数据集的一个子集插入到表中。这个SQL使用URL表函数类型推断从clickhouse.com加载一个数据集的一部分数据: + +```sql +INSERT INTO hits_NoPrimaryKey SELECT + intHash32(c11::UInt64) AS UserID, + c15 AS URL, + c5 AS EventTime +FROM url('https://datasets.clickhouse.com/hits/tsv/hits_v1.tsv.xz') +WHERE URL != ''; +``` +结果: +```response +Ok. + +0 rows in set. Elapsed: 145.993 sec. Processed 8.87 million rows, 18.40 GB (60.78 thousand rows/s., 126.06 MB/s.) +``` + + +ClickHouse客户端输出了执行结果,插入了887万行数据。 + + +最后,为了简化本文后面的讨论,并使图表和结果可重现,我们使用FINAL关键字optimize该表: + +```sql +OPTIMIZE TABLE hits_NoPrimaryKey FINAL; +``` + +:::note +一般来说,不需要也不建议在加载数据后立即执行optimize。对于这个示例,为什么需要这样做是很明显的。 +::: + + +现在我们执行第一个web分析查询。以下是用户id为749927693的互联网用户点击次数最多的前10个url: + +```sql +SELECT URL, count(URL) as Count +FROM hits_NoPrimaryKey +WHERE UserID = 749927693 +GROUP BY URL +ORDER BY Count DESC +LIMIT 10; +``` +结果: +```response +┌─URL────────────────────────────┬─Count─┐ +│ http://auto.ru/chatay-barana.. │ 170 │ +│ http://auto.ru/chatay-id=371...│ 52 │ +│ http://public_search │ 45 │ +│ http://kovrik-medvedevushku-...│ 36 │ +│ http://forumal │ 33 │ +│ http://korablitz.ru/L_1OFFER...│ 14 │ +│ http://auto.ru/chatay-id=371...│ 14 │ +│ http://auto.ru/chatay-john-D...│ 13 │ +│ http://auto.ru/chatay-john-D...│ 10 │ +│ http://wot/html?page/23600_m...│ 9 │ +└────────────────────────────────┴───────┘ + +10 rows in set. Elapsed: 0.022 sec. +// highlight-next-line +Processed 8.87 million rows, +70.45 MB (398.53 million rows/s., 3.17 GB/s.) +``` + + +ClickHouse客户端输出表明,ClickHouse执行了一个完整的表扫描!我们的表的887万行中的每一行都被加载到ClickHouse中,这不是可扩展的。 + +为了使这种(方式)更有效和更快,我们需要使用一个具有适当主键的表。这将允许ClickHouse自动(基于主键的列)创建一个稀疏的主索引,然后可以用于显著加快我们示例查询的执行。 + + + +## 包含主键的表 + +创建一个包含联合主键UserID和URL列的表: + +```sql +CREATE TABLE hits_UserID_URL +( + `UserID` UInt32, + `URL` String, + `EventTime` DateTime +) +ENGINE = MergeTree +// highlight-next-line +PRIMARY KEY (UserID, URL) +ORDER BY (UserID, URL, EventTime) +SETTINGS index_granularity = 8192, index_granularity_bytes = 0; +``` + +[//]: # (
) +
+ + DDL详情 + +

+ +为了简化本文后面的讨论,并使图和结果可重现,使用DDL语句有如下说明: +

    +
  • 通过ORDER BY子句指定表的复合排序键
  • +
    +
  • 通过设置配置控制主索引有多少索引项:
  • +
    +
      +
    • index_granularity: 显式设置为其默认值8192。这意味着对于每一组8192行,主索引将有一个索引条目,例如,如果表包含16384行,那么索引将有两个索引条目。 +
    • +
      +
    • index_granularity_bytes: 设置为0表示禁止字适应索引粒度。自适应索引粒度意味着ClickHouse自动为一组n行创建一个索引条目 +
        +
      • 如果n小于8192,但n行的合并行数据大小大于或等于10MB (index_granularity_bytes的默认值)或
      • +
      • n达到8192
      • +
      +
    • +
    +
+

+
+ + +上面DDL语句中的主键会基于两个指定的键列创建主索引。 + +
+插入数据: + +```sql +INSERT INTO hits_UserID_URL SELECT + intHash32(c11::UInt64) AS UserID, + c15 AS URL, + c5 AS EventTime +FROM url('https://datasets.clickhouse.com/hits/tsv/hits_v1.tsv.xz') +WHERE URL != ''; +``` +结果: +```response +0 rows in set. Elapsed: 149.432 sec. Processed 8.87 million rows, 18.40 GB (59.38 thousand rows/s., 123.16 MB/s.) +``` + + +
+optimize表: + +```sql +OPTIMIZE TABLE hits_UserID_URL FINAL; +``` + +
+我们可以使用下面的查询来获取关于表的元数据: + +```sql +SELECT + part_type, + path, + formatReadableQuantity(rows) AS rows, + formatReadableSize(data_uncompressed_bytes) AS data_uncompressed_bytes, + formatReadableSize(data_compressed_bytes) AS data_compressed_bytes, + formatReadableSize(primary_key_bytes_in_memory) AS primary_key_bytes_in_memory, + marks, + formatReadableSize(bytes_on_disk) AS bytes_on_disk +FROM system.parts +WHERE (table = 'hits_UserID_URL') AND (active = 1) +FORMAT Vertical; +``` + +结果: + +```response +part_type: Wide +path: ./store/d9f/d9f36a1a-d2e6-46d4-8fb5-ffe9ad0d5aed/all_1_9_2/ +rows: 8.87 million +data_uncompressed_bytes: 733.28 MiB +data_compressed_bytes: 206.94 MiB +primary_key_bytes_in_memory: 96.93 KiB +marks: 1083 +bytes_on_disk: 207.07 MiB + + +1 rows in set. Elapsed: 0.003 sec. +``` + +客户端输出表明: + +- 表数据以wide format存储在一个特定目录,每个列有一个数据文件和mark文件。 +- 表有887万行数据。 +- 未压缩的数据有733.28 MB。 +- 压缩之后的数据有206.94 MB。 +- 有1083个主键索引条目,大小是96.93 KB。 +- 在磁盘上,表的数据、标记文件和主索引文件总共占用207.07 MB。 + + +## 针对海量数据规模的索引设计 + +在传统的关系数据库管理系统中,每个表行包含一个主索引。对于我们的数据集,这将导致主索引——通常是一个B(+)-Tree的数据结构——包含887万个条目。 + +这样的索引允许快速定位特定的行,从而提高查找点查和更新的效率。在B(+)-Tree数据结构中搜索一个条目的平均时间复杂度为O(log2n)。对于一个有887万行的表,这意味着需要23步来定位任何索引条目。 + +这种能力是有代价的:额外的磁盘和内存开销,以及向表中添加新行和向索引中添加条目时更高的插入成本(有时还需要重新平衡B-Tree)。 + +考虑到与B-Tee索引相关的挑战,ClickHouse中的表引擎使用了一种不同的方法。ClickHouseMergeTree Engine引擎系列被设计和优化用来处理大量数据。 + +这些表被设计为每秒接收数百万行插入,并存储非常大(100 pb)的数据量。 + +数据被一批一批的快速写入表中,并在后台应用合并规则。 + +在ClickHouse中,每个数据部分(data part)都有自己的主索引。当他们被合并时,合并部分的主索引也被合并。 + +在大规模中情况下,磁盘和内存的效率是非常重要的。因此,不是为每一行创建索引,而是为一组数据行(称为颗粒(granule))构建一个索引条目。 + +之所以可以使用这种稀疏索引,是因为ClickHouse会按照主键列的顺序将一组行存储在磁盘上。 + +与直接定位单个行(如基于B-Tree的索引)不同,稀疏主索引允许它快速(通过对索引项进行二分查找)识别可能匹配查询的行组。 + +然后潜在的匹配行组(颗粒)以并行的方式被加载到ClickHouse引擎中,以便找到匹配的行。 + +这种索引设计允许主索引很小(它可以而且必须完全适合主内存),同时仍然显著加快查询执行时间:特别是对于数据分析用例中常见的范围查询。 + +下面详细说明了ClickHouse是如何构建和使用其稀疏主索引的。在本文后面,我们将讨论如何选择、移除和排序用于构建索引的表列(主键列)的一些最佳实践。 + + + +## 数据按照主键排序存储在磁盘上 + +上面创建的表有: +- 联合主键 (UserID, URL) +- 联合排序键 (UserID, URL, EventTime)。 + +:::note +- 如果我们只指定了排序键,那么主键将隐式定义为排序键。 + +- 为了提高内存效率,我们显式地指定了一个主键,只包含查询过滤的列。基于主键的主索引被完全加载到主内存中。 + +- 为了上下文的一致性和最大的压缩比例,我们单独定义了排序键,排序键包含当前表所有的列(和压缩算法有关,一般排序之后又更好的压缩率)。 + +- 如果同时指定了主键和排序键,则主键必须是排序键的前缀。 +::: + + +插入的行按照主键列(以及排序键的附加EventTime列)的字典序(从小到大)存储在磁盘上。 + +:::note +ClickHouse允许插入具有相同主键列的多行数据。在这种情况下(参见下图中的第1行和第2行),最终的顺序是由指定的排序键决定的,这里是EventTime列的值。 +::: + + +如下图所示:ClickHouse是列存数据库。 +- 在磁盘上,每个表都有一个数据文件(*.bin),该列的所有值都以压缩格式存储,并且 +- 在这个例子中,这887万行按主键列(以及附加的排序键列)的字典升序存储在磁盘上 + - UserID第一位, + - 然后是URL, + - 最后是EventTime: + + +UserID.bin,URL.bin,和EventTime.bin是UserIDURL,和EventTime列的数据文件。 + +
+
+ + +:::note +- 因为主键定义了磁盘上行的字典顺序,所以一个表只能有一个主键。 + +- 我们从0开始对行进行编号,以便与ClickHouse内部行编号方案对齐,该方案也用于记录消息。 +::: + + + +## 数据被组织成颗粒以进行并行数据处理 + +出于数据处理的目的,表的列值在逻辑上被划分为多个颗粒。颗粒是流进ClickHouse进行数据处理的最小的不可分割数据集。这意味着,ClickHouse不是读取单独的行,而是始终读取(以流方式并并行地)整个行组(颗粒)。 +:::note +列值并不物理地存储在颗粒中,颗粒只是用于查询处理的列值的逻辑组织方式。 +::: + +下图显示了如何将表中的887万行(列值)组织成1083个颗粒,这是表的DDL语句包含设置index_granularity(设置为默认值8192)的结果。 + + + +第一个(根据磁盘上的物理顺序)8192行(它们的列值)在逻辑上属于颗粒0,然后下一个8192行(它们的列值)属于颗粒1,以此类推。 + +:::note +- 最后一个颗粒(1082颗粒)是少于8192行的。 + +- 我们将主键列(UserID, URL)中的一些列值标记为橙色。 + + 这些橙色标记的列值是每个颗粒中每个主键列的最小值。这里的例外是最后一个颗粒(上图中的颗粒1082),最后一个颗粒我们标记的是最大的值。 + + 正如我们将在下面看到的,这些橙色标记的列值将是表主索引中的条目。 + +- 我们从0开始对行进行编号,以便与ClickHouse内部行编号方案对齐,该方案也用于记录消息。 +::: + + + +## 每个颗粒对应主索引的一个条目 + +主索引是基于上图中显示的颗粒创建的。这个索引是一个未压缩的扁平数组文件(primary.idx),包含从0开始的所谓的数字索引标记。 + +下面的图显示了索引存储了每个颗粒的最小主键列值(在上面的图中用橙色标记的值)。 +例如: +- 第一个索引条目(下图中的“mark 0”)存储上图中颗粒0的主键列的最小值, +- 第二个索引条目(下图中的“mark 1”)存储上图中颗粒1的主键列的最小值,以此类推。 + + + +在我们的表中,索引总共有1083个条目,887万行数据和1083个颗粒: + + + +:::note +- 最后一个索引条目(上图中的“mark 1082”)存储了上图中颗粒1082的主键列的最大值。 + +- 索引条目(索引标记)不是基于表中的特定行,而是基于颗粒。例如,对于上图中的索引条目‘mark 0’,在我们的表中没有UserID为240.923且URL为“goal://metry=10000467796a411…”的行,相反,对于该表,有一个颗粒0,在该颗粒中,最小UserID值是240.923,最小URL值是“goal://metry=10000467796a411…”,这两个值来自不同的行。 + +- 主索引文件完全加载到主内存中。如果文件大于可用的空闲内存空间,则ClickHouse将发生错误。 +::: + + +主键条目称为索引标记,因为每个索引条目都标志着特定数据范围的开始。对于示例表: +- UserID index marks:
+ 主索引中存储的UserID值按升序排序。
+ 上图中的‘mark 1’指示颗粒1中所有表行的UserID值,以及随后所有颗粒中的UserID值,都保证大于或等于4.073.710。 + + [正如我们稍后将看到的](#query-on-userid-fast), 当查询对主键的第一列进行过滤时,此全局有序使ClickHouse能够对第一个键列的索引标记使用二分查找算法。 + +- URL index marks:
+ 主键列UserIDURL有相同的基数,这意味着第一列之后的所有主键列的索引标记通常只表示每个颗粒的数据范围。
+ 例如,‘mark 0’中的URL列所有的值都大于等于goal://metry=10000467796a411..., 然后颗粒1中的URL并不是如此,这是因为‘mark 1‘与‘mark 0‘具有不同的UserID列值。 + + 稍后我们将更详细地讨论这对查询执行性能的影响。 + +## 主索引被用来选择颗粒 + +现在,我们可以在主索引的支持下执行查询。 + + +下面计算UserID 749927693点击次数最多的10个url。 + +```sql +SELECT URL, count(URL) AS Count +FROM hits_UserID_URL +WHERE UserID = 749927693 +GROUP BY URL +ORDER BY Count DESC +LIMIT 10; +``` + +结果: + + +```response +┌─URL────────────────────────────┬─Count─┐ +│ http://auto.ru/chatay-barana.. │ 170 │ +│ http://auto.ru/chatay-id=371...│ 52 │ +│ http://public_search │ 45 │ +│ http://kovrik-medvedevushku-...│ 36 │ +│ http://forumal │ 33 │ +│ http://korablitz.ru/L_1OFFER...│ 14 │ +│ http://auto.ru/chatay-id=371...│ 14 │ +│ http://auto.ru/chatay-john-D...│ 13 │ +│ http://auto.ru/chatay-john-D...│ 10 │ +│ http://wot/html?page/23600_m...│ 9 │ +└────────────────────────────────┴───────┘ + +10 rows in set. Elapsed: 0.005 sec. +// highlight-next-line +Processed 8.19 thousand rows, +740.18 KB (1.53 million rows/s., 138.59 MB/s.) +``` + +ClickHouse客户端的输出显示,没有进行全表扫描,只有8.19万行流到ClickHouse。 + + +如果trace logging打开了,那ClickHouse服务端日志会显示ClickHouse正在对1083个UserID索引标记执行二分查找以便识别可能包含UserID列值为749927693的行的颗粒。这需要19个步骤,平均时间复杂度为O(log2 n): +```response +...Executor): Key condition: (column 0 in [749927693, 749927693]) +// highlight-next-line +...Executor): Running binary search on index range for part all_1_9_2 (1083 marks) +...Executor): Found (LEFT) boundary mark: 176 +...Executor): Found (RIGHT) boundary mark: 177 +...Executor): Found continuous range in 19 steps +...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, +// highlight-next-line + 1/1083 marks by primary key, 1 marks to read from 1 ranges +...Reading ...approx. 8192 rows starting from 1441792 +``` + + +我们可以在上面的跟踪日志中看到,1083个现有标记中有一个满足查询。 + +
+ + Trace Log详情 + +

+ +Mark 176 was identified (the 'found left boundary mark' is inclusive, the 'found right boundary mark' is exclusive), and therefore all 8192 rows from granule 176 (which starts at row 1.441.792 - we will see that later on in this article) are then streamed into ClickHouse in order to find the actual rows with a UserID column value of 749927693. +

+
+ +我们也可以通过使用EXPLAIN来重现这个结果: +```sql +EXPLAIN indexes = 1 +SELECT URL, count(URL) AS Count +FROM hits_UserID_URL +WHERE UserID = 749927693 +GROUP BY URL +ORDER BY Count DESC +LIMIT 10; +``` + +结果如下: + +```response +┌─explain───────────────────────────────────────────────────────────────────────────────┐ +│ Expression (Projection) │ +│ Limit (preliminary LIMIT (without OFFSET)) │ +│ Sorting (Sorting for ORDER BY) │ +│ Expression (Before ORDER BY) │ +│ Aggregating │ +│ Expression (Before GROUP BY) │ +│ Filter (WHERE) │ +│ SettingQuotaAndLimits (Set limits and quota after reading from storage) │ +│ ReadFromMergeTree │ +│ Indexes: │ +│ PrimaryKey │ +│ Keys: │ +│ UserID │ +│ Condition: (UserID in [749927693, 749927693]) │ +│ Parts: 1/1 │ +// highlight-next-line +│ Granules: 1/1083 │ +└───────────────────────────────────────────────────────────────────────────────────────┘ + +16 rows in set. Elapsed: 0.003 sec. +``` +客户端输出显示,在1083个颗粒中选择了一个可能包含UserID列值为749927693的行。 + + +:::note Conclusion +当查询对联合主键的一部分并且是第一个主键进行过滤时,ClickHouse将主键索引标记运行二分查找算法。 +::: + +
+ + +正如上面所讨论的,ClickHouse使用它的稀疏主索引来快速(通过二分查找算法)选择可能包含匹配查询的行的颗粒。 + +这是ClickHouse查询执行的**第一阶段(颗粒选择)**。 + +在**第二阶段(数据读取中)**, ClickHouse定位所选的颗粒,以便将它们的所有行流到ClickHouse引擎中,以便找到实际匹配查询的行。 + +我们将在下一节更详细地讨论第二阶段。 + + + +## 标记文件用来定位颗粒 + +下图描述了上表主索引文件的一部分。 + + + +如上所述,通过对索引的1083个UserID标记进行二分搜索,确定了第176个标记。因此,它对应的颗粒176可能包含UserID列值为749.927.693的行。 + +
+ + 颗粒选择的具体过程 + +

+ +上图显示,标记176是第一个UserID值小于749.927.693的索引条目,并且下一个标记(标记177)的颗粒177的最小UserID值大于该值的索引条目。因此,只有标记176对应的颗粒176可能包含UserID列值为749.927.693的行。 +

+
+ +为了确认(或排除)颗粒176中的某些行包含UserID列值为749.927.693,需要将属于此颗粒的所有8192行读取到ClickHouse。 + +为了读取这部分数据,ClickHouse需要知道颗粒176的物理地址。 + +在ClickHouse中,我们表的所有颗粒的物理位置都存储在标记文件中。与数据文件类似,每个表的列有一个标记文件。 + +下图显示了三个标记文件UserID.mrk、URL.mrk、EventTime.mrk,为表的UserID、URL和EventTime列存储颗粒的物理位置。 + + + +我们已经讨论了主索引是一个扁平的未压缩数组文件(primary.idx),其中包含从0开始编号的索引标记。 + +类似地,标记文件也是一个扁平的未压缩数组文件(*.mrk),其中包含从0开始编号的标记。 + +一旦ClickHouse确定并选择了可能包含查询所需的匹配行的颗粒的索引标记,就可以在标记文件数组中查找,以获得颗粒的物理位置。 + +每个特定列的标记文件条目以偏移量的形式存储两个位置: + +- 第一个偏移量(上图中的'block_offset')是在包含所选颗粒的压缩版本的压缩列数据文件中定位块。这个压缩块可能包含几个压缩的颗粒。所定位的压缩文件块在读取时被解压到内存中。 + +- 标记文件的第二个偏移量(上图中的“granule_offset”)提供了颗粒在解压数据块中的位置。 + +定位到的颗粒中的所有8192行数据都会被ClickHouse加载然后进一步处理。 + + +:::note 为什么需要mark文件 + +为什么主索引不直接包含与索引标记相对应的颗粒的物理位置? + +因为ClickHouse设计的场景就是超大规模数据,非常高效地使用磁盘和内存非常重要。 + +主索引文件需要放入内存中。 + +对于我们的示例查询,ClickHouse使用了主索引,并选择了可能包含与查询匹配的行的单个颗粒。只有对于这一个颗粒,ClickHouse才需定位物理位置,以便将相应的行组读取以进一步的处理。 + +而且,只有UserID和URL列需要这个偏移量信息。 + +对于查询中不使用的列,例如EventTime,不需要偏移量信息。 + +对于我们的示例查询,Clickhouse只需要UserID数据文件(UserID.bin)中176颗粒的两个物理位置偏移,以及URL数据文件(URL.data)中176颗粒的两个物理位置偏移。 + +由mark文件提供的间接方法避免了直接在主索引中存储所有三个列的所有1083个颗粒的物理位置的条目:因此避免了在主内存中有不必要的(可能未使用的)数据。 + +::: + +下面的图表和文本说明了我们的查询示例,ClickHouse如何在UserID.bin数据文件中定位176颗粒。 + + + +我们在本文前面讨论过,ClickHouse选择了主索引标记176,因此176颗粒可能包含查询所需的匹配行。 + +ClickHouse现在使用从索引中选择的标记号(176)在UserID.mark中进行位置数组查找,以获得两个偏移量,用于定位颗粒176。 + +如图所示,第一个偏移量是定位UserID.bin数据文件中的压缩文件块,该数据文件包含颗粒176的压缩数据。 + +一旦所定位的文件块被解压缩到主内存中,就可以使用标记文件的第二个偏移量在未压缩的数据中定位颗粒176。 + +ClickHouse需要从UserID.bin数据文件和URL.bin数据文件中定位(读取)颗粒176,以便执行我们的示例查询(UserID为749.927.693的互联网用户点击次数最多的10个url)。 + +上图显示了ClickHouse如何定位UserID.bin数据文件的颗粒。 + +同时,ClickHouse对URL.bin数据文件的颗粒176执行相同的操作。这两个不同的颗粒被对齐并加载到ClickHouse引擎以进行进一步的处理,即聚合并计算UserID为749.927.693的所有行的每组URL值,最后以计数降序输出10个最大的URL组。 + + + + +## 查询使用第二位主键的性能问题 + + +当查询对复合键的一部分并且是第一个主键列进行过滤时,ClickHouse将对主键列的索引标记运行二分查找。 + +但是,当查询对联合主键的一部分但不是第一个键列进行过滤时,会发生什么情况? + +:::note +我们讨论了这样一种场景:查询不是显式地对第一个主键列进行过滤,而是对第一个主键列之后的任何键列进行过滤。 + +当查询同时对第一个主键列和第一个主键列之后的任何键列进行过滤时,ClickHouse将对第一个主键列的索引标记运行二分查找。 +::: + +
+
+ + +我们使用一个查询来计算最点击"http://public_search"的最多的前10名用户: + +```sql +SELECT UserID, count(UserID) AS Count +FROM hits_UserID_URL +WHERE URL = 'http://public_search' +GROUP BY UserID +ORDER BY Count DESC +LIMIT 10; +``` + +结果是: +```response +┌─────UserID─┬─Count─┐ +│ 2459550954 │ 3741 │ +│ 1084649151 │ 2484 │ +│ 723361875 │ 729 │ +│ 3087145896 │ 695 │ +│ 2754931092 │ 672 │ +│ 1509037307 │ 582 │ +│ 3085460200 │ 573 │ +│ 2454360090 │ 556 │ +│ 3884990840 │ 539 │ +│ 765730816 │ 536 │ +└────────────┴───────┘ + +10 rows in set. Elapsed: 0.086 sec. +// highlight-next-line +Processed 8.81 million rows, +799.69 MB (102.11 million rows/s., 9.27 GB/s.) +``` + +客户端输出表明,尽管URL列是联合主键的一部分,ClickHouse几乎执行了一一次全表扫描!ClickHouse从表的887万行中读取881万行。 + +如果启用了trace日志,那么ClickHouse服务日志文件显示,ClickHouse在1083个URL索引标记上使用了通用的排除搜索,以便识别那些可能包含URL列值为"http://public_search"的行。 +```response +...Executor): Key condition: (column 1 in ['http://public_search', + 'http://public_search']) +// highlight-next-line +...Executor): Used generic exclusion search over index for part all_1_9_2 + with 1537 steps +...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, +// highlight-next-line + 1076/1083 marks by primary key, 1076 marks to read from 5 ranges +...Executor): Reading approx. 8814592 rows with 10 streams +``` +我们可以在上面的跟踪日志示例中看到,1083个颗粒中有1076个(通过标记)被选中,因为可能包含具有匹配URL值的行。 + +这将导致881万行被读取到ClickHouse引擎中(通过使用10个流并行地读取),以便识别实际包含URL值"http://public_search"的行。 + +然而,[稍后](#query-on-url-fast)仅仅39个颗粒包含匹配的行。 + +虽然基于联合主键(UserID, URL)的主索引对于加快过滤具有特定UserID值的行的查询非常有用,但对于过滤具有特定URL值的行的查询,索引并没有提供显著的帮助。 + +原因是URL列不是第一个主键列,因此ClickHouse是使用一个通用的排除搜索算法(而不是二分查找)查找URL列的索引标志,和UserID主键列不同,它的算法的有效性依赖于URL列的基数。 + +为了说明,我们给出通用的排除搜索算法的工作原理: + +
+ + 通用排除搜索算法 + +

+ + + + +下面将演示当通过第一个列之后的任何列选择颗粒时,当前一个键列具有或高或低的基数时,ClickHouse通用排除搜索算法 是如何工作的。 + +作为这两种情况的例子,我们将假设: +- 搜索URL值为"W3"的行。 +- 点击表抽象简化为只有简单值的UserID和UserID。 +- 相同联合主键(UserID、URL)。这意味着行首先按UserID值排序,具有相同UserID值的行然后再按URL排序。 +- 颗粒大小为2,即每个颗粒包含两行。 + +在下面的图表中,我们用橙色标注了每个颗粒的最小键列值。 + +**前缀主键低基数** + +假设UserID具有较低的基数。在这种情况下,相同的UserID值很可能分布在多个表行和颗粒上,从而分布在索引标记上。对于具有相同UserID的索引标记,索引标记的URL值按升序排序(因为表行首先按UserID排序,然后按URL排序)。这使得有效的过滤如下所述: + + + +在上图中,我们的抽象样本数据的颗粒选择过程有三种不同的场景: + + +1. 如果索引标记0的(最小)URL值小于W3,并且紧接索引标记的URL值也小于W3,则可以排除索引标记0,因为标记0、标记1和标记2具有相同的UserID值。注意,这个排除前提条件确保颗粒0和下一个颗粒1完全由U1 UserID值组成,这样ClickHouse就可以假设颗粒0中的最大URL值也小于W3并排除该颗粒。 + +2. 如果索引标记1的URL值小于(或等于)W3,并且后续索引标记的URL值大于(或等于)W3,则选择索引标记1,因为这意味着粒度1可能包含URL为W3的行)。 + +3. 可以排除URL值大于W3的索引标记2和3,因为主索引的索引标记存储了每个颗粒的最小键列值,因此颗粒2和3不可能包含URL值W3。 + + + +**前缀主键高基数** + +当UserID具有较高的基数时,相同的UserID值不太可能分布在多个表行和颗粒上。这意味着索引标记的URL值不是单调递增的: + + + + +正如在上面的图表中所看到的,所有URL值小于W3的标记都被选中,以便将其关联的颗粒的行加载到ClickHouse引擎中。 + +这是因为虽然图中的所有索引标记都属于上面描述的场景1,但它们不满足前面提到的排除前提条件,即两个直接随后的索引标记都具有与当前标记相同的UserID值,因此不能被排除。 + +例如,考虑索引标记0,其URL值小于W3,并且其直接后续索引标记的URL值也小于W3。这不能排除,因为两个直接随后的索引标记1和2与当前标记0没有相同的UserID值。 + +请注意,随后的两个索引标记需要具有相同的UserID值。这确保了当前和下一个标记的颗粒完全由U1 UserID值组成。如果仅仅是下一个标记具有相同的UserID,那么下一个标记的URL值可能来自具有不同UserID的表行——当您查看上面的图表时,确实是这样的情况,即W2来自U2而不是U1的行。 + +这最终阻止了ClickHouse对颗粒0中的最大URL值进行假设。相反,它必须假设颗粒0可能包含URL值为W3的行,并被迫选择标记0。 + +
+同样的情况也适用于标记1、2和3。 + +

+
+ +:::note 结论 +当查询对联合主键的一部分列(但不是第一个键列)进行过滤时,ClickHouse使用的通用排除搜索算法(而不是二分查找)在前一个键列基数较低时最有效。 +::: + +在我们的示例数据集中,两个键列(UserID、URL)都具有类似的高基数,并且,如前所述,当URL列的前一个键列具有较高基数时,通用排除搜索算法不是很有效。 + +:::note 看下跳数索引 +因为UserID和URL具有较高的基数,[根据URL过滤数据](#query-on-url)不是特别有效,对URL列创建[二级跳数索引](./skipping-indexes.md)同样也不会有太多改善。 + +例如,这两个语句在我们的表的URL列上创建并填充一个minmax跳数索引。 +```sql +ALTER TABLE hits_UserID_URL ADD INDEX url_skipping_index URL TYPE minmax GRANULARITY 4; +ALTER TABLE hits_UserID_URL MATERIALIZE INDEX url_skipping_index; +``` +ClickHouse现在创建了一个额外的索引来存储—每组4个连续的颗粒(注意上面ALTER TABLE语句中的GRANULARITY 4子句)—最小和最大的URL值: + + + +第一个索引条目(上图中的mark 0)存储属于表的前4个颗粒的行的最小和最大URL值。 + +第二个索引条目(mark 1)存储属于表中下一个4个颗粒的行的最小和最大URL值,依此类推。 + +(ClickHouse还为跳数索引创建了一个特殊的标记文件,用于定位与索引标记相关联的颗粒组。) + +由于UserID和URL的基数相似,在执行对URL的查询过滤时,这个二级跳数索引不能帮助排除选择的颗粒。 + +正在寻找的特定URL值('http://public_search')很可能是索引为每组颗粒存储的最小值和最大值之间的值,导致ClickHouse被迫选择这组颗粒(因为它们可能包含匹配查询的行)。 + + + + +::: + + +因此,如果我们想显著提高过滤具有特定URL的行的示例查询的速度,那么我们需要使用针对该查询优化的主索引。 + +此外,如果我们想保持过滤具有特定UserID的行的示例查询的良好性能,那么我们需要使用多个主索引。 + +下面是实现这一目标的方法。 + + + +## 使用多个主键索引进行调优 + + +如果我们想显著加快我们的两个示例查询——一个过滤具有特定UserID的行,一个过滤具有特定URL的行——那么我们需要使用多个主索引,通过使用这三个方法中的一个: + +- 新建一个不同主键的新表。 +- 创建一个雾化视图。 +- 增加projection。 + +这三个方法都会有效地将示例数据复制到另一个表中,以便重新组织表的主索引和行排序顺序。 + +然而,这三个选项的不同之处在于,附加表对于查询和插入语句的路由对用户的透明程度。 + +当创建有不同主键的第二个表时,查询必须显式地发送给最适合查询的表版本,并且必须显式地插入新数据到两个表中,以保持表的同步: + + + + +在物化视图中,额外的表被隐藏,数据自动在两个表之间保持同步: + + + +projection方式是最透明的选项,因为除了自动保持隐藏的附加表与数据变化同步外,ClickHouse还会自动选择最有效的表版本进行查询: + + +下面我们使用真实的例子详细讨论下这三种方式。 + + + +## 通过辅助表使用联合主键索引 + + +我们创建一个新的附加表,其中我们在主键中切换键列的顺序(与原始表相比): + +```sql +CREATE TABLE hits_URL_UserID +( + `UserID` UInt32, + `URL` String, + `EventTime` DateTime +) +ENGINE = MergeTree +// highlight-next-line +PRIMARY KEY (URL, UserID) +ORDER BY (URL, UserID, EventTime) +SETTINGS index_granularity = 8192, index_granularity_bytes = 0; +``` + +写入887万行源表数据: + +```sql +INSERT INTO hits_URL_UserID +SELECT * from hits_UserID_URL; +``` + +结果: + +```response +Ok. + +0 rows in set. Elapsed: 2.898 sec. Processed 8.87 million rows, 838.84 MB (3.06 million rows/s., 289.46 MB/s.) +``` + +最后optimize下: +```sql +OPTIMIZE TABLE hits_URL_UserID FINAL; +``` + +因为我们切换了主键中列的顺序,插入的行现在以不同的字典顺序存储在磁盘上(与我们的原始表相比),因此该表的1083个颗粒也包含了与以前不同的值: + + + +主键索引如下: + + +现在计算最频繁点击URL"http://public_search"的前10名用户,这时候的查询速度是明显加快的: +```sql +SELECT UserID, count(UserID) AS Count +// highlight-next-line +FROM hits_URL_UserID +WHERE URL = 'http://public_search' +GROUP BY UserID +ORDER BY Count DESC +LIMIT 10; +``` + +结果: + + +```response +┌─────UserID─┬─Count─┐ +│ 2459550954 │ 3741 │ +│ 1084649151 │ 2484 │ +│ 723361875 │ 729 │ +│ 3087145896 │ 695 │ +│ 2754931092 │ 672 │ +│ 1509037307 │ 582 │ +│ 3085460200 │ 573 │ +│ 2454360090 │ 556 │ +│ 3884990840 │ 539 │ +│ 765730816 │ 536 │ +└────────────┴───────┘ + +10 rows in set. Elapsed: 0.017 sec. +// highlight-next-line +Processed 319.49 thousand rows, +11.38 MB (18.41 million rows/s., 655.75 MB/s.) +``` + +现在没有全表扫描了,ClickHouse执行高效了很多。 + +对于原始表中的主索引(其中UserID是第一个键列,URL是第二个键列),ClickHouse在索引标记上使用了通用排除搜索来执行该查询,但这不是很有效,因为UserID和URL的基数同样很高。 + +将URL作为主索引的第一列,ClickHouse现在对索引标记运行二分搜索。ClickHouse服务器日志文件中对应的跟踪日志: + +```response +...Executor): Key condition: (column 0 in ['http://public_search', + 'http://public_search']) +// highlight-next-line +...Executor): Running binary search on index range for part all_1_9_2 (1083 marks) +...Executor): Found (LEFT) boundary mark: 644 +...Executor): Found (RIGHT) boundary mark: 683 +...Executor): Found continuous range in 19 steps +...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, +// highlight-next-line + 39/1083 marks by primary key, 39 marks to read from 1 ranges +...Executor): Reading approx. 319488 rows with 2 streams +``` +ClickHouse只选择了39个索引标记,而不是使用通用排除搜索时的1076个。 + +请注意,辅助表经过了优化,以加快对url的示例查询过滤的执行。 + +像之前我们查询过滤URL一样,如果我们现在对辅助表查询过滤UserID,性能同样会比较差,因为现在UserID是第二主索引键列,所以ClickHouse将使用通用排除搜索算法查找颗粒,这对于类似高基数的UserID和URL来说不是很有效。 + +点击下面了解详情: +
+ + 对UserID的查询过滤性能较差 + +

+ +```sql +SELECT URL, count(URL) AS Count +FROM hits_URL_UserID +WHERE UserID = 749927693 +GROUP BY URL +ORDER BY Count DESC +LIMIT 10; +``` + +结果 + +```response +┌─URL────────────────────────────┬─Count─┐ +│ http://auto.ru/chatay-barana.. │ 170 │ +│ http://auto.ru/chatay-id=371...│ 52 │ +│ http://public_search │ 45 │ +│ http://kovrik-medvedevushku-...│ 36 │ +│ http://forumal │ 33 │ +│ http://korablitz.ru/L_1OFFER...│ 14 │ +│ http://auto.ru/chatay-id=371...│ 14 │ +│ http://auto.ru/chatay-john-D...│ 13 │ +│ http://auto.ru/chatay-john-D...│ 10 │ +│ http://wot/html?page/23600_m...│ 9 │ +└────────────────────────────────┴───────┘ + +10 rows in set. Elapsed: 0.024 sec. +// highlight-next-line +Processed 8.02 million rows, +73.04 MB (340.26 million rows/s., 3.10 GB/s.) +``` + +服务端日志: +```response +...Executor): Key condition: (column 1 in [749927693, 749927693]) +// highlight-next-line +...Executor): Used generic exclusion search over index for part all_1_9_2 + with 1453 steps +...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, +// highlight-next-line + 980/1083 marks by primary key, 980 marks to read from 23 ranges +...Executor): Reading approx. 8028160 rows with 10 streams +``` +

+
+ + + +现在我们有了两张表。优化了对UserID和URL的查询过滤,分别: + + + + + + + + + + +## 通过物化视图使用联合主键 + +在原表上创建物化视图: +```sql +CREATE MATERIALIZED VIEW mv_hits_URL_UserID +ENGINE = MergeTree() +PRIMARY KEY (URL, UserID) +ORDER BY (URL, UserID, EventTime) +POPULATE +AS SELECT * FROM hits_UserID_URL; +``` + +结果: + +```response +Ok. + +0 rows in set. Elapsed: 2.935 sec. Processed 8.87 million rows, 838.84 MB (3.02 million rows/s., 285.84 MB/s.) +``` + +:::note +- 我们在视图的主键中切换键列的顺序(与原始表相比) +- 雾化视图由一个隐藏表支持,该表的行顺序和主索引基于给定的主键定义 +- 我们使用POPULATE关键字,以便用源表hits_UserID_URL中的所有887万行立即导入新的物化视图 +- 如果在源表hits_UserID_URL中插入了新行,那么这些行也会自动插入到隐藏表中 +- 实际上,隐式创建的隐藏表的行顺序和主索引与我们上面显式创建的辅助表相同: + + + + + + + +ClickHouse将隐藏表的列数据文件(.bin)、标记文件(.mrk2)和主索引(primary.idx)存储在ClickHouse服务器的数据目录的一个特殊文件夹中: + + + + +::: + + +物化视图背后的隐藏表(和它的主索引)现在可以用来显著加快我们在URL列上查询过滤的执行速度: +```sql +SELECT UserID, count(UserID) AS Count +// highlight-next-line +FROM mv_hits_URL_UserID +WHERE URL = 'http://public_search' +GROUP BY UserID +ORDER BY Count DESC +LIMIT 10; +``` + +结果: + +```response +┌─────UserID─┬─Count─┐ +│ 2459550954 │ 3741 │ +│ 1084649151 │ 2484 │ +│ 723361875 │ 729 │ +│ 3087145896 │ 695 │ +│ 2754931092 │ 672 │ +│ 1509037307 │ 582 │ +│ 3085460200 │ 573 │ +│ 2454360090 │ 556 │ +│ 3884990840 │ 539 │ +│ 765730816 │ 536 │ +└────────────┴───────┘ + +10 rows in set. Elapsed: 0.026 sec. +// highlight-next-line +Processed 335.87 thousand rows, +13.54 MB (12.91 million rows/s., 520.38 MB/s.) +``` + +物化视图背后隐藏表(及其主索引)实际上与我们显式创建的辅助表是相同的,所以查询的执行方式与显式创建的表相同。 + +ClickHouse服务器日志文件中相应的跟踪日志确认了ClickHouse正在对索引标记运行二分搜索: + +```response +...Executor): Key condition: (column 0 in ['http://public_search', + 'http://public_search']) +// highlight-next-line +...Executor): Running binary search on index range ... +... +...Executor): Selected 4/4 parts by partition key, 4 parts by primary key, +// highlight-next-line + 41/1083 marks by primary key, 41 marks to read from 4 ranges +...Executor): Reading approx. 335872 rows with 4 streams +``` + + + +## 通过projections使用联合主键索引 + + +Projections目前是一个实验性的功能,因此我们需要告诉ClickHouse: + +```sql +SET allow_experimental_projection_optimization = 1; +``` + + +在原表上创建projection: +```sql +ALTER TABLE hits_UserID_URL + ADD PROJECTION prj_url_userid + ( + SELECT * + ORDER BY (URL, UserID) + ); +``` + +雾化projection: +```sql +ALTER TABLE hits_UserID_URL + MATERIALIZE PROJECTION prj_url_userid; +``` + +:::note +- 该projection正在创建一个隐藏表,该表的行顺序和主索引基于该projection的给定order BY子句 +- 我们使用MATERIALIZE关键字,以便立即用源表hits_UserID_URL的所有887万行导入隐藏表 +- 如果在源表hits_UserID_URL中插入了新行,那么这些行也会自动插入到隐藏表中 +- 查询总是(从语法上)针对源表hits_UserID_URL,但是如果隐藏表的行顺序和主索引允许更有效地执行查询,那么将使用该隐藏表 +- 实际上,隐式创建的隐藏表的行顺序和主索引与我们显式创建的辅助表相同: + + + +ClickHouse将隐藏表的列数据文件(.bin)、标记文件(.mrk2)和主索引(primary.idx)存储在一个特殊的文件夹中(在下面的截图中用橙色标记),紧挨着源表的数据文件、标记文件和主索引文件: + + +::: + +由投影创建的隐藏表(以及它的主索引)现在可以(隐式地)用于显著加快URL列上查询过滤的执行。注意,查询在语法上针对投影的源表。 + +```sql +SELECT UserID, count(UserID) AS Count +// highlight-next-line +FROM hits_UserID_URL +WHERE URL = 'http://public_search' +GROUP BY UserID +ORDER BY Count DESC +LIMIT 10; +``` + +结果: + +```response +┌─────UserID─┬─Count─┐ +│ 2459550954 │ 3741 │ +│ 1084649151 │ 2484 │ +│ 723361875 │ 729 │ +│ 3087145896 │ 695 │ +│ 2754931092 │ 672 │ +│ 1509037307 │ 582 │ +│ 3085460200 │ 573 │ +│ 2454360090 │ 556 │ +│ 3884990840 │ 539 │ +│ 765730816 │ 536 │ +└────────────┴───────┘ + +10 rows in set. Elapsed: 0.029 sec. +// highlight-next-line +Processed 319.49 thousand rows, 1 +1.38 MB (11.05 million rows/s., 393.58 MB/s.) +``` + +因为由投影创建的隐藏表(及其主索引)实际上与我们显式创建的辅助表相同,所以查询的执行方式与显式创建的表相同。 + +ClickHouse服务器日志文件中跟踪日志确认了ClickHouse正在对索引标记运行二分搜索: + + +```response +...Executor): Key condition: (column 0 in ['http://public_search', + 'http://public_search']) +// highlight-next-line +...Executor): Running binary search on index range for part prj_url_userid (1083 marks) +...Executor): ... +// highlight-next-line +...Executor): Choose complete Normal projection prj_url_userid +...Executor): projection required columns: URL, UserID +...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, +// highlight-next-line + 39/1083 marks by primary key, 39 marks to read from 1 ranges +...Executor): Reading approx. 319488 rows with 2 streams +``` + + +## 移除无效的主键列 + + +带有联合主键(UserID, URL)的表的主索引对于加快UserID的查询过滤非常有用。但是,尽管URL列是联合主键的一部分,但该索引在加速URL查询过滤方面并没有提供显著的帮助。 + +反之亦然:具有复合主键(URL, UserID)的表的主索引加快了URL上的查询过滤,但没有为UserID上的查询过滤提供太多支持。 + +由于主键列UserID和URL的基数同样很高,过滤第二个键列的查询不会因为第二个键列位于索引中而受益太多。 + +因此,从主索引中删除第二个键列(从而减少索引的内存消耗)并使用多个主索引是有意义的。 + +但是,如果复合主键中的键列在基数上有很大的差异,那么查询按基数升序对主键列进行排序是有益的。 + +主键键列之间的基数差越大,主键键列的顺序越重要。我们将在以后的文章中对此进行演示。请继续关注。 From 8b1b059ae96520f6b84ebe6e743199bbdfa07a1f Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Wed, 3 Aug 2022 15:56:32 -0400 Subject: [PATCH 305/672] wrong directory --- .../_category_.yml | 8 - .../skipping-indexes.md | 167 --- .../sparse-primary-indexes.md | 1172 ----------------- 3 files changed, 1347 deletions(-) delete mode 100644 docs/zh/guides/imporoviing-query-performance/_category_.yml delete mode 100644 docs/zh/guides/imporoviing-query-performance/skipping-indexes.md delete mode 100644 docs/zh/guides/imporoviing-query-performance/sparse-primary-indexes.md diff --git a/docs/zh/guides/imporoviing-query-performance/_category_.yml b/docs/zh/guides/imporoviing-query-performance/_category_.yml deleted file mode 100644 index 62885213bfc..00000000000 --- a/docs/zh/guides/imporoviing-query-performance/_category_.yml +++ /dev/null @@ -1,8 +0,0 @@ -position: 10 -label: '优化查询性能' -collapsible: true -collapsed: true -link: - type: generated-index - title: Improving Query Performance - slug: /zh/guides/improving-query-performance diff --git a/docs/zh/guides/imporoviing-query-performance/skipping-indexes.md b/docs/zh/guides/imporoviing-query-performance/skipping-indexes.md deleted file mode 100644 index b3cb82bf769..00000000000 --- a/docs/zh/guides/imporoviing-query-performance/skipping-indexes.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -sidebar_label: Data Skipping Indexes -sidebar_position: 2 ---- - -# 深入理解ClickHouse跳数索引 - -### 跳数索引 - -影响ClickHouse查询性能的因素很多。在大多数场景中,关键因素是ClickHouse在计算查询WHERE子句条件时是否可以使用主键。因此,选择适用于最常见查询模式的主键对于表的设计至关重要。 - -然而,无论如何仔细地调优主键,不可避免地会出现不能有效使用它的查询用例。用户通常依赖于ClickHouse获得时间序列类型的数据,但他们通常希望根据其他业务维度(如客户id、网站URL或产品编号)分析同一批数据。在这种情况下,查询性能可能会相当差,因为应用WHERE子句条件可能需要对每个列值进行完整扫描。虽然ClickHouse在这些情况下仍然相对较快,但计算数百万或数十亿个单独的值将导致“非索引”查询的执行速度比基于主键的查询慢得多。 - -在传统的关系数据库中,解决这个问题的一种方法是将一个或多个“二级”索引附加到表上。这是一个b-树结构,允许数据库在O(log(n))时间内找到磁盘上所有匹配的行,而不是O(n)时间(一次表扫描),其中n是行数。但是,这种类型的二级索引不适用于ClickHouse(或其他面向列的数据库),因为磁盘上没有单独的行可以添加到索引中。 - -相反,ClickHouse提供了一种不同类型的索引,在特定情况下可以显著提高查询速度。这些结构被标记为跳数索引,因为它们使ClickHouse能够跳过保证没有匹配值的数据块。 -### 基本操作 - -用户只能在MergeTree表引擎上使用数据跳数索引。每个跳数索引都有四个主要参数: - -- 索引名称。索引名用于在每个分区中创建索引文件。此外,在删除或具体化索引时需要将其作为参数。 -- 索引的表达式。索引表达式用于计算存储在索引中的值集。它可以是列、简单操作符、函数的子集的组合。 -- 类型。索引的类型控制计算,该计算决定是否可以跳过读取和计算每个索引块。 -- GRANULARITY。每个索引块由颗粒(granule)组成。例如,如果主表索引粒度为8192行,GRANULARITY为4,则每个索引“块”将为32768行。 - -当用户创建数据跳数索引时,表的每个数据部分目录中将有两个额外的文件。 - -- skp_idx_{index_name}.idx:包含排序的表达式值。 -- skp_idx_{index_name}.mrk2:包含关联数据列文件中的相应偏移量。 - -如果在执行查询并读取相关列文件时,WHERE子句过滤条件的某些部分与跳数索引表达式匹配,ClickHouse将使用索引文件数据来确定每个相关的数据块是必须被处理还是可以被绕过(假设块还没有通过应用主键索引被排除)。这里用一个非常简单的示例:考虑以下加载了可预测数据的表。 - -``` -CREATE TABLE skip_table -( - my_key UInt64, - my_value UInt64 -) -ENGINE MergeTree primary key my_key -SETTINGS index_granularity=8192; - -INSERT INTO skip_table SELECT number, intDiv(number,4096) FROM numbers(100000000); -``` - -当执行一个不使用主键的简单查询时,将扫描my_value列所有的一亿条记录: - -``` -SELECT * FROM skip_table WHERE my_value IN (125, 700) - -┌─my_key─┬─my_value─┐ -│ 512000 │ 125 │ -│ 512001 │ 125 │ -│ ... | ... | -└────────┴──────────┘ - -8192 rows in set. Elapsed: 0.079 sec. Processed 100.00 million rows, 800.10 MB (1.26 billion rows/s., 10.10 GB/s. -``` - -增加一个基本的跳数索引: - -``` -ALTER TABLE skip_table ADD INDEX vix my_value TYPE set(100) GRANULARITY 2; -``` - -通常,跳数索引只应用于新插入的数据,所以仅仅添加索引不会影响上述查询。 - -要使已经存在的数据生效,那执行: - -``` -ALTER TABLE skip_table MATERIALIZE INDEX vix; -``` - -重跑SQL: - -``` -SELECT * FROM skip_table WHERE my_value IN (125, 700) - -┌─my_key─┬─my_value─┐ -│ 512000 │ 125 │ -│ 512001 │ 125 │ -│ ... | ... | -└────────┴──────────┘ - -8192 rows in set. Elapsed: 0.051 sec. Processed 32.77 thousand rows, 360.45 KB (643.75 thousand rows/s., 7.08 MB/s.) -``` - -这次没有再去处理1亿行800MB的数据,ClickHouse只读取和分析32768行360KB的数据—4个granule的数据。 - -下图是更直观的展示,这就是如何读取和选择my_value为125的4096行,以及如何跳过以下行而不从磁盘读取: - -![Simple Skip](../../../en/guides/improving-query-performance/images/simple_skip.svg) - -通过在执行查询时启用跟踪,用户可以看到关于跳数索引使用情况的详细信息。在clickhouse-client中设置send_logs_level: - -``` -SET send_logs_level='trace'; -``` -这将在尝试调优查询SQL和表索引时提供有用的调试信息。上面的例子中,调试日志显示跳数索引过滤了大部分granule,只读取了两个: - -``` - default.skip_table (933d4b2c-8cea-4bf9-8c93-c56e900eefd1) (SelectExecutor): Index `vix` has dropped 6102/6104 granules. -``` -### 跳数索引类型 - -#### minmax - -这种轻量级索引类型不需要参数。它存储每个块的索引表达式的最小值和最大值(如果表达式是一个元组,它分别存储元组元素的每个成员的值)。对于倾向于按值松散排序的列,这种类型非常理想。在查询处理期间,这种索引类型的开销通常是最小的。 - -这种类型的索引只适用于标量或元组表达式——索引永远不适用于返回数组或map数据类型的表达式。 - -#### set - -这种轻量级索引类型接受单个参数max_size,即每个块的值集(0允许无限数量的离散值)。这个集合包含块中的所有值(如果值的数量超过max_size则为空)。这种索引类型适用于每组颗粒中基数较低(本质上是“聚集在一起”)但总体基数较高的列。 - -该索引的成本、性能和有效性取决于块中的基数。如果每个块包含大量惟一值,那么针对大型索引集计算查询条件将非常昂贵,或者由于索引超过max_size而为空,因此索引将不应用。 - -#### Bloom Filter Types - -Bloom filter是一种数据结构,它允许对集合成员进行高效的是否存在测试,但代价是有轻微的误报。在跳数索引的使用场景,假阳性不是一个大问题,因为惟一的问题只是读取一些不必要的块。潜在的假阳性意味着索引表达式应该为真,否则有效的数据可能会被跳过。 - -因为Bloom filter可以更有效地处理大量离散值的测试,所以它们可以适用于大量条件表达式判断的场景。特别的是Bloom filter索引可以应用于数组,数组中的每个值都被测试,也可以应用于map,通过使用mapKeys或mapValues函数将键或值转换为数组。 - -有三种基于Bloom过滤器的数据跳数索引类型: - -* 基本的**bloom_filter**接受一个可选参数,该参数表示在0到1之间允许的“假阳性”率(如果未指定,则使用.025)。 - -* 更专业的**tokenbf_v1**。需要三个参数,用来优化布隆过滤器:(1)过滤器的大小字节(大过滤器有更少的假阳性,有更高的存储成本),(2)哈希函数的个数(更多的散列函数可以减少假阳性)。(3)布隆过滤器哈希函数的种子。有关这些参数如何影响布隆过滤器功能的更多细节,请参阅 [这里](https://hur.st/bloomfilter/) 。此索引仅适用于String、FixedString和Map类型的数据。输入表达式被分割为由非字母数字字符分隔的字符序列。例如,列值`This is a candidate for a "full text" search`将被分割为`This` `is` `a` `candidate` `for` `full` `text` `search`。它用于LIKE、EQUALS、in、hasToken()和类似的长字符串中单词和其他值的搜索。例如,一种可能的用途是在非结构的应用程序日志行列中搜索少量的类名或行号。 - -* 更专业的**ngrambf_v1**。该索引的功能与tokenbf_v1相同。在Bloom filter设置之前需要一个额外的参数,即要索引的ngram的大小。一个ngram是长度为n的任何字符串,比如如果n是4,`A short string`会被分割为`A sh`` sho`, `shor`, `hort`, `ort s`, `or st`, `r str`, ` stri`, `trin`, `ring`。这个索引对于文本搜索也很有用,特别是没有单词间断的语言,比如中文。 - -### 跳数索引函数 - -跳数索引核心目的是限制流行查询分析的数据量。鉴于ClickHouse数据的分析特性,这些查询的模式在大多数情况下都包含函数表达式。因此,跳数索引必须与常用函数正确交互才能提高效率。这种情况可能发生在: -* 插入数据并将索引定义为一个函数表达式(表达式的结果存储在索引文件中)或者 -* 处理查询,并将表达式应用于存储的索引值,以确定是否排除数据块。 - -每种类型的跳数索引支持的函数列表可以查看 [这里](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree/#functions-support) 。通常,集合索引和基于Bloom filter的索引(另一种类型的集合索引)都是无序的,因此不能用于范围。相反,最大最小值索引在范围中工作得特别好,因为确定范围是否相交非常快。部分匹配函数LIKE、startsWith、endsWith和hasToken的有效性取决于使用的索引类型、索引表达式和数据的特定形状。 - -### 跳数索引的配置 - -有两个可用的设置可应用于跳数索引。 - -* **use_skip_indexes** (0或1,默认为1)。不是所有查询都可以有效地使用跳过索引。如果一个特定的过滤条件可能包含很多颗粒,那么应用数据跳过索引将导致不必要的、有时甚至是非常大的成本。对于不太可能从任何跳过索引中获益的查询,将该值设置为0。 -* **force_data_skipping_indexes** (以逗号分隔的索引名列表)。此设置可用于防止某些类型的低效查询。在某些情况下,除非使用跳过索引,否则查询表的开销太大,如果将此设置与一个或多个索引名一起使用,则对于任何没有使用所列索引的查询将返回一个异常。这将防止编写糟糕的查询消耗服务器资源。 - -### 最佳实践 - -跳数索引并不直观,特别是对于来自RDMS领域并且习惯二级行索引或来自文档存储的反向索引的用户来说。要获得任何优化,应用ClickHouse数据跳数索引必须避免足够多的颗粒读取,以抵消计算索引的成本。关键是,如果一个值在一个索引块中只出现一次,就意味着整个块必须读入内存并计算,而索引开销是不必要的。 - -考虑以下数据分布: - -![Bad Skip!](../../../en/guides/improving-query-performance/images/bad_skip_1.svg) - - -假设主键/顺序是时间戳,并且在visitor_id上有一个索引。考虑下面的查询: - - `SELECT timestamp, url FROM table WHERE visitor_id = 1001` - -对于这种数据分布,传统的二级索引非常有利。不是读取所有的32678行来查找具有请求的visitor_id的5行,而是二级索引只包含5行位置,并且只从磁盘读取这5行。ClickHouse数据跳过索引的情况正好相反。无论跳转索引的类型是什么,visitor_id列中的所有32678值都将被测试。 - -因此,试图通过简单地向键列添加索引来加速ClickHouse查询的冲动通常是不正确的。只有在研究了其他替代方法之后,才应该使用此高级功能,例如修改主键(查看 [如何选择主键](../improving-query-performance/sparse-primary-indexes.md))、使用投影或使用实体化视图。即使跳数索引是合适的,也经常需要对索引和表进行仔细的调优。 - -在大多数情况下,一个有用的跳数索引需要主键和目标的非主列/表达式之间具有很强的相关性。如果没有相关性(如上图所示),那么在包含数千个值的块中,至少有一行满足过滤条件的可能性很高,并且只有几个块会被跳过。相反,如果主键的值范围(如一天中的时间)与潜在索引列中的值强相关(如电视观众年龄),则最小值类型的索引可能是有益的。注意,在插入数据时,可以增加这种相关性,方法是在sort /ORDER by键中包含额外的列,或者以在插入时对与主键关联的值进行分组的方式对插入进行批处理。例如,即使主键是一个包含大量站点事件的时间戳,特定site_id的所有事件也都可以被分组并由写入进程插入到一起,这将导致许多只包含少量站点id的颗粒,因此当根据特定的site_id值搜索时,可以跳过许多块。 - -跳数索引的另一个候选者是高基数表达式,其中任何一个值在数据中都相对稀疏。一个可能的例子是跟踪API请求中的错误代码的可观察性平台。某些错误代码虽然在数据中很少出现,但对搜索来说可能特别重要。error_code列上的set skip索引将允许绕过绝大多数不包含错误的块,从而显著改善针对错误的查询。 - -最后,关键的最佳实践是测试、测试、再测试。同样,与用于搜索文档的b-树二级索引或倒排索引不同,跳数索引行为是不容易预测的。将它们添加到表中会在数据摄取和查询方面产生很大的成本,这些查询由于各种原因不能从索引中受益。它们应该总是在真实世界的数据类型上进行测试,测试应该包括类型、粒度大小和其他参数的变化。测试通常会暴露仅仅通过思考不能发现的陷阱。 diff --git a/docs/zh/guides/imporoviing-query-performance/sparse-primary-indexes.md b/docs/zh/guides/imporoviing-query-performance/sparse-primary-indexes.md deleted file mode 100644 index 12b9c7c598d..00000000000 --- a/docs/zh/guides/imporoviing-query-performance/sparse-primary-indexes.md +++ /dev/null @@ -1,1172 +0,0 @@ ---- -sidebar_label: Sparse Primary Indexes -sidebar_position: 20 ---- - - - -# ClickHouse主键索引最佳实践 - -在本文中,我们将深入研究ClickHouse索引。我们将对此进行详细说明和讨论: -- ClickHouse的索引与传统的关系数据库有何不同 -- ClickHouse是怎样构建和使用主键稀疏索引的 -- ClickHouse索引的最佳实践 - -您可以选择在自己的机器上执行本文给出的所有Clickhouse SQL语句和查询。 -如何安装和搭建ClickHouse请查看快速上手 - -:::note -这篇文章主要关注稀疏索引。 - -如果想了解二级跳数索引,请查看[教程](./skipping-indexes.md). - -::: - - -## 数据集 - -在本文中,我们将使用一个匿名的web流量数据集。 - -- 我们将使用样本数据集中的887万行(事件)的子集。 -- 未压缩的数据大小为887万个事件和大约700mb。当存储在ClickHouse时,压缩为200mb。 -- 在我们的子集中,每行包含三列,表示在特定时间(EventTime列)单击URL (URL列)的互联网用户(UserID列)。 - -通过这三个列,我们已经可以制定一些典型的web分析查询,如: - -- 某个用户点击次数最多的前10个url是什么? -- 点击某个URL次数最多的前10名用户是谁? -- 用户点击特定URL的最频繁时间(比如一周中的几天)是什么? - -## 测试环境 - -本文档中给出的所有运行时数据都是在带有Apple M1 Pro芯片和16GB RAM的MacBook Pro上本地运行ClickHouse 22.2.1。 - -## 全表扫描 - -为了了解在没有主键的情况下如何对数据集执行查询,我们通过执行以下SQL DDL语句(使用MergeTree表引擎)创建了一个表: - -```sql -CREATE TABLE hits_NoPrimaryKey -( - `UserID` UInt32, - `URL` String, - `EventTime` DateTime -) -ENGINE = MergeTree -PRIMARY KEY tuple(); -``` - - - -接下来,使用以下插入SQL将命中数据集的一个子集插入到表中。这个SQL使用URL表函数类型推断从clickhouse.com加载一个数据集的一部分数据: - -```sql -INSERT INTO hits_NoPrimaryKey SELECT - intHash32(c11::UInt64) AS UserID, - c15 AS URL, - c5 AS EventTime -FROM url('https://datasets.clickhouse.com/hits/tsv/hits_v1.tsv.xz') -WHERE URL != ''; -``` -结果: -```response -Ok. - -0 rows in set. Elapsed: 145.993 sec. Processed 8.87 million rows, 18.40 GB (60.78 thousand rows/s., 126.06 MB/s.) -``` - - -ClickHouse客户端输出了执行结果,插入了887万行数据。 - - -最后,为了简化本文后面的讨论,并使图表和结果可重现,我们使用FINAL关键字optimize该表: - -```sql -OPTIMIZE TABLE hits_NoPrimaryKey FINAL; -``` - -:::note -一般来说,不需要也不建议在加载数据后立即执行optimize。对于这个示例,为什么需要这样做是很明显的。 -::: - - -现在我们执行第一个web分析查询。以下是用户id为749927693的互联网用户点击次数最多的前10个url: - -```sql -SELECT URL, count(URL) as Count -FROM hits_NoPrimaryKey -WHERE UserID = 749927693 -GROUP BY URL -ORDER BY Count DESC -LIMIT 10; -``` -结果: -```response -┌─URL────────────────────────────┬─Count─┐ -│ http://auto.ru/chatay-barana.. │ 170 │ -│ http://auto.ru/chatay-id=371...│ 52 │ -│ http://public_search │ 45 │ -│ http://kovrik-medvedevushku-...│ 36 │ -│ http://forumal │ 33 │ -│ http://korablitz.ru/L_1OFFER...│ 14 │ -│ http://auto.ru/chatay-id=371...│ 14 │ -│ http://auto.ru/chatay-john-D...│ 13 │ -│ http://auto.ru/chatay-john-D...│ 10 │ -│ http://wot/html?page/23600_m...│ 9 │ -└────────────────────────────────┴───────┘ - -10 rows in set. Elapsed: 0.022 sec. -// highlight-next-line -Processed 8.87 million rows, -70.45 MB (398.53 million rows/s., 3.17 GB/s.) -``` - - -ClickHouse客户端输出表明,ClickHouse执行了一个完整的表扫描!我们的表的887万行中的每一行都被加载到ClickHouse中,这不是可扩展的。 - -为了使这种(方式)更有效和更快,我们需要使用一个具有适当主键的表。这将允许ClickHouse自动(基于主键的列)创建一个稀疏的主索引,然后可以用于显著加快我们示例查询的执行。 - - - -## 包含主键的表 - -创建一个包含联合主键UserID和URL列的表: - -```sql -CREATE TABLE hits_UserID_URL -( - `UserID` UInt32, - `URL` String, - `EventTime` DateTime -) -ENGINE = MergeTree -// highlight-next-line -PRIMARY KEY (UserID, URL) -ORDER BY (UserID, URL, EventTime) -SETTINGS index_granularity = 8192, index_granularity_bytes = 0; -``` - -[//]: # (
) -
- - DDL详情 - -

- -为了简化本文后面的讨论,并使图和结果可重现,使用DDL语句有如下说明: -

    -
  • 通过ORDER BY子句指定表的复合排序键
  • -
    -
  • 通过设置配置控制主索引有多少索引项:
  • -
    -
      -
    • index_granularity: 显式设置为其默认值8192。这意味着对于每一组8192行,主索引将有一个索引条目,例如,如果表包含16384行,那么索引将有两个索引条目。 -
    • -
      -
    • index_granularity_bytes: 设置为0表示禁止字适应索引粒度。自适应索引粒度意味着ClickHouse自动为一组n行创建一个索引条目 -
        -
      • 如果n小于8192,但n行的合并行数据大小大于或等于10MB (index_granularity_bytes的默认值)或
      • -
      • n达到8192
      • -
      -
    • -
    -
-

-
- - -上面DDL语句中的主键会基于两个指定的键列创建主索引。 - -
-插入数据: - -```sql -INSERT INTO hits_UserID_URL SELECT - intHash32(c11::UInt64) AS UserID, - c15 AS URL, - c5 AS EventTime -FROM url('https://datasets.clickhouse.com/hits/tsv/hits_v1.tsv.xz') -WHERE URL != ''; -``` -结果: -```response -0 rows in set. Elapsed: 149.432 sec. Processed 8.87 million rows, 18.40 GB (59.38 thousand rows/s., 123.16 MB/s.) -``` - - -
-optimize表: - -```sql -OPTIMIZE TABLE hits_UserID_URL FINAL; -``` - -
-我们可以使用下面的查询来获取关于表的元数据: - -```sql -SELECT - part_type, - path, - formatReadableQuantity(rows) AS rows, - formatReadableSize(data_uncompressed_bytes) AS data_uncompressed_bytes, - formatReadableSize(data_compressed_bytes) AS data_compressed_bytes, - formatReadableSize(primary_key_bytes_in_memory) AS primary_key_bytes_in_memory, - marks, - formatReadableSize(bytes_on_disk) AS bytes_on_disk -FROM system.parts -WHERE (table = 'hits_UserID_URL') AND (active = 1) -FORMAT Vertical; -``` - -结果: - -```response -part_type: Wide -path: ./store/d9f/d9f36a1a-d2e6-46d4-8fb5-ffe9ad0d5aed/all_1_9_2/ -rows: 8.87 million -data_uncompressed_bytes: 733.28 MiB -data_compressed_bytes: 206.94 MiB -primary_key_bytes_in_memory: 96.93 KiB -marks: 1083 -bytes_on_disk: 207.07 MiB - - -1 rows in set. Elapsed: 0.003 sec. -``` - -客户端输出表明: - -- 表数据以wide format存储在一个特定目录,每个列有一个数据文件和mark文件。 -- 表有887万行数据。 -- 未压缩的数据有733.28 MB。 -- 压缩之后的数据有206.94 MB。 -- 有1083个主键索引条目,大小是96.93 KB。 -- 在磁盘上,表的数据、标记文件和主索引文件总共占用207.07 MB。 - - -## 针对海量数据规模的索引设计 - -在传统的关系数据库管理系统中,每个表行包含一个主索引。对于我们的数据集,这将导致主索引——通常是一个B(+)-Tree的数据结构——包含887万个条目。 - -这样的索引允许快速定位特定的行,从而提高查找点查和更新的效率。在B(+)-Tree数据结构中搜索一个条目的平均时间复杂度为O(log2n)。对于一个有887万行的表,这意味着需要23步来定位任何索引条目。 - -这种能力是有代价的:额外的磁盘和内存开销,以及向表中添加新行和向索引中添加条目时更高的插入成本(有时还需要重新平衡B-Tree)。 - -考虑到与B-Tee索引相关的挑战,ClickHouse中的表引擎使用了一种不同的方法。ClickHouseMergeTree Engine引擎系列被设计和优化用来处理大量数据。 - -这些表被设计为每秒接收数百万行插入,并存储非常大(100 pb)的数据量。 - -数据被一批一批的快速写入表中,并在后台应用合并规则。 - -在ClickHouse中,每个数据部分(data part)都有自己的主索引。当他们被合并时,合并部分的主索引也被合并。 - -在大规模中情况下,磁盘和内存的效率是非常重要的。因此,不是为每一行创建索引,而是为一组数据行(称为颗粒(granule))构建一个索引条目。 - -之所以可以使用这种稀疏索引,是因为ClickHouse会按照主键列的顺序将一组行存储在磁盘上。 - -与直接定位单个行(如基于B-Tree的索引)不同,稀疏主索引允许它快速(通过对索引项进行二分查找)识别可能匹配查询的行组。 - -然后潜在的匹配行组(颗粒)以并行的方式被加载到ClickHouse引擎中,以便找到匹配的行。 - -这种索引设计允许主索引很小(它可以而且必须完全适合主内存),同时仍然显著加快查询执行时间:特别是对于数据分析用例中常见的范围查询。 - -下面详细说明了ClickHouse是如何构建和使用其稀疏主索引的。在本文后面,我们将讨论如何选择、移除和排序用于构建索引的表列(主键列)的一些最佳实践。 - - - -## 数据按照主键排序存储在磁盘上 - -上面创建的表有: -- 联合主键 (UserID, URL) -- 联合排序键 (UserID, URL, EventTime)。 - -:::note -- 如果我们只指定了排序键,那么主键将隐式定义为排序键。 - -- 为了提高内存效率,我们显式地指定了一个主键,只包含查询过滤的列。基于主键的主索引被完全加载到主内存中。 - -- 为了上下文的一致性和最大的压缩比例,我们单独定义了排序键,排序键包含当前表所有的列(和压缩算法有关,一般排序之后又更好的压缩率)。 - -- 如果同时指定了主键和排序键,则主键必须是排序键的前缀。 -::: - - -插入的行按照主键列(以及排序键的附加EventTime列)的字典序(从小到大)存储在磁盘上。 - -:::note -ClickHouse允许插入具有相同主键列的多行数据。在这种情况下(参见下图中的第1行和第2行),最终的顺序是由指定的排序键决定的,这里是EventTime列的值。 -::: - - -如下图所示:ClickHouse是列存数据库。 -- 在磁盘上,每个表都有一个数据文件(*.bin),该列的所有值都以压缩格式存储,并且 -- 在这个例子中,这887万行按主键列(以及附加的排序键列)的字典升序存储在磁盘上 - - UserID第一位, - - 然后是URL, - - 最后是EventTime: - - -UserID.bin,URL.bin,和EventTime.bin是UserIDURL,和EventTime列的数据文件。 - -
-
- - -:::note -- 因为主键定义了磁盘上行的字典顺序,所以一个表只能有一个主键。 - -- 我们从0开始对行进行编号,以便与ClickHouse内部行编号方案对齐,该方案也用于记录消息。 -::: - - - -## 数据被组织成颗粒以进行并行数据处理 - -出于数据处理的目的,表的列值在逻辑上被划分为多个颗粒。颗粒是流进ClickHouse进行数据处理的最小的不可分割数据集。这意味着,ClickHouse不是读取单独的行,而是始终读取(以流方式并并行地)整个行组(颗粒)。 -:::note -列值并不物理地存储在颗粒中,颗粒只是用于查询处理的列值的逻辑组织方式。 -::: - -下图显示了如何将表中的887万行(列值)组织成1083个颗粒,这是表的DDL语句包含设置index_granularity(设置为默认值8192)的结果。 - - - -第一个(根据磁盘上的物理顺序)8192行(它们的列值)在逻辑上属于颗粒0,然后下一个8192行(它们的列值)属于颗粒1,以此类推。 - -:::note -- 最后一个颗粒(1082颗粒)是少于8192行的。 - -- 我们将主键列(UserID, URL)中的一些列值标记为橙色。 - - 这些橙色标记的列值是每个颗粒中每个主键列的最小值。这里的例外是最后一个颗粒(上图中的颗粒1082),最后一个颗粒我们标记的是最大的值。 - - 正如我们将在下面看到的,这些橙色标记的列值将是表主索引中的条目。 - -- 我们从0开始对行进行编号,以便与ClickHouse内部行编号方案对齐,该方案也用于记录消息。 -::: - - - -## 每个颗粒对应主索引的一个条目 - -主索引是基于上图中显示的颗粒创建的。这个索引是一个未压缩的扁平数组文件(primary.idx),包含从0开始的所谓的数字索引标记。 - -下面的图显示了索引存储了每个颗粒的最小主键列值(在上面的图中用橙色标记的值)。 -例如: -- 第一个索引条目(下图中的“mark 0”)存储上图中颗粒0的主键列的最小值, -- 第二个索引条目(下图中的“mark 1”)存储上图中颗粒1的主键列的最小值,以此类推。 - - - -在我们的表中,索引总共有1083个条目,887万行数据和1083个颗粒: - - - -:::note -- 最后一个索引条目(上图中的“mark 1082”)存储了上图中颗粒1082的主键列的最大值。 - -- 索引条目(索引标记)不是基于表中的特定行,而是基于颗粒。例如,对于上图中的索引条目‘mark 0’,在我们的表中没有UserID为240.923且URL为“goal://metry=10000467796a411…”的行,相反,对于该表,有一个颗粒0,在该颗粒中,最小UserID值是240.923,最小URL值是“goal://metry=10000467796a411…”,这两个值来自不同的行。 - -- 主索引文件完全加载到主内存中。如果文件大于可用的空闲内存空间,则ClickHouse将发生错误。 -::: - - -主键条目称为索引标记,因为每个索引条目都标志着特定数据范围的开始。对于示例表: -- UserID index marks:
- 主索引中存储的UserID值按升序排序。
- 上图中的‘mark 1’指示颗粒1中所有表行的UserID值,以及随后所有颗粒中的UserID值,都保证大于或等于4.073.710。 - - [正如我们稍后将看到的](#query-on-userid-fast), 当查询对主键的第一列进行过滤时,此全局有序使ClickHouse能够对第一个键列的索引标记使用二分查找算法。 - -- URL index marks:
- 主键列UserIDURL有相同的基数,这意味着第一列之后的所有主键列的索引标记通常只表示每个颗粒的数据范围。
- 例如,‘mark 0’中的URL列所有的值都大于等于goal://metry=10000467796a411..., 然后颗粒1中的URL并不是如此,这是因为‘mark 1‘与‘mark 0‘具有不同的UserID列值。 - - 稍后我们将更详细地讨论这对查询执行性能的影响。 - -## 主索引被用来选择颗粒 - -现在,我们可以在主索引的支持下执行查询。 - - -下面计算UserID 749927693点击次数最多的10个url。 - -```sql -SELECT URL, count(URL) AS Count -FROM hits_UserID_URL -WHERE UserID = 749927693 -GROUP BY URL -ORDER BY Count DESC -LIMIT 10; -``` - -结果: - - -```response -┌─URL────────────────────────────┬─Count─┐ -│ http://auto.ru/chatay-barana.. │ 170 │ -│ http://auto.ru/chatay-id=371...│ 52 │ -│ http://public_search │ 45 │ -│ http://kovrik-medvedevushku-...│ 36 │ -│ http://forumal │ 33 │ -│ http://korablitz.ru/L_1OFFER...│ 14 │ -│ http://auto.ru/chatay-id=371...│ 14 │ -│ http://auto.ru/chatay-john-D...│ 13 │ -│ http://auto.ru/chatay-john-D...│ 10 │ -│ http://wot/html?page/23600_m...│ 9 │ -└────────────────────────────────┴───────┘ - -10 rows in set. Elapsed: 0.005 sec. -// highlight-next-line -Processed 8.19 thousand rows, -740.18 KB (1.53 million rows/s., 138.59 MB/s.) -``` - -ClickHouse客户端的输出显示,没有进行全表扫描,只有8.19万行流到ClickHouse。 - - -如果trace logging打开了,那ClickHouse服务端日志会显示ClickHouse正在对1083个UserID索引标记执行二分查找以便识别可能包含UserID列值为749927693的行的颗粒。这需要19个步骤,平均时间复杂度为O(log2 n): -```response -...Executor): Key condition: (column 0 in [749927693, 749927693]) -// highlight-next-line -...Executor): Running binary search on index range for part all_1_9_2 (1083 marks) -...Executor): Found (LEFT) boundary mark: 176 -...Executor): Found (RIGHT) boundary mark: 177 -...Executor): Found continuous range in 19 steps -...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, -// highlight-next-line - 1/1083 marks by primary key, 1 marks to read from 1 ranges -...Reading ...approx. 8192 rows starting from 1441792 -``` - - -我们可以在上面的跟踪日志中看到,1083个现有标记中有一个满足查询。 - -
- - Trace Log详情 - -

- -Mark 176 was identified (the 'found left boundary mark' is inclusive, the 'found right boundary mark' is exclusive), and therefore all 8192 rows from granule 176 (which starts at row 1.441.792 - we will see that later on in this article) are then streamed into ClickHouse in order to find the actual rows with a UserID column value of 749927693. -

-
- -我们也可以通过使用EXPLAIN来重现这个结果: -```sql -EXPLAIN indexes = 1 -SELECT URL, count(URL) AS Count -FROM hits_UserID_URL -WHERE UserID = 749927693 -GROUP BY URL -ORDER BY Count DESC -LIMIT 10; -``` - -结果如下: - -```response -┌─explain───────────────────────────────────────────────────────────────────────────────┐ -│ Expression (Projection) │ -│ Limit (preliminary LIMIT (without OFFSET)) │ -│ Sorting (Sorting for ORDER BY) │ -│ Expression (Before ORDER BY) │ -│ Aggregating │ -│ Expression (Before GROUP BY) │ -│ Filter (WHERE) │ -│ SettingQuotaAndLimits (Set limits and quota after reading from storage) │ -│ ReadFromMergeTree │ -│ Indexes: │ -│ PrimaryKey │ -│ Keys: │ -│ UserID │ -│ Condition: (UserID in [749927693, 749927693]) │ -│ Parts: 1/1 │ -// highlight-next-line -│ Granules: 1/1083 │ -└───────────────────────────────────────────────────────────────────────────────────────┘ - -16 rows in set. Elapsed: 0.003 sec. -``` -客户端输出显示,在1083个颗粒中选择了一个可能包含UserID列值为749927693的行。 - - -:::note Conclusion -当查询对联合主键的一部分并且是第一个主键进行过滤时,ClickHouse将主键索引标记运行二分查找算法。 -::: - -
- - -正如上面所讨论的,ClickHouse使用它的稀疏主索引来快速(通过二分查找算法)选择可能包含匹配查询的行的颗粒。 - -这是ClickHouse查询执行的**第一阶段(颗粒选择)**。 - -在**第二阶段(数据读取中)**, ClickHouse定位所选的颗粒,以便将它们的所有行流到ClickHouse引擎中,以便找到实际匹配查询的行。 - -我们将在下一节更详细地讨论第二阶段。 - - - -## 标记文件用来定位颗粒 - -下图描述了上表主索引文件的一部分。 - - - -如上所述,通过对索引的1083个UserID标记进行二分搜索,确定了第176个标记。因此,它对应的颗粒176可能包含UserID列值为749.927.693的行。 - -
- - 颗粒选择的具体过程 - -

- -上图显示,标记176是第一个UserID值小于749.927.693的索引条目,并且下一个标记(标记177)的颗粒177的最小UserID值大于该值的索引条目。因此,只有标记176对应的颗粒176可能包含UserID列值为749.927.693的行。 -

-
- -为了确认(或排除)颗粒176中的某些行包含UserID列值为749.927.693,需要将属于此颗粒的所有8192行读取到ClickHouse。 - -为了读取这部分数据,ClickHouse需要知道颗粒176的物理地址。 - -在ClickHouse中,我们表的所有颗粒的物理位置都存储在标记文件中。与数据文件类似,每个表的列有一个标记文件。 - -下图显示了三个标记文件UserID.mrk、URL.mrk、EventTime.mrk,为表的UserID、URL和EventTime列存储颗粒的物理位置。 - - - -我们已经讨论了主索引是一个扁平的未压缩数组文件(primary.idx),其中包含从0开始编号的索引标记。 - -类似地,标记文件也是一个扁平的未压缩数组文件(*.mrk),其中包含从0开始编号的标记。 - -一旦ClickHouse确定并选择了可能包含查询所需的匹配行的颗粒的索引标记,就可以在标记文件数组中查找,以获得颗粒的物理位置。 - -每个特定列的标记文件条目以偏移量的形式存储两个位置: - -- 第一个偏移量(上图中的'block_offset')是在包含所选颗粒的压缩版本的压缩列数据文件中定位块。这个压缩块可能包含几个压缩的颗粒。所定位的压缩文件块在读取时被解压到内存中。 - -- 标记文件的第二个偏移量(上图中的“granule_offset”)提供了颗粒在解压数据块中的位置。 - -定位到的颗粒中的所有8192行数据都会被ClickHouse加载然后进一步处理。 - - -:::note 为什么需要mark文件 - -为什么主索引不直接包含与索引标记相对应的颗粒的物理位置? - -因为ClickHouse设计的场景就是超大规模数据,非常高效地使用磁盘和内存非常重要。 - -主索引文件需要放入内存中。 - -对于我们的示例查询,ClickHouse使用了主索引,并选择了可能包含与查询匹配的行的单个颗粒。只有对于这一个颗粒,ClickHouse才需定位物理位置,以便将相应的行组读取以进一步的处理。 - -而且,只有UserID和URL列需要这个偏移量信息。 - -对于查询中不使用的列,例如EventTime,不需要偏移量信息。 - -对于我们的示例查询,Clickhouse只需要UserID数据文件(UserID.bin)中176颗粒的两个物理位置偏移,以及URL数据文件(URL.data)中176颗粒的两个物理位置偏移。 - -由mark文件提供的间接方法避免了直接在主索引中存储所有三个列的所有1083个颗粒的物理位置的条目:因此避免了在主内存中有不必要的(可能未使用的)数据。 - -::: - -下面的图表和文本说明了我们的查询示例,ClickHouse如何在UserID.bin数据文件中定位176颗粒。 - - - -我们在本文前面讨论过,ClickHouse选择了主索引标记176,因此176颗粒可能包含查询所需的匹配行。 - -ClickHouse现在使用从索引中选择的标记号(176)在UserID.mark中进行位置数组查找,以获得两个偏移量,用于定位颗粒176。 - -如图所示,第一个偏移量是定位UserID.bin数据文件中的压缩文件块,该数据文件包含颗粒176的压缩数据。 - -一旦所定位的文件块被解压缩到主内存中,就可以使用标记文件的第二个偏移量在未压缩的数据中定位颗粒176。 - -ClickHouse需要从UserID.bin数据文件和URL.bin数据文件中定位(读取)颗粒176,以便执行我们的示例查询(UserID为749.927.693的互联网用户点击次数最多的10个url)。 - -上图显示了ClickHouse如何定位UserID.bin数据文件的颗粒。 - -同时,ClickHouse对URL.bin数据文件的颗粒176执行相同的操作。这两个不同的颗粒被对齐并加载到ClickHouse引擎以进行进一步的处理,即聚合并计算UserID为749.927.693的所有行的每组URL值,最后以计数降序输出10个最大的URL组。 - - - - -## 查询使用第二位主键的性能问题 - - -当查询对复合键的一部分并且是第一个主键列进行过滤时,ClickHouse将对主键列的索引标记运行二分查找。 - -但是,当查询对联合主键的一部分但不是第一个键列进行过滤时,会发生什么情况? - -:::note -我们讨论了这样一种场景:查询不是显式地对第一个主键列进行过滤,而是对第一个主键列之后的任何键列进行过滤。 - -当查询同时对第一个主键列和第一个主键列之后的任何键列进行过滤时,ClickHouse将对第一个主键列的索引标记运行二分查找。 -::: - -
-
- - -我们使用一个查询来计算最点击"http://public_search"的最多的前10名用户: - -```sql -SELECT UserID, count(UserID) AS Count -FROM hits_UserID_URL -WHERE URL = 'http://public_search' -GROUP BY UserID -ORDER BY Count DESC -LIMIT 10; -``` - -结果是: -```response -┌─────UserID─┬─Count─┐ -│ 2459550954 │ 3741 │ -│ 1084649151 │ 2484 │ -│ 723361875 │ 729 │ -│ 3087145896 │ 695 │ -│ 2754931092 │ 672 │ -│ 1509037307 │ 582 │ -│ 3085460200 │ 573 │ -│ 2454360090 │ 556 │ -│ 3884990840 │ 539 │ -│ 765730816 │ 536 │ -└────────────┴───────┘ - -10 rows in set. Elapsed: 0.086 sec. -// highlight-next-line -Processed 8.81 million rows, -799.69 MB (102.11 million rows/s., 9.27 GB/s.) -``` - -客户端输出表明,尽管URL列是联合主键的一部分,ClickHouse几乎执行了一一次全表扫描!ClickHouse从表的887万行中读取881万行。 - -如果启用了trace日志,那么ClickHouse服务日志文件显示,ClickHouse在1083个URL索引标记上使用了通用的排除搜索,以便识别那些可能包含URL列值为"http://public_search"的行。 -```response -...Executor): Key condition: (column 1 in ['http://public_search', - 'http://public_search']) -// highlight-next-line -...Executor): Used generic exclusion search over index for part all_1_9_2 - with 1537 steps -...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, -// highlight-next-line - 1076/1083 marks by primary key, 1076 marks to read from 5 ranges -...Executor): Reading approx. 8814592 rows with 10 streams -``` -我们可以在上面的跟踪日志示例中看到,1083个颗粒中有1076个(通过标记)被选中,因为可能包含具有匹配URL值的行。 - -这将导致881万行被读取到ClickHouse引擎中(通过使用10个流并行地读取),以便识别实际包含URL值"http://public_search"的行。 - -然而,[稍后](#query-on-url-fast)仅仅39个颗粒包含匹配的行。 - -虽然基于联合主键(UserID, URL)的主索引对于加快过滤具有特定UserID值的行的查询非常有用,但对于过滤具有特定URL值的行的查询,索引并没有提供显著的帮助。 - -原因是URL列不是第一个主键列,因此ClickHouse是使用一个通用的排除搜索算法(而不是二分查找)查找URL列的索引标志,和UserID主键列不同,它的算法的有效性依赖于URL列的基数。 - -为了说明,我们给出通用的排除搜索算法的工作原理: - -
- - 通用排除搜索算法 - -

- - - - -下面将演示当通过第一个列之后的任何列选择颗粒时,当前一个键列具有或高或低的基数时,ClickHouse通用排除搜索算法 是如何工作的。 - -作为这两种情况的例子,我们将假设: -- 搜索URL值为"W3"的行。 -- 点击表抽象简化为只有简单值的UserID和UserID。 -- 相同联合主键(UserID、URL)。这意味着行首先按UserID值排序,具有相同UserID值的行然后再按URL排序。 -- 颗粒大小为2,即每个颗粒包含两行。 - -在下面的图表中,我们用橙色标注了每个颗粒的最小键列值。 - -**前缀主键低基数** - -假设UserID具有较低的基数。在这种情况下,相同的UserID值很可能分布在多个表行和颗粒上,从而分布在索引标记上。对于具有相同UserID的索引标记,索引标记的URL值按升序排序(因为表行首先按UserID排序,然后按URL排序)。这使得有效的过滤如下所述: - - - -在上图中,我们的抽象样本数据的颗粒选择过程有三种不同的场景: - - -1. 如果索引标记0的(最小)URL值小于W3,并且紧接索引标记的URL值也小于W3,则可以排除索引标记0,因为标记0、标记1和标记2具有相同的UserID值。注意,这个排除前提条件确保颗粒0和下一个颗粒1完全由U1 UserID值组成,这样ClickHouse就可以假设颗粒0中的最大URL值也小于W3并排除该颗粒。 - -2. 如果索引标记1的URL值小于(或等于)W3,并且后续索引标记的URL值大于(或等于)W3,则选择索引标记1,因为这意味着粒度1可能包含URL为W3的行)。 - -3. 可以排除URL值大于W3的索引标记2和3,因为主索引的索引标记存储了每个颗粒的最小键列值,因此颗粒2和3不可能包含URL值W3。 - - - -**前缀主键高基数** - -当UserID具有较高的基数时,相同的UserID值不太可能分布在多个表行和颗粒上。这意味着索引标记的URL值不是单调递增的: - - - - -正如在上面的图表中所看到的,所有URL值小于W3的标记都被选中,以便将其关联的颗粒的行加载到ClickHouse引擎中。 - -这是因为虽然图中的所有索引标记都属于上面描述的场景1,但它们不满足前面提到的排除前提条件,即两个直接随后的索引标记都具有与当前标记相同的UserID值,因此不能被排除。 - -例如,考虑索引标记0,其URL值小于W3,并且其直接后续索引标记的URL值也小于W3。这不能排除,因为两个直接随后的索引标记1和2与当前标记0没有相同的UserID值。 - -请注意,随后的两个索引标记需要具有相同的UserID值。这确保了当前和下一个标记的颗粒完全由U1 UserID值组成。如果仅仅是下一个标记具有相同的UserID,那么下一个标记的URL值可能来自具有不同UserID的表行——当您查看上面的图表时,确实是这样的情况,即W2来自U2而不是U1的行。 - -这最终阻止了ClickHouse对颗粒0中的最大URL值进行假设。相反,它必须假设颗粒0可能包含URL值为W3的行,并被迫选择标记0。 - -
-同样的情况也适用于标记1、2和3。 - -

-
- -:::note 结论 -当查询对联合主键的一部分列(但不是第一个键列)进行过滤时,ClickHouse使用的通用排除搜索算法(而不是二分查找)在前一个键列基数较低时最有效。 -::: - -在我们的示例数据集中,两个键列(UserID、URL)都具有类似的高基数,并且,如前所述,当URL列的前一个键列具有较高基数时,通用排除搜索算法不是很有效。 - -:::note 看下跳数索引 -因为UserID和URL具有较高的基数,[根据URL过滤数据](#query-on-url)不是特别有效,对URL列创建[二级跳数索引](./skipping-indexes.md)同样也不会有太多改善。 - -例如,这两个语句在我们的表的URL列上创建并填充一个minmax跳数索引。 -```sql -ALTER TABLE hits_UserID_URL ADD INDEX url_skipping_index URL TYPE minmax GRANULARITY 4; -ALTER TABLE hits_UserID_URL MATERIALIZE INDEX url_skipping_index; -``` -ClickHouse现在创建了一个额外的索引来存储—每组4个连续的颗粒(注意上面ALTER TABLE语句中的GRANULARITY 4子句)—最小和最大的URL值: - - - -第一个索引条目(上图中的mark 0)存储属于表的前4个颗粒的行的最小和最大URL值。 - -第二个索引条目(mark 1)存储属于表中下一个4个颗粒的行的最小和最大URL值,依此类推。 - -(ClickHouse还为跳数索引创建了一个特殊的标记文件,用于定位与索引标记相关联的颗粒组。) - -由于UserID和URL的基数相似,在执行对URL的查询过滤时,这个二级跳数索引不能帮助排除选择的颗粒。 - -正在寻找的特定URL值('http://public_search')很可能是索引为每组颗粒存储的最小值和最大值之间的值,导致ClickHouse被迫选择这组颗粒(因为它们可能包含匹配查询的行)。 - - - - -::: - - -因此,如果我们想显著提高过滤具有特定URL的行的示例查询的速度,那么我们需要使用针对该查询优化的主索引。 - -此外,如果我们想保持过滤具有特定UserID的行的示例查询的良好性能,那么我们需要使用多个主索引。 - -下面是实现这一目标的方法。 - - - -## 使用多个主键索引进行调优 - - -如果我们想显著加快我们的两个示例查询——一个过滤具有特定UserID的行,一个过滤具有特定URL的行——那么我们需要使用多个主索引,通过使用这三个方法中的一个: - -- 新建一个不同主键的新表。 -- 创建一个雾化视图。 -- 增加projection。 - -这三个方法都会有效地将示例数据复制到另一个表中,以便重新组织表的主索引和行排序顺序。 - -然而,这三个选项的不同之处在于,附加表对于查询和插入语句的路由对用户的透明程度。 - -当创建有不同主键的第二个表时,查询必须显式地发送给最适合查询的表版本,并且必须显式地插入新数据到两个表中,以保持表的同步: - - - - -在物化视图中,额外的表被隐藏,数据自动在两个表之间保持同步: - - - -projection方式是最透明的选项,因为除了自动保持隐藏的附加表与数据变化同步外,ClickHouse还会自动选择最有效的表版本进行查询: - - -下面我们使用真实的例子详细讨论下这三种方式。 - - - -## 通过辅助表使用联合主键索引 - - -我们创建一个新的附加表,其中我们在主键中切换键列的顺序(与原始表相比): - -```sql -CREATE TABLE hits_URL_UserID -( - `UserID` UInt32, - `URL` String, - `EventTime` DateTime -) -ENGINE = MergeTree -// highlight-next-line -PRIMARY KEY (URL, UserID) -ORDER BY (URL, UserID, EventTime) -SETTINGS index_granularity = 8192, index_granularity_bytes = 0; -``` - -写入887万行源表数据: - -```sql -INSERT INTO hits_URL_UserID -SELECT * from hits_UserID_URL; -``` - -结果: - -```response -Ok. - -0 rows in set. Elapsed: 2.898 sec. Processed 8.87 million rows, 838.84 MB (3.06 million rows/s., 289.46 MB/s.) -``` - -最后optimize下: -```sql -OPTIMIZE TABLE hits_URL_UserID FINAL; -``` - -因为我们切换了主键中列的顺序,插入的行现在以不同的字典顺序存储在磁盘上(与我们的原始表相比),因此该表的1083个颗粒也包含了与以前不同的值: - - - -主键索引如下: - - -现在计算最频繁点击URL"http://public_search"的前10名用户,这时候的查询速度是明显加快的: -```sql -SELECT UserID, count(UserID) AS Count -// highlight-next-line -FROM hits_URL_UserID -WHERE URL = 'http://public_search' -GROUP BY UserID -ORDER BY Count DESC -LIMIT 10; -``` - -结果: - - -```response -┌─────UserID─┬─Count─┐ -│ 2459550954 │ 3741 │ -│ 1084649151 │ 2484 │ -│ 723361875 │ 729 │ -│ 3087145896 │ 695 │ -│ 2754931092 │ 672 │ -│ 1509037307 │ 582 │ -│ 3085460200 │ 573 │ -│ 2454360090 │ 556 │ -│ 3884990840 │ 539 │ -│ 765730816 │ 536 │ -└────────────┴───────┘ - -10 rows in set. Elapsed: 0.017 sec. -// highlight-next-line -Processed 319.49 thousand rows, -11.38 MB (18.41 million rows/s., 655.75 MB/s.) -``` - -现在没有全表扫描了,ClickHouse执行高效了很多。 - -对于原始表中的主索引(其中UserID是第一个键列,URL是第二个键列),ClickHouse在索引标记上使用了通用排除搜索来执行该查询,但这不是很有效,因为UserID和URL的基数同样很高。 - -将URL作为主索引的第一列,ClickHouse现在对索引标记运行二分搜索。ClickHouse服务器日志文件中对应的跟踪日志: - -```response -...Executor): Key condition: (column 0 in ['http://public_search', - 'http://public_search']) -// highlight-next-line -...Executor): Running binary search on index range for part all_1_9_2 (1083 marks) -...Executor): Found (LEFT) boundary mark: 644 -...Executor): Found (RIGHT) boundary mark: 683 -...Executor): Found continuous range in 19 steps -...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, -// highlight-next-line - 39/1083 marks by primary key, 39 marks to read from 1 ranges -...Executor): Reading approx. 319488 rows with 2 streams -``` -ClickHouse只选择了39个索引标记,而不是使用通用排除搜索时的1076个。 - -请注意,辅助表经过了优化,以加快对url的示例查询过滤的执行。 - -像之前我们查询过滤URL一样,如果我们现在对辅助表查询过滤UserID,性能同样会比较差,因为现在UserID是第二主索引键列,所以ClickHouse将使用通用排除搜索算法查找颗粒,这对于类似高基数的UserID和URL来说不是很有效。 - -点击下面了解详情: -
- - 对UserID的查询过滤性能较差 - -

- -```sql -SELECT URL, count(URL) AS Count -FROM hits_URL_UserID -WHERE UserID = 749927693 -GROUP BY URL -ORDER BY Count DESC -LIMIT 10; -``` - -结果 - -```response -┌─URL────────────────────────────┬─Count─┐ -│ http://auto.ru/chatay-barana.. │ 170 │ -│ http://auto.ru/chatay-id=371...│ 52 │ -│ http://public_search │ 45 │ -│ http://kovrik-medvedevushku-...│ 36 │ -│ http://forumal │ 33 │ -│ http://korablitz.ru/L_1OFFER...│ 14 │ -│ http://auto.ru/chatay-id=371...│ 14 │ -│ http://auto.ru/chatay-john-D...│ 13 │ -│ http://auto.ru/chatay-john-D...│ 10 │ -│ http://wot/html?page/23600_m...│ 9 │ -└────────────────────────────────┴───────┘ - -10 rows in set. Elapsed: 0.024 sec. -// highlight-next-line -Processed 8.02 million rows, -73.04 MB (340.26 million rows/s., 3.10 GB/s.) -``` - -服务端日志: -```response -...Executor): Key condition: (column 1 in [749927693, 749927693]) -// highlight-next-line -...Executor): Used generic exclusion search over index for part all_1_9_2 - with 1453 steps -...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, -// highlight-next-line - 980/1083 marks by primary key, 980 marks to read from 23 ranges -...Executor): Reading approx. 8028160 rows with 10 streams -``` -

-
- - - -现在我们有了两张表。优化了对UserID和URL的查询过滤,分别: - - - - - - - - - - -## 通过物化视图使用联合主键 - -在原表上创建物化视图: -```sql -CREATE MATERIALIZED VIEW mv_hits_URL_UserID -ENGINE = MergeTree() -PRIMARY KEY (URL, UserID) -ORDER BY (URL, UserID, EventTime) -POPULATE -AS SELECT * FROM hits_UserID_URL; -``` - -结果: - -```response -Ok. - -0 rows in set. Elapsed: 2.935 sec. Processed 8.87 million rows, 838.84 MB (3.02 million rows/s., 285.84 MB/s.) -``` - -:::note -- 我们在视图的主键中切换键列的顺序(与原始表相比) -- 雾化视图由一个隐藏表支持,该表的行顺序和主索引基于给定的主键定义 -- 我们使用POPULATE关键字,以便用源表hits_UserID_URL中的所有887万行立即导入新的物化视图 -- 如果在源表hits_UserID_URL中插入了新行,那么这些行也会自动插入到隐藏表中 -- 实际上,隐式创建的隐藏表的行顺序和主索引与我们上面显式创建的辅助表相同: - - - - - - - -ClickHouse将隐藏表的列数据文件(.bin)、标记文件(.mrk2)和主索引(primary.idx)存储在ClickHouse服务器的数据目录的一个特殊文件夹中: - - - - -::: - - -物化视图背后的隐藏表(和它的主索引)现在可以用来显著加快我们在URL列上查询过滤的执行速度: -```sql -SELECT UserID, count(UserID) AS Count -// highlight-next-line -FROM mv_hits_URL_UserID -WHERE URL = 'http://public_search' -GROUP BY UserID -ORDER BY Count DESC -LIMIT 10; -``` - -结果: - -```response -┌─────UserID─┬─Count─┐ -│ 2459550954 │ 3741 │ -│ 1084649151 │ 2484 │ -│ 723361875 │ 729 │ -│ 3087145896 │ 695 │ -│ 2754931092 │ 672 │ -│ 1509037307 │ 582 │ -│ 3085460200 │ 573 │ -│ 2454360090 │ 556 │ -│ 3884990840 │ 539 │ -│ 765730816 │ 536 │ -└────────────┴───────┘ - -10 rows in set. Elapsed: 0.026 sec. -// highlight-next-line -Processed 335.87 thousand rows, -13.54 MB (12.91 million rows/s., 520.38 MB/s.) -``` - -物化视图背后隐藏表(及其主索引)实际上与我们显式创建的辅助表是相同的,所以查询的执行方式与显式创建的表相同。 - -ClickHouse服务器日志文件中相应的跟踪日志确认了ClickHouse正在对索引标记运行二分搜索: - -```response -...Executor): Key condition: (column 0 in ['http://public_search', - 'http://public_search']) -// highlight-next-line -...Executor): Running binary search on index range ... -... -...Executor): Selected 4/4 parts by partition key, 4 parts by primary key, -// highlight-next-line - 41/1083 marks by primary key, 41 marks to read from 4 ranges -...Executor): Reading approx. 335872 rows with 4 streams -``` - - - -## 通过projections使用联合主键索引 - - -Projections目前是一个实验性的功能,因此我们需要告诉ClickHouse: - -```sql -SET allow_experimental_projection_optimization = 1; -``` - - -在原表上创建projection: -```sql -ALTER TABLE hits_UserID_URL - ADD PROJECTION prj_url_userid - ( - SELECT * - ORDER BY (URL, UserID) - ); -``` - -雾化projection: -```sql -ALTER TABLE hits_UserID_URL - MATERIALIZE PROJECTION prj_url_userid; -``` - -:::note -- 该projection正在创建一个隐藏表,该表的行顺序和主索引基于该projection的给定order BY子句 -- 我们使用MATERIALIZE关键字,以便立即用源表hits_UserID_URL的所有887万行导入隐藏表 -- 如果在源表hits_UserID_URL中插入了新行,那么这些行也会自动插入到隐藏表中 -- 查询总是(从语法上)针对源表hits_UserID_URL,但是如果隐藏表的行顺序和主索引允许更有效地执行查询,那么将使用该隐藏表 -- 实际上,隐式创建的隐藏表的行顺序和主索引与我们显式创建的辅助表相同: - - - -ClickHouse将隐藏表的列数据文件(.bin)、标记文件(.mrk2)和主索引(primary.idx)存储在一个特殊的文件夹中(在下面的截图中用橙色标记),紧挨着源表的数据文件、标记文件和主索引文件: - - -::: - -由投影创建的隐藏表(以及它的主索引)现在可以(隐式地)用于显著加快URL列上查询过滤的执行。注意,查询在语法上针对投影的源表。 - -```sql -SELECT UserID, count(UserID) AS Count -// highlight-next-line -FROM hits_UserID_URL -WHERE URL = 'http://public_search' -GROUP BY UserID -ORDER BY Count DESC -LIMIT 10; -``` - -结果: - -```response -┌─────UserID─┬─Count─┐ -│ 2459550954 │ 3741 │ -│ 1084649151 │ 2484 │ -│ 723361875 │ 729 │ -│ 3087145896 │ 695 │ -│ 2754931092 │ 672 │ -│ 1509037307 │ 582 │ -│ 3085460200 │ 573 │ -│ 2454360090 │ 556 │ -│ 3884990840 │ 539 │ -│ 765730816 │ 536 │ -└────────────┴───────┘ - -10 rows in set. Elapsed: 0.029 sec. -// highlight-next-line -Processed 319.49 thousand rows, 1 -1.38 MB (11.05 million rows/s., 393.58 MB/s.) -``` - -因为由投影创建的隐藏表(及其主索引)实际上与我们显式创建的辅助表相同,所以查询的执行方式与显式创建的表相同。 - -ClickHouse服务器日志文件中跟踪日志确认了ClickHouse正在对索引标记运行二分搜索: - - -```response -...Executor): Key condition: (column 0 in ['http://public_search', - 'http://public_search']) -// highlight-next-line -...Executor): Running binary search on index range for part prj_url_userid (1083 marks) -...Executor): ... -// highlight-next-line -...Executor): Choose complete Normal projection prj_url_userid -...Executor): projection required columns: URL, UserID -...Executor): Selected 1/1 parts by partition key, 1 parts by primary key, -// highlight-next-line - 39/1083 marks by primary key, 39 marks to read from 1 ranges -...Executor): Reading approx. 319488 rows with 2 streams -``` - - -## 移除无效的主键列 - - -带有联合主键(UserID, URL)的表的主索引对于加快UserID的查询过滤非常有用。但是,尽管URL列是联合主键的一部分,但该索引在加速URL查询过滤方面并没有提供显著的帮助。 - -反之亦然:具有复合主键(URL, UserID)的表的主索引加快了URL上的查询过滤,但没有为UserID上的查询过滤提供太多支持。 - -由于主键列UserID和URL的基数同样很高,过滤第二个键列的查询不会因为第二个键列位于索引中而受益太多。 - -因此,从主索引中删除第二个键列(从而减少索引的内存消耗)并使用多个主索引是有意义的。 - -但是,如果复合主键中的键列在基数上有很大的差异,那么查询按基数升序对主键列进行排序是有益的。 - -主键键列之间的基数差越大,主键键列的顺序越重要。我们将在以后的文章中对此进行演示。请继续关注。 From 0e6a0d589f7a5fbe05472f8e1f1c50bf074b20db Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Wed, 3 Aug 2022 16:27:17 -0400 Subject: [PATCH 306/672] moved image dir --- .../sparse-primary-indexes.md | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/zh/guides/improving-query-performance/sparse-primary-indexes.md b/docs/zh/guides/improving-query-performance/sparse-primary-indexes.md index a25293fae24..3d91d75432c 100644 --- a/docs/zh/guides/improving-query-performance/sparse-primary-indexes.md +++ b/docs/zh/guides/improving-query-performance/sparse-primary-indexes.md @@ -306,7 +306,7 @@ ClickHouse允许插入具有相同主键列的多行数据。在这种情况下( - 然后是URL, - 最后是EventTime: - + UserID.bin,URL.bin,和EventTime.bin是UserIDURL,和EventTime列的数据文件。
@@ -330,7 +330,7 @@ UserID.bin,URL.bin,和EventTime.bin是UserID 下图显示了如何将表中的887万行(列值)组织成1083个颗粒,这是表的DDL语句包含设置index_granularity(设置为默认值8192)的结果。 - + 第一个(根据磁盘上的物理顺序)8192行(它们的列值)在逻辑上属于颗粒0,然后下一个8192行(它们的列值)属于颗粒1,以此类推。 @@ -357,11 +357,11 @@ UserID.bin,URL.bin,和EventTime.bin是UserID - 第一个索引条目(下图中的“mark 0”)存储上图中颗粒0的主键列的最小值, - 第二个索引条目(下图中的“mark 1”)存储上图中颗粒1的主键列的最小值,以此类推。 - + 在我们的表中,索引总共有1083个条目,887万行数据和1083个颗粒: - + :::note - 最后一个索引条目(上图中的“mark 1082”)存储了上图中颗粒1082的主键列的最大值。 @@ -514,7 +514,7 @@ LIMIT 10; 下图描述了上表主索引文件的一部分。 - + 如上所述,通过对索引的1083个UserID标记进行二分搜索,确定了第176个标记。因此,它对应的颗粒176可能包含UserID列值为749.927.693的行。 @@ -536,7 +536,7 @@ LIMIT 10; 下图显示了三个标记文件UserID.mrk、URL.mrk、EventTime.mrk,为表的UserID、URL和EventTime列存储颗粒的物理位置。 - + 我们已经讨论了主索引是一个扁平的未压缩数组文件(primary.idx),其中包含从0开始编号的索引标记。 @@ -575,7 +575,7 @@ LIMIT 10; 下面的图表和文本说明了我们的查询示例,ClickHouse如何在UserID.bin数据文件中定位176颗粒。 - + 我们在本文前面讨论过,ClickHouse选择了主索引标记176,因此176颗粒可能包含查询所需的匹配行。 @@ -692,7 +692,7 @@ Processed 8.81 million rows, 假设UserID具有较低的基数。在这种情况下,相同的UserID值很可能分布在多个表行和颗粒上,从而分布在索引标记上。对于具有相同UserID的索引标记,索引标记的URL值按升序排序(因为表行首先按UserID排序,然后按URL排序)。这使得有效的过滤如下所述: - + 在上图中,我们的抽象样本数据的颗粒选择过程有三种不同的场景: @@ -709,7 +709,7 @@ Processed 8.81 million rows, 当UserID具有较高的基数时,相同的UserID值不太可能分布在多个表行和颗粒上。这意味着索引标记的URL值不是单调递增的: - + 正如在上面的图表中所看到的,所有URL值小于W3的标记都被选中,以便将其关联的颗粒的行加载到ClickHouse引擎中。 @@ -744,7 +744,7 @@ ALTER TABLE hits_UserID_URL MATERIALIZE INDEX url_skipping_index; ``` ClickHouse现在创建了一个额外的索引来存储—每组4个连续的颗粒(注意上面ALTER TABLE语句中的GRANULARITY 4子句)—最小和最大的URL值: - + 第一个索引条目(上图中的mark 0)存储属于表的前4个颗粒的行的最小和最大URL值。 @@ -785,15 +785,15 @@ ClickHouse现在创建了一个额外的索引来存储—每组4个连续的颗 当创建有不同主键的第二个表时,查询必须显式地发送给最适合查询的表版本,并且必须显式地插入新数据到两个表中,以保持表的同步: - + 在物化视图中,额外的表被隐藏,数据自动在两个表之间保持同步: - + projection方式是最透明的选项,因为除了自动保持隐藏的附加表与数据变化同步外,ClickHouse还会自动选择最有效的表版本进行查询: - + 下面我们使用真实的例子详细讨论下这三种方式。 @@ -840,10 +840,10 @@ OPTIMIZE TABLE hits_URL_UserID FINAL; 因为我们切换了主键中列的顺序,插入的行现在以不同的字典顺序存储在磁盘上(与我们的原始表相比),因此该表的1083个颗粒也包含了与以前不同的值: - + 主键索引如下: - + 现在计算最频繁点击URL"http://public_search"的前10名用户,这时候的查询速度是明显加快的: ```sql @@ -959,7 +959,7 @@ Processed 8.02 million rows, 现在我们有了两张表。优化了对UserID和URL的查询过滤,分别: - + @@ -999,13 +999,13 @@ Ok. - + ClickHouse将隐藏表的列数据文件(.bin)、标记文件(.mrk2)和主索引(primary.idx)存储在ClickHouse服务器的数据目录的一个特殊文件夹中: - + ::: @@ -1094,11 +1094,11 @@ ALTER TABLE hits_UserID_URL - 查询总是(从语法上)针对源表hits_UserID_URL,但是如果隐藏表的行顺序和主索引允许更有效地执行查询,那么将使用该隐藏表 - 实际上,隐式创建的隐藏表的行顺序和主索引与我们显式创建的辅助表相同: - + ClickHouse将隐藏表的列数据文件(.bin)、标记文件(.mrk2)和主索引(primary.idx)存储在一个特殊的文件夹中(在下面的截图中用橙色标记),紧挨着源表的数据文件、标记文件和主索引文件: - + ::: 由投影创建的隐藏表(以及它的主索引)现在可以(隐式地)用于显著加快URL列上查询过滤的执行。注意,查询在语法上针对投影的源表。 From 1680be19ca24ca15af9e95ed8a4a0c57520e005c Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Wed, 3 Aug 2022 16:36:52 -0400 Subject: [PATCH 307/672] some refactoring --- src/Client/Connection.cpp | 5 +++-- src/Core/ProtocolDefines.h | 2 ++ src/Server/TCPHandler.cpp | 9 ++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Client/Connection.cpp b/src/Client/Connection.cpp index 602f2d812c4..bbd4c380831 100644 --- a/src/Client/Connection.cpp +++ b/src/Client/Connection.cpp @@ -170,7 +170,7 @@ void Connection::connect(const ConnectionTimeouts & timeouts) sendHello(); receiveHello(); - if (server_revision >= DBMS_MIN_PROTOCOL_VERSION_WITH_QUOTA_KEY) + if (server_revision >= DBMS_MIN_PROTOCOL_VERSION_WITH_ADDENDUM) sendAddendum(); LOG_TRACE(log_wrapper.get(), "Connected to {} server version {}.{}.{}.", @@ -271,7 +271,8 @@ void Connection::sendHello() void Connection::sendAddendum() { - writeStringBinary(quota_key, *out); + if (server_revision >= DBMS_MIN_PROTOCOL_VERSION_WITH_QUOTA_KEY) + writeStringBinary(quota_key, *out); out->next(); } diff --git a/src/Core/ProtocolDefines.h b/src/Core/ProtocolDefines.h index 5794cf5f6d6..cf0a9d8b887 100644 --- a/src/Core/ProtocolDefines.h +++ b/src/Core/ProtocolDefines.h @@ -60,4 +60,6 @@ #define DBMS_MIN_PROTOCOL_VERSION_WITH_VIEW_IF_PERMITTED 54457 +#define DBMS_MIN_PROTOCOL_VERSION_WITH_ADDENDUM 54458 + #define DBMS_MIN_PROTOCOL_VERSION_WITH_QUOTA_KEY 54458 diff --git a/src/Server/TCPHandler.cpp b/src/Server/TCPHandler.cpp index 0076ee5eb0c..d0d061590ca 100644 --- a/src/Server/TCPHandler.cpp +++ b/src/Server/TCPHandler.cpp @@ -134,7 +134,7 @@ void TCPHandler::runImpl() { receiveHello(); sendHello(); - if (client_tcp_protocol_version >= DBMS_MIN_PROTOCOL_VERSION_WITH_QUOTA_KEY) + if (client_tcp_protocol_version >= DBMS_MIN_PROTOCOL_VERSION_WITH_ADDENDUM) receiveAddendum(); if (!is_interserver_mode) /// In interserver mode queries are executed without a session context. @@ -1082,8 +1082,11 @@ void TCPHandler::receiveHello() void TCPHandler::receiveAddendum() { - readStringBinary(quota_key, *in); - session->getClientInfo().quota_key = quota_key; + if (client_tcp_protocol_version >= DBMS_MIN_PROTOCOL_VERSION_WITH_QUOTA_KEY) + { + readStringBinary(quota_key, *in); + session->getClientInfo().quota_key = quota_key; + } } From ff26492830551a46ea6c3903baaf3d221a31ed6a Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 4 Aug 2022 00:24:43 +0200 Subject: [PATCH 308/672] Prevent spoiling rollback_stack --- tests/ci/release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/release.py b/tests/ci/release.py index f982c7c6f53..0e2bdae176d 100755 --- a/tests/ci/release.py +++ b/tests/ci/release.py @@ -185,7 +185,7 @@ class Release: def log_rollback(self): if self._rollback_stack: - rollback = self._rollback_stack + rollback = self._rollback_stack.copy() rollback.reverse() logging.info( "To rollback the action run the following commands:\n %s", From 8919fd6e58aa18fd0f783b51458bced9a643ce2b Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 4 Aug 2022 00:40:32 +0200 Subject: [PATCH 309/672] Add handful notes to a post-release logging --- tests/ci/release.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/ci/release.py b/tests/ci/release.py index 0e2bdae176d..c7d54bd77fd 100755 --- a/tests/ci/release.py +++ b/tests/ci/release.py @@ -148,6 +148,7 @@ class Release: with self.stable(): logging.info("Stable part of the releasing is done") + self.log_post_workflows() self.log_rollback() def check_no_tags_after(self): @@ -192,6 +193,15 @@ class Release: "\n ".join(rollback), ) + def log_post_workflows(self): + logging.info( + "To verify all actions are running good visit the following links:\n %s", + "\n ".join( + f"https://github.com/{self.repo}/actions/workflows/{action}.yml" + for action in ("release", "tags_stable") + ), + ) + @contextmanager def create_release_branch(self): self.check_no_tags_after() From 6cc08afb72165e295d59f98acd49a0c10c725d17 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 4 Aug 2022 01:58:08 +0300 Subject: [PATCH 310/672] Update unaligned.h --- base/base/unaligned.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/base/unaligned.h b/base/base/unaligned.h index c469972b4b6..842c3b2cdde 100644 --- a/base/base/unaligned.h +++ b/base/base/unaligned.h @@ -10,13 +10,13 @@ inline void reverseMemcpy(void * dst, const void * src, size_t size) uint8_t * uint_dst = reinterpret_cast(dst); const uint8_t * uint_src = reinterpret_cast(src); - uint_dst += length; + uint_dst += size; while (length) { --uint_dst; *uint_dst = *uint_src; ++uint_src; - --length; + --size; } } From 848a349e42c0512ae24a25c58dbb853e4fb8286e Mon Sep 17 00:00:00 2001 From: pzhdfy <982092332@qq.com> Date: Thu, 4 Aug 2022 11:37:27 +0800 Subject: [PATCH 311/672] fix potential netlink leak when init fail in TaskStatsInfoGetter --- src/Common/TaskStatsInfoGetter.cpp | 45 ++++++++++++++++++------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/Common/TaskStatsInfoGetter.cpp b/src/Common/TaskStatsInfoGetter.cpp index 304ccc84765..9f9c0c2b4fa 100644 --- a/src/Common/TaskStatsInfoGetter.cpp +++ b/src/Common/TaskStatsInfoGetter.cpp @@ -238,27 +238,36 @@ TaskStatsInfoGetter::TaskStatsInfoGetter() if (netlink_socket_fd < 0) throwFromErrno("Can't create PF_NETLINK socket", ErrorCodes::NETLINK_ERROR); - /// On some containerized environments, operation on Netlink socket could hang forever. - /// We set reasonably small timeout to overcome this issue. - - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 50000; - - if (0 != ::setsockopt(netlink_socket_fd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(tv))) - throwFromErrno("Can't set timeout on PF_NETLINK socket", ErrorCodes::NETLINK_ERROR); - - union + try { - ::sockaddr_nl addr{}; - ::sockaddr sockaddr; - }; - addr.nl_family = AF_NETLINK; + /// On some containerized environments, operation on Netlink socket could hang forever. + /// We set reasonably small timeout to overcome this issue. - if (::bind(netlink_socket_fd, &sockaddr, sizeof(addr)) < 0) - throwFromErrno("Can't bind PF_NETLINK socket", ErrorCodes::NETLINK_ERROR); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 50000; - taskstats_family_id = getFamilyId(netlink_socket_fd); + if (0 != ::setsockopt(netlink_socket_fd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(tv))) + throwFromErrno("Can't set timeout on PF_NETLINK socket", ErrorCodes::NETLINK_ERROR); + + union + { + ::sockaddr_nl addr{}; + ::sockaddr sockaddr; + }; + addr.nl_family = AF_NETLINK; + + if (::bind(netlink_socket_fd, &sockaddr, sizeof(addr)) < 0) + throwFromErrno("Can't bind PF_NETLINK socket", ErrorCodes::NETLINK_ERROR); + + taskstats_family_id = getFamilyId(netlink_socket_fd); + } + catch (...) + { + if (netlink_socket_fd >= 0) + close(netlink_socket_fd); + throw; + } } From 24ee9449860f6afae6b6856f66245e3c2144dc6b Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 4 Aug 2022 07:34:36 +0300 Subject: [PATCH 312/672] Update TaskStatsInfoGetter.cpp --- src/Common/TaskStatsInfoGetter.cpp | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Common/TaskStatsInfoGetter.cpp b/src/Common/TaskStatsInfoGetter.cpp index 9f9c0c2b4fa..b81da2f3fe2 100644 --- a/src/Common/TaskStatsInfoGetter.cpp +++ b/src/Common/TaskStatsInfoGetter.cpp @@ -240,33 +240,33 @@ TaskStatsInfoGetter::TaskStatsInfoGetter() try { - /// On some containerized environments, operation on Netlink socket could hang forever. - /// We set reasonably small timeout to overcome this issue. + /// On some containerized environments, operation on Netlink socket could hang forever. + /// We set reasonably small timeout to overcome this issue. - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 50000; + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 50000; - if (0 != ::setsockopt(netlink_socket_fd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(tv))) - throwFromErrno("Can't set timeout on PF_NETLINK socket", ErrorCodes::NETLINK_ERROR); + if (0 != ::setsockopt(netlink_socket_fd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(tv))) + throwFromErrno("Can't set timeout on PF_NETLINK socket", ErrorCodes::NETLINK_ERROR); - union - { - ::sockaddr_nl addr{}; - ::sockaddr sockaddr; - }; - addr.nl_family = AF_NETLINK; + union + { + ::sockaddr_nl addr{}; + ::sockaddr sockaddr; + }; + addr.nl_family = AF_NETLINK; - if (::bind(netlink_socket_fd, &sockaddr, sizeof(addr)) < 0) - throwFromErrno("Can't bind PF_NETLINK socket", ErrorCodes::NETLINK_ERROR); + if (::bind(netlink_socket_fd, &sockaddr, sizeof(addr)) < 0) + throwFromErrno("Can't bind PF_NETLINK socket", ErrorCodes::NETLINK_ERROR); - taskstats_family_id = getFamilyId(netlink_socket_fd); + taskstats_family_id = getFamilyId(netlink_socket_fd); } catch (...) { - if (netlink_socket_fd >= 0) - close(netlink_socket_fd); - throw; + if (netlink_socket_fd >= 0) + close(netlink_socket_fd); + throw; } } From dd96aee8d261606a3cd1dab12a6bfe8edac50ed0 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 4 Aug 2022 08:18:45 +0300 Subject: [PATCH 313/672] Update unaligned.h --- base/base/unaligned.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/base/unaligned.h b/base/base/unaligned.h index 842c3b2cdde..fcaaa38f2fe 100644 --- a/base/base/unaligned.h +++ b/base/base/unaligned.h @@ -11,7 +11,7 @@ inline void reverseMemcpy(void * dst, const void * src, size_t size) const uint8_t * uint_src = reinterpret_cast(src); uint_dst += size; - while (length) + while (size) { --uint_dst; *uint_dst = *uint_src; From 33b0b1828a75f288ccf09b1d061d0f55dc8f289b Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Thu, 4 Aug 2022 01:41:13 -0400 Subject: [PATCH 314/672] test added --- .../0_stateless/02377_quota_key.reference | 1 + tests/queries/0_stateless/02377_quota_key.sh | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/queries/0_stateless/02377_quota_key.reference create mode 100755 tests/queries/0_stateless/02377_quota_key.sh diff --git a/tests/queries/0_stateless/02377_quota_key.reference b/tests/queries/0_stateless/02377_quota_key.reference new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/tests/queries/0_stateless/02377_quota_key.reference @@ -0,0 +1 @@ +1 diff --git a/tests/queries/0_stateless/02377_quota_key.sh b/tests/queries/0_stateless/02377_quota_key.sh new file mode 100755 index 00000000000..8aa6797dc2c --- /dev/null +++ b/tests/queries/0_stateless/02377_quota_key.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Tags: no-parallel + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +${CLICKHOUSE_CLIENT} -q "DROP USER IF EXISTS u_02377" +${CLICKHOUSE_CLIENT} -q "drop quota if exists q_02377" +${CLICKHOUSE_CLIENT} -q "CREATE USER u_02377 IDENTIFIED WITH plaintext_password BY 'password';" +${CLICKHOUSE_CLIENT} -q "CREATE QUOTA q_02377 KEYED BY client_key FOR INTERVAL 1 month MAX queries = 100 TO u_02377;" + +${CLICKHOUSE_CLIENT} --user=u_02377 --password=password --quota_key=q_02377 --query="select 1" + +${CLICKHOUSE_CLIENT} -q "DROP USER IF EXISTS u_02377" +${CLICKHOUSE_CLIENT} -q "drop quota if exists q_02377" From b8c4bae519e0356344a9b7365c9585bbb8c02c00 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Thu, 4 Aug 2022 14:18:32 +0800 Subject: [PATCH 315/672] try fix Stateless tests flaky check flaky --- .../0_stateless/02371_select_projection_normal_agg.sql | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/queries/0_stateless/02371_select_projection_normal_agg.sql b/tests/queries/0_stateless/02371_select_projection_normal_agg.sql index 9743d486ef9..9b510625544 100644 --- a/tests/queries/0_stateless/02371_select_projection_normal_agg.sql +++ b/tests/queries/0_stateless/02371_select_projection_normal_agg.sql @@ -83,7 +83,3 @@ SELECT FROM video_log WHERE (toDate(hour) = '2022-07-22') AND (device_id = '100') --(device_id = '100') Make sure it's not good and doesn't go into prewhere. GROUP BY hour; - -DROP TABLE IF EXISTS video_log; - -DROP TABLE IF EXISTS rng; From 8094f33078c92a7aed27377b34226e2add19a56a Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 4 Aug 2022 06:48:33 +0000 Subject: [PATCH 316/672] Add init file for test --- tests/integration/test_keeper_snapshot_on_exit/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/integration/test_keeper_snapshot_on_exit/__init__.py diff --git a/tests/integration/test_keeper_snapshot_on_exit/__init__.py b/tests/integration/test_keeper_snapshot_on_exit/__init__.py new file mode 100644 index 00000000000..e5a0d9b4834 --- /dev/null +++ b/tests/integration/test_keeper_snapshot_on_exit/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 From fa98338ce158e4a714a7440853cf22985db4b036 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 4 Aug 2022 07:13:42 +0000 Subject: [PATCH 317/672] Add comment for memory tracker blocking --- src/Coordination/KeeperServer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index a7c9b0836f1..6e017e36919 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -176,6 +176,11 @@ struct KeeperServer::KeeperRaftServer : public nuraft::raft_server void commit_in_bg() override { + // For NuRaft, if any commit fails (uncaught exception) the whole server aborts as a safety + // This includes failed allocation which can produce an unknown state for the storage, + // making it impossible to handle correctly. + // We block the memory tracker for all the commit operations (including KeeperStateMachine::commit) + // assuming that the allocations are small MemoryTrackerBlockerInThread blocker; nuraft::raft_server::commit_in_bg(); } From 278921be3b57d6a2ce74af03bad44a6e346398f0 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 4 Aug 2022 07:25:28 +0000 Subject: [PATCH 318/672] Fix style --- src/Coordination/KeeperServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 6e017e36919..82a7a54401d 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -179,7 +179,7 @@ struct KeeperServer::KeeperRaftServer : public nuraft::raft_server // For NuRaft, if any commit fails (uncaught exception) the whole server aborts as a safety // This includes failed allocation which can produce an unknown state for the storage, // making it impossible to handle correctly. - // We block the memory tracker for all the commit operations (including KeeperStateMachine::commit) + // We block the memory tracker for all the commit operations (including KeeperStateMachine::commit) // assuming that the allocations are small MemoryTrackerBlockerInThread blocker; nuraft::raft_server::commit_in_bg(); From 92882fe3374f8dabdd8443ca19306b2b622435df Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Thu, 4 Aug 2022 11:08:59 +0200 Subject: [PATCH 319/672] Update CMakeLists.txt --- contrib/azure-cmake/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/azure-cmake/CMakeLists.txt b/contrib/azure-cmake/CMakeLists.txt index 367131daf0b..1e2a4c97824 100644 --- a/contrib/azure-cmake/CMakeLists.txt +++ b/contrib/azure-cmake/CMakeLists.txt @@ -1,6 +1,6 @@ option (ENABLE_AZURE_BLOB_STORAGE "Enable Azure blob storage" ${ENABLE_LIBRARIES}) -if (NOT ENABLE_AZURE_BLOB_STORAGE OR BUILD_STANDALONE_KEEPER) +if (NOT ENABLE_AZURE_BLOB_STORAGE OR BUILD_STANDALONE_KEEPER OR OS_FREEBSD) message(STATUS "Not using Azure blob storage") return() endif() From 1133425624ba6312c8e9fed1544d10dec0d52142 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 4 Aug 2022 12:16:38 +0300 Subject: [PATCH 320/672] Update storage_conf.xml --- .../test_merge_tree_s3/configs/config.d/storage_conf.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/integration/test_merge_tree_s3/configs/config.d/storage_conf.xml b/tests/integration/test_merge_tree_s3/configs/config.d/storage_conf.xml index e414ae5a259..c021a2163d3 100644 --- a/tests/integration/test_merge_tree_s3/configs/config.d/storage_conf.xml +++ b/tests/integration/test_merge_tree_s3/configs/config.d/storage_conf.xml @@ -90,4 +90,8 @@ 0 + + 0 + 60 + 1 From 1d67344ac8106688cced06ccf18b0b3868445bfd Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 4 Aug 2022 12:37:25 +0300 Subject: [PATCH 321/672] Update 02354_distributed_with_external_aggregation_memory_usage.sql --- ...02354_distributed_with_external_aggregation_memory_usage.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql b/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql index 340eee038ba..2601355ff46 100644 --- a/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql +++ b/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql @@ -1,4 +1,4 @@ --- Tags: long, no-tsan, no-msan, no-asan, no-ubsan +-- Tags: long, no-tsan, no-msan, no-asan, no-ubsan, no-debug create table t_2354_dist_with_external_aggr(a UInt64, b String, c FixedString(100)) engine = MergeTree order by tuple(); From 298fb2431da4a1ee85495d63557d405d9fe21339 Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 25 Jul 2022 18:58:09 +0000 Subject: [PATCH 322/672] Fix column not found for push down with join --- .../Optimizations/filterPushDown.cpp | 78 ++++++++++++++----- ...7_join_pushdown_column_not_found.reference | 4 + ...2367_join_pushdown_column_not_found.sql.j2 | 16 ++++ 3 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 tests/queries/0_stateless/02367_join_pushdown_column_not_found.reference create mode 100644 tests/queries/0_stateless/02367_join_pushdown_column_not_found.sql.j2 diff --git a/src/Processors/QueryPlan/Optimizations/filterPushDown.cpp b/src/Processors/QueryPlan/Optimizations/filterPushDown.cpp index 1c4760504b4..fc9215b9295 100644 --- a/src/Processors/QueryPlan/Optimizations/filterPushDown.cpp +++ b/src/Processors/QueryPlan/Optimizations/filterPushDown.cpp @@ -42,37 +42,50 @@ static bool filterColumnIsNotAmongAggregatesArguments(const AggregateDescription return true; } -static size_t -tryAddNewFilterStep(QueryPlan::Node * parent_node, QueryPlan::Nodes & nodes, const Names & allowed_inputs, - bool can_remove_filter = true, size_t child_idx = 0) +/// Assert that `node->children` has at least `child_num` elements +static void checkChildrenSize(QueryPlan::Node * node, size_t child_num) +{ + auto & child = node->step; + if (child_num > child->getInputStreams().size() || child_num > node->children.size()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Wrong number of children: expected at least {}, got {} children and {} streams", + child_num, child->getInputStreams().size(), node->children.size()); +} + +static ActionsDAGPtr splitFilter(QueryPlan::Node * parent_node, const Names & allowed_inputs, size_t child_idx = 0) { QueryPlan::Node * child_node = parent_node->children.front(); + checkChildrenSize(child_node, child_idx + 1); auto & parent = parent_node->step; auto & child = child_node->step; - auto * filter = static_cast(parent.get()); + auto * filter = assert_cast(parent.get()); const auto & expression = filter->getExpression(); const auto & filter_column_name = filter->getFilterColumnName(); bool removes_filter = filter->removesFilterColumn(); - // std::cerr << "Filter: \n" << expression->dumpDAG() << std::endl; - - if (child_idx >= child->getInputStreams().size() || child_idx >= child_node->children.size()) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Child index {} is out of range (streams: {}, children: {})", - child_idx, child->getInputStreams().size(), child_node->children.size()); - const auto & all_inputs = child->getInputStreams()[child_idx].header.getColumnsWithTypeAndName(); auto split_filter = expression->cloneActionsForFilterPushDown(filter_column_name, removes_filter, allowed_inputs, all_inputs); - if (!split_filter) - return 0; + return split_filter; +} - // std::cerr << "===============\n" << expression->dumpDAG() << std::endl; - // std::cerr << "---------------\n" << split_filter->dumpDAG() << std::endl; +static size_t +tryAddNewFilterStep(QueryPlan::Node * parent_node, QueryPlan::Nodes & nodes, const ActionsDAGPtr & split_filter, + bool can_remove_filter = true, size_t child_idx = 0) +{ + QueryPlan::Node * child_node = parent_node->children.front(); + checkChildrenSize(child_node, child_idx + 1); + + auto & parent = parent_node->step; + auto & child = child_node->step; + + auto * filter = assert_cast(parent.get()); + const auto & expression = filter->getExpression(); + const auto & filter_column_name = filter->getFilterColumnName(); const auto * filter_node = expression->tryFindInIndex(filter_column_name); - if (!filter_node && !removes_filter) + if (!filter_node && !filter->removesFilterColumn()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Filter column {} was removed from ActionsDAG but it is needed in result. DAG:\n{}", filter_column_name, expression->dumpDAG()); @@ -89,9 +102,9 @@ tryAddNewFilterStep(QueryPlan::Node * parent_node, QueryPlan::Nodes & nodes, con /// Expression/Filter -> Aggregating -> Filter -> Something /// New filter column is the first one. - auto split_filter_column_name = (*split_filter->getIndex().begin())->result_name; + String split_filter_column_name = split_filter->getIndex().front()->result_name; node.step = std::make_unique( - node.children.at(0)->step->getOutputStream(), std::move(split_filter), std::move(split_filter_column_name), can_remove_filter); + node.children.at(0)->step->getOutputStream(), split_filter, std::move(split_filter_column_name), can_remove_filter); if (auto * transforming_step = dynamic_cast(child.get())) { @@ -118,6 +131,15 @@ tryAddNewFilterStep(QueryPlan::Node * parent_node, QueryPlan::Nodes & nodes, con return 3; } +static size_t +tryAddNewFilterStep(QueryPlan::Node * parent_node, QueryPlan::Nodes & nodes, const Names & allowed_inputs, + bool can_remove_filter = true) +{ + if (auto split_filter = splitFilter(parent_node, allowed_inputs, 0)) + return tryAddNewFilterStep(parent_node, nodes, split_filter, can_remove_filter, 0); + return 0; +} + size_t tryPushDownFilter(QueryPlan::Node * parent_node, QueryPlan::Nodes & nodes) { if (parent_node->children.size() != 1) @@ -248,12 +270,26 @@ size_t tryPushDownFilter(QueryPlan::Node * parent_node, QueryPlan::Nodes & nodes allowed_keys.push_back(name); } - const bool can_remove_filter - = std::find(source_columns.begin(), source_columns.end(), filter->getFilterColumnName()) == source_columns.end(); - size_t updated_steps = tryAddNewFilterStep(parent_node, nodes, allowed_keys, can_remove_filter, is_left ? 0 : 1); + /// For left JOIN, push down to the first child; for right - to the second one. + const auto child_idx = is_left ? 0 : 1; + ActionsDAGPtr split_filter = splitFilter(parent_node, allowed_keys, child_idx); + if (!split_filter) + return 0; + /* + * We should check the presence of a split filter column name in `source_columns` to avoid removing the required column. + * + * Example: + * A filter expression is `a AND b = c`, but `b` and `c` belong to another side of the join and not in `allowed_keys`, so the final split filter is just `a`. + * In this case `a` can be in `source_columns` but not `and(a, equals(b, c))`. + * + * New filter column is the first one. + */ + const String & split_filter_column_name = split_filter->getIndex().front()->result_name; + bool can_remove_filter = source_columns.end() == std::find(source_columns.begin(), source_columns.end(), split_filter_column_name); + const size_t updated_steps = tryAddNewFilterStep(parent_node, nodes, split_filter, can_remove_filter, child_idx); if (updated_steps > 0) { - LOG_DEBUG(&Poco::Logger::get("tryPushDownFilter"), "Pushed down filter to {} side of join", kind); + LOG_DEBUG(&Poco::Logger::get("QueryPlanOptimizations"), "Pushed down filter to {} side of join", kind); } return updated_steps; }; diff --git a/tests/queries/0_stateless/02367_join_pushdown_column_not_found.reference b/tests/queries/0_stateless/02367_join_pushdown_column_not_found.reference new file mode 100644 index 00000000000..98fb6a68656 --- /dev/null +++ b/tests/queries/0_stateless/02367_join_pushdown_column_not_found.reference @@ -0,0 +1,4 @@ +1 +1 +1 +1 diff --git a/tests/queries/0_stateless/02367_join_pushdown_column_not_found.sql.j2 b/tests/queries/0_stateless/02367_join_pushdown_column_not_found.sql.j2 new file mode 100644 index 00000000000..95f3c5be711 --- /dev/null +++ b/tests/queries/0_stateless/02367_join_pushdown_column_not_found.sql.j2 @@ -0,0 +1,16 @@ +{% for join_algorithm in ['default', 'full_sorting_merge', 'hash', 'partial_merge'] -%} + +SET join_algorithm = '{{ join_algorithm }}'; + +SELECT deleted +FROM ( + SELECT 1 AS deleted, 'k' AS a, 'v' AS b +) AS q +INNER JOIN ( + SELECT 'k' AS a, 'v' AS c +) AS s +ON q.a = s.a +WHERE deleted AND (b = c); + +{% endfor -%} + From 0d892b1cac4f12558b4204f81c68fbd8b9b6907f Mon Sep 17 00:00:00 2001 From: Constantine Peresypkin Date: Thu, 4 Aug 2022 13:23:45 +0200 Subject: [PATCH 323/672] fix broken NFS mkdir introduced in #36341 --- docker/server/entrypoint.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index edda079e7e2..fae11cd267e 100755 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -57,7 +57,15 @@ do # check if variable not empty [ -z "$dir" ] && continue # ensure directories exist - if ! mkdir -p "$dir"; then + if [ "$DO_CHOWN" = "1" ]; then + mkdir="mkdir" + else + # if DO_CHOWN=0 it means that the system does not map root user to "admin" permissions + # it mainly happens on NFS mounts where root==nobody for security reasons + # thus mkdir MUST run with user id/gid and not from nobody that has zero permissions + mkdir="/usr/bin/clickhouse su "${USER}:${GROUP}" mkdir" + fi + if ! $mkdir -p "$dir"; then echo "Couldn't create necessary directory: $dir" exit 1 fi From 7caf4c210e6ead19415bdc1a404b7df43ce0b36f Mon Sep 17 00:00:00 2001 From: Alexander Gololobov <440544+davenger@users.noreply.github.com> Date: Thu, 4 Aug 2022 14:06:19 +0200 Subject: [PATCH 324/672] Update local queue to see current mutation entry --- src/Storages/StorageReplicatedMergeTree.cpp | 9 ++++++--- src/Storages/StorageReplicatedMergeTree.h | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 1ec14f643d4..8bfe025002e 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -501,11 +501,14 @@ bool StorageReplicatedMergeTree::checkFixedGranularityInZookeeper() void StorageReplicatedMergeTree::waitMutationToFinishOnReplicas( - const Strings & replicas, const String & mutation_id) const + const Strings & replicas, const String & mutation_id) { if (replicas.empty()) return; + /// We need to make sure that local queue has been synced from zookeeper and contains the entry for the + /// current mutation. We use local mutation status for checkig for errors. + queue.updateMutations(getZooKeeper()); std::set inactive_replicas; for (const String & replica : replicas) @@ -5958,7 +5961,7 @@ void StorageReplicatedMergeTree::mutate(const MutationCommands & commands, Conte waitMutation(mutation_entry.znode_name, query_context->getSettingsRef().mutations_sync); } -void StorageReplicatedMergeTree::waitMutation(const String & znode_name, size_t mutations_sync) const +void StorageReplicatedMergeTree::waitMutation(const String & znode_name, size_t mutations_sync) { if (!mutations_sync) return; @@ -5974,7 +5977,7 @@ void StorageReplicatedMergeTree::waitMutation(const String & znode_name, size_t { if (*it == replica_name) { - std::iter_swap(it, replicas.rbegin()); + std::iter_swap(it, replicas.begin()); break; } } diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index c35e2d5cf5c..d7c9e047f0b 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -148,7 +148,7 @@ public: void alter(const AlterCommands & commands, ContextPtr query_context, AlterLockHolder & table_lock_holder) override; void mutate(const MutationCommands & commands, ContextPtr context) override; - void waitMutation(const String & znode_name, size_t mutations_sync) const; + void waitMutation(const String & znode_name, size_t mutations_sync); std::vector getMutationsStatus() const override; CancellationCode killMutation(const String & mutation_id) override; @@ -796,7 +796,7 @@ private: /// Wait for timeout seconds mutation is finished on replicas void waitMutationToFinishOnReplicas( - const Strings & replicas, const String & mutation_id) const; + const Strings & replicas, const String & mutation_id); MutationCommands getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const override; From 0a7d2e7b8ab4e45e18b1cf95eca008109f7dcbae Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 4 Aug 2022 13:03:05 +0000 Subject: [PATCH 325/672] Use LockMemoryExceptionThread --- src/Coordination/KeeperServer.cpp | 4 ++-- src/Coordination/KeeperStorage.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 82a7a54401d..24551f27ec2 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include @@ -181,7 +181,7 @@ struct KeeperServer::KeeperRaftServer : public nuraft::raft_server // making it impossible to handle correctly. // We block the memory tracker for all the commit operations (including KeeperStateMachine::commit) // assuming that the allocations are small - MemoryTrackerBlockerInThread blocker; + LockMemoryExceptionInThread blocker{VariableContext::Global}; nuraft::raft_server::commit_in_bg(); } diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index bbc647fd951..693a1b16f0d 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -2127,7 +2127,7 @@ void KeeperStorage::rollbackRequest(int64_t rollback_zxid, bool allow_missing) // if an exception occurs during rollback, the best option is to terminate because we can end up in an inconsistent state // we block memory tracking so we can avoid terminating if we're rollbacking because of memory limit - MemoryTrackerBlockerInThread temporarily_ignore_any_memory_limits; + LockMemoryExceptionInThread blocker{VariableContext::Global}; try { uncommitted_transactions.pop_back(); From 10d7259c2b21c583abaa824507a1238eb5f26fb9 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 4 Aug 2022 13:12:24 +0000 Subject: [PATCH 326/672] Add log for snapshot on exit --- src/Coordination/KeeperStateMachine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index a55acaf9b91..a568bb88302 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -385,6 +385,7 @@ void KeeperStateMachine::create_snapshot(nuraft::snapshot & s, nuraft::async_res if (keeper_context->server_state == KeeperContext::Phase::SHUTDOWN) { + LOG_INFO(log, "Creating a snapshot during shutdown because 'create_snapshot_on_exit' is enabled."); snapshot_task.create_snapshot(std::move(snapshot_task.snapshot)); return; } From f63e4ba261b4ba3ddbb03f3ff591507eb6590d1a Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 4 Aug 2022 13:14:57 +0000 Subject: [PATCH 327/672] Update NuRaft --- contrib/NuRaft | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/NuRaft b/contrib/NuRaft index d73e12adf75..1b0af760b35 160000 --- a/contrib/NuRaft +++ b/contrib/NuRaft @@ -1 +1 @@ -Subproject commit d73e12adf7557a0ebca7f7ecde68c064dee22fa0 +Subproject commit 1b0af760b3506b8e35b50cb7df098cbad5064ff2 From 6a5171c82956ab79d5f20ffaca7cad27e4bb651b Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 4 Aug 2022 17:10:13 +0300 Subject: [PATCH 328/672] Update DatabaseCatalog.cpp --- src/Interpreters/DatabaseCatalog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Interpreters/DatabaseCatalog.cpp b/src/Interpreters/DatabaseCatalog.cpp index a92a82ea821..e0887423459 100644 --- a/src/Interpreters/DatabaseCatalog.cpp +++ b/src/Interpreters/DatabaseCatalog.cpp @@ -891,7 +891,7 @@ void DatabaseCatalog::enqueueDroppedTableCleanup(StorageID table_id, StoragePtr create->setTable(table_id.table_name); try { - table = createTableFromAST(*create, table_id.getDatabaseName(), data_path, getContext(), true).second; + table = createTableFromAST(*create, table_id.getDatabaseName(), data_path, getContext(), /* force_restore */ true).second; table->is_dropped = true; } catch (...) From 60599197b2bfac4beedc5a9f99ee4fb4c34d5d00 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Thu, 4 Aug 2022 15:23:10 +0000 Subject: [PATCH 329/672] Review fixes. --- src/QueryPipeline/QueryPipelineBuilder.h | 2 ++ src/Storages/MergeTree/KeyCondition.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/QueryPipeline/QueryPipelineBuilder.h b/src/QueryPipeline/QueryPipelineBuilder.h index a48828be8b9..346b47a1d1d 100644 --- a/src/QueryPipeline/QueryPipelineBuilder.h +++ b/src/QueryPipeline/QueryPipelineBuilder.h @@ -95,6 +95,8 @@ public: void resize(size_t num_streams, bool force = false, bool strict = false); /// Concat some ports to have no more then size outputs. + /// This method is needed for Merge table engine in case of reading from many tables. + /// It prevents opening too many files at the same time. void narrow(size_t size); /// Unite several pipelines together. Result pipeline would have common_header structure. diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 0fda619d1b5..a44ecd512ea 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -223,7 +223,7 @@ public: return res; } - bool getConstant(const Block & block_with_constants, Field & out_value, DataTypePtr & out_type) const + bool tryGetConstant(const Block & block_with_constants, Field & out_value, DataTypePtr & out_type) const { if (ast) { @@ -719,7 +719,7 @@ static const ActionsDAG::Node & cloneASTWithInversionPushDown( case (ActionsDAG::ActionType::ARRAY_JOIN): { const auto & arg = cloneASTWithInversionPushDown(*node.children.front(), inverted_dag, to_inverted, context, false); - res = &inverted_dag.addArrayJoin(arg, ""); + res = &inverted_dag.addArrayJoin(arg, {}); break; } case (ActionsDAG::ActionType::FUNCTION): @@ -971,7 +971,7 @@ bool KeyCondition::addCondition(const String & column, const Range & range) */ bool KeyCondition::getConstant(const ASTPtr & expr, Block & block_with_constants, Field & out_value, DataTypePtr & out_type) { - return Tree(expr.get()).getConstant(block_with_constants, out_value, out_type); + return Tree(expr.get()).tryGetConstant(block_with_constants, out_value, out_type); } @@ -1612,7 +1612,7 @@ bool KeyCondition::tryParseAtomFromAST(const Tree & node, ContextPtr context, Bl else return false; } - else if (func.getArgumentAt(1).getConstant(block_with_constants, const_value, const_type)) + else if (func.getArgumentAt(1).tryGetConstant(block_with_constants, const_value, const_type)) { if (isKeyPossiblyWrappedByMonotonicFunctions(func.getArgumentAt(0), context, key_column_num, key_expr_type, chain)) { @@ -1635,7 +1635,7 @@ bool KeyCondition::tryParseAtomFromAST(const Tree & node, ContextPtr context, Bl else return false; } - else if (func.getArgumentAt(0).getConstant(block_with_constants, const_value, const_type)) + else if (func.getArgumentAt(0).tryGetConstant(block_with_constants, const_value, const_type)) { if (isKeyPossiblyWrappedByMonotonicFunctions(func.getArgumentAt(1), context, key_column_num, key_expr_type, chain)) { @@ -1762,7 +1762,7 @@ bool KeyCondition::tryParseAtomFromAST(const Tree & node, ContextPtr context, Bl return atom_it->second(out, const_value); } - else if (node.getConstant(block_with_constants, const_value, const_type)) + else if (node.tryGetConstant(block_with_constants, const_value, const_type)) { /// For cases where it says, for example, `WHERE 0 AND something` From 20cf4e8d227aab2d790461ca71a12cc090491145 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 4 Aug 2022 18:04:06 +0200 Subject: [PATCH 330/672] slightly more readable if conditions --- src/Server/TCPHandler.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Server/TCPHandler.cpp b/src/Server/TCPHandler.cpp index ed46abd9c36..6e45969bd34 100644 --- a/src/Server/TCPHandler.cpp +++ b/src/Server/TCPHandler.cpp @@ -759,13 +759,21 @@ void TCPHandler::processTablesStatusRequest() TablesStatusRequest request; request.read(*in, client_tcp_protocol_version); - ContextPtr context_to_resolve_table_names = (session && session->sessionContext()) ? session->sessionContext() : server.context(); - if (is_interserver_mode && !default_database.empty()) + ContextPtr context_to_resolve_table_names; + if (is_interserver_mode) { - // in interserver mode, the session doesn't exist. - ContextMutablePtr interserver_context = Context::createCopy(context_to_resolve_table_names); - interserver_context->setCurrentDatabase(default_database); - context_to_resolve_table_names = interserver_context; + /// In interserver mode session context does not exists, because authentication is done for each query. + /// We also cannot create query context earlier, because it cannot be created before authentication, + /// but query is not received yet. So we have to do this trick. + ContextMutablePtr fake_interserver_context = Context::createCopy(server.context()); + if (!default_database.empty()) + fake_interserver_context->setCurrentDatabase(default_database); + context_to_resolve_table_names = fake_interserver_context; + } + else + { + assert(session); + context_to_resolve_table_names = session->sessionContext(); } TablesStatusResponse response; @@ -1361,7 +1369,7 @@ void TCPHandler::receiveQuery() query_context = session->makeQueryContext(std::move(client_info)); /// Sets the default database if it wasn't set earlier for the session context. - if (!default_database.empty() && !session->sessionContext()) + if (is_interserver_mode && !default_database.empty()) query_context->setCurrentDatabase(default_database); if (state.part_uuids_to_ignore) From 110b60e5b70e59e74f0d32c3a2135a99d65fa274 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 4 Aug 2022 19:48:55 +0200 Subject: [PATCH 331/672] fix tests --- tests/integration/test_merge_tree_s3/test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration/test_merge_tree_s3/test.py b/tests/integration/test_merge_tree_s3/test.py index ab380d31494..8a73cafd30f 100644 --- a/tests/integration/test_merge_tree_s3/test.py +++ b/tests/integration/test_merge_tree_s3/test.py @@ -715,15 +715,15 @@ def test_cache_with_full_disk_space(cluster, node_name): @pytest.mark.parametrize("node_name", ["node"]) def test_store_cleanup_disk_s3(cluster, node_name): node = cluster.instances[node_name] - node.query("DROP TABLE IF EXISTS store_cleanup SYNC") + node.query("DROP TABLE IF EXISTS s3_test SYNC") node.query( - "CREATE TABLE store_cleanup UUID '00000000-1000-4000-8000-000000000001' (n UInt64) Engine=MergeTree() ORDER BY n SETTINGS storage_policy='s3';" + "CREATE TABLE s3_test UUID '00000000-1000-4000-8000-000000000001' (n UInt64) Engine=MergeTree() ORDER BY n SETTINGS storage_policy='s3';" ) - node.query("INSERT INTO store_cleanup SELECT 1") + node.query("INSERT INTO s3_test SELECT 1") node.stop_clickhouse(kill=True) path_to_data = "/var/lib/clickhouse/" - node.exec_in_container(["rm", f"{path_to_data}/metadata/default/store_cleanup.sql"]) + node.exec_in_container(["rm", f"{path_to_data}/metadata/default/s3_test.sql"]) node.start_clickhouse() node.wait_for_log_line( @@ -731,6 +731,6 @@ def test_store_cleanup_disk_s3(cluster, node_name): ) node.wait_for_log_line("directories from store") node.query( - "CREATE TABLE store_cleanup UUID '00000000-1000-4000-8000-000000000001' (n UInt64) Engine=MergeTree() ORDER BY n SETTINGS storage_policy='s3';" + "CREATE TABLE s3_test UUID '00000000-1000-4000-8000-000000000001' (n UInt64) Engine=MergeTree() ORDER BY n SETTINGS storage_policy='s3';" ) - node.query("INSERT INTO store_cleanup SELECT 1") + node.query("INSERT INTO s3_test SELECT 1") From f5e358ecc57d6d545200b9cb09b9c2ed4af0c1fb Mon Sep 17 00:00:00 2001 From: Alexander Gololobov <440544+davenger@users.noreply.github.com> Date: Thu, 4 Aug 2022 20:26:39 +0200 Subject: [PATCH 332/672] Make sure that the current replica is the first on the list --- src/Storages/StorageReplicatedMergeTree.cpp | 19 +++++++++++++------ src/Storages/StorageReplicatedMergeTree.h | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 8bfe025002e..6a3f52f4e23 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -501,17 +501,24 @@ bool StorageReplicatedMergeTree::checkFixedGranularityInZookeeper() void StorageReplicatedMergeTree::waitMutationToFinishOnReplicas( - const Strings & replicas, const String & mutation_id) + const Strings & replicas, const String & mutation_id) const { if (replicas.empty()) return; - /// We need to make sure that local queue has been synced from zookeeper and contains the entry for the - /// current mutation. We use local mutation status for checkig for errors. - queue.updateMutations(getZooKeeper()); + /// Current replica must always be present in the list as the first element because we use local mutation status + /// to check for mutation errors. So if it is not there, just add it. + const Strings * all_required_replicas = &replicas; + Strings extended_list_of_replicas; + if (replicas.front() != replica_name) + { + extended_list_of_replicas.push_back(replica_name); + extended_list_of_replicas.insert(extended_list_of_replicas.end(), replicas.begin(), replicas.end()); + all_required_replicas = &extended_list_of_replicas; + } std::set inactive_replicas; - for (const String & replica : replicas) + for (const String & replica : *all_required_replicas) { LOG_DEBUG(log, "Waiting for {} to apply mutation {}", replica, mutation_id); zkutil::EventPtr wait_event = std::make_shared(); @@ -5961,7 +5968,7 @@ void StorageReplicatedMergeTree::mutate(const MutationCommands & commands, Conte waitMutation(mutation_entry.znode_name, query_context->getSettingsRef().mutations_sync); } -void StorageReplicatedMergeTree::waitMutation(const String & znode_name, size_t mutations_sync) +void StorageReplicatedMergeTree::waitMutation(const String & znode_name, size_t mutations_sync) const { if (!mutations_sync) return; diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index d7c9e047f0b..c35e2d5cf5c 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -148,7 +148,7 @@ public: void alter(const AlterCommands & commands, ContextPtr query_context, AlterLockHolder & table_lock_holder) override; void mutate(const MutationCommands & commands, ContextPtr context) override; - void waitMutation(const String & znode_name, size_t mutations_sync); + void waitMutation(const String & znode_name, size_t mutations_sync) const; std::vector getMutationsStatus() const override; CancellationCode killMutation(const String & mutation_id) override; @@ -796,7 +796,7 @@ private: /// Wait for timeout seconds mutation is finished on replicas void waitMutationToFinishOnReplicas( - const Strings & replicas, const String & mutation_id); + const Strings & replicas, const String & mutation_id) const; MutationCommands getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const override; From c307e9a228ed9be831e111c0f6d99926d5e323aa Mon Sep 17 00:00:00 2001 From: Arthur Passos Date: Thu, 4 Aug 2022 14:50:44 -0300 Subject: [PATCH 333/672] Fix ArrowColumn dictionary to CH low cardinality conversion --- .../Formats/Impl/ArrowColumnToCHColumn.cpp | 53 ++++++++++++++++++- ...rrow_dict_of_nullable_to_lc_of_nullable.sh | 38 +++++++++++++ ...w_dict_of_string_to_lc_of_string.reference | 7 +++ ...78_arrow_dict_of_string_to_lc_of_string.sh | 25 +++++++++ 4 files changed, 122 insertions(+), 1 deletion(-) create mode 100755 tests/queries/0_stateless/02377_arrow_dict_of_nullable_to_lc_of_nullable.sh create mode 100644 tests/queries/0_stateless/02378_arrow_dict_of_string_to_lc_of_string.reference create mode 100755 tests/queries/0_stateless/02378_arrow_dict_of_string_to_lc_of_string.sh diff --git a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp index f922ff048b2..8724b5da673 100644 --- a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp +++ b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp @@ -326,6 +326,37 @@ static std::shared_ptr getNestedArrowColumn(std::shared_ptr return std::make_shared(array_vector); } +static ColumnPtr shift_dictionary_indexes(const ColumnPtr & indexes) +{ + auto indexes_shifted = indexes->cloneEmpty(); + + for (std::size_t i = 0; i < indexes->size(); i++) + { + indexes_shifted->insert(indexes->getUInt(i) + 1); + } + + return indexes_shifted; +} + +static ColumnPtr shift_dictionary_indexes_and_handle_nullable(const ColumnPtr & indexes, const ColumnPtr & nullmap_column) +{ + auto indexes_shifted = indexes->cloneEmpty(); + + for (std::size_t i = 0; i < indexes->size(); i++) + { + if (nullmap_column->getBool(i)) + { + indexes_shifted->insert(0); + } + else + { + indexes_shifted->insert(indexes->getUInt(i) + 1); + } + } + + return indexes_shifted; +} + static ColumnWithTypeAndName readColumnFromArrowColumn( std::shared_ptr & arrow_column, const std::string & column_name, @@ -338,7 +369,8 @@ static ColumnWithTypeAndName readColumnFromArrowColumn( bool & skipped) { if (!is_nullable && arrow_column->null_count() && arrow_column->type()->id() != arrow::Type::LIST - && arrow_column->type()->id() != arrow::Type::MAP && arrow_column->type()->id() != arrow::Type::STRUCT) + && arrow_column->type()->id() != arrow::Type::MAP && arrow_column->type()->id() != arrow::Type::STRUCT && + arrow_column->type()->id() != arrow::Type::DICTIONARY) { auto nested_column = readColumnFromArrowColumn(arrow_column, column_name, format_name, true, dictionary_values, read_ints_as_dates, allow_null_type, skip_columns_with_unsupported_types, skipped); if (skipped) @@ -473,6 +505,25 @@ static ColumnWithTypeAndName readColumnFromArrowColumn( auto arrow_indexes_column = std::make_shared(indexes_array); auto indexes_column = readColumnWithIndexesData(arrow_indexes_column); + + const auto contains_null = arrow_column->null_count() > 0; + + /* + * LC contains a default item at the 0th position. Indexes need to be shifted by one. + * CH doesn't maintain a nullmap. Instead, it points to the default/ null item. + * The below shifting could be omitted / optimized away if the indexes are shifted in-place when reading + * from the ArrowColumn + * */ + if (contains_null) + { + auto nullmap_column = readByteMapFromArrowColumn(arrow_column); + indexes_column = shift_dictionary_indexes_and_handle_nullable(indexes_column, nullmap_column); + } + else + { + indexes_column = shift_dictionary_indexes(indexes_column); + } + auto lc_column = ColumnLowCardinality::create(dict_values->column, indexes_column); auto lc_type = std::make_shared(dict_values->type); return {std::move(lc_column), std::move(lc_type), column_name}; diff --git a/tests/queries/0_stateless/02377_arrow_dict_of_nullable_to_lc_of_nullable.sh b/tests/queries/0_stateless/02377_arrow_dict_of_nullable_to_lc_of_nullable.sh new file mode 100755 index 00000000000..b8e265e80cf --- /dev/null +++ b/tests/queries/0_stateless/02377_arrow_dict_of_nullable_to_lc_of_nullable.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Tags: no-fasttest +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +# ## reading ArrowStream file from python +# import pyarrow as pa +# stream = pa.ipc.open_stream("test.arrows") +# x = stream.read_all() +# print(x) + +## writing ArrowStream file from python +# import pyarrow as pa +# data = [ +# pa.array(["one", None, "three", "four", "five"]).dictionary_encode(), +# ] +# batch = pa.record_batch(data, names=['id', 'lc_nullable', 'lc_int_nullable', 'bool_nullable']) +# writer = pa.ipc.new_stream("test4.arrows", batch.schema) +# writer.write_batch(batch) +# writer.close() + +# cat data.arrow | gzip | base64 + +# https://github.com/ClickHouse/ClickHouse/pull/24341 +# set output_format_arrow_low_cardinality_as_dictionary=1; +# SELECT * +# FROM values('id UInt64, s LowCardinality(String)', (1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')) +# INTO OUTFILE 'test5.arrows' +# FORMAT ArrowStream + +cat < Date: Thu, 4 Aug 2022 15:53:44 -0300 Subject: [PATCH 334/672] Use insertDefault instead of insert(0) --- src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp index 8724b5da673..a27ca5782f7 100644 --- a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp +++ b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp @@ -346,7 +346,7 @@ static ColumnPtr shift_dictionary_indexes_and_handle_nullable(const ColumnPtr & { if (nullmap_column->getBool(i)) { - indexes_shifted->insert(0); + indexes_shifted->insertDefault(); } else { From ea73b98fb9fc2ad962f8cb01dcc69ad85268f6f4 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 3 Aug 2022 11:19:13 +0000 Subject: [PATCH 335/672] Prepare library-bridge for catboost integration - Rename generic file and identifier names in library-bridge to something more dictionary-specific. This is needed because later on, catboost will be integrated into library-bridge. - Also: Some smaller fixes like typos and un-inlining non-performance critical code. - The logic remains unchanged in this commit. --- programs/CMakeLists.txt | 6 +- programs/library-bridge/CMakeLists.txt | 12 +- ...e.cpp => ExternalDictionaryLibraryAPI.cpp} | 23 +- .../ExternalDictionaryLibraryAPI.h | 106 +++++++++ .../ExternalDictionaryLibraryHandler.cpp | 214 ++++++++++++++++++ ...r.h => ExternalDictionaryLibraryHandler.h} | 16 +- ...xternalDictionaryLibraryHandlerFactory.cpp | 62 +++++ ...ExternalDictionaryLibraryHandlerFactory.h} | 8 +- ...ils.h => ExternalDictionaryLibraryUtils.h} | 8 +- programs/library-bridge/HandlerFactory.cpp | 23 -- programs/library-bridge/HandlerFactory.h | 37 --- programs/library-bridge/LibraryBridge.cpp | 16 +- programs/library-bridge/LibraryBridge.h | 13 +- .../LibraryBridgeHandlerFactory.cpp | 34 +++ .../LibraryBridgeHandlerFactory.h | 27 +++ ...Handlers.cpp => LibraryBridgeHandlers.cpp} | 78 ++++--- .../{Handlers.h => LibraryBridgeHandlers.h} | 27 +-- programs/library-bridge/LibraryInterface.h | 110 --------- .../library-bridge/SharedLibraryHandler.cpp | 214 ------------------ .../SharedLibraryHandlerFactory.cpp | 62 ----- programs/library-bridge/library-bridge.cpp | 1 - programs/odbc-bridge/CMakeLists.txt | 6 +- programs/odbc-bridge/ODBCBridge.cpp | 16 +- programs/odbc-bridge/ODBCBridge.h | 13 +- ...dlerFactory.cpp => ODBCHandlerFactory.cpp} | 10 +- ...{HandlerFactory.h => ODBCHandlerFactory.h} | 8 +- src/BridgeHelper/IBridgeHelper.h | 1 - src/BridgeHelper/LibraryBridgeHelper.cpp | 26 +-- src/BridgeHelper/LibraryBridgeHelper.h | 24 +- src/Dictionaries/LibraryDictionarySource.cpp | 4 +- 30 files changed, 598 insertions(+), 607 deletions(-) rename programs/library-bridge/{LibraryInterface.cpp => ExternalDictionaryLibraryAPI.cpp} (55%) create mode 100644 programs/library-bridge/ExternalDictionaryLibraryAPI.h create mode 100644 programs/library-bridge/ExternalDictionaryLibraryHandler.cpp rename programs/library-bridge/{SharedLibraryHandler.h => ExternalDictionaryLibraryHandler.h} (64%) create mode 100644 programs/library-bridge/ExternalDictionaryLibraryHandlerFactory.cpp rename programs/library-bridge/{SharedLibraryHandlerFactory.h => ExternalDictionaryLibraryHandlerFactory.h} (77%) rename programs/library-bridge/{LibraryUtils.h => ExternalDictionaryLibraryUtils.h} (67%) delete mode 100644 programs/library-bridge/HandlerFactory.cpp delete mode 100644 programs/library-bridge/HandlerFactory.h create mode 100644 programs/library-bridge/LibraryBridgeHandlerFactory.cpp create mode 100644 programs/library-bridge/LibraryBridgeHandlerFactory.h rename programs/library-bridge/{Handlers.cpp => LibraryBridgeHandlers.cpp} (80%) rename programs/library-bridge/{Handlers.h => LibraryBridgeHandlers.h} (53%) delete mode 100644 programs/library-bridge/LibraryInterface.h delete mode 100644 programs/library-bridge/SharedLibraryHandler.cpp delete mode 100644 programs/library-bridge/SharedLibraryHandlerFactory.cpp rename programs/odbc-bridge/{HandlerFactory.cpp => ODBCHandlerFactory.cpp} (83%) rename programs/odbc-bridge/{HandlerFactory.h => ODBCHandlerFactory.h} (79%) diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index 31943ef7dae..873a508d94a 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -44,15 +44,13 @@ option (ENABLE_CLICKHOUSE_OBFUSCATOR "Table data obfuscator (convert real data t # https://clickhouse.com/docs/en/operations/utilities/odbc-bridge/ # TODO Also needs NANODBC. if (ENABLE_ODBC AND NOT USE_MUSL) - option (ENABLE_CLICKHOUSE_ODBC_BRIDGE "HTTP-server working like a proxy to ODBC driver" - ${ENABLE_CLICKHOUSE_ALL}) + option (ENABLE_CLICKHOUSE_ODBC_BRIDGE "HTTP-server working like a proxy to ODBC driver" ${ENABLE_CLICKHOUSE_ALL}) else () option (ENABLE_CLICKHOUSE_ODBC_BRIDGE "HTTP-server working like a proxy to ODBC driver" OFF) endif () if (NOT USE_MUSL) - option (ENABLE_CLICKHOUSE_LIBRARY_BRIDGE "HTTP-server working like a proxy to Library dictionary source" - ${ENABLE_CLICKHOUSE_ALL}) + option (ENABLE_CLICKHOUSE_LIBRARY_BRIDGE "HTTP-server working like a proxy to Library dictionary source" ${ENABLE_CLICKHOUSE_ALL}) endif () # https://presentations.clickhouse.com/matemarketing_2020/ diff --git a/programs/library-bridge/CMakeLists.txt b/programs/library-bridge/CMakeLists.txt index 2f52652c929..40cabacded4 100644 --- a/programs/library-bridge/CMakeLists.txt +++ b/programs/library-bridge/CMakeLists.txt @@ -1,13 +1,13 @@ include(${ClickHouse_SOURCE_DIR}/cmake/split_debug_symbols.cmake) set (CLICKHOUSE_LIBRARY_BRIDGE_SOURCES - library-bridge.cpp - LibraryInterface.cpp + ExternalDictionaryLibraryAPI.cpp + ExternalDictionaryLibraryHandler.cpp + ExternalDictionaryLibraryHandlerFactory.cpp LibraryBridge.cpp - Handlers.cpp - HandlerFactory.cpp - SharedLibraryHandler.cpp - SharedLibraryHandlerFactory.cpp + LibraryBridgeHandlerFactory.cpp + LibraryBridgeHandlers.cpp + library-bridge.cpp ) if (OS_LINUX) diff --git a/programs/library-bridge/LibraryInterface.cpp b/programs/library-bridge/ExternalDictionaryLibraryAPI.cpp similarity index 55% rename from programs/library-bridge/LibraryInterface.cpp rename to programs/library-bridge/ExternalDictionaryLibraryAPI.cpp index cae681e901a..70cd6fca375 100644 --- a/programs/library-bridge/LibraryInterface.cpp +++ b/programs/library-bridge/ExternalDictionaryLibraryAPI.cpp @@ -1,4 +1,4 @@ -#include "LibraryInterface.h" +#include "ExternalDictionaryLibraryAPI.h" #include @@ -7,24 +7,7 @@ namespace const char DICT_LOGGER_NAME[] = "LibraryDictionarySourceExternal"; } -namespace ClickHouseLibrary -{ - -std::string_view LIBRARY_CREATE_NEW_FUNC_NAME = "ClickHouseDictionary_v3_libNew"; -std::string_view LIBRARY_CLONE_FUNC_NAME = "ClickHouseDictionary_v3_libClone"; -std::string_view LIBRARY_DELETE_FUNC_NAME = "ClickHouseDictionary_v3_libDelete"; - -std::string_view LIBRARY_DATA_NEW_FUNC_NAME = "ClickHouseDictionary_v3_dataNew"; -std::string_view LIBRARY_DATA_DELETE_FUNC_NAME = "ClickHouseDictionary_v3_dataDelete"; - -std::string_view LIBRARY_LOAD_ALL_FUNC_NAME = "ClickHouseDictionary_v3_loadAll"; -std::string_view LIBRARY_LOAD_IDS_FUNC_NAME = "ClickHouseDictionary_v3_loadIds"; -std::string_view LIBRARY_LOAD_KEYS_FUNC_NAME = "ClickHouseDictionary_v3_loadKeys"; - -std::string_view LIBRARY_IS_MODIFIED_FUNC_NAME = "ClickHouseDictionary_v3_isModified"; -std::string_view LIBRARY_SUPPORTS_SELECTIVE_LOAD_FUNC_NAME = "ClickHouseDictionary_v3_supportsSelectiveLoad"; - -void log(LogLevel level, CString msg) +void ExternalDictionaryLibraryAPI::log(LogLevel level, CString msg) { auto & logger = Poco::Logger::get(DICT_LOGGER_NAME); switch (level) @@ -63,5 +46,3 @@ void log(LogLevel level, CString msg) break; } } - -} diff --git a/programs/library-bridge/ExternalDictionaryLibraryAPI.h b/programs/library-bridge/ExternalDictionaryLibraryAPI.h new file mode 100644 index 00000000000..04201710edb --- /dev/null +++ b/programs/library-bridge/ExternalDictionaryLibraryAPI.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include + +#define CLICKHOUSE_DICTIONARY_LIBRARY_API 1 + +struct ExternalDictionaryLibraryAPI +{ + using CString = const char *; + using ColumnName = CString; + using ColumnNames = ColumnName[]; + + struct CStrings + { + CString * data = nullptr; + uint64_t size = 0; + }; + + struct VectorUInt64 + { + const uint64_t * data = nullptr; + uint64_t size = 0; + }; + + struct ColumnsUInt64 + { + VectorUInt64 * data = nullptr; + uint64_t size = 0; + }; + + struct Field + { + const void * data = nullptr; + uint64_t size = 0; + }; + + struct Row + { + const Field * data = nullptr; + uint64_t size = 0; + }; + + struct Table + { + const Row * data = nullptr; + uint64_t size = 0; + uint64_t error_code = 0; // 0 = ok; !0 = error, with message in error_string + const char * error_string = nullptr; + }; + + enum LogLevel + { + FATAL = 1, + CRITICAL, + ERROR, + WARNING, + NOTICE, + INFORMATION, + DEBUG, + TRACE, + }; + + static void log(LogLevel level, CString msg); + + using LibraryContext = void *; + using LibraryLoggerFunc = void (*)(LogLevel, CString /* message */); + using LibrarySettings = CStrings *; + using LibraryData = void *; + using RawClickHouseLibraryTable = void *; + /// Can be safely casted into const Table * with static_cast + using RequestedColumnsNames = CStrings *; + using RequestedIds = const VectorUInt64 *; + using RequestedKeys = Table *; + + using LibraryNewFunc = LibraryContext (*)(LibrarySettings, LibraryLoggerFunc); + static constexpr const char * LIBRARY_CREATE_NEW_FUNC_NAME = "ClickHouseDictionary_v3_libNew"; + + using LibraryCloneFunc = LibraryContext (*)(LibraryContext); + static constexpr const char * LIBRARY_CLONE_FUNC_NAME = "ClickHouseDictionary_v3_libClone"; + + using LibraryDeleteFunc = void (*)(LibraryContext); + static constexpr const char * LIBRARY_DELETE_FUNC_NAME = "ClickHouseDictionary_v3_libDelete"; + + using LibraryDataNewFunc = LibraryData (*)(LibraryContext); + static constexpr const char * LIBRARY_DATA_NEW_FUNC_NAME = "ClickHouseDictionary_v3_dataNew"; + + using LibraryDataDeleteFunc = void (*)(LibraryContext, LibraryData); + static constexpr const char * LIBRARY_DATA_DELETE_FUNC_NAME = "ClickHouseDictionary_v3_dataDelete"; + + using LibraryLoadAllFunc = RawClickHouseLibraryTable (*)(LibraryData, LibrarySettings, RequestedColumnsNames); + static constexpr const char * LIBRARY_LOAD_ALL_FUNC_NAME = "ClickHouseDictionary_v3_loadAll"; + + using LibraryLoadIdsFunc = RawClickHouseLibraryTable (*)(LibraryData, LibrarySettings, RequestedColumnsNames, RequestedIds); + static constexpr const char * LIBRARY_LOAD_IDS_FUNC_NAME = "ClickHouseDictionary_v3_loadIds"; + + /// There are no requested column names for load keys func + using LibraryLoadKeysFunc = RawClickHouseLibraryTable (*)(LibraryData, LibrarySettings, RequestedKeys); + static constexpr const char * LIBRARY_LOAD_KEYS_FUNC_NAME = "ClickHouseDictionary_v3_loadKeys"; + + using LibraryIsModifiedFunc = bool (*)(LibraryContext, LibrarySettings); + static constexpr const char * LIBRARY_IS_MODIFIED_FUNC_NAME = "ClickHouseDictionary_v3_isModified"; + + using LibrarySupportsSelectiveLoadFunc = bool (*)(LibraryContext, LibrarySettings); + static constexpr const char * LIBRARY_SUPPORTS_SELECTIVE_LOAD_FUNC_NAME = "ClickHouseDictionary_v3_supportsSelectiveLoad"; +}; diff --git a/programs/library-bridge/ExternalDictionaryLibraryHandler.cpp b/programs/library-bridge/ExternalDictionaryLibraryHandler.cpp new file mode 100644 index 00000000000..14850da2ebf --- /dev/null +++ b/programs/library-bridge/ExternalDictionaryLibraryHandler.cpp @@ -0,0 +1,214 @@ +#include "ExternalDictionaryLibraryHandler.h" + +#include +#include +#include +#include + + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int EXTERNAL_LIBRARY_ERROR; + extern const int SIZES_OF_COLUMNS_DOESNT_MATCH; +} + + +ExternalDictionaryLibraryHandler::ExternalDictionaryLibraryHandler( + const std::string & library_path_, + const std::vector & library_settings, + const Block & sample_block_, + const std::vector & attributes_names_) + : library_path(library_path_) + , sample_block(sample_block_) + , attributes_names(attributes_names_) +{ + library = std::make_shared(library_path); + settings_holder = std::make_shared(CStringsHolder(library_settings)); + + auto lib_new = library->tryGet(ExternalDictionaryLibraryAPI::LIBRARY_CREATE_NEW_FUNC_NAME); + + if (lib_new) + lib_data = lib_new(&settings_holder->strings, ExternalDictionaryLibraryAPI::log); + else + throw Exception("Method extDict_libNew failed", ErrorCodes::EXTERNAL_LIBRARY_ERROR); +} + + +ExternalDictionaryLibraryHandler::ExternalDictionaryLibraryHandler(const ExternalDictionaryLibraryHandler & other) + : library_path{other.library_path} + , sample_block{other.sample_block} + , attributes_names{other.attributes_names} + , library{other.library} + , settings_holder{other.settings_holder} +{ + + auto lib_clone = library->tryGet(ExternalDictionaryLibraryAPI::LIBRARY_CLONE_FUNC_NAME); + + if (lib_clone) + { + lib_data = lib_clone(other.lib_data); + } + else + { + auto lib_new = library->tryGet(ExternalDictionaryLibraryAPI::LIBRARY_CREATE_NEW_FUNC_NAME); + + if (lib_new) + lib_data = lib_new(&settings_holder->strings, ExternalDictionaryLibraryAPI::log); + } +} + + +ExternalDictionaryLibraryHandler::~ExternalDictionaryLibraryHandler() +{ + auto lib_delete = library->tryGet(ExternalDictionaryLibraryAPI::LIBRARY_DELETE_FUNC_NAME); + + if (lib_delete) + lib_delete(lib_data); +} + + +bool ExternalDictionaryLibraryHandler::isModified() +{ + auto func_is_modified = library->tryGet(ExternalDictionaryLibraryAPI::LIBRARY_IS_MODIFIED_FUNC_NAME); + + if (func_is_modified) + return func_is_modified(lib_data, &settings_holder->strings); + + return true; +} + + +bool ExternalDictionaryLibraryHandler::supportsSelectiveLoad() +{ + auto func_supports_selective_load = library->tryGet(ExternalDictionaryLibraryAPI::LIBRARY_SUPPORTS_SELECTIVE_LOAD_FUNC_NAME); + + if (func_supports_selective_load) + return func_supports_selective_load(lib_data, &settings_holder->strings); + + return true; +} + + +Block ExternalDictionaryLibraryHandler::loadAll() +{ + auto columns_holder = std::make_unique(attributes_names.size()); + ExternalDictionaryLibraryAPI::CStrings columns{static_cast(columns_holder.get()), attributes_names.size()}; + for (size_t i = 0; i < attributes_names.size(); ++i) + columns.data[i] = attributes_names[i].c_str(); + + auto load_all_func = library->get(ExternalDictionaryLibraryAPI::LIBRARY_LOAD_ALL_FUNC_NAME); + auto data_new_func = library->get(ExternalDictionaryLibraryAPI::LIBRARY_DATA_NEW_FUNC_NAME); + auto data_delete_func = library->get(ExternalDictionaryLibraryAPI::LIBRARY_DATA_DELETE_FUNC_NAME); + + ExternalDictionaryLibraryAPI::LibraryData data_ptr = data_new_func(lib_data); + SCOPE_EXIT(data_delete_func(lib_data, data_ptr)); + + ExternalDictionaryLibraryAPI::RawClickHouseLibraryTable data = load_all_func(data_ptr, &settings_holder->strings, &columns); + return dataToBlock(data); +} + + +Block ExternalDictionaryLibraryHandler::loadIds(const std::vector & ids) +{ + const ExternalDictionaryLibraryAPI::VectorUInt64 ids_data{bit_cast(ids.data()), ids.size()}; + + auto columns_holder = std::make_unique(attributes_names.size()); + ExternalDictionaryLibraryAPI::CStrings columns_pass{static_cast(columns_holder.get()), attributes_names.size()}; + + auto load_ids_func = library->get(ExternalDictionaryLibraryAPI::LIBRARY_LOAD_IDS_FUNC_NAME); + auto data_new_func = library->get(ExternalDictionaryLibraryAPI::LIBRARY_DATA_NEW_FUNC_NAME); + auto data_delete_func = library->get(ExternalDictionaryLibraryAPI::LIBRARY_DATA_DELETE_FUNC_NAME); + + ExternalDictionaryLibraryAPI::LibraryData data_ptr = data_new_func(lib_data); + SCOPE_EXIT(data_delete_func(lib_data, data_ptr)); + + ExternalDictionaryLibraryAPI::RawClickHouseLibraryTable data = load_ids_func(data_ptr, &settings_holder->strings, &columns_pass, &ids_data); + return dataToBlock(data); +} + + +Block ExternalDictionaryLibraryHandler::loadKeys(const Columns & key_columns) +{ + auto holder = std::make_unique(key_columns.size()); + std::vector> column_data_holders; + + for (size_t i = 0; i < key_columns.size(); ++i) + { + auto cell_holder = std::make_unique(key_columns[i]->size()); + + for (size_t j = 0; j < key_columns[i]->size(); ++j) + { + auto data_ref = key_columns[i]->getDataAt(j); + + cell_holder[j] = ExternalDictionaryLibraryAPI::Field{ + .data = static_cast(data_ref.data), + .size = data_ref.size}; + } + + holder[i] = ExternalDictionaryLibraryAPI::Row{ + .data = static_cast(cell_holder.get()), + .size = key_columns[i]->size()}; + + column_data_holders.push_back(std::move(cell_holder)); + } + + ExternalDictionaryLibraryAPI::Table request_cols{ + .data = static_cast(holder.get()), + .size = key_columns.size()}; + + auto load_keys_func = library->get(ExternalDictionaryLibraryAPI::LIBRARY_LOAD_KEYS_FUNC_NAME); + auto data_new_func = library->get(ExternalDictionaryLibraryAPI::LIBRARY_DATA_NEW_FUNC_NAME); + auto data_delete_func = library->get(ExternalDictionaryLibraryAPI::LIBRARY_DATA_DELETE_FUNC_NAME); + + ExternalDictionaryLibraryAPI::LibraryData data_ptr = data_new_func(lib_data); + SCOPE_EXIT(data_delete_func(lib_data, data_ptr)); + + ExternalDictionaryLibraryAPI::RawClickHouseLibraryTable data = load_keys_func(data_ptr, &settings_holder->strings, &request_cols); + return dataToBlock(data); +} + + +Block ExternalDictionaryLibraryHandler::dataToBlock(ExternalDictionaryLibraryAPI::RawClickHouseLibraryTable data) +{ + if (!data) + throw Exception("LibraryDictionarySource: No data returned", ErrorCodes::EXTERNAL_LIBRARY_ERROR); + + const auto * columns_received = static_cast(data); + if (columns_received->error_code) + throw Exception( + "LibraryDictionarySource: Returned error: " + std::to_string(columns_received->error_code) + " " + (columns_received->error_string ? columns_received->error_string : ""), + ErrorCodes::EXTERNAL_LIBRARY_ERROR); + + MutableColumns columns = sample_block.cloneEmptyColumns(); + + for (size_t col_n = 0; col_n < columns_received->size; ++col_n) + { + if (columns.size() != columns_received->data[col_n].size) + throw Exception( + "LibraryDictionarySource: Returned unexpected number of columns: " + std::to_string(columns_received->data[col_n].size) + ", must be " + std::to_string(columns.size()), + ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH); + + for (size_t row_n = 0; row_n < columns_received->data[col_n].size; ++row_n) + { + const auto & field = columns_received->data[col_n].data[row_n]; + if (!field.data) + { + /// sample_block contains null_value (from config) inside corresponding column + const auto & col = sample_block.getByPosition(row_n); + columns[row_n]->insertFrom(*(col.column), 0); + } + else + { + const auto & size = field.size; + columns[row_n]->insertData(static_cast(field.data), size); + } + } + } + + return sample_block.cloneWithColumns(std::move(columns)); +} + +} diff --git a/programs/library-bridge/SharedLibraryHandler.h b/programs/library-bridge/ExternalDictionaryLibraryHandler.h similarity index 64% rename from programs/library-bridge/SharedLibraryHandler.h rename to programs/library-bridge/ExternalDictionaryLibraryHandler.h index 633180257b7..7713e9a6830 100644 --- a/programs/library-bridge/SharedLibraryHandler.h +++ b/programs/library-bridge/ExternalDictionaryLibraryHandler.h @@ -2,7 +2,7 @@ #include #include -#include "LibraryUtils.h" +#include "ExternalDictionaryLibraryUtils.h" namespace DB @@ -10,21 +10,21 @@ namespace DB /// A class that manages all operations with library dictionary. /// Every library dictionary source has its own object of this class, accessed by UUID. -class SharedLibraryHandler +class ExternalDictionaryLibraryHandler { public: - SharedLibraryHandler( + ExternalDictionaryLibraryHandler( const std::string & library_path_, const std::vector & library_settings, const Block & sample_block_, const std::vector & attributes_names_); - SharedLibraryHandler(const SharedLibraryHandler & other); + ExternalDictionaryLibraryHandler(const ExternalDictionaryLibraryHandler & other); - SharedLibraryHandler & operator=(const SharedLibraryHandler & other) = delete; + ExternalDictionaryLibraryHandler & operator=(const ExternalDictionaryLibraryHandler & other) = delete; - ~SharedLibraryHandler(); + ~ExternalDictionaryLibraryHandler(); Block loadAll(); @@ -39,7 +39,7 @@ public: const Block & getSampleBlock() { return sample_block; } private: - Block dataToBlock(const ClickHouseLibrary::RawClickHouseLibraryTable data); + Block dataToBlock(ExternalDictionaryLibraryAPI::RawClickHouseLibraryTable data); std::string library_path; const Block sample_block; @@ -50,6 +50,6 @@ private: void * lib_data; }; -using SharedLibraryHandlerPtr = std::shared_ptr; +using SharedLibraryHandlerPtr = std::shared_ptr; } diff --git a/programs/library-bridge/ExternalDictionaryLibraryHandlerFactory.cpp b/programs/library-bridge/ExternalDictionaryLibraryHandlerFactory.cpp new file mode 100644 index 00000000000..ffa5ff6f493 --- /dev/null +++ b/programs/library-bridge/ExternalDictionaryLibraryHandlerFactory.cpp @@ -0,0 +1,62 @@ +#include "ExternalDictionaryLibraryHandlerFactory.h" + + +namespace DB +{ + +SharedLibraryHandlerPtr ExternalDictionaryLibraryHandlerFactory::get(const std::string & dictionary_id) +{ + std::lock_guard lock(mutex); + auto library_handler = library_handlers.find(dictionary_id); + + if (library_handler != library_handlers.end()) + return library_handler->second; + + return nullptr; +} + + +void ExternalDictionaryLibraryHandlerFactory::create( + const std::string & dictionary_id, + const std::string & library_path, + const std::vector & library_settings, + const Block & sample_block, + const std::vector & attributes_names) +{ + std::lock_guard lock(mutex); + if (!library_handlers.contains(dictionary_id)) + library_handlers.emplace(std::make_pair(dictionary_id, std::make_shared(library_path, library_settings, sample_block, attributes_names))); + else + LOG_WARNING(&Poco::Logger::get("ExternalDictionaryLibraryHandlerFactory"), "Library handler with dictionary id {} already exists", dictionary_id); +} + + +bool ExternalDictionaryLibraryHandlerFactory::clone(const std::string & from_dictionary_id, const std::string & to_dictionary_id) +{ + std::lock_guard lock(mutex); + auto from_library_handler = library_handlers.find(from_dictionary_id); + + if (from_library_handler == library_handlers.end()) + return false; + + /// extDict_libClone method will be called in copy constructor + library_handlers[to_dictionary_id] = std::make_shared(*from_library_handler->second); + return true; +} + + +bool ExternalDictionaryLibraryHandlerFactory::remove(const std::string & dictionary_id) +{ + std::lock_guard lock(mutex); + /// extDict_libDelete is called in destructor. + return library_handlers.erase(dictionary_id); +} + + +ExternalDictionaryLibraryHandlerFactory & ExternalDictionaryLibraryHandlerFactory::instance() +{ + static ExternalDictionaryLibraryHandlerFactory instance; + return instance; +} + +} diff --git a/programs/library-bridge/SharedLibraryHandlerFactory.h b/programs/library-bridge/ExternalDictionaryLibraryHandlerFactory.h similarity index 77% rename from programs/library-bridge/SharedLibraryHandlerFactory.h rename to programs/library-bridge/ExternalDictionaryLibraryHandlerFactory.h index 1a6dfb01e34..d821270c474 100644 --- a/programs/library-bridge/SharedLibraryHandlerFactory.h +++ b/programs/library-bridge/ExternalDictionaryLibraryHandlerFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "SharedLibraryHandler.h" +#include "ExternalDictionaryLibraryHandler.h" #include #include @@ -11,11 +11,11 @@ namespace DB { /// Each library dictionary source has unique UUID. When clone() method is called, a new UUID is generated. -/// There is a unique mapping from diciotnary UUID to sharedLibraryHandler. -class SharedLibraryHandlerFactory final : private boost::noncopyable +/// There is a unique mapping from dictionary UUID to sharedLibraryHandler. +class ExternalDictionaryLibraryHandlerFactory final : private boost::noncopyable { public: - static SharedLibraryHandlerFactory & instance(); + static ExternalDictionaryLibraryHandlerFactory & instance(); SharedLibraryHandlerPtr get(const std::string & dictionary_id); diff --git a/programs/library-bridge/LibraryUtils.h b/programs/library-bridge/ExternalDictionaryLibraryUtils.h similarity index 67% rename from programs/library-bridge/LibraryUtils.h rename to programs/library-bridge/ExternalDictionaryLibraryUtils.h index b2498df0031..e813efab2a6 100644 --- a/programs/library-bridge/LibraryUtils.h +++ b/programs/library-bridge/ExternalDictionaryLibraryUtils.h @@ -5,7 +5,7 @@ #include #include -#include "LibraryInterface.h" +#include "ExternalDictionaryLibraryAPI.h" namespace DB @@ -22,7 +22,7 @@ public: strings_holder = strings_pass; strings.size = strings_holder.size(); - ptr_holder = std::make_unique(strings.size); + ptr_holder = std::make_unique(strings.size); strings.data = ptr_holder.get(); size_t i = 0; @@ -33,10 +33,10 @@ public: } } - ClickHouseLibrary::CStrings strings; // will pass pointer to lib + ExternalDictionaryLibraryAPI::CStrings strings; // will pass pointer to lib private: - std::unique_ptr ptr_holder = nullptr; + std::unique_ptr ptr_holder = nullptr; Container strings_holder; }; diff --git a/programs/library-bridge/HandlerFactory.cpp b/programs/library-bridge/HandlerFactory.cpp deleted file mode 100644 index 43087082c46..00000000000 --- a/programs/library-bridge/HandlerFactory.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "HandlerFactory.h" - -#include -#include -#include "Handlers.h" - - -namespace DB -{ - std::unique_ptr LibraryBridgeHandlerFactory::createRequestHandler(const HTTPServerRequest & request) - { - Poco::URI uri{request.getURI()}; - LOG_DEBUG(log, "Request URI: {}", uri.toString()); - - if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET) - return std::make_unique(keep_alive_timeout, getContext()); - - if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST) - return std::make_unique(keep_alive_timeout, getContext()); - - return nullptr; - } -} diff --git a/programs/library-bridge/HandlerFactory.h b/programs/library-bridge/HandlerFactory.h deleted file mode 100644 index 381745a0e74..00000000000 --- a/programs/library-bridge/HandlerFactory.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include - - -namespace DB -{ - -class SharedLibraryHandler; -using SharedLibraryHandlerPtr = std::shared_ptr; - -/// Factory for '/ping', '/' handlers. -class LibraryBridgeHandlerFactory : public HTTPRequestHandlerFactory, WithContext -{ -public: - LibraryBridgeHandlerFactory( - const std::string & name_, - size_t keep_alive_timeout_, - ContextPtr context_) - : WithContext(context_) - , log(&Poco::Logger::get(name_)) - , name(name_) - , keep_alive_timeout(keep_alive_timeout_) - { - } - - std::unique_ptr createRequestHandler(const HTTPServerRequest & request) override; - -private: - Poco::Logger * log; - std::string name; - size_t keep_alive_timeout; -}; - -} diff --git a/programs/library-bridge/LibraryBridge.cpp b/programs/library-bridge/LibraryBridge.cpp index 2e5d6041151..8a07ca57104 100644 --- a/programs/library-bridge/LibraryBridge.cpp +++ b/programs/library-bridge/LibraryBridge.cpp @@ -1,6 +1,5 @@ #include "LibraryBridge.h" -#pragma GCC diagnostic ignored "-Wmissing-declarations" int mainEntryClickHouseLibraryBridge(int argc, char ** argv) { DB::LibraryBridge app; @@ -15,3 +14,18 @@ int mainEntryClickHouseLibraryBridge(int argc, char ** argv) return code ? code : 1; } } + +namespace DB +{ + +std::string LibraryBridge::bridgeName() const +{ + return "LibraryBridge"; +} + +LibraryBridge::HandlerFactoryPtr LibraryBridge::getHandlerFactoryPtr(ContextPtr context) const +{ + return std::make_shared("LibraryRequestHandlerFactory", keep_alive_timeout, context); +} + +} diff --git a/programs/library-bridge/LibraryBridge.h b/programs/library-bridge/LibraryBridge.h index 9f0d0e148c1..04860a042a3 100644 --- a/programs/library-bridge/LibraryBridge.h +++ b/programs/library-bridge/LibraryBridge.h @@ -2,7 +2,7 @@ #include #include -#include "HandlerFactory.h" +#include "LibraryBridgeHandlerFactory.h" namespace DB @@ -12,15 +12,8 @@ class LibraryBridge : public IBridge { protected: - std::string bridgeName() const override - { - return "LibraryBridge"; - } - - HandlerFactoryPtr getHandlerFactoryPtr(ContextPtr context) const override - { - return std::make_shared("LibraryRequestHandlerFactory-factory", keep_alive_timeout, context); - } + std::string bridgeName() const override; + HandlerFactoryPtr getHandlerFactoryPtr(ContextPtr context) const override; }; } diff --git a/programs/library-bridge/LibraryBridgeHandlerFactory.cpp b/programs/library-bridge/LibraryBridgeHandlerFactory.cpp new file mode 100644 index 00000000000..e8e671db513 --- /dev/null +++ b/programs/library-bridge/LibraryBridgeHandlerFactory.cpp @@ -0,0 +1,34 @@ +#include "LibraryBridgeHandlerFactory.h" + +#include +#include +#include "LibraryBridgeHandlers.h" + + +namespace DB +{ +LibraryBridgeHandlerFactory::LibraryBridgeHandlerFactory( + const std::string & name_, + size_t keep_alive_timeout_, + ContextPtr context_) + : WithContext(context_) + , log(&Poco::Logger::get(name_)) + , name(name_) + , keep_alive_timeout(keep_alive_timeout_) +{ +} + +std::unique_ptr LibraryBridgeHandlerFactory::createRequestHandler(const HTTPServerRequest & request) +{ + Poco::URI uri{request.getURI()}; + LOG_DEBUG(log, "Request URI: {}", uri.toString()); + + if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET) + return std::make_unique(keep_alive_timeout, getContext()); + + if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST) + return std::make_unique(keep_alive_timeout, getContext()); + + return nullptr; +} +} diff --git a/programs/library-bridge/LibraryBridgeHandlerFactory.h b/programs/library-bridge/LibraryBridgeHandlerFactory.h new file mode 100644 index 00000000000..7565052c4cb --- /dev/null +++ b/programs/library-bridge/LibraryBridgeHandlerFactory.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + + +namespace DB +{ + +class LibraryBridgeHandlerFactory : public HTTPRequestHandlerFactory, WithContext +{ +public: + LibraryBridgeHandlerFactory( + const std::string & name_, + size_t keep_alive_timeout_, + ContextPtr context_); + + std::unique_ptr createRequestHandler(const HTTPServerRequest & request) override; + +private: + Poco::Logger * log; + const std::string name; + const size_t keep_alive_timeout; +}; + +} diff --git a/programs/library-bridge/Handlers.cpp b/programs/library-bridge/LibraryBridgeHandlers.cpp similarity index 80% rename from programs/library-bridge/Handlers.cpp rename to programs/library-bridge/LibraryBridgeHandlers.cpp index 58f9bd0a936..9537251954c 100644 --- a/programs/library-bridge/Handlers.cpp +++ b/programs/library-bridge/LibraryBridgeHandlers.cpp @@ -1,5 +1,5 @@ -#include "Handlers.h" -#include "SharedLibraryHandlerFactory.h" +#include "LibraryBridgeHandlers.h" +#include "ExternalDictionaryLibraryHandlerFactory.h" #include #include @@ -78,8 +78,14 @@ static void writeData(Block data, OutputFormatPtr format) executor.execute(); } +LibraryBridgeRequestHandler::LibraryBridgeRequestHandler(size_t keep_alive_timeout_, ContextPtr context_) + : WithContext(context_) + , log(&Poco::Logger::get("LibraryBridgeRequestHandler")) + , keep_alive_timeout(keep_alive_timeout_) +{ +} -void LibraryRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse & response) +void LibraryBridgeRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse & response) { LOG_TRACE(log, "Request URI: {}", request.getURI()); HTMLForm params(getContext()->getSettingsRef(), request); @@ -104,8 +110,8 @@ void LibraryRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServe try { - bool lib_new = (method == "libNew"); - if (method == "libClone") + bool lib_new = (method == "extDict_libNew"); + if (method == "extDict_libClone") { if (!params.has("from_dictionary_id")) { @@ -115,7 +121,7 @@ void LibraryRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServe std::string from_dictionary_id = params.get("from_dictionary_id"); bool cloned = false; - cloned = SharedLibraryHandlerFactory::instance().clone(from_dictionary_id, dictionary_id); + cloned = ExternalDictionaryLibraryHandlerFactory::instance().clone(from_dictionary_id, dictionary_id); if (cloned) { @@ -123,7 +129,7 @@ void LibraryRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServe } else { - LOG_TRACE(log, "Cannot clone from dictionary with id: {}, will call libNew instead", from_dictionary_id); + LOG_TRACE(log, "Cannot clone from dictionary with id: {}, will call extDict_libNew instead", from_dictionary_id); lib_new = true; } } @@ -138,13 +144,14 @@ void LibraryRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServe return; } + std::string library_path = params.get("library_path"); + if (!params.has("library_settings")) { processError(response, "No 'library_settings' in request URL"); return; } - std::string library_path = params.get("library_path"); const auto & settings_string = params.get("library_settings"); LOG_DEBUG(log, "Parsing library settings from binary string"); @@ -197,12 +204,12 @@ void LibraryRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServe LOG_DEBUG(log, "Dictionary sample block with null values: {}", sample_block_with_nulls.dumpStructure()); - SharedLibraryHandlerFactory::instance().create(dictionary_id, library_path, library_settings, sample_block_with_nulls, attributes_names); + ExternalDictionaryLibraryHandlerFactory::instance().create(dictionary_id, library_path, library_settings, sample_block_with_nulls, attributes_names); writeStringBinary("1", out); } - else if (method == "libDelete") + else if (method == "extDict_libDelete") { - auto deleted = SharedLibraryHandlerFactory::instance().remove(dictionary_id); + bool deleted = ExternalDictionaryLibraryHandlerFactory::instance().remove(dictionary_id); /// Do not throw, a warning is ok. if (!deleted) @@ -210,57 +217,57 @@ void LibraryRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServe writeStringBinary("1", out); } - else if (method == "isModified") + else if (method == "extDict_isModified") { - auto library_handler = SharedLibraryHandlerFactory::instance().get(dictionary_id); + auto library_handler = ExternalDictionaryLibraryHandlerFactory::instance().get(dictionary_id); if (!library_handler) throw Exception(ErrorCodes::BAD_REQUEST_PARAMETER, "Not found dictionary with id: {}", dictionary_id); bool res = library_handler->isModified(); writeStringBinary(std::to_string(res), out); } - else if (method == "supportsSelectiveLoad") + else if (method == "extDict_supportsSelectiveLoad") { - auto library_handler = SharedLibraryHandlerFactory::instance().get(dictionary_id); + auto library_handler = ExternalDictionaryLibraryHandlerFactory::instance().get(dictionary_id); if (!library_handler) throw Exception(ErrorCodes::BAD_REQUEST_PARAMETER, "Not found dictionary with id: {}", dictionary_id); bool res = library_handler->supportsSelectiveLoad(); writeStringBinary(std::to_string(res), out); } - else if (method == "loadAll") + else if (method == "extDict_loadAll") { - auto library_handler = SharedLibraryHandlerFactory::instance().get(dictionary_id); + auto library_handler = ExternalDictionaryLibraryHandlerFactory::instance().get(dictionary_id); if (!library_handler) throw Exception(ErrorCodes::BAD_REQUEST_PARAMETER, "Not found dictionary with id: {}", dictionary_id); const auto & sample_block = library_handler->getSampleBlock(); - LOG_DEBUG(log, "Calling loadAll() for dictionary id: {}", dictionary_id); + LOG_DEBUG(log, "Calling extDict_loadAll() for dictionary id: {}", dictionary_id); auto input = library_handler->loadAll(); LOG_DEBUG(log, "Started sending result data for dictionary id: {}", dictionary_id); auto output = FormatFactory::instance().getOutputFormat(FORMAT, out, sample_block, getContext()); writeData(std::move(input), std::move(output)); } - else if (method == "loadIds") + else if (method == "extDict_loadIds") { LOG_DEBUG(log, "Getting diciontary ids for dictionary with id: {}", dictionary_id); String ids_string; std::vector ids = parseIdsFromBinary(request.getStream()); - auto library_handler = SharedLibraryHandlerFactory::instance().get(dictionary_id); + auto library_handler = ExternalDictionaryLibraryHandlerFactory::instance().get(dictionary_id); if (!library_handler) throw Exception(ErrorCodes::BAD_REQUEST_PARAMETER, "Not found dictionary with id: {}", dictionary_id); const auto & sample_block = library_handler->getSampleBlock(); - LOG_DEBUG(log, "Calling loadIds() for dictionary id: {}", dictionary_id); + LOG_DEBUG(log, "Calling extDict_loadIds() for dictionary id: {}", dictionary_id); auto input = library_handler->loadIds(ids); LOG_DEBUG(log, "Started sending result data for dictionary id: {}", dictionary_id); auto output = FormatFactory::instance().getOutputFormat(FORMAT, out, sample_block, getContext()); writeData(std::move(input), std::move(output)); } - else if (method == "loadKeys") + else if (method == "extDict_loadKeys") { if (!params.has("requested_block_sample")) { @@ -289,18 +296,22 @@ void LibraryRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServe Block block; executor.pull(block); - auto library_handler = SharedLibraryHandlerFactory::instance().get(dictionary_id); + auto library_handler = ExternalDictionaryLibraryHandlerFactory::instance().get(dictionary_id); if (!library_handler) throw Exception(ErrorCodes::BAD_REQUEST_PARAMETER, "Not found dictionary with id: {}", dictionary_id); const auto & sample_block = library_handler->getSampleBlock(); - LOG_DEBUG(log, "Calling loadKeys() for dictionary id: {}", dictionary_id); + LOG_DEBUG(log, "Calling extDict_loadKeys() for dictionary id: {}", dictionary_id); auto input = library_handler->loadKeys(block.getColumns()); LOG_DEBUG(log, "Started sending result data for dictionary id: {}", dictionary_id); auto output = FormatFactory::instance().getOutputFormat(FORMAT, out, sample_block, getContext()); writeData(std::move(input), std::move(output)); } + else + { + LOG_WARNING(log, "Unknown library method: '{}'", method); + } } catch (...) { @@ -329,8 +340,14 @@ void LibraryRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServe } } +LibraryBridgeExistsHandler::LibraryBridgeExistsHandler(size_t keep_alive_timeout_, ContextPtr context_) + : WithContext(context_) + , keep_alive_timeout(keep_alive_timeout_) + , log(&Poco::Logger::get("LibraryBridgeExistsHandler")) +{ +} -void LibraryExistsHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse & response) +void LibraryBridgeExistsHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse & response) { try { @@ -344,15 +361,12 @@ void LibraryExistsHandler::handleRequest(HTTPServerRequest & request, HTTPServer } std::string dictionary_id = params.get("dictionary_id"); - auto library_handler = SharedLibraryHandlerFactory::instance().get(dictionary_id); - String res; - if (library_handler) - res = "1"; - else - res = "0"; + auto library_handler = ExternalDictionaryLibraryHandlerFactory::instance().get(dictionary_id); + + String res = library_handler ? "1" : "0"; setResponseDefaultHeaders(response, keep_alive_timeout); - LOG_TRACE(log, "Senging ping response: {} (dictionary id: {})", res, dictionary_id); + LOG_TRACE(log, "Sending ping response: {} (dictionary id: {})", res, dictionary_id); response.sendBuffer(res.data(), res.size()); } catch (...) diff --git a/programs/library-bridge/Handlers.h b/programs/library-bridge/LibraryBridgeHandlers.h similarity index 53% rename from programs/library-bridge/Handlers.h rename to programs/library-bridge/LibraryBridgeHandlers.h index 0a342a5d6c7..454bcc46acc 100644 --- a/programs/library-bridge/Handlers.h +++ b/programs/library-bridge/LibraryBridgeHandlers.h @@ -3,7 +3,7 @@ #include #include #include -#include "SharedLibraryHandler.h" +#include "ExternalDictionaryLibraryHandler.h" namespace DB @@ -11,23 +11,16 @@ namespace DB /// Handler for requests to Library Dictionary Source, returns response in RowBinary format. -/// When a library dictionary source is created, it sends libNew request to library bridge (which is started on first +/// When a library dictionary source is created, it sends 'extDict_libNew' request to library bridge (which is started on first /// request to it, if it was not yet started). On this request a new sharedLibrayHandler is added to a -/// sharedLibraryHandlerFactory by a dictionary uuid. With libNew request come: library_path, library_settings, +/// sharedLibraryHandlerFactory by a dictionary uuid. With 'extDict_libNew' request come: library_path, library_settings, /// names of dictionary attributes, sample block to parse block of null values, block of null values. Everything is /// passed in binary format and is urlencoded. When dictionary is cloned, a new handler is created. /// Each handler is unique to dictionary. -class LibraryRequestHandler : public HTTPRequestHandler, WithContext +class LibraryBridgeRequestHandler : public HTTPRequestHandler, WithContext { public: - - LibraryRequestHandler( - size_t keep_alive_timeout_, ContextPtr context_) - : WithContext(context_) - , log(&Poco::Logger::get("LibraryRequestHandler")) - , keep_alive_timeout(keep_alive_timeout_) - { - } + LibraryBridgeRequestHandler(size_t keep_alive_timeout_, ContextPtr context_); void handleRequest(HTTPServerRequest & request, HTTPServerResponse & response) override; @@ -39,22 +32,16 @@ private: }; -class LibraryExistsHandler : public HTTPRequestHandler, WithContext +class LibraryBridgeExistsHandler : public HTTPRequestHandler, WithContext { public: - explicit LibraryExistsHandler(size_t keep_alive_timeout_, ContextPtr context_) - : WithContext(context_) - , keep_alive_timeout(keep_alive_timeout_) - , log(&Poco::Logger::get("LibraryRequestHandler")) - { - } + LibraryBridgeExistsHandler(size_t keep_alive_timeout_, ContextPtr context_); void handleRequest(HTTPServerRequest & request, HTTPServerResponse & response) override; private: const size_t keep_alive_timeout; Poco::Logger * log; - }; } diff --git a/programs/library-bridge/LibraryInterface.h b/programs/library-bridge/LibraryInterface.h deleted file mode 100644 index d23de59bbb1..00000000000 --- a/programs/library-bridge/LibraryInterface.h +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -#include -#include - -#define CLICKHOUSE_DICTIONARY_LIBRARY_API 1 - -namespace ClickHouseLibrary -{ -using CString = const char *; -using ColumnName = CString; -using ColumnNames = ColumnName[]; - -struct CStrings -{ - CString * data = nullptr; - uint64_t size = 0; -}; - -struct VectorUInt64 -{ - const uint64_t * data = nullptr; - uint64_t size = 0; -}; - -struct ColumnsUInt64 -{ - VectorUInt64 * data = nullptr; - uint64_t size = 0; -}; - -struct Field -{ - const void * data = nullptr; - uint64_t size = 0; -}; - -struct Row -{ - const Field * data = nullptr; - uint64_t size = 0; -}; - -struct Table -{ - const Row * data = nullptr; - uint64_t size = 0; - uint64_t error_code = 0; // 0 = ok; !0 = error, with message in error_string - const char * error_string = nullptr; -}; - -enum LogLevel -{ - FATAL = 1, - CRITICAL, - ERROR, - WARNING, - NOTICE, - INFORMATION, - DEBUG, - TRACE, -}; - -void log(LogLevel level, CString msg); - -extern std::string_view LIBRARY_CREATE_NEW_FUNC_NAME; -extern std::string_view LIBRARY_CLONE_FUNC_NAME; -extern std::string_view LIBRARY_DELETE_FUNC_NAME; - -extern std::string_view LIBRARY_DATA_NEW_FUNC_NAME; -extern std::string_view LIBRARY_DATA_DELETE_FUNC_NAME; - -extern std::string_view LIBRARY_LOAD_ALL_FUNC_NAME; -extern std::string_view LIBRARY_LOAD_IDS_FUNC_NAME; -extern std::string_view LIBRARY_LOAD_KEYS_FUNC_NAME; - -extern std::string_view LIBRARY_IS_MODIFIED_FUNC_NAME; -extern std::string_view LIBRARY_SUPPORTS_SELECTIVE_LOAD_FUNC_NAME; - -using LibraryContext = void *; - -using LibraryLoggerFunc = void (*)(LogLevel, CString /* message */); - -using LibrarySettings = CStrings *; - -using LibraryNewFunc = LibraryContext (*)(LibrarySettings, LibraryLoggerFunc); -using LibraryCloneFunc = LibraryContext (*)(LibraryContext); -using LibraryDeleteFunc = void (*)(LibraryContext); - -using LibraryData = void *; -using LibraryDataNewFunc = LibraryData (*)(LibraryContext); -using LibraryDataDeleteFunc = void (*)(LibraryContext, LibraryData); - -/// Can be safely casted into const Table * with static_cast -using RawClickHouseLibraryTable = void *; -using RequestedColumnsNames = CStrings *; - -using LibraryLoadAllFunc = RawClickHouseLibraryTable (*)(LibraryData, LibrarySettings, RequestedColumnsNames); - -using RequestedIds = const VectorUInt64 *; -using LibraryLoadIdsFunc = RawClickHouseLibraryTable (*)(LibraryData, LibrarySettings, RequestedColumnsNames, RequestedIds); - -using RequestedKeys = Table *; -/// There are no requested column names for load keys func -using LibraryLoadKeysFunc = RawClickHouseLibraryTable (*)(LibraryData, LibrarySettings, RequestedKeys); - -using LibraryIsModifiedFunc = bool (*)(LibraryContext, LibrarySettings); -using LibrarySupportsSelectiveLoadFunc = bool (*)(LibraryContext, LibrarySettings); - -} diff --git a/programs/library-bridge/SharedLibraryHandler.cpp b/programs/library-bridge/SharedLibraryHandler.cpp deleted file mode 100644 index cfdba2c9eb8..00000000000 --- a/programs/library-bridge/SharedLibraryHandler.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#include "SharedLibraryHandler.h" - -#include -#include -#include -#include - - -namespace DB -{ - -namespace ErrorCodes -{ - extern const int EXTERNAL_LIBRARY_ERROR; - extern const int SIZES_OF_COLUMNS_DOESNT_MATCH; -} - - -SharedLibraryHandler::SharedLibraryHandler( - const std::string & library_path_, - const std::vector & library_settings, - const Block & sample_block_, - const std::vector & attributes_names_) - : library_path(library_path_) - , sample_block(sample_block_) - , attributes_names(attributes_names_) -{ - library = std::make_shared(library_path, RTLD_LAZY); - settings_holder = std::make_shared(CStringsHolder(library_settings)); - - auto lib_new = library->tryGet(ClickHouseLibrary::LIBRARY_CREATE_NEW_FUNC_NAME); - - if (lib_new) - lib_data = lib_new(&settings_holder->strings, ClickHouseLibrary::log); - else - throw Exception("Method libNew failed", ErrorCodes::EXTERNAL_LIBRARY_ERROR); -} - - -SharedLibraryHandler::SharedLibraryHandler(const SharedLibraryHandler & other) - : library_path{other.library_path} - , sample_block{other.sample_block} - , attributes_names{other.attributes_names} - , library{other.library} - , settings_holder{other.settings_holder} -{ - - auto lib_clone = library->tryGet(ClickHouseLibrary::LIBRARY_CLONE_FUNC_NAME); - - if (lib_clone) - { - lib_data = lib_clone(other.lib_data); - } - else - { - auto lib_new = library->tryGet(ClickHouseLibrary::LIBRARY_CREATE_NEW_FUNC_NAME); - - if (lib_new) - lib_data = lib_new(&settings_holder->strings, ClickHouseLibrary::log); - } -} - - -SharedLibraryHandler::~SharedLibraryHandler() -{ - auto lib_delete = library->tryGet(ClickHouseLibrary::LIBRARY_DELETE_FUNC_NAME); - - if (lib_delete) - lib_delete(lib_data); -} - - -bool SharedLibraryHandler::isModified() -{ - auto func_is_modified = library->tryGet(ClickHouseLibrary::LIBRARY_IS_MODIFIED_FUNC_NAME); - - if (func_is_modified) - return func_is_modified(lib_data, &settings_holder->strings); - - return true; -} - - -bool SharedLibraryHandler::supportsSelectiveLoad() -{ - auto func_supports_selective_load = library->tryGet(ClickHouseLibrary::LIBRARY_SUPPORTS_SELECTIVE_LOAD_FUNC_NAME); - - if (func_supports_selective_load) - return func_supports_selective_load(lib_data, &settings_holder->strings); - - return true; -} - - -Block SharedLibraryHandler::loadAll() -{ - auto columns_holder = std::make_unique(attributes_names.size()); - ClickHouseLibrary::CStrings columns{static_cast(columns_holder.get()), attributes_names.size()}; - for (size_t i = 0; i < attributes_names.size(); ++i) - columns.data[i] = attributes_names[i].c_str(); - - auto load_all_func = library->get(ClickHouseLibrary::LIBRARY_LOAD_ALL_FUNC_NAME); - auto data_new_func = library->get(ClickHouseLibrary::LIBRARY_DATA_NEW_FUNC_NAME); - auto data_delete_func = library->get(ClickHouseLibrary::LIBRARY_DATA_DELETE_FUNC_NAME); - - ClickHouseLibrary::LibraryData data_ptr = data_new_func(lib_data); - SCOPE_EXIT(data_delete_func(lib_data, data_ptr)); - - ClickHouseLibrary::RawClickHouseLibraryTable data = load_all_func(data_ptr, &settings_holder->strings, &columns); - return dataToBlock(data); -} - - -Block SharedLibraryHandler::loadIds(const std::vector & ids) -{ - const ClickHouseLibrary::VectorUInt64 ids_data{bit_cast(ids.data()), ids.size()}; - - auto columns_holder = std::make_unique(attributes_names.size()); - ClickHouseLibrary::CStrings columns_pass{static_cast(columns_holder.get()), attributes_names.size()}; - - auto load_ids_func = library->get(ClickHouseLibrary::LIBRARY_LOAD_IDS_FUNC_NAME); - auto data_new_func = library->get(ClickHouseLibrary::LIBRARY_DATA_NEW_FUNC_NAME); - auto data_delete_func = library->get(ClickHouseLibrary::LIBRARY_DATA_DELETE_FUNC_NAME); - - ClickHouseLibrary::LibraryData data_ptr = data_new_func(lib_data); - SCOPE_EXIT(data_delete_func(lib_data, data_ptr)); - - ClickHouseLibrary::RawClickHouseLibraryTable data = load_ids_func(data_ptr, &settings_holder->strings, &columns_pass, &ids_data); - return dataToBlock(data); -} - - -Block SharedLibraryHandler::loadKeys(const Columns & key_columns) -{ - auto holder = std::make_unique(key_columns.size()); - std::vector> column_data_holders; - - for (size_t i = 0; i < key_columns.size(); ++i) - { - auto cell_holder = std::make_unique(key_columns[i]->size()); - - for (size_t j = 0; j < key_columns[i]->size(); ++j) - { - auto data_ref = key_columns[i]->getDataAt(j); - - cell_holder[j] = ClickHouseLibrary::Field{ - .data = static_cast(data_ref.data), - .size = data_ref.size}; - } - - holder[i] = ClickHouseLibrary::Row{ - .data = static_cast(cell_holder.get()), - .size = key_columns[i]->size()}; - - column_data_holders.push_back(std::move(cell_holder)); - } - - ClickHouseLibrary::Table request_cols{ - .data = static_cast(holder.get()), - .size = key_columns.size()}; - - auto load_keys_func = library->get(ClickHouseLibrary::LIBRARY_LOAD_KEYS_FUNC_NAME); - auto data_new_func = library->get(ClickHouseLibrary::LIBRARY_DATA_NEW_FUNC_NAME); - auto data_delete_func = library->get(ClickHouseLibrary::LIBRARY_DATA_DELETE_FUNC_NAME); - - ClickHouseLibrary::LibraryData data_ptr = data_new_func(lib_data); - SCOPE_EXIT(data_delete_func(lib_data, data_ptr)); - - ClickHouseLibrary::RawClickHouseLibraryTable data = load_keys_func(data_ptr, &settings_holder->strings, &request_cols); - return dataToBlock(data); -} - - -Block SharedLibraryHandler::dataToBlock(const ClickHouseLibrary::RawClickHouseLibraryTable data) -{ - if (!data) - throw Exception("LibraryDictionarySource: No data returned", ErrorCodes::EXTERNAL_LIBRARY_ERROR); - - const auto * columns_received = static_cast(data); - if (columns_received->error_code) - throw Exception( - "LibraryDictionarySource: Returned error: " + std::to_string(columns_received->error_code) + " " + (columns_received->error_string ? columns_received->error_string : ""), - ErrorCodes::EXTERNAL_LIBRARY_ERROR); - - MutableColumns columns = sample_block.cloneEmptyColumns(); - - for (size_t col_n = 0; col_n < columns_received->size; ++col_n) - { - if (columns.size() != columns_received->data[col_n].size) - throw Exception( - "LibraryDictionarySource: Returned unexpected number of columns: " + std::to_string(columns_received->data[col_n].size) + ", must be " + std::to_string(columns.size()), - ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH); - - for (size_t row_n = 0; row_n < columns_received->data[col_n].size; ++row_n) - { - const auto & field = columns_received->data[col_n].data[row_n]; - if (!field.data) - { - /// sample_block contains null_value (from config) inside corresponding column - const auto & col = sample_block.getByPosition(row_n); - columns[row_n]->insertFrom(*(col.column), 0); - } - else - { - const auto & size = field.size; - columns[row_n]->insertData(static_cast(field.data), size); - } - } - } - - return sample_block.cloneWithColumns(std::move(columns)); -} - -} diff --git a/programs/library-bridge/SharedLibraryHandlerFactory.cpp b/programs/library-bridge/SharedLibraryHandlerFactory.cpp deleted file mode 100644 index 2abc208e502..00000000000 --- a/programs/library-bridge/SharedLibraryHandlerFactory.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "SharedLibraryHandlerFactory.h" - - -namespace DB -{ - -SharedLibraryHandlerPtr SharedLibraryHandlerFactory::get(const std::string & dictionary_id) -{ - std::lock_guard lock(mutex); - auto library_handler = library_handlers.find(dictionary_id); - - if (library_handler != library_handlers.end()) - return library_handler->second; - - return nullptr; -} - - -void SharedLibraryHandlerFactory::create( - const std::string & dictionary_id, - const std::string & library_path, - const std::vector & library_settings, - const Block & sample_block, - const std::vector & attributes_names) -{ - std::lock_guard lock(mutex); - if (!library_handlers.contains(dictionary_id)) - library_handlers.emplace(std::make_pair(dictionary_id, std::make_shared(library_path, library_settings, sample_block, attributes_names))); - else - LOG_WARNING(&Poco::Logger::get("SharedLibraryHandlerFactory"), "Library handler with dictionary id {} already exists", dictionary_id); -} - - -bool SharedLibraryHandlerFactory::clone(const std::string & from_dictionary_id, const std::string & to_dictionary_id) -{ - std::lock_guard lock(mutex); - auto from_library_handler = library_handlers.find(from_dictionary_id); - - if (from_library_handler == library_handlers.end()) - return false; - - /// libClone method will be called in copy constructor - library_handlers[to_dictionary_id] = std::make_shared(*from_library_handler->second); - return true; -} - - -bool SharedLibraryHandlerFactory::remove(const std::string & dictionary_id) -{ - std::lock_guard lock(mutex); - /// libDelete is called in destructor. - return library_handlers.erase(dictionary_id); -} - - -SharedLibraryHandlerFactory & SharedLibraryHandlerFactory::instance() -{ - static SharedLibraryHandlerFactory ret; - return ret; -} - -} diff --git a/programs/library-bridge/library-bridge.cpp b/programs/library-bridge/library-bridge.cpp index 5fff2ffe525..9c44fdb4564 100644 --- a/programs/library-bridge/library-bridge.cpp +++ b/programs/library-bridge/library-bridge.cpp @@ -1,3 +1,2 @@ int mainEntryClickHouseLibraryBridge(int argc, char ** argv); int main(int argc_, char ** argv_) { return mainEntryClickHouseLibraryBridge(argc_, argv_); } - diff --git a/programs/odbc-bridge/CMakeLists.txt b/programs/odbc-bridge/CMakeLists.txt index a40df3cfb6e..f649e81c50a 100644 --- a/programs/odbc-bridge/CMakeLists.txt +++ b/programs/odbc-bridge/CMakeLists.txt @@ -2,17 +2,17 @@ include(${ClickHouse_SOURCE_DIR}/cmake/split_debug_symbols.cmake) set (CLICKHOUSE_ODBC_BRIDGE_SOURCES ColumnInfoHandler.cpp - getIdentifierQuote.cpp - HandlerFactory.cpp IdentifierQuoteHandler.cpp MainHandler.cpp ODBCBlockInputStream.cpp ODBCBlockOutputStream.cpp ODBCBridge.cpp + ODBCHandlerFactory.cpp PingHandler.cpp SchemaAllowedHandler.cpp - validateODBCConnectionString.cpp + getIdentifierQuote.cpp odbc-bridge.cpp + validateODBCConnectionString.cpp ) if (OS_LINUX) diff --git a/programs/odbc-bridge/ODBCBridge.cpp b/programs/odbc-bridge/ODBCBridge.cpp index 0deefe46014..e91cc3158df 100644 --- a/programs/odbc-bridge/ODBCBridge.cpp +++ b/programs/odbc-bridge/ODBCBridge.cpp @@ -1,6 +1,5 @@ #include "ODBCBridge.h" -#pragma GCC diagnostic ignored "-Wmissing-declarations" int mainEntryClickHouseODBCBridge(int argc, char ** argv) { DB::ODBCBridge app; @@ -15,3 +14,18 @@ int mainEntryClickHouseODBCBridge(int argc, char ** argv) return code ? code : 1; } } + +namespace DB +{ + +std::string ODBCBridge::bridgeName() const +{ + return "ODBCBridge"; +} + +ODBCBridge::HandlerFactoryPtr ODBCBridge::getHandlerFactoryPtr(ContextPtr context) const +{ + return std::make_shared("ODBCRequestHandlerFactory-factory", keep_alive_timeout, context); +} + +} diff --git a/programs/odbc-bridge/ODBCBridge.h b/programs/odbc-bridge/ODBCBridge.h index b07ae095d7c..5e56dce7c60 100644 --- a/programs/odbc-bridge/ODBCBridge.h +++ b/programs/odbc-bridge/ODBCBridge.h @@ -3,7 +3,7 @@ #include #include #include -#include "HandlerFactory.h" +#include "ODBCHandlerFactory.h" namespace DB @@ -13,14 +13,7 @@ class ODBCBridge : public IBridge { protected: - std::string bridgeName() const override - { - return "ODBCBridge"; - } - - HandlerFactoryPtr getHandlerFactoryPtr(ContextPtr context) const override - { - return std::make_shared("ODBCRequestHandlerFactory-factory", keep_alive_timeout, context); - } + std::string bridgeName() const override; + HandlerFactoryPtr getHandlerFactoryPtr(ContextPtr context) const override; }; } diff --git a/programs/odbc-bridge/HandlerFactory.cpp b/programs/odbc-bridge/ODBCHandlerFactory.cpp similarity index 83% rename from programs/odbc-bridge/HandlerFactory.cpp rename to programs/odbc-bridge/ODBCHandlerFactory.cpp index 4b6dacafe30..2ae533431d3 100644 --- a/programs/odbc-bridge/HandlerFactory.cpp +++ b/programs/odbc-bridge/ODBCHandlerFactory.cpp @@ -1,4 +1,4 @@ -#include "HandlerFactory.h" +#include "ODBCHandlerFactory.h" #include "PingHandler.h" #include "ColumnInfoHandler.h" #include @@ -9,6 +9,14 @@ namespace DB { +ODBCBridgeHandlerFactory::ODBCBridgeHandlerFactory(const std::string & name_, size_t keep_alive_timeout_, ContextPtr context_) + : WithContext(context_) + , log(&Poco::Logger::get(name_)) + , name(name_) + , keep_alive_timeout(keep_alive_timeout_) +{ +} + std::unique_ptr ODBCBridgeHandlerFactory::createRequestHandler(const HTTPServerRequest & request) { Poco::URI uri{request.getURI()}; diff --git a/programs/odbc-bridge/HandlerFactory.h b/programs/odbc-bridge/ODBCHandlerFactory.h similarity index 79% rename from programs/odbc-bridge/HandlerFactory.h rename to programs/odbc-bridge/ODBCHandlerFactory.h index ffbbe3670af..3e3da7c9f24 100644 --- a/programs/odbc-bridge/HandlerFactory.h +++ b/programs/odbc-bridge/ODBCHandlerFactory.h @@ -17,13 +17,7 @@ namespace DB class ODBCBridgeHandlerFactory : public HTTPRequestHandlerFactory, WithContext { public: - ODBCBridgeHandlerFactory(const std::string & name_, size_t keep_alive_timeout_, ContextPtr context_) - : WithContext(context_) - , log(&Poco::Logger::get(name_)) - , name(name_) - , keep_alive_timeout(keep_alive_timeout_) - { - } + ODBCBridgeHandlerFactory(const std::string & name_, size_t keep_alive_timeout_, ContextPtr context_); std::unique_ptr createRequestHandler(const HTTPServerRequest & request) override; diff --git a/src/BridgeHelper/IBridgeHelper.h b/src/BridgeHelper/IBridgeHelper.h index 7c9b16e9014..67ca5bf5cdd 100644 --- a/src/BridgeHelper/IBridgeHelper.h +++ b/src/BridgeHelper/IBridgeHelper.h @@ -41,7 +41,6 @@ protected: /// Check bridge is running. Can also check something else in the mean time. virtual bool bridgeHandShake() = 0; - /// clickhouse-odbc-bridge, clickhouse-library-bridge virtual String serviceAlias() const = 0; virtual String serviceFileName() const = 0; diff --git a/src/BridgeHelper/LibraryBridgeHelper.cpp b/src/BridgeHelper/LibraryBridgeHelper.cpp index 052ef3329b6..e24ce3d2434 100644 --- a/src/BridgeHelper/LibraryBridgeHelper.cpp +++ b/src/BridgeHelper/LibraryBridgeHelper.cpp @@ -38,10 +38,10 @@ LibraryBridgeHelper::LibraryBridgeHelper( , http_timeout(context_->getGlobalContext()->getSettingsRef().http_receive_timeout.value) , library_data(library_data_) , dictionary_id(dictionary_id_) + , bridge_host(config.getString("library_bridge.host", DEFAULT_HOST)) + , bridge_port(config.getUInt("library_bridge.port", DEFAULT_PORT)) , http_timeouts(ConnectionTimeouts::getHTTPTimeouts(context_)) { - bridge_port = config.getUInt("library_bridge.port", DEFAULT_PORT); - bridge_host = config.getString("library_bridge.host", DEFAULT_HOST); } @@ -91,12 +91,12 @@ bool LibraryBridgeHelper::bridgeHandShake() * 2. Bridge crashed or restarted for some reason while server did not. **/ if (result.size() != 1) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Unexpected message from library bridge: {}. Check bridge and server have the same version.", result); + throw Exception(ErrorCodes::LOGICAL_ERROR, "Unexpected message from library bridge: {}. Check that bridge and server have the same version.", result); UInt8 dictionary_id_exists; auto parsed = tryParse(dictionary_id_exists, result); if (!parsed || (dictionary_id_exists != 0 && dictionary_id_exists != 1)) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Unexpected message from library bridge: {} ({}). Check bridge and server have the same version.", + throw Exception(ErrorCodes::LOGICAL_ERROR, "Unexpected message from library bridge: {} ({}). Check that bridge and server have the same version.", result, parsed ? toString(dictionary_id_exists) : "failed to parse"); LOG_TRACE(log, "dictionary_id: {}, dictionary_id_exists on bridge side: {}, library confirmed to be initialized on server side: {}", @@ -113,7 +113,7 @@ bool LibraryBridgeHelper::bridgeHandShake() bool reinitialized = false; try { - auto uri = createRequestURI(LIB_NEW_METHOD); + auto uri = createRequestURI(EXT_DICT_LIB_NEW_METHOD); reinitialized = executeRequest(uri, getInitLibraryCallback()); } catch (...) @@ -153,7 +153,7 @@ ReadWriteBufferFromHTTP::OutStreamCallback LibraryBridgeHelper::getInitLibraryCa bool LibraryBridgeHelper::initLibrary() { startBridgeSync(); - auto uri = createRequestURI(LIB_NEW_METHOD); + auto uri = createRequestURI(EXT_DICT_LIB_NEW_METHOD); library_initialized = executeRequest(uri, getInitLibraryCallback()); return library_initialized; } @@ -162,7 +162,7 @@ bool LibraryBridgeHelper::initLibrary() bool LibraryBridgeHelper::cloneLibrary(const Field & other_dictionary_id) { startBridgeSync(); - auto uri = createRequestURI(LIB_CLONE_METHOD); + auto uri = createRequestURI(EXT_DICT_LIB_CLONE_METHOD); uri.addQueryParameter("from_dictionary_id", toString(other_dictionary_id)); /// We also pass initialization settings in order to create a library handler /// in case from_dictionary_id does not exist in bridge side (possible in case of bridge crash). @@ -177,7 +177,7 @@ bool LibraryBridgeHelper::removeLibrary() /// because in this case after restart it will not have this dictionaty id in memory anyway. if (bridgeHandShake()) { - auto uri = createRequestURI(LIB_DELETE_METHOD); + auto uri = createRequestURI(EXT_DICT_LIB_DELETE_METHOD); return executeRequest(uri); } return true; @@ -187,7 +187,7 @@ bool LibraryBridgeHelper::removeLibrary() bool LibraryBridgeHelper::isModified() { startBridgeSync(); - auto uri = createRequestURI(IS_MODIFIED_METHOD); + auto uri = createRequestURI(EXT_DICT_IS_MODIFIED_METHOD); return executeRequest(uri); } @@ -195,7 +195,7 @@ bool LibraryBridgeHelper::isModified() bool LibraryBridgeHelper::supportsSelectiveLoad() { startBridgeSync(); - auto uri = createRequestURI(SUPPORTS_SELECTIVE_LOAD_METHOD); + auto uri = createRequestURI(EXT_DICT_SUPPORTS_SELECTIVE_LOAD_METHOD); return executeRequest(uri); } @@ -203,7 +203,7 @@ bool LibraryBridgeHelper::supportsSelectiveLoad() QueryPipeline LibraryBridgeHelper::loadAll() { startBridgeSync(); - auto uri = createRequestURI(LOAD_ALL_METHOD); + auto uri = createRequestURI(EXT_DICT_LOAD_ALL_METHOD); return QueryPipeline(loadBase(uri)); } @@ -211,7 +211,7 @@ QueryPipeline LibraryBridgeHelper::loadAll() QueryPipeline LibraryBridgeHelper::loadIds(const std::vector & ids) { startBridgeSync(); - auto uri = createRequestURI(LOAD_IDS_METHOD); + auto uri = createRequestURI(EXT_DICT_LOAD_IDS_METHOD); uri.addQueryParameter("ids_num", toString(ids.size())); /// Not used parameter, but helpful auto ids_string = getDictIdsString(ids); return QueryPipeline(loadBase(uri, [ids_string](std::ostream & os) { os << ids_string; })); @@ -221,7 +221,7 @@ QueryPipeline LibraryBridgeHelper::loadIds(const std::vector & ids) QueryPipeline LibraryBridgeHelper::loadKeys(const Block & requested_block) { startBridgeSync(); - auto uri = createRequestURI(LOAD_KEYS_METHOD); + auto uri = createRequestURI(EXT_DICT_LOAD_KEYS_METHOD); /// Sample block to parse block from callback uri.addQueryParameter("requested_block_sample", requested_block.getNamesAndTypesList().toString()); ReadWriteBufferFromHTTP::OutStreamCallback out_stream_callback = [requested_block, this](std::ostream & os) diff --git a/src/BridgeHelper/LibraryBridgeHelper.h b/src/BridgeHelper/LibraryBridgeHelper.h index 598e473f07b..25cfd123679 100644 --- a/src/BridgeHelper/LibraryBridgeHelper.h +++ b/src/BridgeHelper/LibraryBridgeHelper.h @@ -45,10 +45,6 @@ public: QueryPipeline loadKeys(const Block & requested_block); - QueryPipeline loadBase(const Poco::URI & uri, ReadWriteBufferFromHTTP::OutStreamCallback out_stream_callback = {}); - - bool executeRequest(const Poco::URI & uri, ReadWriteBufferFromHTTP::OutStreamCallback out_stream_callback = {}) const; - LibraryInitData getLibraryData() const { return library_data; } protected: @@ -74,18 +70,22 @@ protected: Poco::URI createBaseURI() const override; + QueryPipeline loadBase(const Poco::URI & uri, ReadWriteBufferFromHTTP::OutStreamCallback out_stream_callback = {}); + + bool executeRequest(const Poco::URI & uri, ReadWriteBufferFromHTTP::OutStreamCallback out_stream_callback = {}) const; + ReadWriteBufferFromHTTP::OutStreamCallback getInitLibraryCallback() const; private: - static constexpr inline auto LIB_NEW_METHOD = "libNew"; - static constexpr inline auto LIB_CLONE_METHOD = "libClone"; - static constexpr inline auto LIB_DELETE_METHOD = "libDelete"; - static constexpr inline auto LOAD_ALL_METHOD = "loadAll"; - static constexpr inline auto LOAD_IDS_METHOD = "loadIds"; - static constexpr inline auto LOAD_KEYS_METHOD = "loadKeys"; - static constexpr inline auto IS_MODIFIED_METHOD = "isModified"; + static constexpr inline auto EXT_DICT_LIB_NEW_METHOD = "extDict_libNew"; + static constexpr inline auto EXT_DICT_LIB_CLONE_METHOD = "extDict_libClone"; + static constexpr inline auto EXT_DICT_LIB_DELETE_METHOD = "extDict_libDelete"; + static constexpr inline auto EXT_DICT_LOAD_ALL_METHOD = "extDict_loadAll"; + static constexpr inline auto EXT_DICT_LOAD_IDS_METHOD = "extDict_loadIds"; + static constexpr inline auto EXT_DICT_LOAD_KEYS_METHOD = "extDict_loadKeys"; + static constexpr inline auto EXT_DICT_IS_MODIFIED_METHOD = "extDict_isModified"; static constexpr inline auto PING = "ping"; - static constexpr inline auto SUPPORTS_SELECTIVE_LOAD_METHOD = "supportsSelectiveLoad"; + static constexpr inline auto EXT_DICT_SUPPORTS_SELECTIVE_LOAD_METHOD = "extDict_supportsSelectiveLoad"; Poco::URI createRequestURI(const String & method) const; diff --git a/src/Dictionaries/LibraryDictionarySource.cpp b/src/Dictionaries/LibraryDictionarySource.cpp index 936bbd72299..dc240cf20ad 100644 --- a/src/Dictionaries/LibraryDictionarySource.cpp +++ b/src/Dictionaries/LibraryDictionarySource.cpp @@ -12,8 +12,6 @@ #include #include -namespace fs = std::filesystem; - namespace DB { @@ -44,6 +42,8 @@ LibraryDictionarySource::LibraryDictionarySource( if (created_from_ddl && !fileOrSymlinkPathStartsWith(path, dictionaries_lib_path)) throw Exception(ErrorCodes::PATH_ACCESS_DENIED, "File path {} is not inside {}", path, dictionaries_lib_path); + namespace fs = std::filesystem; + if (!fs::exists(path)) throw Exception(ErrorCodes::FILE_DOESNT_EXIST, "LibraryDictionarySource: Can't load library {}: file doesn't exist", path); From 7df30747f382eeb91b4f7c3e206c0f49805669b1 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Thu, 4 Aug 2022 18:12:30 +0000 Subject: [PATCH 336/672] Typos --- tests/integration/README.md | 3 ++- tests/integration/runner | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/integration/README.md b/tests/integration/README.md index 18d46908524..f0160dcd444 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -31,6 +31,7 @@ sudo -H pip install \ kafka-python \ kazoo \ minio \ + lz4 \ protobuf \ psycopg2-binary \ pymongo \ @@ -147,7 +148,7 @@ will automagically detect the types of variables and only the small diff of two ### Troubleshooting -If tests failing for misterious reasons, this may help: +If tests failing for mysterious reasons, this may help: ``` sudo service docker stop diff --git a/tests/integration/runner b/tests/integration/runner index cd07875ad1d..8f666143af1 100755 --- a/tests/integration/runner +++ b/tests/integration/runner @@ -345,7 +345,7 @@ if __name__ == "__main__": f"docker volume create {VOLUME_NAME}_volume", shell=True ) except Exception as ex: - print("Volume creationg failed, probably it already exists, exception", ex) + print("Volume creation failed, probably it already exists, exception", ex) # TODO: this part cleans out stale volumes produced by container name # randomizer, we should remove it after Sep 2022 try: From 20bb8a248ebe501f7fb6513859e7544182a6d49a Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Thu, 4 Aug 2022 18:33:13 +0000 Subject: [PATCH 337/672] Prepare server-side BridgeHelper for catboost integration Wall of text, sorry, but I also had to document some stuff for myself: There are three ways to communicate data using HTTP: - the HTTP verb: for our purposes, PUT and GET, - the HTTP path: '/ping', '/request' etc., - the HTTP URL parameter(s), e.g. 'method=libNew&dictionary_id=1234' The bridge will use different handlers for communication with the external dictionary library and for communication with the catboost library. Handlers are created based on a combination of the HTTP verb and the HTTP method. More specifically, there will be combinations - GET + '/extdict_ping' - PUT + '/extdict_request' - GET + '/catboost_ping' - PUT + '/catboost_request'. For each combination, the bridge expects a certain set of URL parameters, e.g. for the first combination parameter "dictionary_id" is expected. Starting with this commit, the library-bridge creates handlers based on the first two combinations (the latter two combinations will be added later). This makes the handler creation mechanism consistent with it's counterpart in xdbc-bridge. For that, it was necessary to make both IBridgeHelper methods "getMainURI()" and "getPingURI()" pure virtual so that derived classes (LibraryBridgeHelper and XDBCBridgeHelper) must provide custom URLs with custom paths. Side note 1: Previously, LibraryBridgeHelper sent HTTP URL parameter "method=ping" during handshake (PING) but the library-bridge ignored that parameter. We now omit this parameter, i.e. LibraryBridgeHelper::PING was removed. Again, this makes things consistent with xdbc-bridge. Side note 2: xdbc-bridge is unchanged in this commit. Therefore, XDBCBridgeHelper now uses the HTTP paths previously in the base class. For funny reason, XDBCBridgeHelper did not use IBridgeHelper::getMainURI() - it generates the URLs by itself. I kept it that way for now but provided an implementation of getMainURI() anyways. --- .../LibraryBridgeHandlerFactory.cpp | 10 ++++++++-- src/BridgeHelper/IBridgeHelper.cpp | 16 ---------------- src/BridgeHelper/IBridgeHelper.h | 6 ++---- src/BridgeHelper/LibraryBridgeHelper.cpp | 19 ++++++++++++++++++- src/BridgeHelper/LibraryBridgeHelper.h | 7 ++++++- src/BridgeHelper/XDBCBridgeHelper.h | 18 ++++++++++++++++++ 6 files changed, 52 insertions(+), 24 deletions(-) diff --git a/programs/library-bridge/LibraryBridgeHandlerFactory.cpp b/programs/library-bridge/LibraryBridgeHandlerFactory.cpp index e8e671db513..1285be6a07d 100644 --- a/programs/library-bridge/LibraryBridgeHandlerFactory.cpp +++ b/programs/library-bridge/LibraryBridgeHandlerFactory.cpp @@ -24,10 +24,16 @@ std::unique_ptr LibraryBridgeHandlerFactory::createRequestHa LOG_DEBUG(log, "Request URI: {}", uri.toString()); if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET) - return std::make_unique(keep_alive_timeout, getContext()); + { + if (uri.getPath() == "/extdict_ping") + return std::make_unique(keep_alive_timeout, getContext()); + } if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST) - return std::make_unique(keep_alive_timeout, getContext()); + { + if (uri.getPath() == "/extdict_request") + return std::make_unique(keep_alive_timeout, getContext()); + } return nullptr; } diff --git a/src/BridgeHelper/IBridgeHelper.cpp b/src/BridgeHelper/IBridgeHelper.cpp index 01ea1636a12..7d6ce74c698 100644 --- a/src/BridgeHelper/IBridgeHelper.cpp +++ b/src/BridgeHelper/IBridgeHelper.cpp @@ -18,22 +18,6 @@ namespace ErrorCodes } -Poco::URI IBridgeHelper::getMainURI() const -{ - auto uri = createBaseURI(); - uri.setPath(MAIN_HANDLER); - return uri; -} - - -Poco::URI IBridgeHelper::getPingURI() const -{ - auto uri = createBaseURI(); - uri.setPath(PING_HANDLER); - return uri; -} - - void IBridgeHelper::startBridgeSync() { if (!bridgeHandShake()) diff --git a/src/BridgeHelper/IBridgeHelper.h b/src/BridgeHelper/IBridgeHelper.h index 67ca5bf5cdd..5068e84f885 100644 --- a/src/BridgeHelper/IBridgeHelper.h +++ b/src/BridgeHelper/IBridgeHelper.h @@ -19,8 +19,6 @@ class IBridgeHelper: protected WithContext public: static constexpr inline auto DEFAULT_HOST = "127.0.0.1"; - static constexpr inline auto PING_HANDLER = "/ping"; - static constexpr inline auto MAIN_HANDLER = "/"; static constexpr inline auto DEFAULT_FORMAT = "RowBinary"; static constexpr inline auto PING_OK_ANSWER = "Ok."; @@ -31,9 +29,9 @@ public: virtual ~IBridgeHelper() = default; - Poco::URI getMainURI() const; + virtual Poco::URI getMainURI() const = 0; - Poco::URI getPingURI() const; + virtual Poco::URI getPingURI() const = 0; void startBridgeSync(); diff --git a/src/BridgeHelper/LibraryBridgeHelper.cpp b/src/BridgeHelper/LibraryBridgeHelper.cpp index e24ce3d2434..aa603181c76 100644 --- a/src/BridgeHelper/LibraryBridgeHelper.cpp +++ b/src/BridgeHelper/LibraryBridgeHelper.cpp @@ -45,6 +45,23 @@ LibraryBridgeHelper::LibraryBridgeHelper( } +Poco::URI LibraryBridgeHelper::getPingURI() const +{ + auto uri = createBaseURI(); + uri.setPath(PING_HANDLER); + uri.addQueryParameter("dictionary_id", toString(dictionary_id)); + return uri; +} + + +Poco::URI LibraryBridgeHelper::getMainURI() const +{ + auto uri = createBaseURI(); + uri.setPath(MAIN_HANDLER); + return uri; +} + + Poco::URI LibraryBridgeHelper::createRequestURI(const String & method) const { auto uri = getMainURI(); @@ -75,7 +92,7 @@ bool LibraryBridgeHelper::bridgeHandShake() String result; try { - ReadWriteBufferFromHTTP buf(createRequestURI(PING), Poco::Net::HTTPRequest::HTTP_GET, {}, http_timeouts, credentials); + ReadWriteBufferFromHTTP buf(getPingURI(), Poco::Net::HTTPRequest::HTTP_GET, {}, http_timeouts, credentials); readString(result, buf); } catch (...) diff --git a/src/BridgeHelper/LibraryBridgeHelper.h b/src/BridgeHelper/LibraryBridgeHelper.h index 25cfd123679..858b02cc4e3 100644 --- a/src/BridgeHelper/LibraryBridgeHelper.h +++ b/src/BridgeHelper/LibraryBridgeHelper.h @@ -26,6 +26,8 @@ public: }; static constexpr inline size_t DEFAULT_PORT = 9012; + static constexpr inline auto PING_HANDLER = "/extdict_ping"; + static constexpr inline auto MAIN_HANDLER = "/extdict_request"; LibraryBridgeHelper(ContextPtr context_, const Block & sample_block, const Field & dictionary_id_, const LibraryInitData & library_data_); @@ -48,6 +50,10 @@ public: LibraryInitData getLibraryData() const { return library_data; } protected: + Poco::URI getPingURI() const override; + + Poco::URI getMainURI() const override; + bool bridgeHandShake() override; void startBridge(std::unique_ptr cmd) const override; @@ -84,7 +90,6 @@ private: static constexpr inline auto EXT_DICT_LOAD_IDS_METHOD = "extDict_loadIds"; static constexpr inline auto EXT_DICT_LOAD_KEYS_METHOD = "extDict_loadKeys"; static constexpr inline auto EXT_DICT_IS_MODIFIED_METHOD = "extDict_isModified"; - static constexpr inline auto PING = "ping"; static constexpr inline auto EXT_DICT_SUPPORTS_SELECTIVE_LOAD_METHOD = "extDict_supportsSelectiveLoad"; Poco::URI createRequestURI(const String & method) const; diff --git a/src/BridgeHelper/XDBCBridgeHelper.h b/src/BridgeHelper/XDBCBridgeHelper.h index 1eb676f0d87..84aa73ef8e5 100644 --- a/src/BridgeHelper/XDBCBridgeHelper.h +++ b/src/BridgeHelper/XDBCBridgeHelper.h @@ -53,6 +53,8 @@ class XDBCBridgeHelper : public IXDBCBridgeHelper public: static constexpr inline auto DEFAULT_PORT = BridgeHelperMixin::DEFAULT_PORT; + static constexpr inline auto PING_HANDLER = "/ping"; + static constexpr inline auto MAIN_HANDLER = "/"; static constexpr inline auto COL_INFO_HANDLER = "/columns_info"; static constexpr inline auto IDENTIFIER_QUOTE_HANDLER = "/identifier_quote"; static constexpr inline auto SCHEMA_ALLOWED_HANDLER = "/schema_allowed"; @@ -72,6 +74,22 @@ public: } protected: + Poco::URI getPingURI() const override + { + auto uri = createBaseURI(); + uri.setPath(PING_HANDLER); + return uri; + } + + + Poco::URI getMainURI() const override + { + auto uri = createBaseURI(); + uri.setPath(MAIN_HANDLER); + return uri; + } + + bool bridgeHandShake() override { try From cb146ee6f1f90f12844d36c33f16f8b1febf106c Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Thu, 4 Aug 2022 19:19:04 +0000 Subject: [PATCH 338/672] Rename LibraryBridgeHelper to ExternalDictionaryLibraryBridgeHelper - ExternalDictionaryLibraryBridgeHelper provides the server-side interface to access the dictionary part of the library bridge. - In a later commit, CatBoostLibraryBridgeHelper will be added to access the catboost part of the library bridge from the server. --- ...ExternalDictionaryLibraryBridgeHelper.cpp} | 48 +++++++++---------- ...> ExternalDictionaryLibraryBridgeHelper.h} | 4 +- src/Dictionaries/LibraryDictionarySource.cpp | 6 +-- src/Dictionaries/LibraryDictionarySource.h | 6 +-- 4 files changed, 32 insertions(+), 32 deletions(-) rename src/BridgeHelper/{LibraryBridgeHelper.cpp => ExternalDictionaryLibraryBridgeHelper.cpp} (78%) rename src/BridgeHelper/{LibraryBridgeHelper.h => ExternalDictionaryLibraryBridgeHelper.h} (93%) diff --git a/src/BridgeHelper/LibraryBridgeHelper.cpp b/src/BridgeHelper/ExternalDictionaryLibraryBridgeHelper.cpp similarity index 78% rename from src/BridgeHelper/LibraryBridgeHelper.cpp rename to src/BridgeHelper/ExternalDictionaryLibraryBridgeHelper.cpp index aa603181c76..c4df7e74e7a 100644 --- a/src/BridgeHelper/LibraryBridgeHelper.cpp +++ b/src/BridgeHelper/ExternalDictionaryLibraryBridgeHelper.cpp @@ -1,4 +1,4 @@ -#include "LibraryBridgeHelper.h" +#include "ExternalDictionaryLibraryBridgeHelper.h" #include #include @@ -26,13 +26,13 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -LibraryBridgeHelper::LibraryBridgeHelper( +ExternalDictionaryLibraryBridgeHelper::ExternalDictionaryLibraryBridgeHelper( ContextPtr context_, const Block & sample_block_, const Field & dictionary_id_, const LibraryInitData & library_data_) : IBridgeHelper(context_->getGlobalContext()) - , log(&Poco::Logger::get("LibraryBridgeHelper")) + , log(&Poco::Logger::get("ExternalDictionaryLibraryBridgeHelper")) , sample_block(sample_block_) , config(context_->getConfigRef()) , http_timeout(context_->getGlobalContext()->getSettingsRef().http_receive_timeout.value) @@ -45,7 +45,7 @@ LibraryBridgeHelper::LibraryBridgeHelper( } -Poco::URI LibraryBridgeHelper::getPingURI() const +Poco::URI ExternalDictionaryLibraryBridgeHelper::getPingURI() const { auto uri = createBaseURI(); uri.setPath(PING_HANDLER); @@ -54,7 +54,7 @@ Poco::URI LibraryBridgeHelper::getPingURI() const } -Poco::URI LibraryBridgeHelper::getMainURI() const +Poco::URI ExternalDictionaryLibraryBridgeHelper::getMainURI() const { auto uri = createBaseURI(); uri.setPath(MAIN_HANDLER); @@ -62,7 +62,7 @@ Poco::URI LibraryBridgeHelper::getMainURI() const } -Poco::URI LibraryBridgeHelper::createRequestURI(const String & method) const +Poco::URI ExternalDictionaryLibraryBridgeHelper::createRequestURI(const String & method) const { auto uri = getMainURI(); uri.addQueryParameter("dictionary_id", toString(dictionary_id)); @@ -71,7 +71,7 @@ Poco::URI LibraryBridgeHelper::createRequestURI(const String & method) const } -Poco::URI LibraryBridgeHelper::createBaseURI() const +Poco::URI ExternalDictionaryLibraryBridgeHelper::createBaseURI() const { Poco::URI uri; uri.setHost(bridge_host); @@ -81,13 +81,13 @@ Poco::URI LibraryBridgeHelper::createBaseURI() const } -void LibraryBridgeHelper::startBridge(std::unique_ptr cmd) const +void ExternalDictionaryLibraryBridgeHelper::startBridge(std::unique_ptr cmd) const { getContext()->addBridgeCommand(std::move(cmd)); } -bool LibraryBridgeHelper::bridgeHandShake() +bool ExternalDictionaryLibraryBridgeHelper::bridgeHandShake() { String result; try @@ -148,11 +148,11 @@ bool LibraryBridgeHelper::bridgeHandShake() } -ReadWriteBufferFromHTTP::OutStreamCallback LibraryBridgeHelper::getInitLibraryCallback() const +ReadWriteBufferFromHTTP::OutStreamCallback ExternalDictionaryLibraryBridgeHelper::getInitLibraryCallback() const { /// Sample block must contain null values WriteBufferFromOwnString out; - auto output_format = getContext()->getOutputFormat(LibraryBridgeHelper::DEFAULT_FORMAT, out, sample_block); + auto output_format = getContext()->getOutputFormat(ExternalDictionaryLibraryBridgeHelper::DEFAULT_FORMAT, out, sample_block); formatBlock(output_format, sample_block); auto block_string = out.str(); @@ -167,7 +167,7 @@ ReadWriteBufferFromHTTP::OutStreamCallback LibraryBridgeHelper::getInitLibraryCa } -bool LibraryBridgeHelper::initLibrary() +bool ExternalDictionaryLibraryBridgeHelper::initLibrary() { startBridgeSync(); auto uri = createRequestURI(EXT_DICT_LIB_NEW_METHOD); @@ -176,7 +176,7 @@ bool LibraryBridgeHelper::initLibrary() } -bool LibraryBridgeHelper::cloneLibrary(const Field & other_dictionary_id) +bool ExternalDictionaryLibraryBridgeHelper::cloneLibrary(const Field & other_dictionary_id) { startBridgeSync(); auto uri = createRequestURI(EXT_DICT_LIB_CLONE_METHOD); @@ -188,7 +188,7 @@ bool LibraryBridgeHelper::cloneLibrary(const Field & other_dictionary_id) } -bool LibraryBridgeHelper::removeLibrary() +bool ExternalDictionaryLibraryBridgeHelper::removeLibrary() { /// Do not force bridge restart if it is not running in case of removeLibrary /// because in this case after restart it will not have this dictionaty id in memory anyway. @@ -201,7 +201,7 @@ bool LibraryBridgeHelper::removeLibrary() } -bool LibraryBridgeHelper::isModified() +bool ExternalDictionaryLibraryBridgeHelper::isModified() { startBridgeSync(); auto uri = createRequestURI(EXT_DICT_IS_MODIFIED_METHOD); @@ -209,7 +209,7 @@ bool LibraryBridgeHelper::isModified() } -bool LibraryBridgeHelper::supportsSelectiveLoad() +bool ExternalDictionaryLibraryBridgeHelper::supportsSelectiveLoad() { startBridgeSync(); auto uri = createRequestURI(EXT_DICT_SUPPORTS_SELECTIVE_LOAD_METHOD); @@ -217,7 +217,7 @@ bool LibraryBridgeHelper::supportsSelectiveLoad() } -QueryPipeline LibraryBridgeHelper::loadAll() +QueryPipeline ExternalDictionaryLibraryBridgeHelper::loadAll() { startBridgeSync(); auto uri = createRequestURI(EXT_DICT_LOAD_ALL_METHOD); @@ -225,7 +225,7 @@ QueryPipeline LibraryBridgeHelper::loadAll() } -QueryPipeline LibraryBridgeHelper::loadIds(const std::vector & ids) +QueryPipeline ExternalDictionaryLibraryBridgeHelper::loadIds(const std::vector & ids) { startBridgeSync(); auto uri = createRequestURI(EXT_DICT_LOAD_IDS_METHOD); @@ -235,7 +235,7 @@ QueryPipeline LibraryBridgeHelper::loadIds(const std::vector & ids) } -QueryPipeline LibraryBridgeHelper::loadKeys(const Block & requested_block) +QueryPipeline ExternalDictionaryLibraryBridgeHelper::loadKeys(const Block & requested_block) { startBridgeSync(); auto uri = createRequestURI(EXT_DICT_LOAD_KEYS_METHOD); @@ -244,14 +244,14 @@ QueryPipeline LibraryBridgeHelper::loadKeys(const Block & requested_block) ReadWriteBufferFromHTTP::OutStreamCallback out_stream_callback = [requested_block, this](std::ostream & os) { WriteBufferFromOStream out_buffer(os); - auto output_format = getContext()->getOutputFormat(LibraryBridgeHelper::DEFAULT_FORMAT, out_buffer, requested_block.cloneEmpty()); + auto output_format = getContext()->getOutputFormat(ExternalDictionaryLibraryBridgeHelper::DEFAULT_FORMAT, out_buffer, requested_block.cloneEmpty()); formatBlock(output_format, requested_block); }; return QueryPipeline(loadBase(uri, out_stream_callback)); } -bool LibraryBridgeHelper::executeRequest(const Poco::URI & uri, ReadWriteBufferFromHTTP::OutStreamCallback out_stream_callback) const +bool ExternalDictionaryLibraryBridgeHelper::executeRequest(const Poco::URI & uri, ReadWriteBufferFromHTTP::OutStreamCallback out_stream_callback) const { ReadWriteBufferFromHTTP buf( uri, @@ -265,7 +265,7 @@ bool LibraryBridgeHelper::executeRequest(const Poco::URI & uri, ReadWriteBufferF } -QueryPipeline LibraryBridgeHelper::loadBase(const Poco::URI & uri, ReadWriteBufferFromHTTP::OutStreamCallback out_stream_callback) +QueryPipeline ExternalDictionaryLibraryBridgeHelper::loadBase(const Poco::URI & uri, ReadWriteBufferFromHTTP::OutStreamCallback out_stream_callback) { auto read_buf_ptr = std::make_unique( uri, @@ -278,13 +278,13 @@ QueryPipeline LibraryBridgeHelper::loadBase(const Poco::URI & uri, ReadWriteBuff getContext()->getReadSettings(), ReadWriteBufferFromHTTP::HTTPHeaderEntries{}); - auto source = FormatFactory::instance().getInput(LibraryBridgeHelper::DEFAULT_FORMAT, *read_buf_ptr, sample_block, getContext(), DEFAULT_BLOCK_SIZE); + auto source = FormatFactory::instance().getInput(ExternalDictionaryLibraryBridgeHelper::DEFAULT_FORMAT, *read_buf_ptr, sample_block, getContext(), DEFAULT_BLOCK_SIZE); source->addBuffer(std::move(read_buf_ptr)); return QueryPipeline(std::move(source)); } -String LibraryBridgeHelper::getDictIdsString(const std::vector & ids) +String ExternalDictionaryLibraryBridgeHelper::getDictIdsString(const std::vector & ids) { WriteBufferFromOwnString out; writeVectorBinary(ids, out); diff --git a/src/BridgeHelper/LibraryBridgeHelper.h b/src/BridgeHelper/ExternalDictionaryLibraryBridgeHelper.h similarity index 93% rename from src/BridgeHelper/LibraryBridgeHelper.h rename to src/BridgeHelper/ExternalDictionaryLibraryBridgeHelper.h index 858b02cc4e3..d7504bbcf0e 100644 --- a/src/BridgeHelper/LibraryBridgeHelper.h +++ b/src/BridgeHelper/ExternalDictionaryLibraryBridgeHelper.h @@ -14,7 +14,7 @@ namespace DB class Pipe; -class LibraryBridgeHelper : public IBridgeHelper +class ExternalDictionaryLibraryBridgeHelper : public IBridgeHelper { public: @@ -29,7 +29,7 @@ public: static constexpr inline auto PING_HANDLER = "/extdict_ping"; static constexpr inline auto MAIN_HANDLER = "/extdict_request"; - LibraryBridgeHelper(ContextPtr context_, const Block & sample_block, const Field & dictionary_id_, const LibraryInitData & library_data_); + ExternalDictionaryLibraryBridgeHelper(ContextPtr context_, const Block & sample_block, const Field & dictionary_id_, const LibraryInitData & library_data_); bool initLibrary(); diff --git a/src/Dictionaries/LibraryDictionarySource.cpp b/src/Dictionaries/LibraryDictionarySource.cpp index dc240cf20ad..7eb4d803fe8 100644 --- a/src/Dictionaries/LibraryDictionarySource.cpp +++ b/src/Dictionaries/LibraryDictionarySource.cpp @@ -49,14 +49,14 @@ LibraryDictionarySource::LibraryDictionarySource( description.init(sample_block); - LibraryBridgeHelper::LibraryInitData library_data + ExternalDictionaryLibraryBridgeHelper::LibraryInitData library_data { .library_path = path, .library_settings = getLibrarySettingsString(config, config_prefix + ".settings"), .dict_attributes = getDictAttributesString() }; - bridge_helper = std::make_shared(context, description.sample_block, dictionary_id, library_data); + bridge_helper = std::make_shared(context, description.sample_block, dictionary_id, library_data); if (!bridge_helper->initLibrary()) throw Exception(ErrorCodes::EXTERNAL_LIBRARY_ERROR, "Failed to create shared library from path: {}", path); @@ -87,7 +87,7 @@ LibraryDictionarySource::LibraryDictionarySource(const LibraryDictionarySource & , context(other.context) , description{other.description} { - bridge_helper = std::make_shared(context, description.sample_block, dictionary_id, other.bridge_helper->getLibraryData()); + bridge_helper = std::make_shared(context, description.sample_block, dictionary_id, other.bridge_helper->getLibraryData()); if (!bridge_helper->cloneLibrary(other.dictionary_id)) throw Exception(ErrorCodes::EXTERNAL_LIBRARY_ERROR, "Failed to clone library"); } diff --git a/src/Dictionaries/LibraryDictionarySource.h b/src/Dictionaries/LibraryDictionarySource.h index 09f95b17dab..57ab9976a3b 100644 --- a/src/Dictionaries/LibraryDictionarySource.h +++ b/src/Dictionaries/LibraryDictionarySource.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include "DictionaryStructure.h" @@ -28,7 +28,7 @@ namespace ErrorCodes } class CStringsHolder; -using LibraryBridgeHelperPtr = std::shared_ptr; +using ExternalDictionaryLibraryBridgeHelperPtr = std::shared_ptr; class LibraryDictionarySource final : public IDictionarySource { @@ -85,7 +85,7 @@ private: Block sample_block; ContextPtr context; - LibraryBridgeHelperPtr bridge_helper; + ExternalDictionaryLibraryBridgeHelperPtr bridge_helper; ExternalResultDescription description; }; From 6a3a9dcf964f59428eeff8eed250dae629c67797 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Fri, 5 Aug 2022 05:29:26 +0800 Subject: [PATCH 339/672] Fix building aggregate projections when external aggregation is on (#39671) --- src/Storages/ProjectionsDescription.cpp | 4 +++- .../01710_projection_aggregation_in_order.sql | 4 ++++ ...10_projection_external_aggregate.reference | 0 .../01710_projection_external_aggregate.sql | 24 +++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/01710_projection_external_aggregate.reference create mode 100644 tests/queries/0_stateless/01710_projection_external_aggregate.sql diff --git a/src/Storages/ProjectionsDescription.cpp b/src/Storages/ProjectionsDescription.cpp index 0f24aecf72a..969577fdf3f 100644 --- a/src/Storages/ProjectionsDescription.cpp +++ b/src/Storages/ProjectionsDescription.cpp @@ -286,7 +286,9 @@ Block ProjectionDescription::calculate(const Block & block, ContextPtr context) : QueryProcessingStage::WithMergeableState}) .buildQueryPipeline(); builder.resize(1); - builder.addTransform(std::make_shared(builder.getHeader(), block.rows(), block.bytes())); + // Generate aggregated blocks with rows less or equal than the original block. + // There should be only one output block after this transformation. + builder.addTransform(std::make_shared(builder.getHeader(), block.rows(), 0)); auto pipeline = QueryPipelineBuilder::getPipeline(std::move(builder)); PullingPipelineExecutor executor(pipeline); diff --git a/tests/queries/0_stateless/01710_projection_aggregation_in_order.sql b/tests/queries/0_stateless/01710_projection_aggregation_in_order.sql index 06f05e36237..31d32da0ed3 100644 --- a/tests/queries/0_stateless/01710_projection_aggregation_in_order.sql +++ b/tests/queries/0_stateless/01710_projection_aggregation_in_order.sql @@ -31,6 +31,8 @@ SET allow_experimental_projection_optimization=1, optimize_aggregation_in_order= WITH toStartOfHour(ts) AS a SELECT sum(value) v FROM normal WHERE ts > '2021-12-06 22:00:00' GROUP BY a ORDER BY v LIMIT 5; WITH toStartOfHour(ts) AS a SELECT sum(value) v FROM normal WHERE ts > '2021-12-06 22:00:00' GROUP BY toStartOfHour(ts), a ORDER BY v LIMIT 5; +DROP TABLE normal; + DROP TABLE IF EXISTS agg; CREATE TABLE agg @@ -60,3 +62,5 @@ SET allow_experimental_projection_optimization=1, optimize_aggregation_in_order= WITH toStartOfHour(ts) AS a SELECT sum(value) v FROM agg WHERE ts > '2021-12-06 22:00:00' GROUP BY a ORDER BY v LIMIT 5; WITH toStartOfHour(ts) AS a SELECT sum(value) v FROM agg WHERE ts > '2021-12-06 22:00:00' GROUP BY toStartOfHour(ts), a ORDER BY v LIMIT 5; + +DROP TABLE agg; diff --git a/tests/queries/0_stateless/01710_projection_external_aggregate.reference b/tests/queries/0_stateless/01710_projection_external_aggregate.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/01710_projection_external_aggregate.sql b/tests/queries/0_stateless/01710_projection_external_aggregate.sql new file mode 100644 index 00000000000..8c9c1522af8 --- /dev/null +++ b/tests/queries/0_stateless/01710_projection_external_aggregate.sql @@ -0,0 +1,24 @@ +DROP TABLE IF EXISTS agg; + +CREATE TABLE agg +( + `key` UInt32, + `ts` DateTime, + `value` UInt32, + PROJECTION aaaa + ( + SELECT + ts, + key, + sum(value) + GROUP BY ts, key + ) +) +ENGINE = MergeTree +ORDER BY (key, ts); + +SET max_bytes_before_external_group_by=1; + +INSERT INTO agg SELECT 1, toDateTime('2021-12-06 00:00:00') + number, number FROM numbers(100000); + +DROP TABLE agg; From a25b85809717dea097cd67d39e5395fc10aee3f6 Mon Sep 17 00:00:00 2001 From: santrancisco Date: Fri, 5 Aug 2022 09:51:12 +1000 Subject: [PATCH 340/672] Move username and password from URL parameter to Basic Authentication header --- programs/server/play.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/programs/server/play.html b/programs/server/play.html index c7ea5e4ef12..f678da6f353 100644 --- a/programs/server/play.html +++ b/programs/server/play.html @@ -505,8 +505,6 @@ (server_address.indexOf('?') >= 0 ? '&' : '?') + /// Ask server to allow cross-domain requests. 'add_http_cors_header=1' + - '&user=' + encodeURIComponent(user) + - '&password=' + encodeURIComponent(password) + '&default_format=JSONCompact' + /// Safety settings to prevent results that browser cannot display. '&max_result_rows=1000&max_result_bytes=10000000&result_overflow_mode=break'; @@ -514,6 +512,7 @@ const xhr = new XMLHttpRequest; xhr.open('POST', url, true); + xhr.setRequestHeader("Authorization", "Basic " + btoa(user+":"+password)); xhr.onreadystatechange = function() { From 5363e2cf47d9e8f9c6f092194ab304758f694847 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 5 Aug 2022 05:13:46 +0300 Subject: [PATCH 341/672] Remove cache flush from the Docs Check --- docs/tools/release.sh | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/tools/release.sh b/docs/tools/release.sh index b55841f9da2..1d344457bf1 100755 --- a/docs/tools/release.sh +++ b/docs/tools/release.sh @@ -36,15 +36,4 @@ then # Push to GitHub rewriting the existing contents. # Sometimes it does not work with error message "! [remote rejected] master -> master (cannot lock ref 'refs/heads/master': is at 42a0f6b6b6c7be56a469441b4bf29685c1cebac3 but expected 520e9b02c0d4678a2a5f41d2f561e6532fb98cc1)" for _ in {1..10}; do git push --force origin master && break; sleep 5; done - - # Turn off logging. - set +x - - if [[ -n "${CLOUDFLARE_TOKEN}" ]] - then - sleep 1m - # https://api.cloudflare.com/#zone-purge-files-by-cache-tags,-host-or-prefix - POST_DATA='{"hosts":["clickhouse.com"]}' - curl -X POST "https://api.cloudflare.com/client/v4/zones/4fc6fb1d46e87851605aa7fa69ca6fe0/purge_cache" -H "Authorization: Bearer ${CLOUDFLARE_TOKEN}" -H "Content-Type:application/json" --data "${POST_DATA}" - fi fi From 3f4baa765890b7008f31215cdd270da1f57367b5 Mon Sep 17 00:00:00 2001 From: Denny Crane Date: Thu, 4 Aug 2022 23:49:19 -0300 Subject: [PATCH 342/672] Update mergetree.md --- docs/en/engines/table-engines/mergetree-family/mergetree.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/en/engines/table-engines/mergetree-family/mergetree.md b/docs/en/engines/table-engines/mergetree-family/mergetree.md index 2ca07276e63..9339ec2454f 100644 --- a/docs/en/engines/table-engines/mergetree-family/mergetree.md +++ b/docs/en/engines/table-engines/mergetree-family/mergetree.md @@ -878,8 +878,6 @@ User can assign new big parts to different disks of a [JBOD](https://en.wikipedi `MergeTree` family table engines can store data to [S3](https://aws.amazon.com/s3/) using a disk with type `s3`. -This feature is under development and not ready for production. There are known drawbacks such as very low performance. - Configuration markup: ``` xml From c7f0598a6ff4a42847438964c28ab8a21ac1feed Mon Sep 17 00:00:00 2001 From: santrancisco Date: Fri, 5 Aug 2022 13:10:45 +1000 Subject: [PATCH 343/672] Fall back to url parameters if file is opened locally --- programs/server/play.html | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/programs/server/play.html b/programs/server/play.html index f678da6f353..ab04722609c 100644 --- a/programs/server/play.html +++ b/programs/server/play.html @@ -501,7 +501,8 @@ const server_address = document.getElementById('url').value; - const url = server_address + + + var url = server_address + (server_address.indexOf('?') >= 0 ? '&' : '?') + /// Ask server to allow cross-domain requests. 'add_http_cors_header=1' + @@ -509,11 +510,19 @@ /// Safety settings to prevent results that browser cannot display. '&max_result_rows=1000&max_result_bytes=10000000&result_overflow_mode=break'; + // If play.html is opened locally, append username and password to the URL parameter to avoid CORS issue. + if (document.location.href.startsWith("file://")) { + url += '&user=' + encodeURIComponent(user) + + '&password=' + encodeURIComponent(password) + } + const xhr = new XMLHttpRequest; xhr.open('POST', url, true); - xhr.setRequestHeader("Authorization", "Basic " + btoa(user+":"+password)); - + // If play.html is open normally, use Basic auth to prevent username and password being exposed in URL parameters + if (!document.location.href.startsWith("file://")) { + xhr.setRequestHeader("Authorization", "Basic " + btoa(user+":"+password)); + } xhr.onreadystatechange = function() { if (posted_request_num != request_num) { From 67a6b32a8985854667a0850abfbdd9bd7feda4eb Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 5 Aug 2022 07:12:50 +0300 Subject: [PATCH 344/672] Update entrypoint.sh --- docker/server/entrypoint.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index fae11cd267e..89dd501bf38 100755 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -58,12 +58,12 @@ do [ -z "$dir" ] && continue # ensure directories exist if [ "$DO_CHOWN" = "1" ]; then - mkdir="mkdir" + mkdir="mkdir" else - # if DO_CHOWN=0 it means that the system does not map root user to "admin" permissions - # it mainly happens on NFS mounts where root==nobody for security reasons - # thus mkdir MUST run with user id/gid and not from nobody that has zero permissions - mkdir="/usr/bin/clickhouse su "${USER}:${GROUP}" mkdir" + # if DO_CHOWN=0 it means that the system does not map root user to "admin" permissions + # it mainly happens on NFS mounts where root==nobody for security reasons + # thus mkdir MUST run with user id/gid and not from nobody that has zero permissions + mkdir="/usr/bin/clickhouse su "${USER}:${GROUP}" mkdir" fi if ! $mkdir -p "$dir"; then echo "Couldn't create necessary directory: $dir" From 96c1dcdbb1699e2c408d82026e0f793fb9a4a1a8 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Fri, 5 Aug 2022 02:32:52 -0400 Subject: [PATCH 345/672] add HTTP test --- tests/queries/0_stateless/02377_quota_key.reference | 1 + tests/queries/0_stateless/02377_quota_key.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/02377_quota_key.reference b/tests/queries/0_stateless/02377_quota_key.reference index d00491fd7e5..6ed281c757a 100644 --- a/tests/queries/0_stateless/02377_quota_key.reference +++ b/tests/queries/0_stateless/02377_quota_key.reference @@ -1 +1,2 @@ 1 +1 diff --git a/tests/queries/0_stateless/02377_quota_key.sh b/tests/queries/0_stateless/02377_quota_key.sh index 8aa6797dc2c..9c81bcae8f2 100755 --- a/tests/queries/0_stateless/02377_quota_key.sh +++ b/tests/queries/0_stateless/02377_quota_key.sh @@ -11,6 +11,7 @@ ${CLICKHOUSE_CLIENT} -q "CREATE USER u_02377 IDENTIFIED WITH plaintext_password ${CLICKHOUSE_CLIENT} -q "CREATE QUOTA q_02377 KEYED BY client_key FOR INTERVAL 1 month MAX queries = 100 TO u_02377;" ${CLICKHOUSE_CLIENT} --user=u_02377 --password=password --quota_key=q_02377 --query="select 1" +curl -sS "${CLICKHOUSE_URL}&user=editor_api&password=password"a_key=editor_api&query=SELECT%201" ${CLICKHOUSE_CLIENT} -q "DROP USER IF EXISTS u_02377" ${CLICKHOUSE_CLIENT} -q "drop quota if exists q_02377" From 10c76917ea7e5aec87c534c274b55bef16283620 Mon Sep 17 00:00:00 2001 From: Constantine Peresypkin Date: Sun, 24 Jul 2022 16:13:17 +0200 Subject: [PATCH 346/672] fix nats-io TLS support nats-io library needs `NATS_HAS_TLS` define to correctly compile-in TLS support fixes #39525 --- contrib/nats-io-cmake/CMakeLists.txt | 2 + .../runner/compose/docker_compose_nats.yml | 6 +- src/Storages/NATS/NATSConnection.cpp | 8 +- src/Storages/NATS/NATSConnection.h | 3 + tests/integration/helpers/cluster.py | 54 +++++++++-- .../test_storage_nats/nats_certs.sh | 13 +++ tests/integration/test_storage_nats/test.py | 92 ++++++++++--------- 7 files changed, 124 insertions(+), 54 deletions(-) create mode 100755 tests/integration/test_storage_nats/nats_certs.sh diff --git a/contrib/nats-io-cmake/CMakeLists.txt b/contrib/nats-io-cmake/CMakeLists.txt index 5588d5750c4..579bf6f8ae4 100644 --- a/contrib/nats-io-cmake/CMakeLists.txt +++ b/contrib/nats-io-cmake/CMakeLists.txt @@ -18,6 +18,8 @@ elseif(WIN32) set(NATS_PLATFORM_INCLUDE "apple") endif() +add_definitions(-DNATS_HAS_TLS) + file(GLOB PS_SOURCES "${NATS_IO_SOURCE_DIR}/${NATS_PLATFORM_INCLUDE}/*.c") set(SRCS "${NATS_IO_SOURCE_DIR}/asynccb.c" diff --git a/docker/test/integration/runner/compose/docker_compose_nats.yml b/docker/test/integration/runner/compose/docker_compose_nats.yml index 19ae4c162b1..2122f0f639f 100644 --- a/docker/test/integration/runner/compose/docker_compose_nats.yml +++ b/docker/test/integration/runner/compose/docker_compose_nats.yml @@ -4,4 +4,8 @@ services: image: nats ports: - "${NATS_EXTERNAL_PORT}:${NATS_INTERNAL_PORT}" - command: "-p 4444 --user click --pass house" \ No newline at end of file + command: "-p 4444 --user click --pass house --tls --tlscert=/etc/certs/server-cert.pem --tlskey=/etc/certs/server-key.pem" + volumes: + - type: bind + source: "${NATS_CERT_DIR}/nats" + target: /etc/certs diff --git a/src/Storages/NATS/NATSConnection.cpp b/src/Storages/NATS/NATSConnection.cpp index 359754bb144..64beb9f2dff 100644 --- a/src/Storages/NATS/NATSConnection.cpp +++ b/src/Storages/NATS/NATSConnection.cpp @@ -9,7 +9,6 @@ namespace DB { -//static const auto CONNECT_SLEEP = 200; static const auto RETRIES_MAX = 20; static const auto CONNECTED_TO_BUFFER_SIZE = 256; @@ -19,6 +18,10 @@ NATSConnectionManager::NATSConnectionManager(const NATSConfiguration & configura , log(log_) , event_handler(loop.getLoop(), log) { + const char * val = std::getenv("CLICKHOUSE_NATS_TLS_SECURE"); + std::string tls_secure = val == nullptr ? std::string("1") : std::string(val); + if (tls_secure == "0") + skip_verification = true; } @@ -92,6 +95,9 @@ void NATSConnectionManager::connectImpl() if (configuration.secure) { natsOptions_SetSecure(options, true); + } + if (skip_verification) + { natsOptions_SkipServerVerification(options, true); } if (!configuration.url.empty()) diff --git a/src/Storages/NATS/NATSConnection.h b/src/Storages/NATS/NATSConnection.h index 78a273164db..c699f859446 100644 --- a/src/Storages/NATS/NATSConnection.h +++ b/src/Storages/NATS/NATSConnection.h @@ -65,6 +65,9 @@ private: // true if at any point successfully connected to NATS bool has_connection = false; + // use CLICKHOUSE_NATS_TLS_SECURE=0 env var to skip TLS verification of server cert + bool skip_verification = false; + std::mutex mutex; }; diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 7700fc2dffd..43b08883ae5 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -30,6 +30,7 @@ try: import pymongo import pymysql import nats + import ssl import meilisearch from confluent_kafka.avro.cached_schema_registry_client import ( CachedSchemaRegistryClient, @@ -215,9 +216,27 @@ def check_rabbitmq_is_available(rabbitmq_id): return p.returncode == 0 -async def check_nats_is_available(nats_ip): - nc = await nats.connect("{}:4444".format(nats_ip), user="click", password="house") - return nc.is_connected +async def check_nats_is_available(nats_port, ssl_ctx=None): + nc = await nats_connect_ssl( + nats_port, user="click", password="house", ssl_ctx=ssl_ctx + ) + available = nc.is_connected + await nc.close() + return available + + +async def nats_connect_ssl(nats_port, user, password, ssl_ctx=None): + if not ssl_ctx: + ssl_ctx = ssl.create_default_context() + ssl_ctx.check_hostname = False + ssl_ctx.verify_mode = ssl.CERT_NONE + nc = await nats.connect( + "tls://localhost:{}".format(nats_port), + user=user, + password=password, + tls=ssl_ctx, + ) + return nc def enable_consistent_hash_plugin(rabbitmq_id): @@ -336,6 +355,7 @@ class ClickHouseCluster: self.env_variables = {} self.env_variables["TSAN_OPTIONS"] = "second_deadlock_stack=1" self.env_variables["CLICKHOUSE_WATCHDOG_ENABLE"] = "0" + self.env_variables["CLICKHOUSE_NATS_TLS_SECURE"] = "0" self.up_called = False custom_dockerd_host = custom_dockerd_host or os.environ.get( @@ -464,9 +484,11 @@ class ClickHouseCluster: self.rabbitmq_logs_dir = os.path.join(self.rabbitmq_dir, "logs") self.nats_host = "nats1" - self.nats_ip = None self.nats_port = 4444 self.nats_docker_id = None + self.nats_dir = p.abspath(p.join(self.instances_dir, "nats")) + self.nats_cert_dir = os.path.join(self.nats_dir, "cert") + self.nats_ssl_context = None # available when with_nginx == True self.nginx_host = "nginx" @@ -1046,6 +1068,7 @@ class ClickHouseCluster: env_variables["NATS_HOST"] = self.nats_host env_variables["NATS_INTERNAL_PORT"] = "4444" env_variables["NATS_EXTERNAL_PORT"] = str(self.nats_port) + env_variables["NATS_CERT_DIR"] = self.nats_cert_dir self.base_cmd.extend( ["--file", p.join(docker_compose_yml_dir, "docker_compose_nats.yml")] @@ -1967,10 +1990,12 @@ class ClickHouseCluster: raise Exception("Cannot wait RabbitMQ container") return False - def wait_nats_is_available(self, nats_ip, max_retries=5): + def wait_nats_is_available(self, max_retries=5): retries = 0 while True: - if asyncio.run(check_nats_is_available(nats_ip)): + if asyncio.run( + check_nats_is_available(self.nats_port, ssl_ctx=self.nats_ssl_context) + ): break else: retries += 1 @@ -2453,11 +2478,24 @@ class ClickHouseCluster: if self.with_nats and self.base_nats_cmd: logging.debug("Setup NATS") + os.makedirs(self.nats_cert_dir) + env = os.environ.copy() + env["NATS_CERT_DIR"] = self.nats_cert_dir + run_and_check( + p.join(self.base_dir, "nats_certs.sh"), + env=env, + detach=False, + nothrow=False, + ) + + self.nats_ssl_context = ssl.create_default_context() + self.nats_ssl_context.load_verify_locations( + p.join(self.nats_cert_dir, "ca", "ca-cert.pem") + ) subprocess_check_call(self.base_nats_cmd + common_opts) self.nats_docker_id = self.get_instance_docker_id("nats1") self.up_called = True - self.nats_ip = self.get_instance_ip("nats1") - self.wait_nats_is_available(self.nats_ip) + self.wait_nats_is_available() if self.with_hdfs and self.base_hdfs_cmd: logging.debug("Setup HDFS") diff --git a/tests/integration/test_storage_nats/nats_certs.sh b/tests/integration/test_storage_nats/nats_certs.sh new file mode 100755 index 00000000000..689221c39e4 --- /dev/null +++ b/tests/integration/test_storage_nats/nats_certs.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -euxo pipefail + +mkdir -p "${NATS_CERT_DIR}/ca" +mkdir -p "${NATS_CERT_DIR}/nats" +openssl req -newkey rsa:4096 -x509 -days 365 -nodes -batch -keyout "${NATS_CERT_DIR}/ca/ca-key.pem" -out "${NATS_CERT_DIR}/ca/ca-cert.pem" -subj "/C=RU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=ca" +openssl req -newkey rsa:4096 -nodes -batch -keyout "${NATS_CERT_DIR}/nats/server-key.pem" -out "${NATS_CERT_DIR}/nats/server-req.pem" -subj "/C=RU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=server" +openssl x509 -req -days 365 -in "${NATS_CERT_DIR}/nats/server-req.pem" -CA "${NATS_CERT_DIR}/ca/ca-cert.pem" -CAkey "${NATS_CERT_DIR}/ca/ca-key.pem" -CAcreateserial -out "${NATS_CERT_DIR}/nats/server-cert.pem" -extfile <( +cat <<-EOF +subjectAltName = DNS:localhost, DNS:nats1 +EOF +) +rm -f "${NATS_CERT_DIR}/nats/server-req.pem" diff --git a/tests/integration/test_storage_nats/test.py b/tests/integration/test_storage_nats/test.py index a952f4b78a6..63dde8922a6 100644 --- a/tests/integration/test_storage_nats/test.py +++ b/tests/integration/test_storage_nats/test.py @@ -9,11 +9,10 @@ from random import randrange import math import asyncio -import nats import pytest from google.protobuf.internal.encoder import _VarintBytes from helpers.client import QueryRuntimeException -from helpers.cluster import ClickHouseCluster, check_nats_is_available +from helpers.cluster import ClickHouseCluster, check_nats_is_available, nats_connect_ssl from helpers.test_tools import TSV from . import nats_pb2 @@ -35,11 +34,11 @@ instance = cluster.add_instance( # Helpers -def wait_nats_to_start(nats_ip, timeout=180): +def wait_nats_to_start(nats_port, ssl_ctx=None, timeout=180): start = time.time() while time.time() - start < timeout: try: - if asyncio.run(check_nats_is_available(nats_ip)): + if asyncio.run(check_nats_is_available(nats_port, ssl_ctx=ssl_ctx)): logging.debug("NATS is available") return time.sleep(0.5) @@ -63,10 +62,10 @@ def kill_nats(nats_id): return p.returncode == 0 -def revive_nats(nats_id, nats_ip): +def revive_nats(nats_id, nats_port): p = subprocess.Popen(("docker", "start", nats_id), stdout=subprocess.PIPE) p.communicate() - wait_nats_to_start(nats_ip) + wait_nats_to_start(nats_port) # Fixtures @@ -96,8 +95,13 @@ def nats_setup_teardown(): # Tests -async def nats_produce_messages(ip, subject, messages=(), bytes=None): - nc = await nats.connect("{}:4444".format(ip), user="click", password="house") +async def nats_produce_messages(cluster_inst, subject, messages=(), bytes=None): + nc = await nats_connect_ssl( + cluster_inst.nats_port, + user="click", + password="house", + ssl_ctx=cluster_inst.nats_ssl_context, + ) logging.debug("NATS connection status: " + str(nc.is_connected)) for message in messages: @@ -136,7 +140,7 @@ def test_nats_select(nats_cluster): messages = [] for i in range(50): messages.append(json.dumps({"key": i, "value": i})) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "select", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "select", messages)) # The order of messages in select * from test.nats is not guaranteed, so sleep to collect everything in one select time.sleep(1) @@ -186,13 +190,13 @@ def test_nats_json_without_delimiter(nats_cluster): messages += json.dumps({"key": i, "value": i}) + "\n" all_messages = [messages] - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "json", all_messages)) + asyncio.run(nats_produce_messages(nats_cluster, "json", all_messages)) messages = "" for i in range(25, 50): messages += json.dumps({"key": i, "value": i}) + "\n" all_messages = [messages] - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "json", all_messages)) + asyncio.run(nats_produce_messages(nats_cluster, "json", all_messages)) time.sleep(1) @@ -229,7 +233,7 @@ def test_nats_csv_with_delimiter(nats_cluster): for i in range(50): messages.append("{i}, {i}".format(i=i)) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "csv", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "csv", messages)) time.sleep(1) @@ -268,7 +272,7 @@ def test_nats_tsv_with_delimiter(nats_cluster): for i in range(50): messages.append("{i}\t{i}".format(i=i)) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "tsv", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "tsv", messages)) result = "" for _ in range(60): @@ -299,7 +303,7 @@ def test_nats_macros(nats_cluster): message = "" for i in range(50): message += json.dumps({"key": i, "value": i}) + "\n" - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "macro", [message])) + asyncio.run(nats_produce_messages(nats_cluster, "macro", [message])) time.sleep(1) @@ -344,7 +348,7 @@ def test_nats_materialized_view(nats_cluster): for i in range(50): messages.append(json.dumps({"key": i, "value": i})) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "mv", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "mv", messages)) time_limit_sec = 60 deadline = time.monotonic() + time_limit_sec @@ -389,7 +393,7 @@ def test_nats_materialized_view_with_subquery(nats_cluster): messages = [] for i in range(50): messages.append(json.dumps({"key": i, "value": i})) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "mvsq", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "mvsq", messages)) time_limit_sec = 60 deadline = time.monotonic() + time_limit_sec @@ -434,7 +438,7 @@ def test_nats_many_materialized_views(nats_cluster): messages = [] for i in range(50): messages.append(json.dumps({"key": i, "value": i})) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "mmv", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "mmv", messages)) time_limit_sec = 60 deadline = time.monotonic() + time_limit_sec @@ -485,7 +489,7 @@ def test_nats_protobuf(nats_cluster): msg.value = str(i) serialized_msg = msg.SerializeToString() data = data + _VarintBytes(len(serialized_msg)) + serialized_msg - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "pb", bytes=data)) + asyncio.run(nats_produce_messages(nats_cluster, "pb", bytes=data)) data = b"" for i in range(20, 21): msg = nats_pb2.ProtoKeyValue() @@ -493,7 +497,7 @@ def test_nats_protobuf(nats_cluster): msg.value = str(i) serialized_msg = msg.SerializeToString() data = data + _VarintBytes(len(serialized_msg)) + serialized_msg - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "pb", bytes=data)) + asyncio.run(nats_produce_messages(nats_cluster, "pb", bytes=data)) data = b"" for i in range(21, 50): msg = nats_pb2.ProtoKeyValue() @@ -501,7 +505,7 @@ def test_nats_protobuf(nats_cluster): msg.value = str(i) serialized_msg = msg.SerializeToString() data = data + _VarintBytes(len(serialized_msg)) + serialized_msg - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "pb", bytes=data)) + asyncio.run(nats_produce_messages(nats_cluster, "pb", bytes=data)) result = "" time_limit_sec = 60 @@ -542,7 +546,7 @@ def test_nats_big_message(nats_cluster): logging.debug("Table test.nats is not yet ready") time.sleep(0.5) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "big", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "big", messages)) while True: result = instance.query("SELECT count() FROM test.view") @@ -600,7 +604,7 @@ def test_nats_mv_combo(nats_cluster): for _ in range(messages_num): messages.append(json.dumps({"key": i[0], "value": i[0]})) i[0] += 1 - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "combo", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "combo", messages)) threads = [] threads_num = 20 @@ -662,8 +666,11 @@ def test_nats_insert(nats_cluster): insert_messages = [] async def sub_to_nats(): - nc = await nats.connect( - "{}:4444".format(nats_cluster.nats_ip), user="click", password="house" + nc = await nats_connect_ssl( + nats_cluster.nats_port, + user="click", + password="house", + ssl_ctx=nats_cluster.nats_ssl_context, ) sub = await nc.subscribe("insert") await sub.unsubscribe(50) @@ -771,8 +778,11 @@ def test_nats_many_subjects_insert_right(nats_cluster): insert_messages = [] async def sub_to_nats(): - nc = await nats.connect( - "{}:4444".format(nats_cluster.nats_ip), user="click", password="house" + nc = await nats_connect_ssl( + nats_cluster.nats_port, + user="click", + password="house", + ssl_ctx=nats_cluster.nats_ssl_context, ) sub = await nc.subscribe("right_insert1") await sub.unsubscribe(50) @@ -1003,7 +1013,7 @@ def test_nats_virtual_column(nats_cluster): messages.append(json.dumps({"key": i, "value": i})) i += 1 - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "virtuals", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "virtuals", messages)) while True: result = instance.query("SELECT count() FROM test.view") @@ -1067,7 +1077,7 @@ def test_nats_virtual_column_with_materialized_view(nats_cluster): messages.append(json.dumps({"key": i, "value": i})) i += 1 - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "virtuals_mv", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "virtuals_mv", messages)) while True: result = instance.query("SELECT count() FROM test.view") @@ -1147,9 +1157,7 @@ def test_nats_many_consumers_to_each_queue(nats_cluster): for _ in range(messages_num): messages.append(json.dumps({"key": i[0], "value": i[0]})) i[0] += 1 - asyncio.run( - nats_produce_messages(nats_cluster.nats_ip, "many_consumers", messages) - ) + asyncio.run(nats_produce_messages(nats_cluster, "many_consumers", messages)) threads = [] threads_num = 20 @@ -1243,7 +1251,7 @@ def test_nats_restore_failed_connection_without_losses_on_write(nats_cluster): kill_nats(nats_cluster.nats_docker_id) time.sleep(4) - revive_nats(nats_cluster.nats_docker_id, nats_cluster.nats_ip) + revive_nats(nats_cluster.nats_docker_id, nats_cluster.nats_port) while True: result = instance.query("SELECT count(DISTINCT key) FROM test.view") @@ -1310,7 +1318,7 @@ def test_nats_no_connection_at_startup_2(nats_cluster): messages = [] for i in range(messages_num): messages.append(json.dumps({"key": i, "value": i})) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "cs", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "cs", messages)) for _ in range(20): result = instance.query("SELECT count() FROM test.view") @@ -1353,9 +1361,7 @@ def test_nats_format_factory_settings(nats_cluster): """SELECT parseDateTimeBestEffort(CAST('2021-01-19T14:42:33.1829214Z', 'String'))""" ) - asyncio.run( - nats_produce_messages(nats_cluster.nats_ip, "format_settings", [message]) - ) + asyncio.run(nats_produce_messages(nats_cluster, "format_settings", [message])) while True: result = instance.query("SELECT date FROM test.format_settings") @@ -1372,9 +1378,7 @@ def test_nats_format_factory_settings(nats_cluster): """ ) - asyncio.run( - nats_produce_messages(nats_cluster.nats_ip, "format_settings", [message]) - ) + asyncio.run(nats_produce_messages(nats_cluster, "format_settings", [message])) while True: result = instance.query("SELECT date FROM test.view") if result == expected: @@ -1424,13 +1428,13 @@ def test_nats_drop_mv(nats_cluster): messages = [] for i in range(20): messages.append(json.dumps({"key": i, "value": i})) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "mv", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "mv", messages)) instance.query("DROP VIEW test.consumer") messages = [] for i in range(20, 40): messages.append(json.dumps({"key": i, "value": i})) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "mv", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "mv", messages)) instance.query( """ @@ -1441,7 +1445,7 @@ def test_nats_drop_mv(nats_cluster): messages = [] for i in range(40, 50): messages.append(json.dumps({"key": i, "value": i})) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "mv", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "mv", messages)) while True: result = instance.query("SELECT * FROM test.view ORDER BY key") @@ -1454,7 +1458,7 @@ def test_nats_drop_mv(nats_cluster): messages = [] for i in range(50, 60): messages.append(json.dumps({"key": i, "value": i})) - asyncio.run(nats_produce_messages(nats_cluster.nats_ip, "mv", messages)) + asyncio.run(nats_produce_messages(nats_cluster, "mv", messages)) count = 0 while True: @@ -1477,7 +1481,7 @@ def test_nats_predefined_configuration(nats_cluster): asyncio.run( nats_produce_messages( - nats_cluster.nats_ip, "named", [json.dumps({"key": 1, "value": 2})] + nats_cluster, "named", [json.dumps({"key": 1, "value": 2})] ) ) while True: From 4bf7a2ca3a3bc8f0093c619e8a23d26b61d6dc41 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 5 Aug 2022 12:05:50 +0200 Subject: [PATCH 347/672] fix warning --- src/Storages/MergeTree/MergeTreeData.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 4fc8f77b5b6..6b4ec3a6dee 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -5009,7 +5009,13 @@ MergeTreeData::DataPartsVector MergeTreeData::Transaction::commit(MergeTreeData: DataPartsVector covered_parts = data.getActivePartsToReplace(part->info, part->name, covering_part, *owing_parts_lock); if (covering_part) { - LOG_WARNING(data.log, "Tried to commit obsolete part {} covered by {}", part->name, covering_part->getNameWithState()); + /// It's totally fine for zero-level parts, because of possible race condition between ReplicatedMergeTreeSink and + /// background queue execution (new part is added to ZK before this function is called, + /// so other replica may produce covering part and replication queue may download covering part). + if (part->info.level) + LOG_WARNING(data.log, "Tried to commit obsolete part {} covered by {}", part->name, covering_part->getNameWithState()); + else + LOG_INFO(data.log, "Tried to commit obsolete part {} covered by {}", part->name, covering_part->getNameWithState()); part->remove_time.store(0, std::memory_order_relaxed); /// The part will be removed without waiting for old_parts_lifetime seconds. data.modifyPartState(part, DataPartState::Outdated); From 4efa847a1f08dc71d5062dc6f3e36e4aecb9ddf2 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 20 Jul 2022 22:29:46 +0300 Subject: [PATCH 348/672] Fix LOGICAL_ERROR on race between DROP and INSERT with materialized views In case of parallel INSERT (max_insert_threads > 1) it is possible for VIEW to be DROP/DETACH'ed while building pipeline for various paralell streams, and in this case the header will not match since when you have VIEW you will got empty header and non-empty header otherwise. And this leads to LOGICAL_ERROR later, while checking that output headers are the same (in QueryPipelineBuilder::addChains() -> Pipe::addChains()). However it also makes the pipeline different for various parallel streams, and it looks like it is better to fail in this case, so instead of always returning empty header from buildChainImpl() explicit check had been added. Note, that I wasn't able to reproduce the issue with the added test, but CI may have more "luck" (although I've verified it manually). Fixes: #35902 Cc: @KochetovNicolai Signed-off-by: Azat Khuzhin --- src/Interpreters/InterpreterInsertQuery.cpp | 10 +++ .../02380_insert_mv_race.reference | 0 .../0_stateless/02380_insert_mv_race.sh | 66 +++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 tests/queries/0_stateless/02380_insert_mv_race.reference create mode 100755 tests/queries/0_stateless/02380_insert_mv_race.sh diff --git a/src/Interpreters/InterpreterInsertQuery.cpp b/src/Interpreters/InterpreterInsertQuery.cpp index 7b6066575ae..493d8df57ff 100644 --- a/src/Interpreters/InterpreterInsertQuery.cpp +++ b/src/Interpreters/InterpreterInsertQuery.cpp @@ -44,6 +44,7 @@ namespace ErrorCodes extern const int NO_SUCH_COLUMN_IN_TABLE; extern const int ILLEGAL_COLUMN; extern const int DUPLICATE_COLUMN; + extern const int TABLE_IS_DROPPED; } InterpreterInsertQuery::InterpreterInsertQuery( @@ -424,6 +425,15 @@ BlockIO InterpreterInsertQuery::execute() for (size_t i = 0; i < out_streams_size; ++i) { auto out = buildChainImpl(table, metadata_snapshot, query_sample_block, nullptr, nullptr); + if (!out_chains.empty()) + { + if (out.getProcessors().size() != out_chains.back().getProcessors().size()) + { + throw Exception(ErrorCodes::TABLE_IS_DROPPED, + "Some VIEW is gone in between ({} vs {} processors, on {} parallel stream)", + out.getProcessors().size(), out_chains.back().getProcessors().size(), i); + } + } out_chains.emplace_back(std::move(out)); } } diff --git a/tests/queries/0_stateless/02380_insert_mv_race.reference b/tests/queries/0_stateless/02380_insert_mv_race.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02380_insert_mv_race.sh b/tests/queries/0_stateless/02380_insert_mv_race.sh new file mode 100755 index 00000000000..ba002832715 --- /dev/null +++ b/tests/queries/0_stateless/02380_insert_mv_race.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Tags: long, race + +# Regression test for INSERT into table with MV attached, +# to avoid possible errors if some table will disappears, +# in case of multiple streams was used (i.e. max_insert_threads>1) + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +function bootstrap() +{ + $CLICKHOUSE_CLIENT -nm -q " + DROP TABLE IF EXISTS null; + CREATE TABLE null (key Int) ENGINE = Null; + + DROP TABLE IF EXISTS mv; + CREATE MATERIALIZED VIEW mv ENGINE = Null() AS SELECT * FROM null; + " +} + +function insert_thread() +{ + local opts=( + --max_insert_threads 100 + --max_threads 100 + ) + local patterns=( + -e UNKNOWN_TABLE + -e TABLE_IS_DROPPED + ) + + while :; do + $CLICKHOUSE_CLIENT "${opts[@]}" -q "INSERT INTO null SELECT * FROM numbers_mt(1e6)" |& { + grep -F "DB::Exception: " | grep -v -F "${patterns[@]}" + } + done +} +export -f insert_thread + +function drop_thread() +{ + local opts=( + --database_atomic_wait_for_drop_and_detach_synchronously 1 + ) + + while :; do + $CLICKHOUSE_CLIENT -nm "${opts[@]}" -q "DETACH TABLE mv" + sleep 0.01 + $CLICKHOUSE_CLIENT -nm "${opts[@]}" -q "ATTACH TABLE mv" + done +} +export -f drop_thread + +function main() +{ + local test_timeout=1m + + bootstrap + timeout "$test_timeout" bash -c insert_thread & + timeout "$test_timeout" bash -c drop_thread & + + wait +} +main "$@" From cf342326755973e1f0f54d1c9346fa3d8f2ff71d Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 1 Aug 2022 11:48:36 +0000 Subject: [PATCH 349/672] Output header is now empty for every MV chain. Instead of checking that number of processors different for different threads, simply always return empty header from buildChainImpl(), by adding explicit conversion. v2: ignore UNKNOWN_TABLE errors in test --- src/Interpreters/InterpreterInsertQuery.cpp | 10 --- .../Transforms/buildPushingToViewsChain.cpp | 8 +++ .../0_stateless/02380_insert_mv_race.sh | 68 ++++--------------- 3 files changed, 22 insertions(+), 64 deletions(-) diff --git a/src/Interpreters/InterpreterInsertQuery.cpp b/src/Interpreters/InterpreterInsertQuery.cpp index 493d8df57ff..7b6066575ae 100644 --- a/src/Interpreters/InterpreterInsertQuery.cpp +++ b/src/Interpreters/InterpreterInsertQuery.cpp @@ -44,7 +44,6 @@ namespace ErrorCodes extern const int NO_SUCH_COLUMN_IN_TABLE; extern const int ILLEGAL_COLUMN; extern const int DUPLICATE_COLUMN; - extern const int TABLE_IS_DROPPED; } InterpreterInsertQuery::InterpreterInsertQuery( @@ -425,15 +424,6 @@ BlockIO InterpreterInsertQuery::execute() for (size_t i = 0; i < out_streams_size; ++i) { auto out = buildChainImpl(table, metadata_snapshot, query_sample_block, nullptr, nullptr); - if (!out_chains.empty()) - { - if (out.getProcessors().size() != out_chains.back().getProcessors().size()) - { - throw Exception(ErrorCodes::TABLE_IS_DROPPED, - "Some VIEW is gone in between ({} vs {} processors, on {} parallel stream)", - out.getProcessors().size(), out_chains.back().getProcessors().size(), i); - } - } out_chains.emplace_back(std::move(out)); } } diff --git a/src/Processors/Transforms/buildPushingToViewsChain.cpp b/src/Processors/Transforms/buildPushingToViewsChain.cpp index a8890f5bccb..d71d6901cee 100644 --- a/src/Processors/Transforms/buildPushingToViewsChain.cpp +++ b/src/Processors/Transforms/buildPushingToViewsChain.cpp @@ -410,6 +410,14 @@ Chain buildPushingToViewsChain( if (result_chain.empty()) result_chain.addSink(std::make_shared(storage_header)); + if (result_chain.getOutputHeader().columns() != 0) + { + /// Convert result header to empty block. + auto dag = ActionsDAG::makeConvertingActions(result_chain.getOutputHeader().getColumnsWithTypeAndName(), {}, ActionsDAG::MatchColumnsMode::Name); + auto actions = std::make_shared(std::move(dag)); + result_chain.addSink(std::make_shared(result_chain.getOutputHeader(), std::move(actions))); + } + return result_chain; } diff --git a/tests/queries/0_stateless/02380_insert_mv_race.sh b/tests/queries/0_stateless/02380_insert_mv_race.sh index ba002832715..d66e7b62d89 100755 --- a/tests/queries/0_stateless/02380_insert_mv_race.sh +++ b/tests/queries/0_stateless/02380_insert_mv_race.sh @@ -9,58 +9,18 @@ CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CUR_DIR"/../shell_config.sh -function bootstrap() -{ - $CLICKHOUSE_CLIENT -nm -q " - DROP TABLE IF EXISTS null; - CREATE TABLE null (key Int) ENGINE = Null; +$CLICKHOUSE_CLIENT -nm -q " + DROP TABLE IF EXISTS null; + CREATE TABLE null (key Int) ENGINE = Null; + DROP TABLE IF EXISTS mv; + CREATE MATERIALIZED VIEW mv ENGINE = Null() AS SELECT * FROM null; +" - DROP TABLE IF EXISTS mv; - CREATE MATERIALIZED VIEW mv ENGINE = Null() AS SELECT * FROM null; - " -} - -function insert_thread() -{ - local opts=( - --max_insert_threads 100 - --max_threads 100 - ) - local patterns=( - -e UNKNOWN_TABLE - -e TABLE_IS_DROPPED - ) - - while :; do - $CLICKHOUSE_CLIENT "${opts[@]}" -q "INSERT INTO null SELECT * FROM numbers_mt(1e6)" |& { - grep -F "DB::Exception: " | grep -v -F "${patterns[@]}" - } - done -} -export -f insert_thread - -function drop_thread() -{ - local opts=( - --database_atomic_wait_for_drop_and_detach_synchronously 1 - ) - - while :; do - $CLICKHOUSE_CLIENT -nm "${opts[@]}" -q "DETACH TABLE mv" - sleep 0.01 - $CLICKHOUSE_CLIENT -nm "${opts[@]}" -q "ATTACH TABLE mv" - done -} -export -f drop_thread - -function main() -{ - local test_timeout=1m - - bootstrap - timeout "$test_timeout" bash -c insert_thread & - timeout "$test_timeout" bash -c drop_thread & - - wait -} -main "$@" +$CLICKHOUSE_CLIENT -q "INSERT INTO null SELECT * FROM numbers_mt(1000) settings max_threads=1000, max_insert_threads=1000, max_block_size=1" |& { + # To avoid handling stacktrace here, get only first line (-m1) + # this should be OK, since you cannot have multiple exceptions from the client anyway. + grep -m1 -F 'DB::Exception:' | grep -F -v -e 'UNKNOWN_TABLE' +} & +sleep 0.05 +$CLICKHOUSE_CLIENT -q "DETACH TABLE mv" +wait From e59f217f8de701e9529dcb4c88e98fd34ae1ad0c Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 5 Aug 2022 13:33:27 +0200 Subject: [PATCH 350/672] add test logs on connection close --- src/Server/TCPHandler.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Server/TCPHandler.cpp b/src/Server/TCPHandler.cpp index 05565063893..16599c378e7 100644 --- a/src/Server/TCPHandler.cpp +++ b/src/Server/TCPHandler.cpp @@ -190,7 +190,10 @@ void TCPHandler::runImpl() /// If we need to shut down, or client disconnects. if (!tcp_server.isOpen() || server.isCancelled() || in->eof()) + { + LOG_TEST(log, "Closing connection (open: {}, cancelled: {}, eof: {})", tcp_server.isOpen(), server.isCancelled(), in->eof()); break; + } Stopwatch watch; state.reset(); @@ -406,6 +409,8 @@ void TCPHandler::runImpl() if (e.code() == ErrorCodes::UNKNOWN_PACKET_FROM_CLIENT) throw; + LOG_TEST(log, "Going to close connection due to exception: {}", e.message()); + /// If there is UNEXPECTED_PACKET_FROM_CLIENT emulate network_error /// to break the loop, but do not throw to send the exception to /// the client. @@ -435,7 +440,7 @@ void TCPHandler::runImpl() // Server should die on std logic errors in debug, like with assert() // or ErrorCodes::LOGICAL_ERROR. This helps catch these errors in // tests. -#ifndef NDEBUG +#ifdef ABORT_ON_LOGICAL_ERROR catch (const std::logic_error & e) { state.io.onException(); @@ -554,14 +559,14 @@ bool TCPHandler::readDataNext() Stopwatch watch(CLOCK_MONOTONIC_COARSE); /// Poll interval should not be greater than receive_timeout - constexpr UInt64 min_timeout_ms = 5000; // 5 ms - UInt64 timeout_ms = std::max(min_timeout_ms, std::min(poll_interval * 1000000, static_cast(receive_timeout.totalMicroseconds()))); + constexpr UInt64 min_timeout_us = 5000; // 5 ms + UInt64 timeout_us = std::max(min_timeout_us, std::min(poll_interval * 1000000, static_cast(receive_timeout.totalMicroseconds()))); bool read_ok = false; /// We are waiting for a packet from the client. Thus, every `POLL_INTERVAL` seconds check whether we need to shut down. while (true) { - if (static_cast(*in).poll(timeout_ms)) + if (static_cast(*in).poll(timeout_us)) { /// If client disconnected. if (in->eof()) From c3608de37eab41fb96ed6ceddb34b20be53c795a Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 5 Aug 2022 14:50:39 +0200 Subject: [PATCH 351/672] fix flaky test 02360_send_logs_level_colors --- tests/queries/0_stateless/02360_send_logs_level_colors.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02360_send_logs_level_colors.sh b/tests/queries/0_stateless/02360_send_logs_level_colors.sh index 4e5ce057702..0585e779815 100755 --- a/tests/queries/0_stateless/02360_send_logs_level_colors.sh +++ b/tests/queries/0_stateless/02360_send_logs_level_colors.sh @@ -13,7 +13,7 @@ function run() command=$1 expect << EOF log_user 0 -set timeout 3 +set timeout 60 match_max 100000 spawn bash -c "$command" From 340ff4f1adbbe3c2f32e3eabe82b203bc5cb5faf Mon Sep 17 00:00:00 2001 From: Nikita Taranov Date: Fri, 5 Aug 2022 14:51:18 +0200 Subject: [PATCH 352/672] impl (#39908) --- ...th_external_aggregation_memory_usage.reference | 1 - ...ted_with_external_aggregation_memory_usage.sql | 15 +++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.reference b/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.reference index d00491fd7e5..e69de29bb2d 100644 --- a/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.reference +++ b/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.reference @@ -1 +0,0 @@ -1 diff --git a/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql b/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql index 2601355ff46..c1cb2280381 100644 --- a/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql +++ b/tests/queries/0_stateless/02354_distributed_with_external_aggregation_memory_usage.sql @@ -9,15 +9,14 @@ set max_bytes_before_external_group_by = '2G', aggregation_memory_efficient_merge_threads = 16, distributed_aggregation_memory_efficient = 1, prefer_localhost_replica = 1, - group_by_two_level_threshold = 100000; + group_by_two_level_threshold = 100000, + group_by_two_level_threshold_bytes = 1000000, + max_block_size = 65505; +-- whole aggregation state of local aggregation uncompressed is 5.8G +-- it is hard to provide an accurate estimation for memory usage, so 4G is just the actual value taken from the logs + delta select a, b, c, sum(a) as s from remote('127.0.0.{1,2}', currentDatabase(), t_2354_dist_with_external_aggr) group by a, b, c -format Null; - -system flush logs; - -select memory_usage < 4 * 1024 * 1024 * 1024 -- whole aggregation state of local aggregation uncompressed is 5.8G -from system.query_log -where event_time >= now() - interval '15 minute' and type = 'QueryFinish' and is_initial_query and query like '%t_2354_dist_with_external_aggr%group_by%' and current_database = currentDatabase(); +format Null +settings max_memory_usage = '4G'; From ea51edde78f0ad16226192a2ae3f744d0e0940c5 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Fri, 5 Aug 2022 09:39:20 -0400 Subject: [PATCH 353/672] dedicated http test --- .../0_stateless/02377_quota_key.reference | 1 - tests/queries/0_stateless/02377_quota_key.sh | 1 - .../0_stateless/02377_quota_key_http.reference | 1 + .../queries/0_stateless/02377_quota_key_http.sh | 16 ++++++++++++++++ 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 tests/queries/0_stateless/02377_quota_key_http.reference create mode 100755 tests/queries/0_stateless/02377_quota_key_http.sh diff --git a/tests/queries/0_stateless/02377_quota_key.reference b/tests/queries/0_stateless/02377_quota_key.reference index 6ed281c757a..d00491fd7e5 100644 --- a/tests/queries/0_stateless/02377_quota_key.reference +++ b/tests/queries/0_stateless/02377_quota_key.reference @@ -1,2 +1 @@ 1 -1 diff --git a/tests/queries/0_stateless/02377_quota_key.sh b/tests/queries/0_stateless/02377_quota_key.sh index 9c81bcae8f2..8aa6797dc2c 100755 --- a/tests/queries/0_stateless/02377_quota_key.sh +++ b/tests/queries/0_stateless/02377_quota_key.sh @@ -11,7 +11,6 @@ ${CLICKHOUSE_CLIENT} -q "CREATE USER u_02377 IDENTIFIED WITH plaintext_password ${CLICKHOUSE_CLIENT} -q "CREATE QUOTA q_02377 KEYED BY client_key FOR INTERVAL 1 month MAX queries = 100 TO u_02377;" ${CLICKHOUSE_CLIENT} --user=u_02377 --password=password --quota_key=q_02377 --query="select 1" -curl -sS "${CLICKHOUSE_URL}&user=editor_api&password=password"a_key=editor_api&query=SELECT%201" ${CLICKHOUSE_CLIENT} -q "DROP USER IF EXISTS u_02377" ${CLICKHOUSE_CLIENT} -q "drop quota if exists q_02377" diff --git a/tests/queries/0_stateless/02377_quota_key_http.reference b/tests/queries/0_stateless/02377_quota_key_http.reference new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/tests/queries/0_stateless/02377_quota_key_http.reference @@ -0,0 +1 @@ +1 diff --git a/tests/queries/0_stateless/02377_quota_key_http.sh b/tests/queries/0_stateless/02377_quota_key_http.sh new file mode 100755 index 00000000000..616396aa876 --- /dev/null +++ b/tests/queries/0_stateless/02377_quota_key_http.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Tags: no-parallel + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +curl -sS "${CLICKHOUSE_URL}" --data "DROP USER IF EXISTS u_02377" +curl -sS "${CLICKHOUSE_URL}" --data "drop quota if exists q_02377" +curl -sS "${CLICKHOUSE_URL}" --data "CREATE USER u_02377 IDENTIFIED WITH plaintext_password BY 'password'" +curl -sS "${CLICKHOUSE_URL}" --data "CREATE QUOTA q_02377 KEYED BY client_key FOR INTERVAL 1 month MAX queries = 100 TO u_02377" + +curl -sS -G "${CLICKHOUSE_URL}&user=editor_api&password=password"a_key=editor_api&query=SELECT%201" + +curl -sS "${CLICKHOUSE_URL}" --data "DROP USER IF EXISTS u_02377" +curl -sS "${CLICKHOUSE_URL}" --data "drop quota if exists q_02377" From 1a52fa183da6a27574c095583507e09ef7bf06af Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Sat, 30 Jul 2022 01:07:22 -0400 Subject: [PATCH 354/672] rename Simple Check to Mergeable Check, refactor processing --- tests/ci/ast_fuzzer_check.py | 2 +- tests/ci/build_report_check.py | 20 ++++---- tests/ci/ci_config.py | 8 +++ tests/ci/codebrowser_check.py | 2 +- tests/ci/commit_status_helper.py | 62 ++++++++++++++++++------ tests/ci/compatibility_check.py | 2 +- tests/ci/docker_images_check.py | 2 +- tests/ci/docker_manifests_merge.py | 2 +- tests/ci/docker_server.py | 2 +- tests/ci/docs_check.py | 2 +- tests/ci/docs_release.py | 2 +- tests/ci/fast_test_check.py | 8 +-- tests/ci/finish_check.py | 2 +- tests/ci/functional_test_check.py | 2 +- tests/ci/get_robot_token.py | 2 +- tests/ci/integration_test_check.py | 2 +- tests/ci/keeper_jepsen_check.py | 2 +- tests/ci/performance_comparison_check.py | 2 +- tests/ci/pr_info.py | 2 +- tests/ci/run_check.py | 6 +-- tests/ci/split_build_smoke_check.py | 2 +- tests/ci/stress_check.py | 2 +- tests/ci/style_check.py | 6 ++- tests/ci/unit_tests_check.py | 2 +- 24 files changed, 96 insertions(+), 50 deletions(-) diff --git a/tests/ci/ast_fuzzer_check.py b/tests/ci/ast_fuzzer_check.py index 9ccae89b403..918e27a4e11 100644 --- a/tests/ci/ast_fuzzer_check.py +++ b/tests/ci/ast_fuzzer_check.py @@ -58,7 +58,7 @@ if __name__ == "__main__": pr_info = PRInfo() - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) rerun_helper = RerunHelper(gh, pr_info, check_name) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/build_report_check.py b/tests/ci/build_report_check.py index 4bb7a619b9f..c9fc6776827 100644 --- a/tests/ci/build_report_check.py +++ b/tests/ci/build_report_check.py @@ -4,6 +4,7 @@ import json import logging import os import sys +import atexit from typing import Dict, List, Tuple from github import Github @@ -21,7 +22,7 @@ from get_robot_token import get_best_robot_token from pr_info import PRInfo from commit_status_helper import ( get_commit, - fail_simple_check, + update_mergeable_check, ) from ci_config import CI_CONFIG from rerun_helper import RerunHelper @@ -154,16 +155,19 @@ def main(): needs_data = json.load(file_handler) required_builds = len(needs_data) - # A report might be empty in case of `do not test` label, for example. - # We should still be able to merge such PRs. - all_skipped = needs_data is not None and all( + if needs_data is not None and all( i["result"] == "skipped" for i in needs_data.values() - ) + ): + logging.info("All builds are skipped, exiting") + sys.exit(0) logging.info("The next builds are required: %s", ", ".join(needs_data)) - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) pr_info = PRInfo() + + atexit.register(update_mergeable_check, gh, pr_info, build_check_name) + rerun_helper = RerunHelper(gh, pr_info, build_check_name) if rerun_helper.is_already_finished_by_status(): logging.info("Check is already finished according to github status, exiting") @@ -237,8 +241,6 @@ def main(): total_groups = len(build_results) logging.info("Totally got %s artifact groups", total_groups) if total_groups == 0: - if not all_skipped: - fail_simple_check(gh, pr_info, f"{build_check_name} failed") logging.error("No success builds, failing check") sys.exit(1) @@ -308,8 +310,6 @@ def main(): ) if summary_status == "error": - if not all_skipped: - fail_simple_check(gh, pr_info, f"{build_check_name} failed") sys.exit(1) diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index 10db5d05ad4..252f2581792 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -345,3 +345,11 @@ CI_CONFIG = { }, }, } # type: dict + +# checks required by Mergeable Check +REQUIRED_CHECKS = [ + "Fast test", + "Style Check", + "ClickHouse build check", + "ClickHouse special build check", +] diff --git a/tests/ci/codebrowser_check.py b/tests/ci/codebrowser_check.py index 6e7f98bd82a..230a778c598 100644 --- a/tests/ci/codebrowser_check.py +++ b/tests/ci/codebrowser_check.py @@ -35,7 +35,7 @@ if __name__ == "__main__": temp_path = os.getenv("TEMP_PATH", os.path.abspath(".")) - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) if not os.path.exists(temp_path): os.makedirs(temp_path) diff --git a/tests/ci/commit_status_helper.py b/tests/ci/commit_status_helper.py index 83b6203c050..8b9d28502c1 100644 --- a/tests/ci/commit_status_helper.py +++ b/tests/ci/commit_status_helper.py @@ -4,12 +4,13 @@ import csv import os import time from typing import Optional +import logging -from ci_config import CI_CONFIG +from ci_config import CI_CONFIG, REQUIRED_CHECKS from env_helper import GITHUB_REPOSITORY, GITHUB_RUN_URL from github import Github from github.Commit import Commit -from pr_info import SKIP_SIMPLE_CHECK_LABEL +from pr_info import SKIP_MERGEABLE_CHECK_LABEL RETRY = 5 @@ -82,26 +83,59 @@ def post_labels(gh, pr_info, labels_names): pull_request.add_to_labels(label) -def fail_simple_check(gh, pr_info, description): - if SKIP_SIMPLE_CHECK_LABEL in pr_info.labels: - return - commit = get_commit(gh, pr_info.sha) +def fail_mergeable_check(commit, description): commit.create_status( - context="Simple Check", + context="Mergeable Check", description=description, state="failure", target_url=GITHUB_RUN_URL, ) -def create_simple_check(gh, pr_info): - commit = get_commit(gh, pr_info.sha) - for status in commit.get_statuses(): - if "Simple Check" in status.context: - return +def reset_mergeable_check(commit, description=""): commit.create_status( - context="Simple Check", - description="Skipped", + context="Mergeable Check", + description=description, state="success", target_url=GITHUB_RUN_URL, ) + + +def update_mergeable_check(gh, pr_info, check_name): + if SKIP_MERGEABLE_CHECK_LABEL in pr_info.labels: + return + + logging.info("Update Mergeable Check by %s", check_name) + + commit = get_commit(gh, pr_info.sha) + checks = { + check.context: check.state + for check in filter( + lambda check: (check.context in REQUIRED_CHECKS), + # get_statuses() returns generator, which cannot be reversed - we need comprehension + # pylint: disable=unnecessary-comprehension + reversed([status for status in commit.get_statuses()]), + ) + } + + success = [] + fail = [] + for name, state in checks.items(): + if state == "success": + success.append(name) + else: + fail.append(name) + + if fail: + description = "failed: " + ", ".join(fail) + if success: + description += "; succeeded: " + ", ".join(success) + if len(description) > 140: + description = description[:137] + "..." + fail_mergeable_check(commit, description) + return + + description = ", ".join(success) + if len(description) > 140: + description = description[:137] + "..." + reset_mergeable_check(commit, description) diff --git a/tests/ci/compatibility_check.py b/tests/ci/compatibility_check.py index 71a959a064c..2a1b9716189 100644 --- a/tests/ci/compatibility_check.py +++ b/tests/ci/compatibility_check.py @@ -119,7 +119,7 @@ if __name__ == "__main__": pr_info = PRInfo() - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) rerun_helper = RerunHelper(gh, pr_info, CHECK_NAME) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/docker_images_check.py b/tests/ci/docker_images_check.py index 5742bc6c22e..8b838defa8b 100644 --- a/tests/ci/docker_images_check.py +++ b/tests/ci/docker_images_check.py @@ -477,7 +477,7 @@ def main(): if not args.reports: return - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) post_commit_status(gh, pr_info.sha, NAME, description, status, url) prepared_events = prepare_tests_results_for_clickhouse( diff --git a/tests/ci/docker_manifests_merge.py b/tests/ci/docker_manifests_merge.py index aa13bbea2fb..00ab0b9e77f 100644 --- a/tests/ci/docker_manifests_merge.py +++ b/tests/ci/docker_manifests_merge.py @@ -221,7 +221,7 @@ def main(): if len(description) >= 140: description = description[:136] + "..." - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) post_commit_status(gh, pr_info.sha, NAME, description, status, url) prepared_events = prepare_tests_results_for_clickhouse( diff --git a/tests/ci/docker_server.py b/tests/ci/docker_server.py index 710c18a56cb..09a75206442 100644 --- a/tests/ci/docker_server.py +++ b/tests/ci/docker_server.py @@ -351,7 +351,7 @@ def main(): if len(description) >= 140: description = description[:136] + "..." - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) post_commit_status(gh, pr_info.sha, NAME, description, status, url) prepared_events = prepare_tests_results_for_clickhouse( diff --git a/tests/ci/docs_check.py b/tests/ci/docs_check.py index d6131535ef8..cf4fd8da692 100644 --- a/tests/ci/docs_check.py +++ b/tests/ci/docs_check.py @@ -47,7 +47,7 @@ if __name__ == "__main__": pr_info = PRInfo(need_changed_files=True) - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) rerun_helper = RerunHelper(gh, pr_info, NAME) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/docs_release.py b/tests/ci/docs_release.py index 4a9686ec99f..35203486fae 100644 --- a/tests/ci/docs_release.py +++ b/tests/ci/docs_release.py @@ -39,7 +39,7 @@ if __name__ == "__main__": temp_path = TEMP_PATH repo_path = REPO_COPY - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) pr_info = PRInfo() rerun_helper = RerunHelper(gh, pr_info, NAME) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/fast_test_check.py b/tests/ci/fast_test_check.py index 2768eccd53a..9852175ca92 100644 --- a/tests/ci/fast_test_check.py +++ b/tests/ci/fast_test_check.py @@ -5,6 +5,7 @@ import subprocess import os import csv import sys +import atexit from github import Github @@ -16,7 +17,7 @@ from upload_result_helper import upload_results from docker_pull_helper import get_image_with_version from commit_status_helper import ( post_commit_status, - fail_simple_check, + update_mergeable_check, ) from clickhouse_helper import ( ClickHouseHelper, @@ -93,7 +94,9 @@ if __name__ == "__main__": pr_info = PRInfo() - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) + + atexit.register(update_mergeable_check, gh, pr_info, NAME) rerun_helper = RerunHelper(gh, pr_info, NAME) if rerun_helper.is_already_finished_by_status(): @@ -222,5 +225,4 @@ if __name__ == "__main__": if FORCE_TESTS_LABEL in pr_info.labels and state != "error": print(f"'{FORCE_TESTS_LABEL}' enabled, will report success") else: - fail_simple_check(gh, pr_info, f"{NAME} failed") sys.exit(1) diff --git a/tests/ci/finish_check.py b/tests/ci/finish_check.py index 0697f52abed..a0b7f14ecfb 100644 --- a/tests/ci/finish_check.py +++ b/tests/ci/finish_check.py @@ -30,7 +30,7 @@ if __name__ == "__main__": logging.basicConfig(level=logging.INFO) pr_info = PRInfo(need_orgs=True) - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) commit = get_commit(gh, pr_info.sha) url = GITHUB_RUN_URL diff --git a/tests/ci/functional_test_check.py b/tests/ci/functional_test_check.py index b73e6f9d708..e5a05b3e816 100644 --- a/tests/ci/functional_test_check.py +++ b/tests/ci/functional_test_check.py @@ -205,7 +205,7 @@ if __name__ == "__main__": flaky_check = "flaky" in check_name.lower() run_changed_tests = flaky_check or validate_bugix_check - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) pr_info = PRInfo(need_changed_files=run_changed_tests) diff --git a/tests/ci/get_robot_token.py b/tests/ci/get_robot_token.py index cb79d9ae01a..4fb8cb8f49f 100644 --- a/tests/ci/get_robot_token.py +++ b/tests/ci/get_robot_token.py @@ -15,7 +15,7 @@ def get_best_robot_token(token_prefix_env_name="github_robot_token_", total_toke for i in range(1, total_tokens + 1): token_name = token_prefix_env_name + str(i) token = get_parameter_from_ssm(token_name, True, client) - gh = Github(token) + gh = Github(token, per_page=100) rest, _ = gh.rate_limiting tokens[token] = rest diff --git a/tests/ci/integration_test_check.py b/tests/ci/integration_test_check.py index 1c53247c072..565864d576c 100644 --- a/tests/ci/integration_test_check.py +++ b/tests/ci/integration_test_check.py @@ -180,7 +180,7 @@ if __name__ == "__main__": logging.info("Skipping '%s' (no pr-bugfix)", check_name) sys.exit(0) - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) rerun_helper = RerunHelper(gh, pr_info, check_name_with_group) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/keeper_jepsen_check.py b/tests/ci/keeper_jepsen_check.py index 3c2f72f73d1..88ccf8e8828 100644 --- a/tests/ci/keeper_jepsen_check.py +++ b/tests/ci/keeper_jepsen_check.py @@ -159,7 +159,7 @@ if __name__ == "__main__": logging.info("Not jepsen test label in labels list, skipping") sys.exit(0) - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) rerun_helper = RerunHelper(gh, pr_info, CHECK_NAME) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/performance_comparison_check.py b/tests/ci/performance_comparison_check.py index baf2593130a..57a52dcaa6a 100644 --- a/tests/ci/performance_comparison_check.py +++ b/tests/ci/performance_comparison_check.py @@ -80,7 +80,7 @@ if __name__ == "__main__": with open(GITHUB_EVENT_PATH, "r", encoding="utf-8") as event_file: event = json.load(event_file) - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) pr_info = PRInfo(event) commit = get_commit(gh, pr_info.sha) diff --git a/tests/ci/pr_info.py b/tests/ci/pr_info.py index 86b3081f98c..2acd0e4c811 100644 --- a/tests/ci/pr_info.py +++ b/tests/ci/pr_info.py @@ -15,7 +15,7 @@ from env_helper import ( ) FORCE_TESTS_LABEL = "force tests" -SKIP_SIMPLE_CHECK_LABEL = "skip simple check" +SKIP_MERGEABLE_CHECK_LABEL = "skip mergeable check" DIFF_IN_DOCUMENTATION_EXT = [ ".html", diff --git a/tests/ci/run_check.py b/tests/ci/run_check.py index 2777fe55fd7..5e6542f6e4c 100644 --- a/tests/ci/run_check.py +++ b/tests/ci/run_check.py @@ -10,7 +10,7 @@ from commit_status_helper import ( get_commit, post_labels, remove_labels, - create_simple_check, + reset_mergeable_check, ) from env_helper import GITHUB_RUN_URL, GITHUB_REPOSITORY, GITHUB_SERVER_URL from get_robot_token import get_best_robot_token @@ -196,7 +196,7 @@ if __name__ == "__main__": pr_info = PRInfo(need_orgs=True, pr_event_from_api=True, need_changed_files=True) can_run, description, labels_state = should_run_checks_for_pr(pr_info) - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) commit = get_commit(gh, pr_info.sha) description_error, category = check_pr_description(pr_info) @@ -228,7 +228,7 @@ if __name__ == "__main__": if pr_labels_to_remove: remove_labels(gh, pr_info, pr_labels_to_remove) - create_simple_check(gh, pr_info) + reset_mergeable_check(commit, "skipped") if description_error: print( diff --git a/tests/ci/split_build_smoke_check.py b/tests/ci/split_build_smoke_check.py index 9237df23a26..87a528d2761 100644 --- a/tests/ci/split_build_smoke_check.py +++ b/tests/ci/split_build_smoke_check.py @@ -76,7 +76,7 @@ if __name__ == "__main__": pr_info = PRInfo() - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) rerun_helper = RerunHelper(gh, pr_info, CHECK_NAME) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/stress_check.py b/tests/ci/stress_check.py index 6bd4c580687..e63f66e2e50 100644 --- a/tests/ci/stress_check.py +++ b/tests/ci/stress_check.py @@ -109,7 +109,7 @@ if __name__ == "__main__": pr_info = PRInfo() - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) rerun_helper = RerunHelper(gh, pr_info, check_name) if rerun_helper.is_already_finished_by_status(): diff --git a/tests/ci/style_check.py b/tests/ci/style_check.py index 7ba0dc2a297..66837ccb84e 100644 --- a/tests/ci/style_check.py +++ b/tests/ci/style_check.py @@ -5,6 +5,7 @@ import logging import os import subprocess import sys +import atexit from clickhouse_helper import ( @@ -12,7 +13,7 @@ from clickhouse_helper import ( mark_flaky_tests, prepare_tests_results_for_clickhouse, ) -from commit_status_helper import fail_simple_check, post_commit_status +from commit_status_helper import post_commit_status, update_mergeable_check from docker_pull_helper import get_image_with_version from env_helper import GITHUB_WORKSPACE, RUNNER_TEMP from get_robot_token import get_best_robot_token @@ -150,6 +151,8 @@ if __name__ == "__main__": gh = GitHub(get_best_robot_token()) + atexit.register(update_mergeable_check, gh, pr_info, NAME) + rerun_helper = RerunHelper(gh, pr_info, NAME) if rerun_helper.is_already_finished_by_status(): logging.info("Check is already finished according to github status, exiting") @@ -202,5 +205,4 @@ if __name__ == "__main__": ch_helper.insert_events_into(db="default", table="checks", events=prepared_events) if state in ["error", "failure"]: - fail_simple_check(gh, pr_info, f"{NAME} failed") sys.exit(1) diff --git a/tests/ci/unit_tests_check.py b/tests/ci/unit_tests_check.py index c2329fab955..4441709cb7b 100644 --- a/tests/ci/unit_tests_check.py +++ b/tests/ci/unit_tests_check.py @@ -114,7 +114,7 @@ if __name__ == "__main__": pr_info = PRInfo() - gh = Github(get_best_robot_token()) + gh = Github(get_best_robot_token(), per_page=100) rerun_helper = RerunHelper(gh, pr_info, check_name) if rerun_helper.is_already_finished_by_status(): From 04c16cfeed5ba53bd7ca45059bd9c2fe0da9b157 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Fri, 5 Aug 2022 10:55:31 -0400 Subject: [PATCH 355/672] no-fasttest for http --- tests/queries/0_stateless/02377_quota_key_http.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02377_quota_key_http.sh b/tests/queries/0_stateless/02377_quota_key_http.sh index 616396aa876..daa17f471be 100755 --- a/tests/queries/0_stateless/02377_quota_key_http.sh +++ b/tests/queries/0_stateless/02377_quota_key_http.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-parallel +# Tags: no-fasttest, no-parallel CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From b41b716245dea528cc2f592dea9da3954b088a5a Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Fri, 5 Aug 2022 18:16:35 +0200 Subject: [PATCH 356/672] Update AsynchronousMetrics.cpp --- src/Interpreters/AsynchronousMetrics.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index 81fdef3d8a6..c1102a0652d 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -34,12 +34,6 @@ #endif -namespace CurrentMetrics -{ - extern const Metric MemoryTracking; -} - - namespace DB { From cbd01a3a73cfa39e37b8ebe9608fe3a174a511de Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Fri, 5 Aug 2022 11:07:42 -0700 Subject: [PATCH 357/672] Fix Endian issue in KeeperSnapshotManager --- src/Coordination/KeeperSnapshotManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Coordination/KeeperSnapshotManager.cpp b/src/Coordination/KeeperSnapshotManager.cpp index 0057fd7e96e..fa1e35f89bf 100644 --- a/src/Coordination/KeeperSnapshotManager.cpp +++ b/src/Coordination/KeeperSnapshotManager.cpp @@ -635,7 +635,11 @@ nuraft::ptr KeeperSnapshotManager::serializeSnapshotToBuffer(con bool KeeperSnapshotManager::isZstdCompressed(nuraft::ptr buffer) { +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + static constexpr uint32_t ZSTD_COMPRESSED_MAGIC = 0x28B52FFD; +#else static constexpr uint32_t ZSTD_COMPRESSED_MAGIC = 0xFD2FB528; +#endif ReadBufferFromNuraftBuffer reader(buffer); uint32_t magic_from_buffer; reader.readStrict(reinterpret_cast(&magic_from_buffer), sizeof(magic_from_buffer)); From f2c4cad2558d441955d088ec20c136c34e1c3912 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 5 Aug 2022 21:41:02 +0200 Subject: [PATCH 358/672] add flag that enables automatic canversion from Ordinary to Atomic --- programs/server/Server.cpp | 3 +- src/Interpreters/loadMetadata.cpp | 90 ++++++++--- src/Interpreters/loadMetadata.h | 8 +- src/Storages/StorageLog.cpp | 1 + src/Storages/StorageReplicatedMergeTree.cpp | 25 ++- src/Storages/StorageStripeLog.cpp | 1 + .../test_convert_ordinary.py | 151 +++++++++++++++++- 7 files changed, 252 insertions(+), 27 deletions(-) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index b86ce4a841c..acb88502068 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -1519,7 +1519,7 @@ int Server::main(const std::vector & /*args*/) /// We load temporary database first, because projections need it. database_catalog.initializeAndLoadTemporaryDatabase(); loadMetadataSystem(global_context); - maybeConvertOrdinaryDatabaseToAtomic(global_context, DatabaseCatalog::instance().getSystemDatabase()); + maybeConvertSystemDatabase(global_context); /// After attaching system databases we can initialize system log. global_context->initializeSystemLogs(); global_context->setSystemZooKeeperLogAfterInitializationIfNeeded(); @@ -1533,6 +1533,7 @@ int Server::main(const std::vector & /*args*/) database_catalog.loadMarkedAsDroppedTables(); /// Then, load remaining databases loadMetadata(global_context, default_database); + convertDatabasesEnginesIfNeed(global_context); startupSystemTables(); database_catalog.loadDatabases(); /// After loading validate that default database exists diff --git a/src/Interpreters/loadMetadata.cpp b/src/Interpreters/loadMetadata.cpp index 15d4f7929f8..50cfce64823 100644 --- a/src/Interpreters/loadMetadata.cpp +++ b/src/Interpreters/loadMetadata.cpp @@ -1,7 +1,5 @@ #include -#include - #include #include #include @@ -13,6 +11,7 @@ #include #include +#include #include #include @@ -211,13 +210,11 @@ static void loadSystemDatabaseImpl(ContextMutablePtr context, const String & dat } } -static void convertOrdinaryDatabaseToAtomic(ContextMutablePtr context, const DatabasePtr & database) +static void convertOrdinaryDatabaseToAtomic(Poco::Logger * log, ContextMutablePtr context, const DatabasePtr & database) { /// It's kind of C++ script that creates temporary database with Atomic engine, /// moves all tables to it, drops old database and then renames new one to old name. - Poco::Logger * log = &Poco::Logger::get("loadMetadata"); - String name = database->getDatabaseName(); String tmp_name = fmt::format(".tmp_convert.{}.{}", name, thread_local_rng()); @@ -235,19 +232,34 @@ static void convertOrdinaryDatabaseToAtomic(ContextMutablePtr context, const Dat assert(tmp_database->getEngineName() == "Atomic"); size_t num_tables = 0; + std::unordered_set inner_mv_tables; for (auto iterator = database->getTablesIterator(context); iterator->isValid(); iterator->next()) { ++num_tables; auto id = iterator->table()->getStorageID(); id.database_name = tmp_name; + /// We need some uuid for checkTableCanBeRenamed + id.uuid = UUIDHelpers::generateV4(); iterator->table()->checkTableCanBeRenamed(id); + if (const auto * mv = dynamic_cast(iterator->table().get())) + { + /// We should not rename inner tables of MVs, because MVs are responsible for renaming it... + if (mv->hasInnerTable()) + inner_mv_tables.emplace(mv->getTargetTable()->getStorageID().table_name); + } } - LOG_INFO(log, "Will move {} tables to {}", num_tables, tmp_name_quoted); + LOG_INFO(log, "Will move {} tables to {} (including {} inner tables of MVs)", num_tables, tmp_name_quoted, inner_mv_tables.size()); for (auto iterator = database->getTablesIterator(context); iterator->isValid(); iterator->next()) { auto id = iterator->table()->getStorageID(); + if (inner_mv_tables.contains(id.table_name)) + { + LOG_DEBUG(log, "Do not rename {}, because it will be renamed together with MV", id.getNameForLogs()); + continue; + } + String qualified_quoted_name = id.getFullTableName(); id.database_name = tmp_name; String tmp_qualified_quoted_name = id.getFullTableName(); @@ -275,31 +287,43 @@ static void convertOrdinaryDatabaseToAtomic(ContextMutablePtr context, const Dat LOG_INFO(log, "Finished database engine conversion of {}", name_quoted); } -void maybeConvertOrdinaryDatabaseToAtomic(ContextMutablePtr context, const DatabasePtr & database) +/// Converts database with Ordinary engine to Atomic. Does nothing if database is not Ordinary. +/// Can be called only during server startup when there are no queries from users. +static void maybeConvertOrdinaryDatabaseToAtomic(ContextMutablePtr context, const String & database_name, bool tables_started) { - if (database->getEngineName() != "Ordinary") - return; + Poco::Logger * log = &Poco::Logger::get("loadMetadata"); - if (context->getSettingsRef().allow_deprecated_database_ordinary) + auto database = DatabaseCatalog::instance().getDatabase(database_name); + if (!database) + { + LOG_WARNING(log, "Database {} not found (while trying to convert it from Ordinary to Atomic)", database_name); + return; + } + + if (database->getEngineName() != "Ordinary") return; try { - /// It's not quite correct to run DDL queries while database is not started up. - startupSystemTables(); + if (!tables_started) + { + /// It's not quite correct to run DDL queries while database is not started up. + ThreadPool pool; + DatabaseCatalog::instance().getSystemDatabase()->startupTables(pool, /* force_restore */ true, /* force_attach */ true); + } auto local_context = Context::createCopy(context); local_context->setSetting("check_table_dependencies", false); - convertOrdinaryDatabaseToAtomic(local_context, database); + convertOrdinaryDatabaseToAtomic(log, local_context, database); - auto new_database = DatabaseCatalog::instance().getDatabase(DatabaseCatalog::SYSTEM_DATABASE); + auto new_database = DatabaseCatalog::instance().getDatabase(database_name); UUID db_uuid = new_database->getUUID(); std::vector tables_uuids; for (auto iterator = new_database->getTablesIterator(context); iterator->isValid(); iterator->next()) tables_uuids.push_back(iterator->uuid()); /// Reload database just in case (and update logger name) - String detach_query = fmt::format("DETACH DATABASE {}", backQuoteIfNeed(DatabaseCatalog::SYSTEM_DATABASE)); + String detach_query = fmt::format("DETACH DATABASE {}", backQuoteIfNeed(database_name)); auto res = executeQuery(detach_query, context, true); executeTrivialBlockIO(res, context); res = {}; @@ -310,23 +334,51 @@ void maybeConvertOrdinaryDatabaseToAtomic(ContextMutablePtr context, const Datab for (const auto & uuid : tables_uuids) DatabaseCatalog::instance().removeUUIDMappingFinally(uuid); - loadSystemDatabaseImpl(context, DatabaseCatalog::SYSTEM_DATABASE, "Atomic"); + String path = context->getPath() + "metadata/" + escapeForFileName(database_name); + /// force_restore_data is needed to re-create metadata symlinks + loadDatabase(context, database_name, path, /* force_restore_data */ true); + TablesLoader::Databases databases = { - {DatabaseCatalog::SYSTEM_DATABASE, DatabaseCatalog::instance().getSystemDatabase()}, + {database_name, DatabaseCatalog::instance().getDatabase(database_name)}, }; TablesLoader loader{context, databases, /* force_restore */ true, /* force_attach */ true}; loader.loadTables(); - /// Will startup tables usual way + /// Startup tables if they were started before conversion and detach/attach + if (tables_started) + loader.startupTables(); } catch (Exception & e) { - e.addMessage("While trying to convert {} to Atomic", database->getDatabaseName()); + e.addMessage("While trying to convert {} to Atomic", database_name); throw; } } +void maybeConvertSystemDatabase(ContextMutablePtr context) +{ + /// TODO remove this check, convert system database unconditionally + if (context->getSettingsRef().allow_deprecated_database_ordinary) + return; + + maybeConvertOrdinaryDatabaseToAtomic(context, DatabaseCatalog::SYSTEM_DATABASE, /* tables_started */ false); +} + +void convertDatabasesEnginesIfNeed(ContextMutablePtr context) +{ + auto convert_flag_path = fs::path(context->getFlagsPath()) / "convert_ordinary_to_atomic"; + if (!fs::exists(convert_flag_path)) + return; + + LOG_INFO(&Poco::Logger::get("loadMetadata"), "Found convert_ordinary_to_atomic file in flags directory, " + "will try to convert all Ordinary databases to Atomic"); + fs::remove(convert_flag_path); + + for (const auto & [name, _] : DatabaseCatalog::instance().getDatabases()) + if (name != DatabaseCatalog::SYSTEM_DATABASE) + maybeConvertOrdinaryDatabaseToAtomic(context, name, /* tables_started */ true); +} void startupSystemTables() { diff --git a/src/Interpreters/loadMetadata.h b/src/Interpreters/loadMetadata.h index 8dc332defc5..b229a2b4c31 100644 --- a/src/Interpreters/loadMetadata.h +++ b/src/Interpreters/loadMetadata.h @@ -19,8 +19,10 @@ void loadMetadata(ContextMutablePtr context, const String & default_database_nam /// so we startup system tables after all databases are loaded. void startupSystemTables(); -/// Converts database with Ordinary engine to Atomic. Does nothing if database is not Ordinary. -/// Can be called only during server startup when there are no queries from users. -void maybeConvertOrdinaryDatabaseToAtomic(ContextMutablePtr context, const DatabasePtr & database); +/// Converts `system` database from Ordinary to Atomic (if needed) +void maybeConvertSystemDatabase(ContextMutablePtr context); + +/// Converts all databases (except system) from Ordinary to Atomic if convert_ordinary_to_atomic flag exists +void convertDatabasesEnginesIfNeed(ContextMutablePtr context); } diff --git a/src/Storages/StorageLog.cpp b/src/Storages/StorageLog.cpp index ccb88992732..c6bc55fd620 100644 --- a/src/Storages/StorageLog.cpp +++ b/src/Storages/StorageLog.cpp @@ -729,6 +729,7 @@ void StorageLog::rename(const String & new_path_to_table_data, const StorageID & { assert(table_path != new_path_to_table_data); { + disk->createDirectories(new_path_to_table_data); disk->moveDirectory(table_path, new_path_to_table_data); table_path = new_path_to_table_data; diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index cfe77777650..b46084c8dc5 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -5132,9 +5132,28 @@ void StorageReplicatedMergeTree::checkTableCanBeRenamed(const StorageID & new_na return; if (renaming_restrictions == RenamingRestrictions::DO_NOT_ALLOW) - throw Exception("Cannot rename Replicated table, because zookeeper_path contains implicit 'database' or 'table' macro. " - "We cannot rename path in ZooKeeper, so path may become inconsistent with table name. If you really want to rename table, " - "you should edit metadata file first and restart server or reattach the table.", ErrorCodes::NOT_IMPLEMENTED); + { + auto old_name = getStorageID(); + bool is_server_startup = Context::getGlobalContextInstance()->getApplicationType() == Context::ApplicationType::SERVER + && !Context::getGlobalContextInstance()->isServerCompletelyStarted(); + bool move_to_atomic = old_name.uuid == UUIDHelpers::Nil && new_name.uuid != UUIDHelpers::Nil; + + bool likely_converting_ordinary_to_atomic = is_server_startup && move_to_atomic; + if (likely_converting_ordinary_to_atomic) + { + LOG_INFO(log, "Table {} should not be renamed, because zookeeper_path contains implicit 'database' or 'table' macro. " + "We cannot rename path in ZooKeeper, so path may become inconsistent with table name. " + "However, we allow renaming while converting Ordinary database to Atomic, because all tables will be renamed back", + old_name.getNameForLogs()); + return; + } + + throw Exception( + "Cannot rename Replicated table, because zookeeper_path contains implicit 'database' or 'table' macro. " + "We cannot rename path in ZooKeeper, so path may become inconsistent with table name. If you really want to rename table, " + "you should edit metadata file first and restart server or reattach the table.", + ErrorCodes::NOT_IMPLEMENTED); + } assert(renaming_restrictions == RenamingRestrictions::ALLOW_PRESERVING_UUID); if (!new_name.hasUUID() && getStorageID().hasUUID()) diff --git a/src/Storages/StorageStripeLog.cpp b/src/Storages/StorageStripeLog.cpp index e3f477936db..0ecbdb0db10 100644 --- a/src/Storages/StorageStripeLog.cpp +++ b/src/Storages/StorageStripeLog.cpp @@ -320,6 +320,7 @@ void StorageStripeLog::rename(const String & new_path_to_table_data, const Stora { assert(table_path != new_path_to_table_data); { + disk->createDirectories(new_path_to_table_data); disk->moveDirectory(table_path, new_path_to_table_data); table_path = new_path_to_table_data; diff --git a/tests/integration/test_backward_compatibility/test_convert_ordinary.py b/tests/integration/test_backward_compatibility/test_convert_ordinary.py index c509dade0b8..0ed77458050 100644 --- a/tests/integration/test_backward_compatibility/test_convert_ordinary.py +++ b/tests/integration/test_backward_compatibility/test_convert_ordinary.py @@ -7,6 +7,7 @@ node = cluster.add_instance( image="yandex/clickhouse-server", tag="19.17.8.54", stay_alive=True, + with_zookeeper=True, with_installed_binary=True, ) @@ -25,7 +26,7 @@ def q(query): return node.query(query, settings={"log_queries": 1}) -def test_convert_system_db_to_atomic(start_cluster): +def check_convert_system_db_to_atomic(): q( "CREATE TABLE t(date Date, id UInt32) ENGINE = MergeTree PARTITION BY toYYYYMM(date) ORDER BY id" ) @@ -75,3 +76,151 @@ def test_convert_system_db_to_atomic(start_cluster): "1\n" == errors_count and "1\n" == node.count_in_log("Can't receive Netlink response") ) + + +def create_some_tables(db): + node.query("CREATE TABLE {}.t1 (n int) ENGINE=Memory".format(db)) + node.query( + "CREATE TABLE {}.mt1 (n int) ENGINE=MergeTree order by n".format(db), + ) + node.query( + "CREATE TABLE {}.mt2 (n int) ENGINE=MergeTree order by n".format(db), + ) + node.query( + "CREATE TABLE {}.rmt1 (n int, m int) ENGINE=ReplicatedMergeTree('/test/rmt1/{}', '1') order by n".format( + db, db + ), + ) + node.query( + "CREATE TABLE {}.rmt2 (n int, m int) ENGINE=ReplicatedMergeTree('/test/{}/rmt2', '1') order by n".format( + db, db + ), + ) + node.exec_in_container( + [ + "bash", + "-c", + f"sed --follow-symlinks -i 's|/test/{db}/rmt2|/test/{{database}}/{{table}}|' /var/lib/clickhouse/metadata/{db}/rmt2.sql", + ] + ) + node.query( + "CREATE MATERIALIZED VIEW {}.mv1 (n int) ENGINE=ReplicatedMergeTree('/test/{}/mv1/', '1') order by n AS SELECT n FROM {}.rmt1".format( + db, db, db + ), + ) + node.query( + "CREATE MATERIALIZED VIEW {}.mv2 (n int) ENGINE=MergeTree order by n AS SELECT n FROM {}.rmt2".format( + db, db + ), + ) + node.query( + "CREATE DICTIONARY {}.d1 (n int DEFAULT 0, m int DEFAULT 1) PRIMARY KEY n " + "SOURCE(CLICKHOUSE(HOST 'localhost' PORT 9000 USER 'default' TABLE 'rmt1' PASSWORD '' DB '{}')) " + "LIFETIME(MIN 1 MAX 10) LAYOUT(FLAT())".format(db, db) + ) + node.query( + "CREATE DICTIONARY {}.d2 (n int DEFAULT 0, m int DEFAULT 1) PRIMARY KEY n " + "SOURCE(CLICKHOUSE(HOST 'localhost' PORT 9000 USER 'default' TABLE 'rmt2' PASSWORD '' DB '{}')) " + "LIFETIME(MIN 1 MAX 10) LAYOUT(FLAT())".format(db, db) + ) + node.query( + "CREATE TABLE {}.merge (n int) ENGINE=Merge('{}', '(mt)|(mv)')".format(db, db) + ) + + +def check_convert_all_dbs_to_atomic(): + node.query( + "CREATE DATABASE ordinary ENGINE=Ordinary", + settings={"allow_deprecated_database_ordinary": 1}, + ) + node.query( + "CREATE DATABASE other ENGINE=Ordinary", + settings={"allow_deprecated_database_ordinary": 1}, + ) + node.query( + "CREATE DATABASE `.o r d i n a r y.` ENGINE=Ordinary", + settings={"allow_deprecated_database_ordinary": 1}, + ) + node.query("CREATE DATABASE atomic ENGINE=Atomic") + node.query("CREATE DATABASE mem ENGINE=Memory") + node.query("CREATE DATABASE lazy ENGINE=Lazy(1)") + + tables_with_data = ["mt1", "mt2", "rmt1", "rmt2", "mv1", "mv2"] + + for db in ["ordinary", "other", "atomic"]: + create_some_tables(db) + for table in tables_with_data: + node.query("INSERT INTO {}.{} (n) VALUES ({})".format(db, table, len(db))) + + node.query( + "CREATE TABLE `.o r d i n a r y.`.`t. a. b. l. e.` (n int) ENGINE=MergeTree ORDER BY n" + ) + node.query("CREATE TABLE lazy.table (n int) ENGINE=Log") + + # Introduce some cross dependencies + node.query( + "CREATE TABLE ordinary.l (n DEFAULT dictGet('other.d1', 'm', toUInt64(3))) ENGINE=Log" + ) + node.query( + "CREATE TABLE other.l (n DEFAULT dictGet('ordinary.d1', 'm', toUInt64(3))) ENGINE=StripeLog" + ) + + node.query( + "CREATE TABLE atomic.l (n DEFAULT dictGet('ordinary.d1', 'm', toUInt64(3))) ENGINE=TinyLog" + ) + + tables_without_data = ["t1", "d1", "d2", "merge", "l"] + + # 6 tables + 2 inner tables of MVs, each contains 2 rows + for db in ["ordinary", "other"]: + assert "12\t{}\n".format(12 * len(db)) == node.query( + "SELECT count(), sum(n) FROM {}.merge".format(db) + ) + + # 6 tables, MVs contain 2 rows (inner tables does not match regexp) + assert "8\t{}\n".format(8 * len("atomic")) == node.query( + "SELECT count(), sum(n) FROM atomic.merge".format(db) + ) + + node.exec_in_container( + ["bash", "-c", f"touch /var/lib/clickhouse/flags/convert_ordinary_to_atomic"] + ) + node.restart_clickhouse() + + assert ( + ".o r d i n a r y.\natomic\ndefault\nordinary\nother\nsystem\n" + == node.query( + "SELECT name FROM system.databases WHERE engine='Atomic' ORDER BY name" + ) + ) + assert "Lazy\nMemory\n" == node.query( + "SELECT engine FROM system.databases WHERE name IN ('mem', 'lazy') ORDER BY name" + ) + assert "t. a. b. l. e.\n" == node.query("SHOW TABLES FROM `.o r d i n a r y.`") + assert "table\n" == node.query("SHOW TABLES FROM lazy") + + for db in ["ordinary", "other", "atomic"]: + assert "\n".join( + sorted(tables_with_data + tables_without_data) + [""] + ) == node.query("SHOW TABLES FROM {} NOT LIKE '%inner%'".format(db)) + + for db in ["ordinary", "other"]: + assert "8\t{}\n".format(8 * len(db)) == node.query( + "SELECT count(), sum(n) FROM {}.merge".format(db) + ) + + for db in ["ordinary", "other", "atomic"]: + for table in tables_with_data: + node.query( + "INSERT INTO {}.{} (n) VALUES ({})".format(db, table, len(db) * 3) + ) + + for db in ["ordinary", "other", "atomic"]: + assert "16\t{}\n".format(16 * len(db) * 2) == node.query( + "SELECT count(), sum(n) FROM {}.merge".format(db) + ) + + +def test_convert_ordinary_to_atomic(start_cluster): + check_convert_system_db_to_atomic() + check_convert_all_dbs_to_atomic() From 8278da6475e752ce4c5d3c24e38f780354f3b323 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Fri, 5 Aug 2022 21:29:57 +0000 Subject: [PATCH 359/672] Fix: read row counts before move columns out of chunk --- .../Transforms/DistinctTransform.cpp | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Processors/Transforms/DistinctTransform.cpp b/src/Processors/Transforms/DistinctTransform.cpp index a5cd179ec6a..4d78adb6e22 100644 --- a/src/Processors/Transforms/DistinctTransform.cpp +++ b/src/Processors/Transforms/DistinctTransform.cpp @@ -11,7 +11,7 @@ namespace ErrorCodes DistinctTransform::DistinctTransform( const Block & header_, const SizeLimits & set_size_limits_, - UInt64 limit_hint_, + const UInt64 limit_hint_, const Names & columns_) : ISimpleTransform(header_, header_, true) , limit_hint(limit_hint_) @@ -50,9 +50,13 @@ void DistinctTransform::buildFilter( void DistinctTransform::transform(Chunk & chunk) { + if (unlikely(!chunk.hasRows())) + return; + /// Convert to full column, because SetVariant for sparse column is not implemented. convertToFullIfSparse(chunk); + const auto num_rows = chunk.getNumRows(); auto columns = chunk.detachColumns(); /// Special case, - only const columns, return single row @@ -66,13 +70,6 @@ void DistinctTransform::transform(Chunk & chunk) return; } - /// Stop reading if we already reach the limit. - if (limit_hint && data.getTotalRowCount() >= limit_hint) - { - stopReading(); - return; - } - ColumnRawPtrs column_ptrs; column_ptrs.reserve(key_columns_pos.size()); for (auto pos : key_columns_pos) @@ -82,7 +79,6 @@ void DistinctTransform::transform(Chunk & chunk) data.init(SetVariants::chooseMethod(column_ptrs, key_sizes)); const auto old_set_size = data.getTotalRowCount(); - const auto num_rows = chunk.getNumRows(); IColumn::Filter filter(num_rows); switch (data.type) @@ -108,6 +104,13 @@ void DistinctTransform::transform(Chunk & chunk) column = column->filter(filter, -1); chunk.setColumns(std::move(columns), data.getTotalRowCount() - old_set_size); + + /// Stop reading if we already reach the limit + if (limit_hint && data.getTotalRowCount() >= limit_hint) + { + stopReading(); + return; + } } } From b1f45fa787e522a639bb5e4b1c605d627c1be7d1 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Fri, 5 Aug 2022 21:48:40 -0400 Subject: [PATCH 360/672] Don't create self-extracting clickhouse for split build --- programs/CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index 31943ef7dae..7fd1d6baa68 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -18,7 +18,12 @@ option (ENABLE_CLICKHOUSE_SERVER "Server mode (main mode)" ${ENABLE_CLICKHOUSE_A option (ENABLE_CLICKHOUSE_CLIENT "Client mode (interactive tui/shell that connects to the server)" ${ENABLE_CLICKHOUSE_ALL}) -option (ENABLE_CLICKHOUSE_SELF_EXTRACTING "Self-extracting executable" ON) +if (SPLIT_SHARED_LIBRARIES) + # Don't create self-extracting clickhouse for split build + option (ENABLE_CLICKHOUSE_SELF_EXTRACTING "Self-extracting executable" OFF) +else () + option (ENABLE_CLICKHOUSE_SELF_EXTRACTING "Self-extracting executable" ON) +endif () # https://clickhouse.com/docs/en/operations/utilities/clickhouse-local/ option (ENABLE_CLICKHOUSE_LOCAL "Local files fast processing mode" ${ENABLE_CLICKHOUSE_ALL}) From 3772415588905694c13f9a020a47e08dca0e3753 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 6 Aug 2022 12:32:12 +0300 Subject: [PATCH 361/672] tests/stress: add dmesg output (to see OOM details) max_server_memory_usage already set to 75%, so OOM should not happens, the reason is that because RSS does not match with memory tracker statistics: 2022.08.05 12:36:57.869896 [ 82524 ] {} AsynchronousMetrics: MemoryTracking: was 64.69 GiB, peak 65.26 GiB, will set to 62.80 GiB (RSS), difference: -1.89 GiB ... 2022.08.05 12:37:00.213440 [ 82334 ] {} void DB::MergeTreeBackgroundExecutor::routine(DB::TaskRuntimeDataPtr) [Queue = DB::MergeMutateRuntimeQueue]: Code: 241. DB::Exception: Memory limit (total) exceeded: would use 64.68 GiB (attempt to allocate chunk of 1298794 bytes), maximum: 51.44 GiB. OvercommitTracker decision: Memory overcommit isn't used. Waiting time or orvercommit denominator are set to zero.. (MEMORY_LIMIT_EXCEEDED), Stack trace (when copying this message, always include the lines below): Signed-off-by: Azat Khuzhin --- docker/test/stress/run.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index 577fb0a659a..6c32e9a73ea 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -456,3 +456,5 @@ for core in core.*; do pigz $core mv $core.gz /test_output/ done + +dmesg -T > /test_output/dmesg.log From 2d7de7f683abed08412a333447ca99c2372f9742 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 5 Aug 2022 14:42:40 +0300 Subject: [PATCH 362/672] Remove dictionaries from prometheus metrics on DETACH/DROP Fixes: #23436 (cc @kitaisreal) Introduced-in: #9622 (cc @YiuRULE) Signed-off-by: Azat Khuzhin --- src/Interpreters/ExternalLoader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Interpreters/ExternalLoader.cpp b/src/Interpreters/ExternalLoader.cpp index 1dcf48da130..704dff325b7 100644 --- a/src/Interpreters/ExternalLoader.cpp +++ b/src/Interpreters/ExternalLoader.cpp @@ -1299,6 +1299,7 @@ scope_guard ExternalLoader::addConfigRepository(std::unique_ptrremoveConfigRepository(ptr); + CurrentStatusInfo::unset(CurrentStatusInfo::DictionaryStatus, name); reloadConfig(name); }; } From 479ea9e6a6de10be44b9ddb8765134e836fdec4c Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 5 Aug 2022 15:06:34 +0300 Subject: [PATCH 363/672] tests: enable prometheus exporter Signed-off-by: Azat Khuzhin --- tests/config/config.d/prometheus.xml | 6 ++++++ tests/config/install.sh | 1 + 2 files changed, 7 insertions(+) create mode 100644 tests/config/config.d/prometheus.xml diff --git a/tests/config/config.d/prometheus.xml b/tests/config/config.d/prometheus.xml new file mode 100644 index 00000000000..7f8dbd1601f --- /dev/null +++ b/tests/config/config.d/prometheus.xml @@ -0,0 +1,6 @@ + + + /metrics + 9988 + + diff --git a/tests/config/install.sh b/tests/config/install.sh index 478601620e1..af492bb18b4 100755 --- a/tests/config/install.sh +++ b/tests/config/install.sh @@ -35,6 +35,7 @@ ln -sf $SRC_PATH/config.d/logging_no_rotate.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/merge_tree.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/metadata_cache.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/tcp_with_proxy.xml $DEST_SERVER_PATH/config.d/ +ln -sf $SRC_PATH/config.d/prometheus.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/top_level_domains_lists.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/top_level_domains_path.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/transactions.xml $DEST_SERVER_PATH/config.d/ From 96429e293a429b7eef3499c7dc689d6391aebd57 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 5 Aug 2022 15:20:37 +0300 Subject: [PATCH 364/672] tests: cover ClickHouseStatusInfo_DictionaryStatus in prometheus metrics Signed-off-by: Azat Khuzhin --- ...HouseStatusInfo_DictionaryStatus.reference | 18 +++++++++ ...s_ClickHouseStatusInfo_DictionaryStatus.sh | 37 +++++++++++++++++++ tests/queries/shell_config.sh | 4 ++ 3 files changed, 59 insertions(+) create mode 100644 tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.reference create mode 100755 tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.sh diff --git a/tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.reference b/tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.reference new file mode 100644 index 00000000000..50c91c3fa0c --- /dev/null +++ b/tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.reference @@ -0,0 +1,18 @@ +status before reload +status after reload +NOT_LOADED 0 +LOADED 0 +FAILED 1 +LOADING 0 +FAILED_AND_RELOADING 0 +LOADED_AND_RELOADING 0 +NOT_EXIST 0 +status after reload, table exists +NOT_LOADED 0 +LOADED 1 +FAILED 0 +LOADING 0 +FAILED_AND_RELOADING 0 +LOADED_AND_RELOADING 0 +NOT_EXIST 0 +status after drop diff --git a/tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.sh b/tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.sh new file mode 100755 index 00000000000..43f6d62bd10 --- /dev/null +++ b/tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +function get_dictionary_status() +{ + local name=$1 && shift + $CLICKHOUSE_CURL -sS "$CLICKHOUSE_URL_PROMETHEUS" | { + awk -F'[{}=," ]' -vname="$name" '/ClickHouseStatusInfo_DictionaryStatus{/ && $(NF-3) == name { print $4, $NF }' + } +} + +$CLICKHOUSE_CLIENT -q "CREATE DICTIONARY dict (key Int, value String) PRIMARY KEY key SOURCE(CLICKHOUSE(TABLE data)) LAYOUT(HASHED()) LIFETIME(0)" +uuid="$($CLICKHOUSE_CLIENT -q "SELECT uuid FROM system.dictionaries WHERE database = '$CLICKHOUSE_DATABASE' AND name = 'dict'")" + +echo 'status before reload' +get_dictionary_status "$uuid" + +# source table does not exists +# NOTE: when dictionary does not exist it produce BAD_ARGUMENTS error, so using UNKNOWN_TABLE is safe +$CLICKHOUSE_CLIENT -n -q "SYSTEM RELOAD DICTIONARY dict -- { serverError UNKNOWN_TABLE }" +echo 'status after reload' +get_dictionary_status "$uuid" + +# create source +$CLICKHOUSE_CLIENT -q "CREATE TABLE data (key Int, value String) Engine=Null" +$CLICKHOUSE_CLIENT -q "SYSTEM RELOAD DICTIONARY dict" +echo 'status after reload, table exists' +get_dictionary_status "$uuid" + +# remove dictionary +$CLICKHOUSE_CLIENT -q "DROP DICTIONARY dict" +$CLICKHOUSE_CLIENT -q "DROP TABLE data" +echo 'status after drop' +get_dictionary_status "$uuid" diff --git a/tests/queries/shell_config.sh b/tests/queries/shell_config.sh index ab5d5ddc1b6..963ac384148 100644 --- a/tests/queries/shell_config.sh +++ b/tests/queries/shell_config.sh @@ -66,6 +66,8 @@ export CLICKHOUSE_PORT_TCP_WITH_PROXY=${CLICKHOUSE_PORT_TCP_WITH_PROXY:=$(${CLIC export CLICKHOUSE_PORT_TCP_WITH_PROXY=${CLICKHOUSE_PORT_TCP_WITH_PROXY:="9010"} export CLICKHOUSE_PORT_HTTP=${CLICKHOUSE_PORT_HTTP:=$(${CLICKHOUSE_EXTRACT_CONFIG} --key=http_port 2>/dev/null)} export CLICKHOUSE_PORT_HTTP=${CLICKHOUSE_PORT_HTTP:="8123"} +export CLICKHOUSE_PORT_PROMTHEUS_PORT=${CLICKHOUSE_PORT_PROMTHEUS_PORT:=$(${CLICKHOUSE_EXTRACT_CONFIG} --key=prometheus.port 2>/dev/null)} +export CLICKHOUSE_PORT_PROMTHEUS_PORT=${CLICKHOUSE_PORT_PROMTHEUS_PORT:="9988"} export CLICKHOUSE_PORT_HTTPS=${CLICKHOUSE_PORT_HTTPS:=$(${CLICKHOUSE_EXTRACT_CONFIG} --try --key=https_port 2>/dev/null)} 2>/dev/null export CLICKHOUSE_PORT_HTTPS=${CLICKHOUSE_PORT_HTTPS:="8443"} export CLICKHOUSE_PORT_HTTP_PROTO=${CLICKHOUSE_PORT_HTTP_PROTO:="http"} @@ -98,6 +100,8 @@ then export CLICKHOUSE_URL_HTTPS="${CLICKHOUSE_URL_HTTPS}?${CLICKHOUSE_URL_PARAMS}" fi +export CLICKHOUSE_URL_PROMETHEUS=${CLICKHOUSE_URL_PROMETHEUS:="${CLICKHOUSE_PORT_HTTP_PROTO}://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_PROMTHEUS_PORT}/metrics"} + export CLICKHOUSE_PORT_INTERSERVER=${CLICKHOUSE_PORT_INTERSERVER:=$(${CLICKHOUSE_EXTRACT_CONFIG} --try --key=interserver_http_port 2>/dev/null)} 2>/dev/null export CLICKHOUSE_PORT_INTERSERVER=${CLICKHOUSE_PORT_INTERSERVER:="9009"} export CLICKHOUSE_URL_INTERSERVER=${CLICKHOUSE_URL_INTERSERVER:="${CLICKHOUSE_PORT_HTTP_PROTO}://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_INTERSERVER}/"} From 791377e4dc0247b9dec39281779bde927b7e8424 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 6 Aug 2022 13:00:53 +0300 Subject: [PATCH 365/672] tests: avoid prometheus.port overlap for replicated database Signed-off-by: Azat Khuzhin --- docker/test/stateless/run.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/test/stateless/run.sh b/docker/test/stateless/run.sh index 075f588cae3..23210e6a7c9 100755 --- a/docker/test/stateless/run.sh +++ b/docker/test/stateless/run.sh @@ -58,6 +58,7 @@ if [[ -n "$USE_DATABASE_REPLICATED" ]] && [[ "$USE_DATABASE_REPLICATED" -eq 1 ]] --tcp_port 19000 --tcp_port_secure 19440 --http_port 18123 --https_port 18443 --interserver_http_port 19009 --tcp_with_proxy_port 19010 \ --mysql_port 19004 --postgresql_port 19005 \ --keeper_server.tcp_port 19181 --keeper_server.server_id 2 \ + --prometheus.port 19988 \ --macros.replica r2 # It doesn't work :( mkdir -p /var/run/clickhouse-server2 @@ -69,6 +70,7 @@ if [[ -n "$USE_DATABASE_REPLICATED" ]] && [[ "$USE_DATABASE_REPLICATED" -eq 1 ]] --tcp_port 29000 --tcp_port_secure 29440 --http_port 28123 --https_port 28443 --interserver_http_port 29009 --tcp_with_proxy_port 29010 \ --mysql_port 29004 --postgresql_port 29005 \ --keeper_server.tcp_port 29181 --keeper_server.server_id 3 \ + --prometheus.port 29988 \ --macros.shard s2 # It doesn't work :( MAX_RUN_TIME=$((MAX_RUN_TIME < 9000 ? MAX_RUN_TIME : 9000)) # min(MAX_RUN_TIME, 2.5 hours) From d0189e8f662e3ea81782d8adff02c0da51cd131c Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 6 Aug 2022 12:51:38 +0300 Subject: [PATCH 366/672] Create metadata directory on CREATE for FileLog engine This will allow to distinguish really corrupted data, since right now if you will CREATE/DETACH/ATTACH such engine you will have the following error [1]: 2022.08.05 20:02:20.726398 [ 696405 ] {} StorageFileLog (file_log): Metadata files of table file_log are lost. [1]: https://s3.amazonaws.com/clickhouse-test-reports/39926/72961328f68b1ec05300d6dc4411a87618a2f46b/stress_test__debug_.html Likely that previously it was not created to avoid creating empty directories, however this should be a problem I guess. Refs: #25969 (@ucasfl) Signed-off-by: Azat Khuzhin --- src/Storages/FileLog/StorageFileLog.cpp | 44 ++++++++++++------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/Storages/FileLog/StorageFileLog.cpp b/src/Storages/FileLog/StorageFileLog.cpp index 323bcdc100d..7848b75deec 100644 --- a/src/Storages/FileLog/StorageFileLog.cpp +++ b/src/Storages/FileLog/StorageFileLog.cpp @@ -37,6 +37,7 @@ namespace ErrorCodes extern const int CANNOT_READ_ALL_DATA; extern const int LOGICAL_ERROR; extern const int TABLE_METADATA_ALREADY_EXISTS; + extern const int DIRECTORY_DOESNT_EXIST; extern const int CANNOT_SELECT; extern const int QUERY_NOT_ALLOWED; } @@ -72,6 +73,25 @@ StorageFileLog::StorageFileLog( try { + if (!attach) + { + std::error_code ec; + std::filesystem::create_directories(metadata_base_path, ec); + + if (ec) + { + if (ec == std::make_error_code(std::errc::file_exists)) + { + throw Exception(ErrorCodes::TABLE_METADATA_ALREADY_EXISTS, + "Metadata files already exist by path: {}, remove them manually if it is intended", + metadata_base_path); + } + else + throw Exception(ErrorCodes::DIRECTORY_DOESNT_EXIST, + "Could not create directory {}, reason: {}", metadata_base_path, ec.message()); + } + } + loadMetaFiles(attach); loadFiles(); @@ -117,19 +137,6 @@ void StorageFileLog::loadMetaFiles(bool attach) /// Load all meta info to file_infos; deserialize(); } - /// Create table, just create meta data directory - else - { - if (std::filesystem::exists(metadata_base_path)) - { - throw Exception( - ErrorCodes::TABLE_METADATA_ALREADY_EXISTS, - "Metadata files already exist by path: {}, remove them manually if it is intended", - metadata_base_path); - } - /// We do not create the metadata_base_path directory at creation time, create it at the moment of serializing - /// meta files, such that can avoid unnecessarily create this directory if create table failed. - } } void StorageFileLog::loadFiles() @@ -218,10 +225,6 @@ void StorageFileLog::loadFiles() void StorageFileLog::serialize() const { - if (!std::filesystem::exists(metadata_base_path)) - { - std::filesystem::create_directories(metadata_base_path); - } for (const auto & [inode, meta] : file_infos.meta_by_inode) { auto full_name = getFullMetaPath(meta.file_name); @@ -242,10 +245,6 @@ void StorageFileLog::serialize() const void StorageFileLog::serialize(UInt64 inode, const FileMeta & file_meta) const { - if (!std::filesystem::exists(metadata_base_path)) - { - std::filesystem::create_directories(metadata_base_path); - } auto full_name = getFullMetaPath(file_meta.file_name); if (!std::filesystem::exists(full_name)) { @@ -379,8 +378,7 @@ void StorageFileLog::drop() { try { - if (std::filesystem::exists(metadata_base_path)) - std::filesystem::remove_all(metadata_base_path); + std::filesystem::remove_all(metadata_base_path); } catch (...) { From 766d816df676da194e9a6d42fd4c4884a37ecfab Mon Sep 17 00:00:00 2001 From: Constantine Peresypkin Date: Wed, 3 Aug 2022 17:00:24 +0200 Subject: [PATCH 367/672] fix incorrect format for functions with settings when support for settings was added in #39681 the formating was not altered to support settings now it's supported --- src/Parsers/ASTFunction.cpp | 11 +++++++++++ .../02377_executable_function_settings.reference | 8 ++++++++ .../02377_executable_function_settings.sql | 5 +++++ 3 files changed, 24 insertions(+) create mode 100644 tests/queries/0_stateless/02377_executable_function_settings.reference create mode 100644 tests/queries/0_stateless/02377_executable_function_settings.sql diff --git a/src/Parsers/ASTFunction.cpp b/src/Parsers/ASTFunction.cpp index 39d89f56e91..ac340eef987 100644 --- a/src/Parsers/ASTFunction.cpp +++ b/src/Parsers/ASTFunction.cpp @@ -14,6 +14,7 @@ #include #include #include +#include using namespace std::literals; @@ -549,6 +550,8 @@ void ASTFunction::formatImplWithoutAlias(const FormatSettings & settings, Format { if (i != 0) settings.ostr << (settings.hilite ? hilite_operator : "") << func[1] << (settings.hilite ? hilite_none : ""); + if (arguments->children[i]->as()) + settings.ostr << "SETTINGS "; arguments->children[i]->formatImpl(settings, state, nested_need_parens); } if (frame.need_parens) @@ -565,6 +568,8 @@ void ASTFunction::formatImplWithoutAlias(const FormatSettings & settings, Format { if (i != 0) settings.ostr << ", "; + if (arguments->children[i]->as()) + settings.ostr << "SETTINGS "; arguments->children[i]->formatImpl(settings, state, nested_dont_need_parens); } settings.ostr << (settings.hilite ? hilite_operator : "") << ']' << (settings.hilite ? hilite_none : ""); @@ -578,6 +583,8 @@ void ASTFunction::formatImplWithoutAlias(const FormatSettings & settings, Format { if (i != 0) settings.ostr << ", "; + if (arguments->children[i]->as()) + settings.ostr << "SETTINGS "; arguments->children[i]->formatImpl(settings, state, nested_dont_need_parens); } settings.ostr << (settings.hilite ? hilite_operator : "") << ')' << (settings.hilite ? hilite_none : ""); @@ -591,6 +598,8 @@ void ASTFunction::formatImplWithoutAlias(const FormatSettings & settings, Format { if (i != 0) settings.ostr << ", "; + if (arguments->children[i]->as()) + settings.ostr << "SETTINGS "; arguments->children[i]->formatImpl(settings, state, nested_dont_need_parens); } settings.ostr << (settings.hilite ? hilite_operator : "") << ')' << (settings.hilite ? hilite_none : ""); @@ -625,6 +634,8 @@ void ASTFunction::formatImplWithoutAlias(const FormatSettings & settings, Format { if (i != 0) settings.ostr << ", "; + if (arguments->children[i]->as()) + settings.ostr << "SETTINGS "; bool special_hilite = false; if (i == 1 && special_hilite_regexp) diff --git a/tests/queries/0_stateless/02377_executable_function_settings.reference b/tests/queries/0_stateless/02377_executable_function_settings.reference new file mode 100644 index 00000000000..5eef5774e14 --- /dev/null +++ b/tests/queries/0_stateless/02377_executable_function_settings.reference @@ -0,0 +1,8 @@ +SELECT data +FROM executable(\'\', \'JSON\', \'data String\') +-------------------- +SELECT data +FROM executable(\'\', \'JSON\', \'data String\', SETTINGS max_command_execution_time = 100) +-------------------- +SELECT data +FROM executable(\'\', \'JSON\', \'data String\', SETTINGS max_command_execution_time = 100, command_read_timeout = 1) diff --git a/tests/queries/0_stateless/02377_executable_function_settings.sql b/tests/queries/0_stateless/02377_executable_function_settings.sql new file mode 100644 index 00000000000..be60ad2d89b --- /dev/null +++ b/tests/queries/0_stateless/02377_executable_function_settings.sql @@ -0,0 +1,5 @@ +EXPLAIN SYNTAX SELECT * from executable('', 'JSON', 'data String'); +SELECT '--------------------'; +EXPLAIN SYNTAX SELECT * from executable('', 'JSON', 'data String', SETTINGS max_command_execution_time=100); +SELECT '--------------------'; +EXPLAIN SYNTAX SELECT * from executable('', 'JSON', 'data String', SETTINGS max_command_execution_time=100, command_read_timeout=1); From 384a7ae901a0dac47c911b254e96bfd451f733e4 Mon Sep 17 00:00:00 2001 From: flynn Date: Sat, 6 Aug 2022 17:29:33 +0000 Subject: [PATCH 368/672] Fix read of StorageFile with virtual columns --- src/Storages/StorageFile.cpp | 88 ++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/src/Storages/StorageFile.cpp b/src/Storages/StorageFile.cpp index d138104018a..ba8ec57edb8 100644 --- a/src/Storages/StorageFile.cpp +++ b/src/Storages/StorageFile.cpp @@ -444,36 +444,24 @@ public: using FilesInfoPtr = std::shared_ptr; - static Block getHeader(const StorageMetadataPtr & metadata_snapshot, bool need_path_column, bool need_file_column) + static Block getBlockForSource(const Block & block_for_format, const FilesInfoPtr & files_info) { - auto header = metadata_snapshot->getSampleBlock(); - - /// Note: AddingDefaultsTransform doesn't change header. - - if (need_path_column) - header.insert( + auto res = block_for_format; + if (files_info->need_path_column) + { + res.insert( {DataTypeLowCardinality{std::make_shared()}.createColumn(), std::make_shared(std::make_shared()), "_path"}); - if (need_file_column) - header.insert( + } + if (files_info->need_file_column) + { + res.insert( {DataTypeLowCardinality{std::make_shared()}.createColumn(), std::make_shared(std::make_shared()), "_file"}); - - return header; - } - - static Block getBlockForSource( - const StorageFilePtr & storage, - const StorageSnapshotPtr & storage_snapshot, - const ColumnsDescription & columns_description, - const FilesInfoPtr & files_info) - { - if (storage->supportsSubsetOfColumns()) - return storage_snapshot->getSampleBlockForColumns(columns_description.getNamesOfPhysical()); - else - return getHeader(storage_snapshot->metadata, files_info->need_path_column, files_info->need_file_column); + } + return res; } StorageFileSource( @@ -483,13 +471,15 @@ public: UInt64 max_block_size_, FilesInfoPtr files_info_, ColumnsDescription columns_description_, + const Block & block_for_format_, std::unique_ptr read_buf_) - : ISource(getBlockForSource(storage_, storage_snapshot_, columns_description_, files_info_)) + : ISource(getBlockForSource(block_for_format_, files_info_)) , storage(std::move(storage_)) , storage_snapshot(storage_snapshot_) , files_info(std::move(files_info_)) , read_buf(std::move(read_buf_)) , columns_description(std::move(columns_description_)) + , block_for_format(block_for_format_) , context(context_) , max_block_size(max_block_size_) { @@ -533,15 +523,8 @@ public: if (!read_buf) read_buf = createReadBuffer(current_path, storage->use_table_fd, storage->getName(), storage->table_fd, storage->compression_method, context); - auto get_block_for_format = [&]() -> Block - { - if (storage->supportsSubsetOfColumns()) - return storage_snapshot->getSampleBlockForColumns(columns_description.getNamesOfPhysical()); - return storage_snapshot->metadata->getSampleBlock(); - }; - - auto format = context->getInputFormat( - storage->format_name, *read_buf, get_block_for_format(), max_block_size, storage->format_settings); + auto format + = context->getInputFormat(storage->format_name, *read_buf, block_for_format, max_block_size, storage->format_settings); QueryPipelineBuilder builder; builder.init(Pipe(format)); @@ -627,6 +610,7 @@ private: std::unique_ptr reader; ColumnsDescription columns_description; + Block block_for_format; ContextPtr context; /// TODO Untangle potential issues with context lifetime. UInt64 max_block_size; @@ -693,13 +677,30 @@ Pipe StorageFile::read( for (size_t i = 0; i < num_streams; ++i) { - const auto get_columns_for_format = [&]() -> ColumnsDescription + ColumnsDescription columns_description; + Block block_for_format; + if (supportsSubsetOfColumns()) { - if (supportsSubsetOfColumns()) - return storage_snapshot->getDescriptionForColumns(column_names); - else - return storage_snapshot->metadata->getColumns(); - }; + auto fetch_columns = column_names; + const auto & virtuals = getVirtuals(); + std::erase_if( + fetch_columns, + [&](const String & col) + { + return std::any_of( + virtuals.begin(), virtuals.end(), [&](const NameAndTypePair & virtual_col) { return col == virtual_col.name; }); + }); + + if (fetch_columns.empty()) + fetch_columns.push_back(ExpressionActions::getSmallestColumn(storage_snapshot->metadata->getColumns().getAllPhysical())); + columns_description = storage_snapshot->getDescriptionForColumns(fetch_columns); + block_for_format = storage_snapshot->getSampleBlockForColumns(columns_description.getNamesOfPhysical()); + } + else + { + columns_description = storage_snapshot->metadata->getColumns(); + block_for_format = storage_snapshot->getSampleBlockForColumns(columns_description.getNamesOfPhysical()); + } /// In case of reading from fd we have to check whether we have already created /// the read buffer from it in Storage constructor (for schema inference) or not. @@ -710,7 +711,14 @@ Pipe StorageFile::read( read_buffer = std::move(peekable_read_buffer_from_fd); pipes.emplace_back(std::make_shared( - this_ptr, storage_snapshot, context, max_block_size, files_info, get_columns_for_format(), std::move(read_buffer))); + this_ptr, + storage_snapshot, + context, + max_block_size, + files_info, + columns_description, + block_for_format, + std::move(read_buffer))); } return Pipe::unitePipes(std::move(pipes)); From 8b08d56473ffb1249d370cc50511cd28ae5de8fa Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 6 Aug 2022 13:25:42 +0300 Subject: [PATCH 369/672] tests: fix 02352_rwlock flakiness It is possible that the next after INSERT query will be executed only when INSERT finished, it is unlikely, but it happened few times on CI [1]: 2022.08.05 19:32:13.964046 [ 1565 ] {insert-qjnxxoaebk} executeQuery: (from [::1]:57512) (comment: 02352_rwlock.sh) INSERT INTO test_7ybr6x_ordinary.data_02352 SELECT sleepEachRow(1) FROM numbers(20) GROUP BY number (stage: Complete) ... 2022.08.05 19:32:14.945738 [ 1594 ] {245b7446-58da-452b-9669-55dad643bc34} executeQuery: (from [::1]:57516) (comment: 02352_rwlock.sh) select count() from system.processes where query_id = 'insert-qjnxxoaebk' (stage: Complete) ... 2022.08.05 19:32:33.997257 [ 1565 ] {insert-qjnxxoaebk} TCPHandler: Processed in 20.056755494 sec. ... 2022.08.05 19:32:34.614789 [ 908 ] {3be7ade3-6461-4906-8767-7cc11f83a0b8} executeQuery: (from [::1]:57522) (comment: 02352_rwlock.sh) select count() from system.processes where query_id = 'drop-zoyhjyewja' (stage: Complete) ... 2022.08.05 19:32:34.674039 [ 1175 ] {drop-zoyhjyewja} executeQuery: (from [::1]:57528) (comment: 02352_rwlock.sh) DROP TABLE test_7ybr6x_ordinary.data_02352 SYNC (stage: Complete) ... [1]: https://s3.amazonaws.com/clickhouse-test-reports/39826/8e545958495937c6c6e61951601a6470119d0813/stateless_tests__debug__[1/3].html And to avoid flakiness in this case, add a retry loop and no-parallel tag. Refs: #38864 Signed-off-by: Azat Khuzhin --- tests/queries/0_stateless/02352_rwlock.sh | 78 ++++++++++++++++++----- 1 file changed, 61 insertions(+), 17 deletions(-) diff --git a/tests/queries/0_stateless/02352_rwlock.sh b/tests/queries/0_stateless/02352_rwlock.sh index 8054be81781..56b7ab27410 100755 --- a/tests/queries/0_stateless/02352_rwlock.sh +++ b/tests/queries/0_stateless/02352_rwlock.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# Tags: no-parallel +# Tag no-parallel -- to avoid running it in parallel, this will avoid possible issues due to high pressure # Test that ensures that WRITE lock failure notifies READ. # In other words to ensure that after WRITE lock failure (DROP), @@ -13,31 +15,73 @@ function wait_query_by_id_started() local query_id=$1 && shift # wait for query to be started while [ "$($CLICKHOUSE_CLIENT "$@" -q "select count() from system.processes where query_id = '$query_id'")" -ne 1 ]; do - sleep 0.1 + if [ "$( + $CLICKHOUSE_CLIENT -nm -q " + system flush logs; + + select count() from system.query_log + where + event_date >= yesterday() and + current_database = '$CLICKHOUSE_DATABASE' and + type = 'QueryFinish' and + query_id = '$query_id' + " + )" -eq 1 ]; then + return 1 + else + sleep 0.1 + fi done + + return 0 } # to avoid removal via separate thread $CLICKHOUSE_CLIENT -q "CREATE DATABASE ${CLICKHOUSE_DATABASE}_ordinary Engine=Ordinary" --allow_deprecated_database_ordinary=1 -$CLICKHOUSE_CLIENT -q "CREATE TABLE ${CLICKHOUSE_DATABASE}_ordinary.data_02352 (key Int) Engine=Null()" -query_id="insert-$(random_str 10)" -# 20 seconds sleep -$CLICKHOUSE_CLIENT --query_id "$query_id" -q "INSERT INTO ${CLICKHOUSE_DATABASE}_ordinary.data_02352 SELECT sleepEachRow(1) FROM numbers(20) GROUP BY number" & -wait_query_by_id_started "$query_id" +# It is possible that the subsequent after INSERT query will be processed +# only after INSERT finished, it is unlikely, but it happens few times in +# debug build on CI, so if this will happen, then DROP query will be +# finished instantly, and to avoid flakiness we will retry in this case +while :; do + $CLICKHOUSE_CLIENT -nm -q " + DROP TABLE IF EXISTS ${CLICKHOUSE_DATABASE}_ordinary.data_02352; + CREATE TABLE ${CLICKHOUSE_DATABASE}_ordinary.data_02352 (key Int) Engine=Null(); + " -query_id="drop-$(random_str 10)" -# 10 second wait -$CLICKHOUSE_CLIENT --query_id "$query_id" -q "DROP TABLE ${CLICKHOUSE_DATABASE}_ordinary.data_02352 SYNC" --lock_acquire_timeout 10 > >(grep -m1 -o 'WRITE locking attempt on ".*" has timed out') 2>&1 & -wait_query_by_id_started "$query_id" + insert_query_id="insert-$(random_str 10)" + # 20 seconds sleep + $CLICKHOUSE_CLIENT --query_id "$insert_query_id" -q "INSERT INTO ${CLICKHOUSE_DATABASE}_ordinary.data_02352 SELECT sleepEachRow(1) FROM numbers(20) GROUP BY number" & + if ! wait_query_by_id_started "$insert_query_id"; then + wait + continue + fi -# NOTE: we need to run SELECT after DROP and -# if the bug is there, then the query will wait 20 seconds (INSERT), instead of 10 (DROP) and will fail -# -# 11 seconds wait (DROP + 1 second lag) -$CLICKHOUSE_CLIENT -q "SELECT * FROM ${CLICKHOUSE_DATABASE}_ordinary.data_02352" --lock_acquire_timeout 11 + drop_query_id="drop-$(random_str 10)" + # 10 second wait + $CLICKHOUSE_CLIENT --query_id "$drop_query_id" -q "DROP TABLE ${CLICKHOUSE_DATABASE}_ordinary.data_02352 SYNC" --lock_acquire_timeout 10 > >( + grep -m1 -o 'WRITE locking attempt on ".*" has timed out' + ) 2>&1 & + if ! wait_query_by_id_started "$drop_query_id"; then + wait + continue + fi + # Check INSERT query again, and retry if it does not exist. + if ! wait_query_by_id_started "$insert_query_id"; then + wait + continue + fi -# wait DROP and INSERT -wait + # NOTE: we need to run SELECT after DROP and + # if the bug is there, then the query will wait 20 seconds (INSERT), instead of 10 (DROP) and will fail + # + # 11 seconds wait (DROP + 1 second lag) + $CLICKHOUSE_CLIENT -q "SELECT * FROM ${CLICKHOUSE_DATABASE}_ordinary.data_02352" --lock_acquire_timeout 11 + + # wait DROP and INSERT + wait + + break +done | uniq $CLICKHOUSE_CLIENT -q "DROP DATABASE ${CLICKHOUSE_DATABASE}_ordinary" From d365aeafb74332764043bb0962d798357d2a1161 Mon Sep 17 00:00:00 2001 From: flynn Date: Sat, 6 Aug 2022 17:41:35 +0000 Subject: [PATCH 370/672] add test --- .../0_stateless/02377_fix_file_virtual_column.reference | 3 +++ .../queries/0_stateless/02377_fix_file_virtual_column.sql | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 tests/queries/0_stateless/02377_fix_file_virtual_column.reference create mode 100644 tests/queries/0_stateless/02377_fix_file_virtual_column.sql diff --git a/tests/queries/0_stateless/02377_fix_file_virtual_column.reference b/tests/queries/0_stateless/02377_fix_file_virtual_column.reference new file mode 100644 index 00000000000..c98e1059325 --- /dev/null +++ b/tests/queries/0_stateless/02377_fix_file_virtual_column.reference @@ -0,0 +1,3 @@ +1 s +2 x +3 y diff --git a/tests/queries/0_stateless/02377_fix_file_virtual_column.sql b/tests/queries/0_stateless/02377_fix_file_virtual_column.sql new file mode 100644 index 00000000000..5d79e7f12cd --- /dev/null +++ b/tests/queries/0_stateless/02377_fix_file_virtual_column.sql @@ -0,0 +1,7 @@ +drop table if exists test_02377; +create table test_02377 (n UInt32, s String) engine=File(CSVWithNames); +insert into test_02377 values(1, 's') (2, 'x') (3, 'y'); +select * from test_02377 order by n; +select *, _path, _file from test_02377 format Null; +select _path, _file from test_02377 format Null; +drop table test_02377; From 22ab9cfed3e12d1dc6a7a9c04d03808646c1187c Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 7 Aug 2022 01:11:57 +0200 Subject: [PATCH 371/672] Remove old code --- docs/tools/website.py | 3 +- website/benchmark/dbms/index.html | 955 ------------------ website/benchmark/hardware/index.html | 110 -- website/benchmark/hardware/queries.js | 179 ---- .../hardware/results/amd_epyc_7402p.json | 158 --- .../hardware/results/amd_epyc_7502p.json | 54 - .../hardware/results/amd_epyc_7551.json | 55 - .../hardware/results/amd_epyc_7642.json | 56 - .../hardware/results/amd_epyc_7662.json | 54 - .../hardware/results/amd_epyc_7702.json | 54 - .../hardware/results/amd_epyc_7702_zfs.json | 54 - .../hardware/results/amd_epyc_7742.json | 54 - .../hardware/results/amd_ryzen_9_3950x.json | 106 -- .../hardware/results/amd_ryzen_9_5950x.json | 54 - .../results/amd_ryzen_threadripper.json | 54 - .../benchmark/hardware/results/asus_a15.json | 54 - .../hardware/results/aws_a1_4xlarge.json | 54 - .../hardware/results/aws_c5a_24xlarge.json | 106 -- .../hardware/results/aws_c5metal_100.json | 54 - .../hardware/results/aws_c5metal_300.json | 54 - .../hardware/results/aws_c6a_metal.json | 54 - .../hardware/results/aws_c6g_16xlarge.json | 54 - .../hardware/results/aws_c6g_2xlarge.json | 54 - .../hardware/results/aws_c6i_32xlarge.json | 54 - .../hardware/results/aws_c6metal.json | 54 - .../hardware/results/aws_c7g_16xlarge.json | 54 - .../hardware/results/aws_i3_8xlarge.json | 54 - .../hardware/results/aws_i3en_24xlarge.json | 54 - .../hardware/results/aws_lightsail_4vcpu.json | 54 - .../hardware/results/aws_m5a_4xlarge.json | 55 - .../hardware/results/aws_m5a_8xlarge.json | 55 - .../hardware/results/aws_m5ad_24xlarge.json | 54 - .../hardware/results/aws_m5d_24xlarge.json | 54 - .../hardware/results/aws_m6g_16xlarge.json | 54 - .../hardware/results/azure_ds3v2.json | 106 -- .../hardware/results/azure_e32s.json | 54 - .../hardware/results/cavium_4core.json | 54 - .../results/core_i5_3210M_lenovo_b580.json | 54 - .../results/core_i5_9600K_asus_z390.json | 54 - .../results/core_i7_11800h_lenovo_p15.json | 54 - .../results/core_i7_6770hq_intel_nuc.json | 54 - .../results/core_i7_8550u_lenovo_x1.json | 54 - .../benchmark/hardware/results/dell_r530.json | 54 - .../benchmark/hardware/results/dell_r740.json | 54 - .../benchmark/hardware/results/dell_xps.json | 56 - .../results/do_storage_optimized.json | 380 ------- .../hardware/results/do_xeon_6140_4.json | 56 - .../hardware/results/efs_vs_ebs.json | 107 -- .../results/equinix_metal_n3_xlarge.json | 54 - .../benchmark/hardware/results/gcp_c2.json | 54 - .../benchmark/hardware/results/gcp_n2.json | 54 - .../benchmark/hardware/results/gcp_n2d.json | 106 -- .../benchmark/hardware/results/gp1_s_16x.json | 54 - .../benchmark/hardware/results/gp1_s_8x.json | 54 - .../hardware/results/hetzner_epyc.json | 106 -- .../hardware/results/hetzner_ex62.json | 54 - .../results/huawei_taishan_2280_3.json | 54 - .../results/huawei_taishan_2280_v2.json | 54 - .../hardware/results/huawei_taishan_920.json | 54 - .../hardware/results/i3_2xlarge.json | 54 - .../hardware/results/ibm_cloud_baremetal.json | 210 ---- .../hardware/results/im4gn_16xlarge.json | 54 - .../hardware/results/im4gn_4xlarge.json | 54 - .../hardware/results/im4gn_8xlarge.json | 54 - .../hardware/results/intel_core_i5_4440.json | 54 - .../results/intel_core_i9_11900kf.json | 54 - .../results/intel_xeon_scaleflux_csd3000.json | 54 - .../results/linode_cloud_amd_epyc_7601.json | 54 - .../hardware/results/macbook_air_m1.json | 54 - .../results/macbook_pro_core_i7_2014.json | 54 - .../results/macbook_pro_core_i7_2018.json | 54 - .../results/macbook_pro_core_i7_2020.json | 54 - .../hardware/results/macbook_pro_m1_2020.json | 54 - .../hardware/results/macbook_pro_m1_2021.json | 54 - .../benchmark/hardware/results/oracle.json | 54 - .../hardware/results/pinebook_pro.json | 54 - .../benchmark/hardware/results/pixel_3a.json | 57 -- .../benchmark/hardware/results/powerpc.json | 54 - .../qemu_aarch64_cascade_lake_80_vcpu.json | 55 - .../hardware/results/raspberry_pi_b.json | 54 - .../benchmark/hardware/results/rock_pi.json | 54 - .../hardware/results/scaleway_epyc.json | 54 - .../results/selectel_cloud_16vcpu.json | 158 --- .../hardware/results/skylake_kvm.json | 54 - .../benchmark/hardware/results/ssdnodes.json | 54 - .../hardware/results/upcloud_8cpu_32gb.json | 54 - .../hardware/results/xeon_2176g.json | 54 - .../hardware/results/xeon_clx_6230r.json | 56 - .../hardware/results/xeon_e5645.json | 54 - .../hardware/results/xeon_e5_1650v3.json | 54 - .../hardware/results/xeon_e5_2640v4.json | 54 - .../hardware/results/xeon_e5_2650_4hdd.json | 54 - .../hardware/results/xeon_e5_2650_8hdd.json | 54 - .../hardware/results/xeon_e5_2650l_v3.json | 54 - .../hardware/results/xeon_gold_6140.json | 56 - .../hardware/results/xeon_gold_6230.json | 56 - .../hardware/results/xeon_gold_6266.json | 56 - .../hardware/results/xeon_silver_4114.json | 54 - .../hardware/results/xeon_sp_gold.json | 56 - .../hardware/results/xeon_x5675.json | 106 -- .../yandex_cloud_broadwell_4_vcpu.json | 55 - .../yandex_cloud_broadwell_8_vcpu.json | 55 - .../yandex_cloud_broadwell_8_vcpu_s3.json | 55 - .../yandex_cloud_cascade_lake_32_vcpu.json | 55 - .../yandex_cloud_cascade_lake_4_vcpu.json | 55 - .../yandex_cloud_cascade_lake_64_vcpu.json | 108 -- .../yandex_cloud_cascade_lake_80_vcpu.json | 55 - .../yandex_managed_clickhouse_s3_3xlarge.json | 107 -- .../backgrounds/bg-card-pattern-blue-1.png | Bin 153 -> 0 bytes .../backgrounds/bg-card-pattern-blue-2.png | Bin 162 -> 0 bytes .../backgrounds/bg-card-pattern-red.png | Bin 554 -> 0 bytes website/images/backgrounds/bg-footer-cta.svg | 1 - website/images/backgrounds/bg-hero-home.svg | 1 - website/images/backgrounds/bg-hero.svg | 1 - website/images/backgrounds/bg-quick-start.svg | 1 - website/images/backgrounds/bg-quotes.svg | 1 - website/images/curl.svg | 1 - website/images/dots.svg | 1 - website/images/flags/en.svg | 1 - website/images/flags/es.svg | 1 - website/images/flags/fa.svg | 1 - website/images/flags/fr.svg | 1 - website/images/flags/ja.svg | 1 - website/images/flags/ru.svg | 1 - website/images/flags/tr.svg | 1 - website/images/flags/zh.svg | 1 - website/images/icons/icon-arrow.svg | 1 - website/images/icons/icon-blog-black.svg | 1 - website/images/icons/icon-facebook-gray.svg | 1 - website/images/icons/icon-facebook.svg | 1 - website/images/icons/icon-github.svg | 1 - website/images/icons/icon-google.svg | 1 - .../images/icons/icon-linkedin-alt-gray.svg | 1 - website/images/icons/icon-linkedin-gray.svg | 1 - website/images/icons/icon-linkedin.png | Bin 3247 -> 0 bytes website/images/icons/icon-menu.svg | 1 - website/images/icons/icon-performance.svg | 1 - website/images/icons/icon-reliability.svg | 1 - website/images/icons/icon-scalability.svg | 1 - website/images/icons/icon-security.svg | 1 - website/images/icons/icon-slack-black.svg | 1 - website/images/icons/icon-slack.svg | 1 - website/images/icons/icon-stack-overflow.svg | 1 - website/images/icons/icon-telegram.svg | 1 - website/images/icons/icon-twitter-gray.svg | 1 - website/images/icons/icon-twitter.svg | 1 - website/images/icons/icon-youtube-black.svg | 1 - website/images/icons/icon-youtube.svg | 1 - website/images/index/blog.svg | 1 - website/images/index/flash.svg | 1 - website/images/index/github.svg | 1 - website/images/index/google-groups.svg | 1 - website/images/index/hackernews.svg | 1 - website/images/index/hardware-efficient.svg | 1 - website/images/index/heart.svg | 1 - website/images/index/intro.svg | 1 - website/images/index/linearly-scalable.svg | 1 - website/images/index/meetup.svg | 1 - website/images/index/reddit.svg | 1 - website/images/index/safe.svg | 1 - website/images/index/scale.svg | 1 - website/images/index/shield.svg | 1 - website/images/index/slack.svg | 1 - website/images/index/stack-overflow.svg | 1 - website/images/index/telegram.svg | 1 - website/images/index/twitter.svg | 1 - website/images/index/youtube.svg | 1 - website/images/logos/logo-almaz-capital.svg | 1 - .../images/logos/logo-altimeter-capital.png | Bin 3600 -> 0 bytes .../images/logos/logo-benchmark-capital.png | Bin 3154 -> 0 bytes website/images/logos/logo-cloudflare.svg | 1 - website/images/logos/logo-coatue.png | Bin 6128 -> 0 bytes website/images/logos/logo-deutsche-bank.png | Bin 2792 -> 0 bytes website/images/logos/logo-ebay.png | Bin 2933 -> 0 bytes website/images/logos/logo-firstmark.svg | 1 - website/images/logos/logo-index-ventures.png | Bin 5475 -> 0 bytes .../images/logos/logo-leadedge-capital.png | Bin 3029 -> 0 bytes website/images/logos/logo-lightspeed.png | Bin 3751 -> 0 bytes website/images/logos/logo-redpoint.png | Bin 6109 -> 0 bytes website/images/logos/logo-spotify.png | Bin 3965 -> 0 bytes website/images/logos/logo-uber.png | Bin 1841 -> 0 bytes website/images/logos/logo-yandex.png | Bin 7266 -> 0 bytes website/images/mkdocs/copy.svg | 1 - website/images/mkdocs/edit.svg | 1 - website/images/mkdocs/multi.svg | 1 - website/images/mkdocs/pdf.svg | 1 - website/images/mkdocs/search.svg | 1 - website/images/mkdocs/single.svg | 1 - website/images/photos/aaron-katz.jpg | Bin 44550 -> 0 bytes website/images/photos/alexander-sapin.jpg | Bin 91455 -> 0 bytes website/images/photos/alexander-tokmakov.jpg | Bin 103648 -> 0 bytes website/images/photos/alexey-milovidov.jpg | Bin 46450 -> 0 bytes website/images/photos/andy-james.jpg | Bin 26569 -> 0 bytes website/images/photos/anne-carlhoff.jpg | Bin 25133 -> 0 bytes website/images/photos/anne-krechmer.jpg | Bin 22606 -> 0 bytes website/images/photos/anton-popov.jpg | Bin 92054 -> 0 bytes website/images/photos/arno-van-driel.jpg | Bin 18278 -> 0 bytes website/images/photos/artur-filatenkov.jpg | Bin 12541 -> 0 bytes website/images/photos/baird-garrett.jpg | Bin 20004 -> 0 bytes website/images/photos/bastian-spanneberg.jpg | Bin 40832 -> 0 bytes website/images/photos/brian-hunter.jpg | Bin 25983 -> 0 bytes website/images/photos/caryn-marooney.jpg | Bin 27016 -> 0 bytes website/images/photos/christoph-wurm.jpg | Bin 20591 -> 0 bytes website/images/photos/claire-lucas.jpg | Bin 22886 -> 0 bytes website/images/photos/dale-mcdiarmid.jpg | Bin 10928 -> 0 bytes website/images/photos/dmitry-novik.jpg | Bin 25601 -> 0 bytes website/images/photos/dorota-szeremeta.jpg | Bin 21198 -> 0 bytes website/images/photos/elissa-weve.jpg | Bin 28656 -> 0 bytes website/images/photos/geoffrey-genz.jpg | Bin 16233 -> 0 bytes website/images/photos/ilya-yatsishin.jpg | Bin 35219 -> 0 bytes website/images/photos/ivan-blinkov.jpg | Bin 92933 -> 0 bytes website/images/photos/jason-chan.jpg | Bin 16772 -> 0 bytes website/images/photos/kevin-wang.jpg | Bin 22029 -> 0 bytes website/images/photos/kristina-frost.jpg | Bin 20462 -> 0 bytes website/images/photos/kseniia-sumarokova.jpg | Bin 108389 -> 0 bytes website/images/photos/maksim-kita.jpg | Bin 102345 -> 0 bytes website/images/photos/manas-alekar.jpg | Bin 25333 -> 0 bytes website/images/photos/marcel-birkner.jpg | Bin 22130 -> 0 bytes website/images/photos/marcelo-rodriguez.jpg | Bin 27061 -> 0 bytes website/images/photos/mark-zitnik.jpg | Bin 16076 -> 0 bytes website/images/photos/martin-choluj.jpg | Bin 20690 -> 0 bytes website/images/photos/melvyn-peignon.jpg | Bin 10432 -> 0 bytes website/images/photos/michael-lex.jpg | Bin 21565 -> 0 bytes website/images/photos/miel-donkers.jpg | Bin 14642 -> 0 bytes website/images/photos/mihir-gokhale.jpg | Bin 25840 -> 0 bytes website/images/photos/mike-hayes.jpg | Bin 22074 -> 0 bytes website/images/photos/mike-volpi.jpg | Bin 25414 -> 0 bytes website/images/photos/mikhail-fursov.jpg | Bin 14426 -> 0 bytes website/images/photos/mikhail-shiryaev.jpg | Bin 12923 -> 0 bytes website/images/photos/niek-lok.jpg | Bin 19205 -> 0 bytes website/images/photos/nihat-hosgur.jpg | Bin 25938 -> 0 bytes website/images/photos/nikita-mikhailov.jpg | Bin 88588 -> 0 bytes website/images/photos/nikolai-kochetov.jpg | Bin 91705 -> 0 bytes website/images/photos/nikolay-degterinsky.jpg | Bin 31782 -> 0 bytes website/images/photos/nir-peled.jpg | Bin 16177 -> 0 bytes .../photos/pascal-van-den-nieuwendijk.jpg | Bin 12796 -> 0 bytes website/images/photos/pavel-kruglov.jpg | Bin 87833 -> 0 bytes website/images/photos/peter-fenton.jpg | Bin 12028 -> 0 bytes website/images/photos/placeholder.png | Bin 2037 -> 0 bytes website/images/photos/rich-raposa.jpg | Bin 22523 -> 0 bytes website/images/photos/roopa-tangirala.jpg | Bin 28713 -> 0 bytes website/images/photos/ryadh-dahimene.jpg | Bin 27217 -> 0 bytes website/images/photos/sergei-trifonov.jpg | Bin 13150 -> 0 bytes website/images/photos/shavoyne-mccowan.jpg | Bin 27762 -> 0 bytes website/images/photos/tanya-bragin.jpg | Bin 17216 -> 0 bytes website/images/photos/thom-oconnor.jpg | Bin 28116 -> 0 bytes website/images/photos/tom-schreiber.jpg | Bin 30041 -> 0 bytes website/images/photos/tyler-hannan.jpg | Bin 27020 -> 0 bytes website/images/photos/vitaly-baranov.jpg | Bin 12158 -> 0 bytes website/images/photos/vladimir-cherkasov.jpg | Bin 89741 -> 0 bytes website/images/photos/yossi-kahlon.jpg | Bin 13330 -> 0 bytes website/images/photos/yuko-takagi.jpg | Bin 21425 -> 0 bytes website/images/photos/yury-izrailevsky.jpg | Bin 47756 -> 0 bytes website/images/placeholder-contact-form.png | Bin 20069 -> 0 bytes .../images/placeholder-newsletter-form.png | Bin 5410 -> 0 bytes website/images/welcome-home.png | Bin 31219 -> 0 bytes website/images/yandex.png | Bin 4177 -> 0 bytes 257 files changed, 1 insertion(+), 8117 deletions(-) delete mode 100644 website/benchmark/dbms/index.html delete mode 100644 website/benchmark/hardware/index.html delete mode 100644 website/benchmark/hardware/queries.js delete mode 100644 website/benchmark/hardware/results/amd_epyc_7402p.json delete mode 100644 website/benchmark/hardware/results/amd_epyc_7502p.json delete mode 100644 website/benchmark/hardware/results/amd_epyc_7551.json delete mode 100644 website/benchmark/hardware/results/amd_epyc_7642.json delete mode 100644 website/benchmark/hardware/results/amd_epyc_7662.json delete mode 100644 website/benchmark/hardware/results/amd_epyc_7702.json delete mode 100644 website/benchmark/hardware/results/amd_epyc_7702_zfs.json delete mode 100644 website/benchmark/hardware/results/amd_epyc_7742.json delete mode 100644 website/benchmark/hardware/results/amd_ryzen_9_3950x.json delete mode 100644 website/benchmark/hardware/results/amd_ryzen_9_5950x.json delete mode 100644 website/benchmark/hardware/results/amd_ryzen_threadripper.json delete mode 100644 website/benchmark/hardware/results/asus_a15.json delete mode 100644 website/benchmark/hardware/results/aws_a1_4xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_c5a_24xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_c5metal_100.json delete mode 100644 website/benchmark/hardware/results/aws_c5metal_300.json delete mode 100644 website/benchmark/hardware/results/aws_c6a_metal.json delete mode 100644 website/benchmark/hardware/results/aws_c6g_16xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_c6g_2xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_c6i_32xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_c6metal.json delete mode 100644 website/benchmark/hardware/results/aws_c7g_16xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_i3_8xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_i3en_24xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_lightsail_4vcpu.json delete mode 100644 website/benchmark/hardware/results/aws_m5a_4xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_m5a_8xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_m5ad_24xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_m5d_24xlarge.json delete mode 100644 website/benchmark/hardware/results/aws_m6g_16xlarge.json delete mode 100644 website/benchmark/hardware/results/azure_ds3v2.json delete mode 100644 website/benchmark/hardware/results/azure_e32s.json delete mode 100644 website/benchmark/hardware/results/cavium_4core.json delete mode 100644 website/benchmark/hardware/results/core_i5_3210M_lenovo_b580.json delete mode 100644 website/benchmark/hardware/results/core_i5_9600K_asus_z390.json delete mode 100644 website/benchmark/hardware/results/core_i7_11800h_lenovo_p15.json delete mode 100644 website/benchmark/hardware/results/core_i7_6770hq_intel_nuc.json delete mode 100644 website/benchmark/hardware/results/core_i7_8550u_lenovo_x1.json delete mode 100644 website/benchmark/hardware/results/dell_r530.json delete mode 100644 website/benchmark/hardware/results/dell_r740.json delete mode 100644 website/benchmark/hardware/results/dell_xps.json delete mode 100644 website/benchmark/hardware/results/do_storage_optimized.json delete mode 100644 website/benchmark/hardware/results/do_xeon_6140_4.json delete mode 100644 website/benchmark/hardware/results/efs_vs_ebs.json delete mode 100644 website/benchmark/hardware/results/equinix_metal_n3_xlarge.json delete mode 100644 website/benchmark/hardware/results/gcp_c2.json delete mode 100644 website/benchmark/hardware/results/gcp_n2.json delete mode 100644 website/benchmark/hardware/results/gcp_n2d.json delete mode 100644 website/benchmark/hardware/results/gp1_s_16x.json delete mode 100644 website/benchmark/hardware/results/gp1_s_8x.json delete mode 100644 website/benchmark/hardware/results/hetzner_epyc.json delete mode 100644 website/benchmark/hardware/results/hetzner_ex62.json delete mode 100644 website/benchmark/hardware/results/huawei_taishan_2280_3.json delete mode 100644 website/benchmark/hardware/results/huawei_taishan_2280_v2.json delete mode 100644 website/benchmark/hardware/results/huawei_taishan_920.json delete mode 100644 website/benchmark/hardware/results/i3_2xlarge.json delete mode 100644 website/benchmark/hardware/results/ibm_cloud_baremetal.json delete mode 100644 website/benchmark/hardware/results/im4gn_16xlarge.json delete mode 100644 website/benchmark/hardware/results/im4gn_4xlarge.json delete mode 100644 website/benchmark/hardware/results/im4gn_8xlarge.json delete mode 100644 website/benchmark/hardware/results/intel_core_i5_4440.json delete mode 100644 website/benchmark/hardware/results/intel_core_i9_11900kf.json delete mode 100644 website/benchmark/hardware/results/intel_xeon_scaleflux_csd3000.json delete mode 100644 website/benchmark/hardware/results/linode_cloud_amd_epyc_7601.json delete mode 100644 website/benchmark/hardware/results/macbook_air_m1.json delete mode 100644 website/benchmark/hardware/results/macbook_pro_core_i7_2014.json delete mode 100644 website/benchmark/hardware/results/macbook_pro_core_i7_2018.json delete mode 100644 website/benchmark/hardware/results/macbook_pro_core_i7_2020.json delete mode 100644 website/benchmark/hardware/results/macbook_pro_m1_2020.json delete mode 100644 website/benchmark/hardware/results/macbook_pro_m1_2021.json delete mode 100644 website/benchmark/hardware/results/oracle.json delete mode 100644 website/benchmark/hardware/results/pinebook_pro.json delete mode 100644 website/benchmark/hardware/results/pixel_3a.json delete mode 100644 website/benchmark/hardware/results/powerpc.json delete mode 100644 website/benchmark/hardware/results/qemu_aarch64_cascade_lake_80_vcpu.json delete mode 100644 website/benchmark/hardware/results/raspberry_pi_b.json delete mode 100644 website/benchmark/hardware/results/rock_pi.json delete mode 100644 website/benchmark/hardware/results/scaleway_epyc.json delete mode 100644 website/benchmark/hardware/results/selectel_cloud_16vcpu.json delete mode 100644 website/benchmark/hardware/results/skylake_kvm.json delete mode 100644 website/benchmark/hardware/results/ssdnodes.json delete mode 100644 website/benchmark/hardware/results/upcloud_8cpu_32gb.json delete mode 100644 website/benchmark/hardware/results/xeon_2176g.json delete mode 100644 website/benchmark/hardware/results/xeon_clx_6230r.json delete mode 100644 website/benchmark/hardware/results/xeon_e5645.json delete mode 100644 website/benchmark/hardware/results/xeon_e5_1650v3.json delete mode 100644 website/benchmark/hardware/results/xeon_e5_2640v4.json delete mode 100644 website/benchmark/hardware/results/xeon_e5_2650_4hdd.json delete mode 100644 website/benchmark/hardware/results/xeon_e5_2650_8hdd.json delete mode 100644 website/benchmark/hardware/results/xeon_e5_2650l_v3.json delete mode 100644 website/benchmark/hardware/results/xeon_gold_6140.json delete mode 100644 website/benchmark/hardware/results/xeon_gold_6230.json delete mode 100644 website/benchmark/hardware/results/xeon_gold_6266.json delete mode 100644 website/benchmark/hardware/results/xeon_silver_4114.json delete mode 100644 website/benchmark/hardware/results/xeon_sp_gold.json delete mode 100644 website/benchmark/hardware/results/xeon_x5675.json delete mode 100644 website/benchmark/hardware/results/yandex_cloud_broadwell_4_vcpu.json delete mode 100644 website/benchmark/hardware/results/yandex_cloud_broadwell_8_vcpu.json delete mode 100644 website/benchmark/hardware/results/yandex_cloud_broadwell_8_vcpu_s3.json delete mode 100644 website/benchmark/hardware/results/yandex_cloud_cascade_lake_32_vcpu.json delete mode 100644 website/benchmark/hardware/results/yandex_cloud_cascade_lake_4_vcpu.json delete mode 100644 website/benchmark/hardware/results/yandex_cloud_cascade_lake_64_vcpu.json delete mode 100644 website/benchmark/hardware/results/yandex_cloud_cascade_lake_80_vcpu.json delete mode 100644 website/benchmark/hardware/results/yandex_managed_clickhouse_s3_3xlarge.json delete mode 100644 website/images/backgrounds/bg-card-pattern-blue-1.png delete mode 100644 website/images/backgrounds/bg-card-pattern-blue-2.png delete mode 100644 website/images/backgrounds/bg-card-pattern-red.png delete mode 100644 website/images/backgrounds/bg-footer-cta.svg delete mode 100644 website/images/backgrounds/bg-hero-home.svg delete mode 100644 website/images/backgrounds/bg-hero.svg delete mode 100644 website/images/backgrounds/bg-quick-start.svg delete mode 100644 website/images/backgrounds/bg-quotes.svg delete mode 100644 website/images/curl.svg delete mode 100644 website/images/dots.svg delete mode 100644 website/images/flags/en.svg delete mode 100644 website/images/flags/es.svg delete mode 100644 website/images/flags/fa.svg delete mode 100644 website/images/flags/fr.svg delete mode 100644 website/images/flags/ja.svg delete mode 100644 website/images/flags/ru.svg delete mode 100644 website/images/flags/tr.svg delete mode 100644 website/images/flags/zh.svg delete mode 100644 website/images/icons/icon-arrow.svg delete mode 100644 website/images/icons/icon-blog-black.svg delete mode 100644 website/images/icons/icon-facebook-gray.svg delete mode 100644 website/images/icons/icon-facebook.svg delete mode 100644 website/images/icons/icon-github.svg delete mode 100644 website/images/icons/icon-google.svg delete mode 100644 website/images/icons/icon-linkedin-alt-gray.svg delete mode 100644 website/images/icons/icon-linkedin-gray.svg delete mode 100644 website/images/icons/icon-linkedin.png delete mode 100644 website/images/icons/icon-menu.svg delete mode 100644 website/images/icons/icon-performance.svg delete mode 100644 website/images/icons/icon-reliability.svg delete mode 100644 website/images/icons/icon-scalability.svg delete mode 100644 website/images/icons/icon-security.svg delete mode 100644 website/images/icons/icon-slack-black.svg delete mode 100644 website/images/icons/icon-slack.svg delete mode 100644 website/images/icons/icon-stack-overflow.svg delete mode 100644 website/images/icons/icon-telegram.svg delete mode 100644 website/images/icons/icon-twitter-gray.svg delete mode 100644 website/images/icons/icon-twitter.svg delete mode 100644 website/images/icons/icon-youtube-black.svg delete mode 100644 website/images/icons/icon-youtube.svg delete mode 100644 website/images/index/blog.svg delete mode 100644 website/images/index/flash.svg delete mode 100644 website/images/index/github.svg delete mode 100644 website/images/index/google-groups.svg delete mode 100644 website/images/index/hackernews.svg delete mode 100644 website/images/index/hardware-efficient.svg delete mode 100644 website/images/index/heart.svg delete mode 100644 website/images/index/intro.svg delete mode 100644 website/images/index/linearly-scalable.svg delete mode 100644 website/images/index/meetup.svg delete mode 100644 website/images/index/reddit.svg delete mode 100644 website/images/index/safe.svg delete mode 100644 website/images/index/scale.svg delete mode 100644 website/images/index/shield.svg delete mode 100644 website/images/index/slack.svg delete mode 100644 website/images/index/stack-overflow.svg delete mode 100644 website/images/index/telegram.svg delete mode 100644 website/images/index/twitter.svg delete mode 100644 website/images/index/youtube.svg delete mode 100644 website/images/logos/logo-almaz-capital.svg delete mode 100644 website/images/logos/logo-altimeter-capital.png delete mode 100644 website/images/logos/logo-benchmark-capital.png delete mode 100644 website/images/logos/logo-cloudflare.svg delete mode 100644 website/images/logos/logo-coatue.png delete mode 100644 website/images/logos/logo-deutsche-bank.png delete mode 100644 website/images/logos/logo-ebay.png delete mode 100644 website/images/logos/logo-firstmark.svg delete mode 100644 website/images/logos/logo-index-ventures.png delete mode 100644 website/images/logos/logo-leadedge-capital.png delete mode 100644 website/images/logos/logo-lightspeed.png delete mode 100644 website/images/logos/logo-redpoint.png delete mode 100644 website/images/logos/logo-spotify.png delete mode 100644 website/images/logos/logo-uber.png delete mode 100644 website/images/logos/logo-yandex.png delete mode 100644 website/images/mkdocs/copy.svg delete mode 100644 website/images/mkdocs/edit.svg delete mode 100644 website/images/mkdocs/multi.svg delete mode 100644 website/images/mkdocs/pdf.svg delete mode 100644 website/images/mkdocs/search.svg delete mode 100644 website/images/mkdocs/single.svg delete mode 100644 website/images/photos/aaron-katz.jpg delete mode 100644 website/images/photos/alexander-sapin.jpg delete mode 100644 website/images/photos/alexander-tokmakov.jpg delete mode 100644 website/images/photos/alexey-milovidov.jpg delete mode 100644 website/images/photos/andy-james.jpg delete mode 100644 website/images/photos/anne-carlhoff.jpg delete mode 100644 website/images/photos/anne-krechmer.jpg delete mode 100644 website/images/photos/anton-popov.jpg delete mode 100644 website/images/photos/arno-van-driel.jpg delete mode 100644 website/images/photos/artur-filatenkov.jpg delete mode 100644 website/images/photos/baird-garrett.jpg delete mode 100644 website/images/photos/bastian-spanneberg.jpg delete mode 100644 website/images/photos/brian-hunter.jpg delete mode 100644 website/images/photos/caryn-marooney.jpg delete mode 100644 website/images/photos/christoph-wurm.jpg delete mode 100644 website/images/photos/claire-lucas.jpg delete mode 100644 website/images/photos/dale-mcdiarmid.jpg delete mode 100644 website/images/photos/dmitry-novik.jpg delete mode 100644 website/images/photos/dorota-szeremeta.jpg delete mode 100644 website/images/photos/elissa-weve.jpg delete mode 100644 website/images/photos/geoffrey-genz.jpg delete mode 100644 website/images/photos/ilya-yatsishin.jpg delete mode 100644 website/images/photos/ivan-blinkov.jpg delete mode 100644 website/images/photos/jason-chan.jpg delete mode 100644 website/images/photos/kevin-wang.jpg delete mode 100644 website/images/photos/kristina-frost.jpg delete mode 100644 website/images/photos/kseniia-sumarokova.jpg delete mode 100644 website/images/photos/maksim-kita.jpg delete mode 100644 website/images/photos/manas-alekar.jpg delete mode 100644 website/images/photos/marcel-birkner.jpg delete mode 100644 website/images/photos/marcelo-rodriguez.jpg delete mode 100644 website/images/photos/mark-zitnik.jpg delete mode 100644 website/images/photos/martin-choluj.jpg delete mode 100644 website/images/photos/melvyn-peignon.jpg delete mode 100644 website/images/photos/michael-lex.jpg delete mode 100644 website/images/photos/miel-donkers.jpg delete mode 100644 website/images/photos/mihir-gokhale.jpg delete mode 100644 website/images/photos/mike-hayes.jpg delete mode 100644 website/images/photos/mike-volpi.jpg delete mode 100644 website/images/photos/mikhail-fursov.jpg delete mode 100644 website/images/photos/mikhail-shiryaev.jpg delete mode 100644 website/images/photos/niek-lok.jpg delete mode 100644 website/images/photos/nihat-hosgur.jpg delete mode 100644 website/images/photos/nikita-mikhailov.jpg delete mode 100644 website/images/photos/nikolai-kochetov.jpg delete mode 100644 website/images/photos/nikolay-degterinsky.jpg delete mode 100644 website/images/photos/nir-peled.jpg delete mode 100644 website/images/photos/pascal-van-den-nieuwendijk.jpg delete mode 100644 website/images/photos/pavel-kruglov.jpg delete mode 100644 website/images/photos/peter-fenton.jpg delete mode 100644 website/images/photos/placeholder.png delete mode 100644 website/images/photos/rich-raposa.jpg delete mode 100644 website/images/photos/roopa-tangirala.jpg delete mode 100644 website/images/photos/ryadh-dahimene.jpg delete mode 100644 website/images/photos/sergei-trifonov.jpg delete mode 100644 website/images/photos/shavoyne-mccowan.jpg delete mode 100644 website/images/photos/tanya-bragin.jpg delete mode 100644 website/images/photos/thom-oconnor.jpg delete mode 100644 website/images/photos/tom-schreiber.jpg delete mode 100644 website/images/photos/tyler-hannan.jpg delete mode 100644 website/images/photos/vitaly-baranov.jpg delete mode 100644 website/images/photos/vladimir-cherkasov.jpg delete mode 100644 website/images/photos/yossi-kahlon.jpg delete mode 100644 website/images/photos/yuko-takagi.jpg delete mode 100644 website/images/photos/yury-izrailevsky.jpg delete mode 100644 website/images/placeholder-contact-form.png delete mode 100644 website/images/placeholder-newsletter-form.png delete mode 100644 website/images/welcome-home.png delete mode 100644 website/images/yandex.png diff --git a/docs/tools/website.py b/docs/tools/website.py index a44464b6828..21d91ef1068 100644 --- a/docs/tools/website.py +++ b/docs/tools/website.py @@ -237,10 +237,9 @@ def minify_website(args): def process_benchmark_results(args): benchmark_root = os.path.join(args.website_dir, "benchmark") required_keys = { - "hardware": ["result", "system", "system_full", "kind"], "versions": ["version", "system"], } - for benchmark_kind in ["hardware", "versions"]: + for benchmark_kind in ["versions"]: results = [] results_root = os.path.join(benchmark_root, benchmark_kind, "results") for result in sorted(os.listdir(results_root)): diff --git a/website/benchmark/dbms/index.html b/website/benchmark/dbms/index.html deleted file mode 100644 index 8fae6311a78..00000000000 --- a/website/benchmark/dbms/index.html +++ /dev/null @@ -1,955 +0,0 @@ - - - - - ClickBench — a Benchmark For Analytical DBMS - - - - - - - - - - - -
- 🌚🌞 -

ClickBench — a Benchmark For Analytical DBMS

- Methodology | Reproduce and Validate the Results | Add a System | Report Mistake | Hardware Benchmark -
- - - - - - - - - - - - - - - - - - - - - - -
System: - All -
Type: - All -
Machine: - All -
Cluster size: - All -
Metric: - Cold Run - Hot Run - Load Time - Storage Size -
- - - - - - - - - - -
- System & Machine - - Relative time (lower is better) -
- -
Nothing selected
- -
-

Detailed Comparison

-
- - - - - - - - -
- - - - diff --git a/website/benchmark/hardware/index.html b/website/benchmark/hardware/index.html deleted file mode 100644 index 1dcb7d84cfd..00000000000 --- a/website/benchmark/hardware/index.html +++ /dev/null @@ -1,110 +0,0 @@ -{% extends 'templates/base.html' %} - -{% set title = 'Performance comparison of ClickHouse on various hardware' %} -{% set extra_js = [ - 'queries.js?' + rev_short, - 'results.js?' + rev_short, - '../benchmark.js?' + rev_short] -%} -{% set url = 'https://clickhouse.com/benchmark/hardware/' %} -{% set no_footer = True %} - -{% block content %} -
- -
-
- - ClickHouse - -

{{ title }}

-
-
- -
-
-
- -
-
-

Relative query processing time (lower is better)

-
-
-
- -
-
-

Full results

-
-
-
- -
-
-

Comments

-

Submit your own results: https://clickhouse.com/docs/en/operations/performance-test/

-

-Results for Lenovo B580 Laptop are from Ragıp Ünal. 16GB RAM 1600 GHz, 240GB SSD, Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz (2 Core / 4 HT)
-Results for Time4vps.eu are from Ragıp Ünal.
-Results for Dell PowerEdge R640, R641 (in Hetzner) are from Dmirty Titov.
-Results for Dell PowerEdge R730 are from Amos Bird.
-Results for Dell R530 are from Yuriy Zolkin.
-Results for Xeon 2176G are from Sergey Golod.
-Results for Azure DS3v2 are from Boris Granveaud.
-Results for AWS are from Wolf Kreuzerkrieg.
-Results for Huawei Taishan are from Peng Gao in sina.com.
-Results for Huawei Taishan (2) are from Kurmaev Roman at Huawei.
-Results for Selectel and AMD EPYC 7402P are from Andrey Dudin.
-Results for ProLiant are from Denis Ustinov.
-Results for AMD EPYC 7502P 128GiB are from Kostiantyn Velychkovskyi.
-Results for AMD EPYC 7502P 512GiB are from Sergey Zakharov.
-Results for Pinebook Pro are from Aleksey R. @kITerE.
-Results for AMD Ryzen are from Alexey Milovidov. Firefox was running in background.
-Results for Azure E32s are from Piotr Maśko.
-Results for MacBook Pro are from Denis Glazachev. MacOS Catalina Version 10.15.4 (19E266). For "drop caches", the "Free Up RAM" in CleanMyMac is used.
-Results for AMD EPYC 7702 are from Peng Gao in sina.com.
-Results for Intel NUC are from Alexander Zaitsev, Altinity.
-Xeon Gold 6230 server is using 4 x SAMSUNG datacenter class SSD in RAID-10.
-Results for Yandex Managed ClickHouse for "cold cache" are biased and should not be compared, because cache was not flushed for every next query.
-Results for AWS Lightsail is from Vamsi Krishna B.
-Results for Dell XPS laptop and Google Pixel phone is from Alexander Kuzmenkov.
-Results for Android phones for "cold cache" are done without cache flushing, so they are not "cold" and cannot be compared.
-Results for Digital Ocean are from Zimin Aleksey.
-Results for 2x EPYC 7642 w/ 512 GB RAM (192 Cores) + 12X 1TB SSD (RAID6) are from Yiğit Konur and Metehan Çetinkaya of seo.do.
-Results for Raspberry Pi and Digital Ocean CPU-optimized are from Fritz Wijaya.
-Results for Digitalocean (Storage-intesinve VMs) + (CPU/GP) are from Yiğit Konur and Metehan Çetinkaya of seo.do.
-Results for 2x AMD EPYC 7F72 3.2 Ghz (Total 96 Cores, IBM Cloud's Bare Metal Service) from Yiğit Konur and Metehan Çetinkaya of seo.do.
-Results for 2x AMD EPYC 7742 (128 physical cores, 1 TB DDR4-3200 RAM) from Yedige Davletgaliyev and Nikita Zhavoronkov of blockchair.com.
-Results for ASUS A15 (Ryzen laptop) are from Kimmo Linna.
-Results for MacBook Air M1 are from Denis Glazachev.
-Results for Xeon Gold 6140 are from Shiv Iyer (ChistaDATA Labs).
-Comparison of EBS and EFS is from Ramazan Polat.
-Results for Hetzner and Scaleway are from Anthony Najjar Simon (Panelbear).
-Results for GCP are from Vy Nguyen Tan.
-Results for ThinkPad P15 are from Mikhail Shiryaev.
-Results for RockPi4 are from Kirill Zholnay.
-Results for Xeon 6266C are from David in Shanghai.
-Results for SSDNodes and Cavium are from Lorenzo QXIP.
-Results for AMD EPYC 7662 64-Core Processor are from Evgeniy Kuts.
-Results for scaleway GP1-S 8x x86 64bit 32GB ram 300gb NVMe are from Dag Vilmar Tveit.
-Results for scaleway GP1-M 16x x86 64bit 64GB ram 600gb NVMe are from Dag Vilmar Tveit.
-Results for Intel(R) Core(TM) i5-4440 CPU @ 3.10GHz are from Peter, Chun-Sheng, Li.
-Results for MacBook Pro M1 are from Filatenkov Arthur.
-Results for AWS instance type im4gn.4xlarge are from Ananth Gundabattula (Darwinium).
-Results for AWS instance type im4gn.8xlarge are from Ananth Gundabattula (Darwinium).
-Results for AWS instance type im4gn.16xlarge are from Ananth Gundabattula (Darwinium).
-Results for AWS instance type i3.2xlarge are from Ananth Gundabattula (Darwinium).
-Results for 2x EPYC 7702 on ZFS mirror NVME are from Alibek A.
-Results for Intel 11th Gen Core i9-11900KF are from Tim Xian.
-Results for AWS instance type m5a.4xlarge are from Daniel Chimeno.
-Results for Huawei Taishan 920 are from Yu ZiChange at EioTek.
-Results for Macbook Pro Intel Core i7 (2014) are from Vladislav.
-Results for Hetzner EX62-NVME are from Talles Airan.
-Results for AMD Ryzen 9 5950X are from Stefan.
-Results for ScaleFlux CSD 3000 are from Cliicy Luo of ScaleFlux.
-Results for Equinix metal n3.xlarge.x84 are from Dave Cottlehuber. -Results for Dell PowerEdge R740xd are from Yu ZiChange at EioTek.
-

-
-
-{% endblock %} diff --git a/website/benchmark/hardware/queries.js b/website/benchmark/hardware/queries.js deleted file mode 100644 index 3e80f71f9cc..00000000000 --- a/website/benchmark/hardware/queries.js +++ /dev/null @@ -1,179 +0,0 @@ -var current_data_size = 0; - -var current_systems = []; - -var queries = - [ - { - "query": "SELECT count() FROM hits", - "comment": "", - }, - { - "query": "SELECT count() FROM hits WHERE AdvEngineID != 0", - "comment": "", - }, - { - "query": "SELECT sum(AdvEngineID), count(), avg(ResolutionWidth) FROM hits", - "comment": "", - }, - { - "query": "SELECT sum(UserID) FROM hits", - "comment": "", - }, - { - "query": "SELECT uniq(UserID) FROM hits", - "comment": "", - }, - { - "query": "SELECT uniq(SearchPhrase) FROM hits", - "comment": "", - }, - { - "query": "SELECT min(EventDate), max(EventDate) FROM hits", - "comment": "", - }, - { - "query": "SELECT AdvEngineID, count() FROM hits WHERE AdvEngineID != 0 GROUP BY AdvEngineID ORDER BY count() DESC", - "comment": "", - }, - { - "query": "SELECT RegionID, uniq(UserID) AS u FROM hits GROUP BY RegionID ORDER BY u DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT RegionID, sum(AdvEngineID), count() AS c, avg(ResolutionWidth), uniq(UserID) FROM hits GROUP BY RegionID ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT MobilePhoneModel, uniq(UserID) AS u FROM hits WHERE MobilePhoneModel != '' GROUP BY MobilePhoneModel ORDER BY u DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT MobilePhone, MobilePhoneModel, uniq(UserID) AS u FROM hits WHERE MobilePhoneModel != '' GROUP BY MobilePhone, MobilePhoneModel ORDER BY u DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase, count() AS c FROM hits WHERE SearchPhrase != '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase, uniq(UserID) AS u FROM hits WHERE SearchPhrase != '' GROUP BY SearchPhrase ORDER BY u DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchEngineID, SearchPhrase, count() AS c FROM hits WHERE SearchPhrase != '' GROUP BY SearchEngineID, SearchPhrase ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT UserID, count() FROM hits GROUP BY UserID ORDER BY count() DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT UserID, SearchPhrase, count() FROM hits GROUP BY UserID, SearchPhrase ORDER BY count() DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT UserID, SearchPhrase, count() FROM hits GROUP BY UserID, SearchPhrase LIMIT 10", - "comment": "", - }, - { - "query": "SELECT UserID, toMinute(EventTime) AS m, SearchPhrase, count() FROM hits GROUP BY UserID, m, SearchPhrase ORDER BY count() DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT UserID FROM hits WHERE UserID = 12345678901234567890", - "comment": "", - }, - { - "query": "SELECT count() FROM hits WHERE URL LIKE '%metrika%'", - "comment": "", - }, - { - "query": "SELECT SearchPhrase, any(URL), count() AS c FROM hits WHERE URL LIKE '%metrika%' AND SearchPhrase != '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase, any(URL), any(Title), count() AS c, uniq(UserID) FROM hits WHERE Title LIKE '%Яндекс%' AND URL NOT LIKE '%.yandex.%' AND SearchPhrase != '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT * FROM hits WHERE URL LIKE '%metrika%' ORDER BY EventTime LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase FROM hits WHERE SearchPhrase != '' ORDER BY EventTime LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase FROM hits WHERE SearchPhrase != '' ORDER BY SearchPhrase LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase FROM hits WHERE SearchPhrase != '' ORDER BY EventTime, SearchPhrase LIMIT 10", - "comment": "", - }, - { - "query": "SELECT CounterID, avg(length(URL)) AS l, count() AS c FROM hits WHERE URL != '' GROUP BY CounterID HAVING c > 100000 ORDER BY l DESC LIMIT 25", - "comment": "", - }, - { - "query": "SELECT domainWithoutWWW(Referer) AS key, avg(length(Referer)) AS l, count() AS c, any(Referer) FROM hits WHERE Referer != '' GROUP BY key HAVING c > 100000 ORDER BY l DESC LIMIT 25", - "comment": "", - }, - { - "query": "SELECT sum(ResolutionWidth), sum(ResolutionWidth + 1), sum(ResolutionWidth + 2), sum(ResolutionWidth + 3), sum(ResolutionWidth + 4), sum(ResolutionWidth + 5), sum(ResolutionWidth + 6), sum(ResolutionWidth + 7), sum(ResolutionWidth + 8), sum(ResolutionWidth + 9), sum(ResolutionWidth + 10), sum(ResolutionWidth + 11), sum(ResolutionWidth + 12), sum(ResolutionWidth + 13), sum(ResolutionWidth + 14), sum(ResolutionWidth + 15), sum(ResolutionWidth + 16), sum(ResolutionWidth + 17), sum(ResolutionWidth + 18), sum(ResolutionWidth + 19), sum(ResolutionWidth + 20), sum(ResolutionWidth + 21), sum(ResolutionWidth + 22), sum(ResolutionWidth + 23), sum(ResolutionWidth + 24), sum(ResolutionWidth + 25), sum(ResolutionWidth + 26), sum(ResolutionWidth + 27), sum(ResolutionWidth + 28), sum(ResolutionWidth + 29), sum(ResolutionWidth + 30), sum(ResolutionWidth + 31), sum(ResolutionWidth + 32), sum(ResolutionWidth + 33), sum(ResolutionWidth + 34), sum(ResolutionWidth + 35), sum(ResolutionWidth + 36), sum(ResolutionWidth + 37), sum(ResolutionWidth + 38), sum(ResolutionWidth + 39), sum(ResolutionWidth + 40), sum(ResolutionWidth + 41), sum(ResolutionWidth + 42), sum(ResolutionWidth + 43), sum(ResolutionWidth + 44), sum(ResolutionWidth + 45), sum(ResolutionWidth + 46), sum(ResolutionWidth + 47), sum(ResolutionWidth + 48), sum(ResolutionWidth + 49), sum(ResolutionWidth + 50), sum(ResolutionWidth + 51), sum(ResolutionWidth + 52), sum(ResolutionWidth + 53), sum(ResolutionWidth + 54), sum(ResolutionWidth + 55), sum(ResolutionWidth + 56), sum(ResolutionWidth + 57), sum(ResolutionWidth + 58), sum(ResolutionWidth + 59), sum(ResolutionWidth + 60), sum(ResolutionWidth + 61), sum(ResolutionWidth + 62), sum(ResolutionWidth + 63), sum(ResolutionWidth + 64), sum(ResolutionWidth + 65), sum(ResolutionWidth + 66), sum(ResolutionWidth + 67), sum(ResolutionWidth + 68), sum(ResolutionWidth + 69), sum(ResolutionWidth + 70), sum(ResolutionWidth + 71), sum(ResolutionWidth + 72), sum(ResolutionWidth + 73), sum(ResolutionWidth + 74), sum(ResolutionWidth + 75), sum(ResolutionWidth + 76), sum(ResolutionWidth + 77), sum(ResolutionWidth + 78), sum(ResolutionWidth + 79), sum(ResolutionWidth + 80), sum(ResolutionWidth + 81), sum(ResolutionWidth + 82), sum(ResolutionWidth + 83), sum(ResolutionWidth + 84), sum(ResolutionWidth + 85), sum(ResolutionWidth + 86), sum(ResolutionWidth + 87), sum(ResolutionWidth + 88), sum(ResolutionWidth + 89) FROM hits", - "comment": "", - }, - { - "query": "SELECT SearchEngineID, ClientIP, count() AS c, sum(Refresh), avg(ResolutionWidth) FROM hits WHERE SearchPhrase != '' GROUP BY SearchEngineID, ClientIP ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT WatchID, ClientIP, count() AS c, sum(Refresh), avg(ResolutionWidth) FROM hits WHERE SearchPhrase != '' GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT WatchID, ClientIP, count() AS c, sum(Refresh), avg(ResolutionWidth) FROM hits GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT URL, count() AS c FROM hits GROUP BY URL ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT 1, URL, count() AS c FROM hits GROUP BY 1, URL ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT ClientIP AS x, x - 1, x - 2, x - 3, count() AS c FROM hits GROUP BY x, x - 1, x - 2, x - 3 ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT URL, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT DontCountHits AND NOT Refresh AND notEmpty(URL) GROUP BY URL ORDER BY PageViews DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT Title, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT DontCountHits AND NOT Refresh AND notEmpty(Title) GROUP BY Title ORDER BY PageViews DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT URL, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT Refresh AND IsLink AND NOT IsDownload GROUP BY URL ORDER BY PageViews DESC LIMIT 1000", - "comment": "", - }, - { - "query": "SELECT TraficSourceID, SearchEngineID, AdvEngineID, ((SearchEngineID = 0 AND AdvEngineID = 0) ? Referer : '') AS Src, URL AS Dst, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT Refresh GROUP BY TraficSourceID, SearchEngineID, AdvEngineID, Src, Dst ORDER BY PageViews DESC LIMIT 1000", - "comment": "", - }, - { - "query": "SELECT URLHash, EventDate, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT Refresh AND TraficSourceID IN (-1, 6) AND RefererHash = halfMD5('http://yandex.ru/') GROUP BY URLHash, EventDate ORDER BY PageViews DESC LIMIT 100", - "comment": "", - }, - { - "query": "SELECT WindowClientWidth, WindowClientHeight, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT Refresh AND NOT DontCountHits AND URLHash = halfMD5('http://yandex.ru/') GROUP BY WindowClientWidth, WindowClientHeight ORDER BY PageViews DESC LIMIT 10000;", - "comment": "", - }, - { - "query": "SELECT toStartOfMinute(EventTime) AS Minute, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-02') AND NOT Refresh AND NOT DontCountHits GROUP BY Minute ORDER BY Minute;", - "comment": "", - } - ]; diff --git a/website/benchmark/hardware/results/amd_epyc_7402p.json b/website/benchmark/hardware/results/amd_epyc_7402p.json deleted file mode 100644 index a1dcf5cf267..00000000000 --- a/website/benchmark/hardware/results/amd_epyc_7402p.json +++ /dev/null @@ -1,158 +0,0 @@ -[ - { - "system": "AMD EPYC 7402P SSD", - "system_full": "AMD EPYC 7402P 2.8 GHz, 128 GB DDR4, SSD RAID1 2×1920 GB SSD (INTEL SSDSC2KB019T7)", - "time": "2020-01-26 00:00:00", - "kind": "server", - "result": - [ - [0.014, 0.002, 0.002], - [0.031, 0.014, 0.010], - [0.077, 0.015, 0.015], - [0.255, 0.020, 0.019], - [0.286, 0.075, 0.073], - [0.452, 0.136, 0.135], - [0.025, 0.012, 0.012], - [0.021, 0.011, 0.011], - [0.431, 0.188, 0.188], - [0.491, 0.213, 0.214], - [0.308, 0.099, 0.097], - [0.319, 0.102, 0.098], - [0.491, 0.247, 0.248], - [0.786, 0.323, 0.316], - [0.574, 0.291, 0.291], - [0.414, 0.266, 0.267], - [1.097, 0.847, 0.835], - [0.748, 0.507, 0.505], - [1.977, 1.467, 1.488], - [0.264, 0.018, 0.029], - [2.937, 0.281, 0.254], - [3.288, 0.301, 0.283], - [6.502, 0.698, 0.687], - [7.260, 0.358, 0.351], - [0.796, 0.096, 0.095], - [0.399, 0.084, 0.083], - [0.873, 0.099, 0.101], - [3.215, 0.318, 0.300], - [2.680, 0.394, 0.391], - [1.099, 1.058, 1.055], - [0.802, 0.250, 0.251], - [1.823, 0.340, 0.341], - [2.750, 2.168, 2.157], - [3.638, 1.301, 1.267], - [3.583, 1.289, 1.288], - [0.455, 0.392, 0.393], - [0.279, 0.170, 0.159], - [0.089, 0.068, 0.066], - [0.135, 0.063, 0.061], - [0.479, 0.329, 0.341], - [0.059, 0.021, 0.020], - [0.042, 0.018, 0.020], - [0.011, 0.006, 0.006] - ] - }, - { - "system": "AMD EPYC 7402P HDD", - "system_full": "AMD EPYC 7402P 2.8 GHz, 128 GB DDR4, HDD RAID1 2×8000 GB HDD (TOSHIBA MG06ACA800E)", - "time": "2020-01-26 00:00:00", - "kind": "server", - "result": - [ - [0.149, 0.002, 0.002], - [0.263, 0.012, 0.011], - [0.631, 0.017, 0.016], - [1.829, 0.023, 0.020], - [2.073, 0.078, 0.076], - [2.981, 0.176, 0.138], - [0.204, 0.022, 0.012], - [0.195, 0.011, 0.011], - [2.652, 0.195, 0.193], - [2.949, 0.226, 0.218], - [2.124, 0.101, 0.099], - [2.369, 0.106, 0.102], - [2.978, 0.254, 0.248], - [4.546, 0.328, 0.321], - [3.391, 0.298, 0.297], - [2.211, 0.269, 0.268], - [4.889, 0.850, 0.842], - [4.627, 0.514, 0.505], - [8.805, 1.506, 1.432], - [1.979, 0.034, 0.044], - [18.744, 0.315, 0.248], - [22.946, 0.301, 0.276], - [41.584, 0.703, 0.692], - [42.963, 0.392, 0.335], - [5.992, 0.130, 0.096], - [3.050, 0.096, 0.085], - [6.390, 0.115, 0.101], - [20.038, 0.319, 0.296], - [17.610, 0.408, 0.396], - [1.187, 1.056, 1.055], - [5.134, 0.254, 0.249], - [10.690, 0.348, 0.341], - [9.296, 2.190, 2.149], - [20.999, 1.258, 1.258], - [22.020, 1.256, 1.254], - [1.715, 0.400, 0.390], - [0.403, 0.169, 0.164], - [0.147, 0.069, 0.069], - [0.137, 0.063, 0.062], - [0.568, 0.344, 0.359], - [0.152, 0.027, 0.021], - [0.076, 0.018, 0.017], - [0.021, 0.006, 0.006] - ] - }, - { - "system": "AMD EPYC 7502P nVME", - "system_full": "AMD EPYC 7502P / 128G DDR4 / 2NVME SAMSUNG MZQLB960HAJR", - "time": "2020-03-05 00:00:00", - "kind": "server", - "result": - [ - [0.012, 0.019, 0.009], - [0.042, 0.026, 0.038], - [0.026, 0.032, 0.017], - [0.058, 0.025, 0.027], - [0.095, 0.080, 0.087], - [0.143, 0.125, 0.124], - [0.018, 0.010, 0.016], - [0.013, 0.012, 0.013], - [0.201, 0.182, 0.182], - [0.228, 0.204, 0.204], - [0.093, 0.078, 0.077], - [0.100, 0.080, 0.081], - [0.241, 0.222, 0.218], - [0.291, 0.265, 0.270], - [0.268, 0.254, 0.256], - [0.255, 0.241, 0.242], - [0.623, 0.593, 0.599], - [0.373, 0.343, 0.339], - [1.354, 1.318, 1.311], - [0.054, 0.020, 0.022], - [0.495, 0.247, 0.242], - [0.520, 0.258, 0.248], - [0.957, 0.646, 0.652], - [null, null, null], - [0.149, 0.105, 0.099], - [0.091, 0.070, 0.069], - [0.150, 0.096, 0.094], - [0.499, 0.315, 0.309], - [0.437, 0.354, 0.357], - [1.002, 0.996, 0.991], - [0.234, 0.205, 0.207], - [0.380, 0.305, 0.305], - [1.733, 1.651, 1.655], - [1.230, 1.134, 1.132], - [1.217, 1.130, 1.114], - [0.396, 0.385, 0.383], - [0.156, 0.148, 0.160], - [0.065, 0.062, 0.063], - [0.057, 0.052, 0.052], - [0.368, 0.342, 0.336], - [0.030, 0.025, 0.027], - [0.022, 0.017, 0.019], - [0.005, 0.004, 0.004] - ] - } -] diff --git a/website/benchmark/hardware/results/amd_epyc_7502p.json b/website/benchmark/hardware/results/amd_epyc_7502p.json deleted file mode 100644 index 0f82bd7dfc8..00000000000 --- a/website/benchmark/hardware/results/amd_epyc_7502p.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AMD EPYC 7502P", - "system_full": "AMD EPYC 7502P 32-Core Processor with HT (64 thread) / 512 Gb RAM / mdadm RAID1 SAMSUNG MZQLB3T8HALS-00007 + LVM", - "time": "2020-04-16 00:00:00", - "kind": "server", - "result": - [ - [0.007, 0.002, 0.002], - [0.022, 0.011, 0.011], - [0.028, 0.017, 0.017], - [0.050, 0.022, 0.022], - [0.098, 0.079, 0.078], - [0.149, 0.125, 0.126], - [0.020, 0.014, 0.014], - [0.015, 0.013, 0.013], - [0.169, 0.148, 0.148], - [0.237, 0.171, 0.168], - [0.103, 0.084, 0.082], - [0.099, 0.085, 0.084], - [0.262, 0.221, 0.221], - [0.312, 0.281, 0.282], - [0.274, 0.259, 0.255], - [0.255, 0.237, 0.237], - [0.616, 0.592, 0.589], - [0.398, 0.364, 0.359], - [1.358, 1.301, 1.292], - [0.056, 0.025, 0.022], - [0.485, 0.265, 0.263], - [0.510, 0.271, 0.255], - [0.938, 0.693, 0.674], - [1.262, 0.396, 0.367], - [0.144, 0.082, 0.081], - [0.088, 0.066, 0.065], - [0.141, 0.084, 0.082], - [0.488, 0.329, 0.325], - [0.441, 0.376, 0.374], - [1.054, 1.049, 1.068], - [0.222, 0.190, 0.189], - [0.386, 0.321, 0.319], - [1.703, 1.625, 1.660], - [1.272, 1.202, 1.207], - [1.276, 1.185, 1.204], - [0.398, 0.382, 0.382], - [0.171, 0.160, 0.160], - [0.071, 0.058, 0.059], - [0.059, 0.055, 0.053], - [0.364, 0.341, 0.354], - [0.028, 0.022, 0.027], - [0.027, 0.020, 0.018], - [0.010, 0.008, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/amd_epyc_7551.json b/website/benchmark/hardware/results/amd_epyc_7551.json deleted file mode 100644 index 9ae945a0d32..00000000000 --- a/website/benchmark/hardware/results/amd_epyc_7551.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "system": "Dell PowerEdge R6415", - "system_full": "Dell PowerEdge R6415 DX180 AMD EPYC™ 7551P 32-Core Naples (Zen), 128 GB RAM, 2x SSD 960 GB RAID-1", - "cpu_vendor": "AMD", - "time": "2020-01-13 00:00:00", - "kind": "server", - "result": - [ - [0.007, 0.002, 0.001], - [0.030, 0.016, 0.014], - [0.042, 0.026, 0.026], - [0.078, 0.043, 0.042], - [0.143, 0.120, 0.117], - [0.239, 0.198, 0.198], - [0.022, 0.014, 0.014], - [0.016, 0.013, 0.015], - [0.388, 0.380, 0.384], - [0.476, 0.429, 0.411], - [0.201, 0.192, 0.191], - [0.204, 0.207, 0.192], - [0.676, 0.654, 0.637], - [0.890, 0.932, 0.940], - [0.730, 0.789, 0.738], - [0.658, 0.641, 0.678], - [1.556, 1.430, 1.529], - [0.819, 1.096, 0.906], - [3.569, 3.626, 3.508], - [0.083, 0.047, 0.077], - [0.812, 1.010, 0.601], - [1.097, 0.847, 0.864], - [2.654, 3.146, 3.169], - [1.595, 0.922, 0.877], - [0.259, 0.227, 0.236], - [0.206, 0.187, 0.181], - [0.245, 0.235, 0.232], - [0.974, 1.018, 1.012], - [1.280, 1.398, 1.243], - [2.171, 2.270, 2.284], - [0.594, 0.592, 0.602], - [0.976, 0.946, 0.966], - [4.543, 4.471, 4.364], - [3.844, 4.052, 3.858], - [3.932, 3.961, 3.982], - [1.128, 1.117, 1.146], - [0.233, 0.216, 0.221], - [0.088, 0.082, 0.085], - [0.075, 0.070, 0.070], - [0.465, 0.445, 0.435], - [0.036, 0.026, 0.031], - [0.028, 0.024, 0.021], - [0.010, 0.006, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/amd_epyc_7642.json b/website/benchmark/hardware/results/amd_epyc_7642.json deleted file mode 100644 index b60146d515f..00000000000 --- a/website/benchmark/hardware/results/amd_epyc_7642.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "system": "AMD EPYC 7642", - "system_full": "2x AMD EPYC 7642 / 512 GB RAM / 12x 1TB SSD (RAID 6)", - "time": "2020-09-21 00:00:00", - "kind": "server", - "result": - [ - [0.003, 0.003, 0.002], - [0.039, 0.041, 0.024], - [0.052, 0.029, 0.029], - [0.087, 0.031, 0.032], - [0.152, 0.106, 0.105], - [0.204, 0.128, 0.128], - [0.049, 0.028, 0.027], - [0.031, 0.024, 0.027], - [0.190, 0.130, 0.125], - [0.210, 0.142, 0.138], - [0.142, 0.091, 0.087], - [0.143, 0.101, 0.097], - [0.318, 0.170, 0.163], - [0.303, 0.193, 0.191], - [0.240, 0.175, 0.166], - [0.200, 0.166, 0.161], - [0.466, 0.364, 0.345], - [0.298, 0.244, 0.231], - [1.288, 0.901, 0.859], - [0.087, 0.031, 0.025], - [0.663, 0.201, 0.191], - [0.661, 0.213, 0.154], - [1.118, 0.599, 0.593], - [1.708, 0.392, 0.318], - [0.202, 0.065, 0.066], - [0.135, 0.061, 0.057], - [0.203, 0.066, 0.067], - [0.630, 0.296, 0.290], - [0.578, 0.281, 0.262], - [0.662, 0.670, 0.639], - [0.241, 0.153, 0.150], - [0.424, 0.235, 0.231], - [1.505, 1.090, 1.090], - [1.038, 0.818, 0.799], - [1.064, 0.856, 0.809], - [0.332, 0.297, 0.275], - [0.200, 0.169, 0.168], - [0.083, 0.070, 0.071], - [0.090, 0.059, 0.063], - [0.416, 0.419, 0.398], - [0.048, 0.032, 0.032], - [0.036, 0.027, 0.025], - [0.007, 0.007, 0.007] - ] - } -] - - diff --git a/website/benchmark/hardware/results/amd_epyc_7662.json b/website/benchmark/hardware/results/amd_epyc_7662.json deleted file mode 100644 index 436c0099992..00000000000 --- a/website/benchmark/hardware/results/amd_epyc_7662.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AMD EPYC 7662", - "system_full": "AMD EPYC 7662 64-Core Processor", - "time": "2022-01-26 11:28:55", - "kind": "server", - "result": - [ - [0.001, 0.001, 0.001], - [0.037, 0.019, 0.020], - [0.082, 0.034, 0.026], - [0.298, 0.045, 0.038], - [0.424, 0.188, 0.178], - [0.594, 0.229, 0.227], - [0.037, 0.028, 0.032], - [0.060, 0.028, 0.027], - [0.496, 0.185, 0.192], - [0.611, 0.210, 0.214], - [0.400, 0.148, 0.137], - [0.424, 0.155, 0.144], - [0.639, 0.256, 0.239], - [0.944, 0.404, 0.309], - [0.699, 0.326, 0.288], - [0.461, 0.221, 0.216], - [1.176, 0.539, 0.561], - [1.070, 0.410, 0.426], - [2.080, 0.950, 0.866], - [0.351, 0.066, 0.130], - [3.248, 0.461, 0.313], - [3.612, 0.261, 0.231], - [6.720, 0.682, 0.671], - [6.300, 0.517, 0.488], - [0.982, 0.136, 0.125], - [0.531, 0.112, 0.109], - [1.006, 0.133, 0.118], - [3.184, 0.324, 0.310], - [2.799, 0.327, 0.308], - [0.569, 0.492, 0.493], - [0.900, 0.212, 0.221], - [1.925, 0.353, 0.326], - [2.489, 1.173, 1.248], - [3.626, 0.990, 0.897], - [3.743, 0.935, 0.915], - [0.419, 0.311, 0.339], - [0.278, 0.244, 0.236], - [0.111, 0.099, 0.098], - [0.139, 0.086, 0.084], - [0.664, 0.520, 0.552], - [0.072, 0.028, 0.036], - [0.050, 0.031, 0.022], - [0.005, 0.005, 0.011] - ] - } -] diff --git a/website/benchmark/hardware/results/amd_epyc_7702.json b/website/benchmark/hardware/results/amd_epyc_7702.json deleted file mode 100644 index cd92fbfd1dc..00000000000 --- a/website/benchmark/hardware/results/amd_epyc_7702.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AMD EPYC 7702", - "system_full": "AMD EPYC 7702, 256 cores, 512 GiB, NVMe SSD", - "time": "2020-04-09 00:00:00", - "kind": "server", - "result": - [ - [0.006, 0.002, 0.002], - [0.252, 0.072, 0.057], - [0.113, 0.066, 0.057], - [0.197, 0.055, 0.065], - [0.311, 0.199, 0.217], - [0.360, 0.200, 0.183], - [0.119, 0.050, 0.045], - [0.066, 0.061, 0.057], - [0.320, 0.150, 0.144], - [0.346, 0.170, 0.162], - [0.226, 0.117, 0.115], - [0.265, 0.112, 0.118], - [0.402, 0.249, 0.250], - [0.561, 0.327, 0.332], - [0.397, 0.267, 0.257], - [0.323, 0.221, 0.233], - [0.710, 0.527, 0.517], - [0.667, 0.437, 0.443], - [1.269, 0.936, 0.957], - [0.189, 0.043, 0.043], - [1.673, 0.206, 0.169], - [1.937, 0.214, 0.184], - [3.527, 0.755, 0.737], - [3.197, 0.551, 0.523], - [0.519, 0.076, 0.086], - [0.268, 0.060, 0.080], - [0.522, 0.075, 0.079], - [1.693, 0.345, 0.351], - [1.466, 0.330, 0.318], - [1.078, 0.974, 1.019], - [0.501, 0.196, 0.200], - [1.032, 0.266, 0.271], - [1.621, 1.156, 1.169], - [2.089, 0.998, 0.972], - [2.106, 0.974, 0.959], - [0.366, 0.305, 0.305], - [0.190, 0.187, 0.183], - [0.071, 0.066, 0.075], - [0.072, 0.068, 0.062], - [0.415, 0.353, 0.457], - [0.034, 0.032, 0.028], - [0.031, 0.027, 0.032], - [0.024, 0.007, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/amd_epyc_7702_zfs.json b/website/benchmark/hardware/results/amd_epyc_7702_zfs.json deleted file mode 100644 index 9e7c15f579f..00000000000 --- a/website/benchmark/hardware/results/amd_epyc_7702_zfs.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "2x EPYC 7702 on ZFS mirror NVME", - "system_full": "2x EPYC 7702 on ZFS mirror NVME, AMD EPYC 7702 64-Core Processor", - "time": "2022-01-14 21:07:13", - "kind": "server", - "result": - [ - [0.001, 0.002, 0.001], - [0.033, 0.021, 0.022], - [0.026, 0.022, 0.024], - [0.032, 0.024, 0.027], - [0.114, 0.115, 0.116], - [0.156, 0.150, 0.156], - [0.035, 0.023, 0.022], - [0.035, 0.023, 0.023], - [0.134, 0.148, 0.133], - [0.165, 0.150, 0.156], - [0.132, 0.087, 0.083], - [0.103, 0.124, 0.094], - [0.273, 0.221, 0.229], - [0.305, 0.263, 0.267], - [0.273, 0.267, 0.239], - [0.210, 0.228, 0.241], - [0.641, 0.518, 0.498], - [0.413, 0.423, 0.485], - [1.044, 0.991, 0.999], - [0.091, 0.144, 0.071], - [0.203, 0.190, 0.203], - [0.199, 0.210, 0.189], - [0.662, 0.753, 0.705], - [0.636, 0.461, 0.445], - [0.093, 0.079, 0.082], - [0.066, 0.070, 0.072], - [0.086, 0.080, 0.091], - [0.293, 0.280, 0.298], - [0.301, 0.258, 0.268], - [0.624, 0.611, 0.613], - [0.170, 0.168, 0.170], - [0.317, 0.269, 0.273], - [1.801, 1.071, 1.183], - [1.049, 1.080, 0.957], - [0.904, 0.892, 0.898], - [0.293, 0.288, 0.291], - [0.176, 0.173, 0.176], - [0.068, 0.068, 0.070], - [0.060, 0.060, 0.061], - [0.412, 0.388, 0.382], - [0.021, 0.019, 0.019], - [0.019, 0.022, 0.015], - [0.004, 0.010, 0.009] - ] - } -] diff --git a/website/benchmark/hardware/results/amd_epyc_7742.json b/website/benchmark/hardware/results/amd_epyc_7742.json deleted file mode 100644 index 61b76d9e37b..00000000000 --- a/website/benchmark/hardware/results/amd_epyc_7742.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AMD EPYC 7742", - "system_full": "AMD EPYC 7742, 256 cores, 1 TiB Samsung 16x64GB DDR4-3200 ECC, 2 * Samsung PM1725b 12.8TB (RAID 0)", - "time": "2021-02-23 00:00:00", - "kind": "server", - "result": - [ -[0.001, 0.001, 0.001], -[0.075, 0.080, 0.015], -[0.030, 0.029, 0.025], -[0.052, 0.027, 0.028], -[0.141, 0.104, 0.105], -[0.180, 0.127, 0.129], -[0.023, 0.018, 0.017], -[0.018, 0.016, 0.016], -[0.135, 0.097, 0.096], -[0.146, 0.109, 0.108], -[0.106, 0.070, 0.069], -[0.105, 0.068, 0.070], -[0.180, 0.137, 0.136], -[0.216, 0.171, 0.175], -[0.193, 0.160, 0.156], -[0.148, 0.123, 0.132], -[0.364, 0.317, 0.317], -[0.276, 0.245, 0.240], -[0.636, 0.562, 0.615], -[0.070, 0.018, 0.015], -[0.576, 0.131, 0.125], -[0.595, 0.124, 0.121], -[0.861, 0.596, 0.610], -[1.172, 0.262, 0.276], -[0.152, 0.054, 0.050], -[0.114, 0.046, 0.046], -[0.149, 0.052, 0.051], -[0.536, 0.288, 0.306], -[0.587, 0.207, 0.217], -[0.436, 0.419, 0.406], -[0.178, 0.110, 0.113], -[0.337, 0.165, 0.162], -[1.072, 0.855, 0.882], -[0.908, 0.600, 0.612], -[0.904, 0.604, 0.629], -[0.223, 0.207, 0.216], -[0.151, 0.143, 0.172], -[0.063, 0.061, 0.060], -[0.059, 0.053, 0.054], -[0.321, 0.318, 0.321], -[0.026, 0.021, 0.020], -[0.018, 0.015, 0.016], -[0.005, 0.005, 0.004] - ] - } -] diff --git a/website/benchmark/hardware/results/amd_ryzen_9_3950x.json b/website/benchmark/hardware/results/amd_ryzen_9_3950x.json deleted file mode 100644 index caa5a443e54..00000000000 --- a/website/benchmark/hardware/results/amd_ryzen_9_3950x.json +++ /dev/null @@ -1,106 +0,0 @@ -[ - { - "system": "AMD Ryzen 9 (2020)", - "system_full": "AMD Ryzen 9 3950X 16-Core Processor, 64 GiB RAM, Intel Optane 900P 280 GB", - "time": "2020-03-14 00:00:00", - "kind": "desktop", - "result": - [ - [0.002, 0.001, 0.001], - [0.018, 0.013, 0.012], - [0.041, 0.027, 0.026], - [0.091, 0.040, 0.041], - [0.115, 0.075, 0.074], - [0.201, 0.157, 0.153], - [0.017, 0.015, 0.014], - [0.013, 0.013, 0.013], - [0.363, 0.321, 0.313], - [0.441, 0.390, 0.389], - [0.189, 0.164, 0.162], - [0.201, 0.172, 0.178], - [0.879, 0.809, 0.802], - [1.081, 1.030, 1.027], - [0.875, 0.832, 0.837], - [1.042, 1.013, 1.019], - [2.604, 2.544, 2.529], - [1.435, 1.396, 1.414], - [4.208, 4.148, 4.132], - [0.094, 0.052, 0.037], - [0.965, 0.451, 0.450], - [1.141, 0.762, 0.763], - [2.813, 2.378, 2.383], - [2.127, 0.749, 0.733], - [0.301, 0.228, 0.228], - [0.218, 0.180, 0.174], - [0.301, 0.228, 0.227], - [1.241, 0.965, 0.958], - [1.469, 1.271, 1.274], - [3.782, 3.780, 3.762], - [0.738, 0.656, 0.655], - [1.147, 1.015, 1.009], - [7.114, 6.989, 7.074], - [4.277, 4.085, 4.069], - [4.256, 4.032, 4.073], - [1.811, 1.787, 1.785], - [0.138, 0.128, 0.129], - [0.057, 0.051, 0.053], - [0.058, 0.051, 0.047], - [0.283, 0.284, 0.278], - [0.023, 0.024, 0.027], - [0.024, 0.015, 0.021], - [0.007, 0.013, 0.006] - ] - }, - { - "system": "AMD Ryzen 9 (2021)", - "system_full": "AMD Ryzen 9 3950X 16-Core Processor, 64 GiB RAM, Samsung evo 970 plus 1TB", - "time": "2021-03-08 00:00:00", - "kind": "desktop", - "result": - [ -[0.002, 0.002, 0.002], -[0.013, 0.011, 0.010], -[0.030, 0.023, 0.023], -[0.071, 0.033, 0.031], -[0.090, 0.068, 0.066], -[0.165, 0.137, 0.137], -[0.015, 0.014, 0.015], -[0.013, 0.012, 0.012], -[0.317, 0.268, 0.261], -[0.337, 0.303, 0.301], -[0.108, 0.089, 0.091], -[0.127, 0.110, 0.114], -[0.714, 0.690, 0.643], -[0.888, 0.835, 0.809], -[0.748, 0.727, 0.704], -[0.666, 0.653, 0.652], -[1.868, 1.821, 1.826], -[1.007, 0.958, 0.957], -[4.466, 4.371, 4.377], -[0.074, 0.027, 0.027], -[0.748, 0.326, 0.307], -[0.844, 0.427, 0.408], -[2.040, 1.545, 1.552], -[1.392, 0.609, 0.560], -[0.237, 0.155, 0.142], -[0.140, 0.112, 0.114], -[0.233, 0.151, 0.146], -[0.790, 0.567, 0.545], -[0.981, 0.751, 0.752], -[3.532, 3.522, 3.516], -[0.505, 0.478, 0.456], -[1.078, 0.959, 0.959], -[5.653, 5.600, 5.570], -[3.572, 3.399, 3.405], -[3.619, 3.445, 3.429], -[1.176, 1.174, 1.165], -[0.140, 0.127, 0.124], -[0.054, 0.052, 0.052], -[0.052, 0.049, 0.048], -[0.275, 0.265, 0.265], -[0.025, 0.024, 0.025], -[0.020, 0.021, 0.019], -[0.006, 0.005, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/amd_ryzen_9_5950x.json b/website/benchmark/hardware/results/amd_ryzen_9_5950x.json deleted file mode 100644 index 0f7f4194960..00000000000 --- a/website/benchmark/hardware/results/amd_ryzen_9_5950x.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AMD Ryzen 9 (2022)", - "system_full": " AMD Ryzen 9 5950X 16-Core Processor, 125Gi RAM", - "time": "2022-05-03 00:00:00", - "kind": "desktop", - "result": - [ - [0.001, 0.001, 0.010], - [0.008, 0.006, 0.006], - [0.017, 0.014, 0.014], - [0.039, 0.022, 0.028], - [0.074, 0.064, 0.074], - [0.150, 0.128, 0.133], - [0.001, 0.001, 0.001], - [0.006, 0.007, 0.006], - [0.215, 0.195, 0.197], - [0.247, 0.221, 0.221], - [0.100, 0.089, 0.098], - [0.112, 0.111, 0.111], - [0.467, 0.445, 0.444], - [0.588, 0.563, 0.554], - [0.521, 0.482, 0.485], - [0.503, 0.616, 0.619], - [1.465, 1.636, 1.630], - [0.907, 0.881, 0.893], - [3.401, 2.832, 2.822], - [0.040, 0.025, 0.022], - [0.465, 0.322, 0.309], - [0.501, 0.356, 0.358], - [1.207, 0.944, 0.938], - [0.869, 0.415, 0.401], - [0.143, 0.101, 0.099], - [0.101, 0.090, 0.090], - [0.139, 0.096, 0.096], - [0.467, 0.332, 0.317], - [0.558, 0.468, 0.464], - [2.288, 2.128, 2.058], - [0.322, 0.285, 0.283], - [0.768, 0.545, 0.537], - [4.126, 4.078, 4.155], - [2.730, 2.511, 2.510], - [2.658, 2.536, 2.566], - [0.877, 0.732, 0.747], - [0.096, 0.085, 0.082], - [0.038, 0.035, 0.036], - [0.038, 0.034, 0.034], - [0.228, 0.218, 0.219], - [0.014, 0.014, 0.016], - [0.016, 0.013, 0.010], - [0.003, 0.006, 0.003] - ] - } -] \ No newline at end of file diff --git a/website/benchmark/hardware/results/amd_ryzen_threadripper.json b/website/benchmark/hardware/results/amd_ryzen_threadripper.json deleted file mode 100644 index d6d239fda14..00000000000 --- a/website/benchmark/hardware/results/amd_ryzen_threadripper.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AMD Ryzen Threadripper PRO 3995WX 64-Cores", - "system_full": "AMD Ryzen Threadripper PRO 3995WX 64-Cores, 256 GB RAM (8-channel)", - "time": "2022-07-11 00:00:00", - "kind": "desktop", - "result": - [ -[0.003, 0.015, 0.002], -[0.259, 0.140, 0.041], -[0.072, 0.047, 0.050], -[0.046, 0.038, 0.020], -[0.107, 0.092, 0.088], -[0.137, 0.122, 0.117], -[0.004, 0.002, 0.001], -[0.054, 0.050, 0.067], -[0.155, 0.132, 0.122], -[0.180, 0.129, 0.134], -[0.103, 0.083, 0.089], -[0.105, 0.088, 0.081], -[0.212, 0.189, 0.177], -[0.244, 0.220, 0.221], -[0.212, 0.183, 0.177], -[0.169, 0.198, 0.192], -[0.492, 0.533, 0.537], -[0.354, 0.365, 0.349], -[1.097, 1.108, 1.075], -[0.105, 0.057, 0.051], -[0.465, 0.246, 0.196], -[0.486, 0.273, 0.178], -[0.867, 0.755, 0.704], -[0.743, 0.290, 0.233], -[0.137, 0.059, 0.057], -[0.079, 0.050, 0.058], -[0.134, 0.067, 0.061], -[0.436, 0.254, 0.244], -[0.392, 0.255, 0.252], -[0.832, 0.834, 0.827], -[0.163, 0.115, 0.115], -[0.318, 0.214, 0.217], -[1.596, 1.535, 1.526], -[1.192, 1.201, 1.028], -[1.075, 0.983, 0.979], -[0.319, 0.365, 0.365], -[0.070, 0.062, 0.065], -[0.045, 0.026, 0.026], -[0.036, 0.030, 0.033], -[0.165, 0.135, 0.140], -[0.032, 0.023, 0.014], -[0.016, 0.010, 0.019], -[0.011, 0.005, 0.008] - ] - } -] diff --git a/website/benchmark/hardware/results/asus_a15.json b/website/benchmark/hardware/results/asus_a15.json deleted file mode 100644 index 983dbde8681..00000000000 --- a/website/benchmark/hardware/results/asus_a15.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Asus A15", - "system_full": "Asus A15 (16 × AMD Ryzen 7 4800H, 16 GiB RAM)", - "time": "2021-03-23 00:00:00", - "kind": "laptop", - "result": - [ -[0.004, 0.003, 0.003], -[0.019, 0.013, 0.012], -[0.053, 0.041, 0.037], -[0.106, 0.057, 0.056], -[0.158, 0.115, 0.110], -[0.324, 0.266, 0.262], -[0.027, 0.024, 0.026], -[0.017, 0.016, 0.017], -[0.644, 0.589, 0.582], -[0.733, 0.679, 0.679], -[0.233, 0.201, 0.197], -[0.276, 0.235, 0.236], -[1.025, 0.962, 0.962], -[1.342, 1.270, 1.264], -[1.170, 1.129, 1.124], -[1.375, 1.346, 1.351], -[3.271, 3.210, 3.242], -[1.960, 1.898, 1.907], -[5.997, 5.965, 5.983], -[0.106, 0.065, 0.055], -[1.264, 0.990, 0.989], -[1.555, 1.241, 1.239], -[3.798, 3.307, 3.280], -[1.949, 1.022, 0.995], -[0.393, 0.292, 0.292], -[0.307, 0.254, 0.255], -[0.378, 0.297, 0.290], -[1.632, 1.399, 1.386], -[2.111, 1.909, 1.900], -[3.349, 3.352, 3.357], -[0.892, 0.824, 0.816], -[1.505, 1.392, 1.378], -[9.105, 8.951, 8.914], -[5.195, 4.975, 4.919], -[5.150, 5.021, 4.955], -[1.756, 1.743, 1.749], -[0.161, 0.154, 0.158], -[0.108, 0.058, 0.055], -[0.101, 0.102, 0.052], -[0.365, 0.309, 0.334], -[0.050, 0.023, 0.023], -[0.037, 0.019, 0.015], -[0.023, 0.013, 0.018] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_a1_4xlarge.json b/website/benchmark/hardware/results/aws_a1_4xlarge.json deleted file mode 100644 index 7367732efa6..00000000000 --- a/website/benchmark/hardware/results/aws_a1_4xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS a1.4xlarge", - "system_full": "AWS a1.4xlarge (Graviton) 16 vCPU, 2.3 GHz, 32 GiB RAM, EBS", - "time": "2020-02-13 00:00:00", - "kind": "cloud", - "result": - [ - [0.012, 0.003, 0.003], - [0.073, 0.031, 0.031], - [0.098, 0.053, 0.053], - [0.209, 0.139, 0.141], - [0.251, 0.200, 0.202], - [0.662, 0.439, 0.436], - [0.062, 0.041, 0.041], - [0.040, 0.033, 0.032], - [3.379, 0.720, 0.722], - [0.934, 0.847, 0.845], - [0.436, 0.379, 0.377], - [0.500, 0.417, 0.430], - [1.536, 1.381, 1.373], - [1.956, 1.832, 1.855], - [1.527, 1.458, 1.466], - [1.613, 1.576, 1.581], - [3.644, 3.490, 3.530], - [2.143, 1.982, 1.965], - [7.808, 7.617, 7.764], - [0.390, 0.179, 0.168], - [8.797, 2.308, 2.257], - [10.138, 2.533, 2.517], - [19.626, 5.738, 5.707], - [20.183, 2.195, 2.156], - [1.841, 0.577, 0.578], - [0.535, 0.479, 0.476], - [1.830, 0.578, 0.577], - [8.786, 2.521, 2.524], - [7.364, 2.941, 2.926], - [3.373, 3.186, 3.203], - [1.641, 1.213, 1.209], - [4.890, 1.964, 1.913], - [10.442, 10.410, 10.427], - [11.183, 7.431, 7.402], - [11.175, 7.460, 7.487], - [2.317, 2.232, 2.221], - [0.473, 0.406, 0.418], - [0.201, 0.187, 0.183], - [0.193, 0.144, 0.160], - [0.901, 0.811, 0.836], - [0.090, 0.046, 0.041], - [0.053, 0.032, 0.033], - [0.015, 0.012, 0.012] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_c5a_24xlarge.json b/website/benchmark/hardware/results/aws_c5a_24xlarge.json deleted file mode 100644 index a2c4c8945ef..00000000000 --- a/website/benchmark/hardware/results/aws_c5a_24xlarge.json +++ /dev/null @@ -1,106 +0,0 @@ -[ - { - "system": "AWS c5a.24xlarge", - "system_full": "AWS c5a.24xlarge 96vCPU 192GiB 200GB gp2 EBS, AMD EPYC 7R32, Amazon Linux", - "time": "2021-11-27 00:00:00", - "kind": "cloud", - "result": - [ -[0.001, 0.001, 0.002], -[0.040, 0.009, 0.008], -[0.034, 0.013, 0.012], -[0.091, 0.018, 0.017], -[0.447, 0.064, 0.063], -[1.068, 0.123, 0.118], -[0.020, 0.010, 0.010], -[0.037, 0.008, 0.008], -[0.406, 0.141, 0.139], -[0.882, 0.158, 0.152], -[0.411, 0.077, 0.093], -[0.570, 0.079, 0.073], -[1.107, 0.196, 0.185], -[1.670, 0.258, 0.244], -[0.788, 0.239, 0.226], -[0.260, 0.236, 0.248], -[1.758, 0.693, 0.686], -[1.271, 0.435, 0.413], -[3.411, 1.279, 1.270], -[0.098, 0.035, 0.029], -[9.300, 0.313, 0.280], -[10.168, 0.287, 0.248], -[19.590, 0.617, 0.558], -[15.743, 0.410, 0.367], -[1.965, 0.086, 0.082], -[0.986, 0.075, 0.067], -[2.481, 0.080, 0.080], -[9.411, 0.270, 0.253], -[7.447, 0.373, 0.339], -[0.878, 0.866, 0.866], -[1.294, 0.168, 0.154], -[4.710, 0.240, 0.223], -[3.982, 1.787, 1.804], -[9.229, 1.079, 1.019], -[9.220, 1.066, 1.023], -[0.407, 0.349, 0.331], -[0.172, 0.149, 0.152], -[0.077, 0.061, 0.061], -[0.078, 0.056, 0.055], -[0.397, 0.342, 0.347], -[0.046, 0.025, 0.025], -[0.032, 0.014, 0.014], -[0.007, 0.010, 0.003] - ] - }, - { - "system": "AWS c5a.24xlarge", - "system_full": "AWS c5a.24xlarge 96vCPU 192GiB 200GB gp2 EBS, AMD EPYC 7R32, Ubuntu 20.04", - "time": "2021-11-27 00:00:00", - "kind": "cloud", - "result": - [ -[0.001, 0.001, 0.002], -[0.030, 0.010, 0.009], -[0.031, 0.013, 0.013], -[0.089, 0.017, 0.016], -[0.495, 0.064, 0.064], -[1.087, 0.126, 0.124], -[0.018, 0.011, 0.011], -[0.036, 0.009, 0.009], -[0.452, 0.138, 0.137], -[0.904, 0.158, 0.160], -[0.452, 0.087, 0.083], -[0.580, 0.088, 0.086], -[1.114, 0.208, 0.197], -[1.642, 0.264, 0.256], -[0.785, 0.256, 0.247], -[0.274, 0.258, 0.262], -[1.726, 0.734, 0.709], -[1.251, 0.450, 0.420], -[3.392, 1.346, 1.288], -[0.096, 0.038, 0.031], -[9.311, 0.301, 0.271], -[10.161, 0.281, 0.255], -[19.575, 0.615, 0.582], -[15.841, 0.376, 0.350], -[1.923, 0.083, 0.079], -[1.005, 0.077, 0.073], -[2.488, 0.085, 0.080], -[9.397, 0.274, 0.257], -[7.437, 0.388, 0.363], -[0.873, 0.870, 0.870], -[1.297, 0.184, 0.167], -[4.685, 0.251, 0.233], -[3.992, 1.809, 1.870], -[9.235, 1.211, 1.063], -[9.217, 1.117, 1.050], -[0.399, 0.329, 0.340], -[0.183, 0.163, 0.166], -[0.080, 0.069, 0.070], -[0.086, 0.060, 0.059], -[0.436, 0.366, 0.376], -[0.050, 0.025, 0.032], -[0.024, 0.023, 0.014], -[0.016, 0.004, 0.021] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_c5metal_100.json b/website/benchmark/hardware/results/aws_c5metal_100.json deleted file mode 100644 index 4bb0a1f1f52..00000000000 --- a/website/benchmark/hardware/results/aws_c5metal_100.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS c5.metal 100GB", - "system_full": "AWS c5.metal 96vCPU 192GiB 100GB SSD", - "time": "2020-01-17 00:00:00", - "kind": "cloud", - "result": - [ - [0.013, 0.003, 0.002], - [0.056, 0.019, 0.018], - [0.107, 0.028, 0.028], - [0.812, 0.033, 0.033], - [1.609, 0.094, 0.092], - [2.326, 0.132, 0.132], - [0.038, 0.022, 0.024], - [0.024, 0.018, 0.018], - [1.550, 0.173, 0.169], - [2.113, 0.192, 0.184], - [1.236, 0.097, 0.093], - [1.507, 0.101, 0.100], - [2.331, 0.251, 0.240], - [3.702, 0.310, 0.296], - [2.155, 0.296, 0.280], - [1.051, 0.317, 0.332], - [3.714, 0.737, 0.710], - [3.429, 0.469, 0.451], - [6.934, 1.432, 1.397], - [0.687, 0.050, 0.038], - [18.873, 0.327, 0.258], - [20.783, 0.331, 0.310], - [38.997, 0.780, 0.754], - [39.683, 0.437, 0.387], - [4.477, 0.104, 0.094], - [2.286, 0.096, 0.094], - [5.133, 0.101, 0.095], - [18.727, 0.323, 0.311], - [15.252, 0.461, 0.445], - [0.892, 0.835, 0.843], - [3.458, 0.238, 0.230], - [9.557, 0.351, 0.340], - [7.828, 2.075, 2.031], - [18.546, 1.063, 1.000], - [18.519, 1.187, 1.136], - [0.517, 0.425, 0.427], - [0.269, 0.175, 0.177], - [0.109, 0.083, 0.081], - [0.112, 0.075, 0.076], - [0.551, 0.379, 0.351], - [0.085, 0.047, 0.047], - [0.072, 0.038, 0.034], - [0.021, 0.022, 0.008] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_c5metal_300.json b/website/benchmark/hardware/results/aws_c5metal_300.json deleted file mode 100644 index 87435f6fb45..00000000000 --- a/website/benchmark/hardware/results/aws_c5metal_300.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS c5.metal 300GB", - "system_full": "AWS c5.metal 96vCPU 192GiB 300GB SSD", - "time": "2020-09-23 00:00:00", - "kind": "cloud", - "result": - [ -[0.012, 0.002, 0.002], -[0.066, 0.018, 0.018], -[0.066, 0.028, 0.027], -[0.186, 0.033, 0.031], -[0.362, 0.095, 0.093], -[1.092, 0.141, 0.142], -[0.035, 0.020, 0.021], -[0.023, 0.018, 0.018], -[0.303, 0.176, 0.181], -[0.817, 0.198, 0.198], -[0.322, 0.091, 0.092], -[0.600, 0.098, 0.098], -[1.059, 0.265, 0.253], -[1.542, 0.318, 0.310], -[0.682, 0.286, 0.283], -[0.372, 0.320, 0.295], -[1.610, 0.832, 0.750], -[1.301, 0.492, 0.458], -[3.446, 1.361, 1.330], -[0.189, 0.050, 0.035], -[9.246, 0.338, 0.265], -[10.163, 0.277, 0.249], -[19.616, 0.663, 0.639], -[20.068, 0.418, 0.367], -[1.812, 0.097, 0.093], -[0.976, 0.090, 0.083], -[2.458, 0.097, 0.095], -[9.397, 0.344, 0.323], -[7.320, 0.415, 0.413], -[0.780, 0.753, 0.748], -[1.328, 0.226, 0.223], -[4.643, 0.339, 0.329], -[4.136, 2.049, 2.021], -[9.213, 1.080, 0.923], -[9.192, 1.019, 0.959], -[0.410, 0.360, 0.378], -[0.244, 0.155, 0.163], -[0.102, 0.077, 0.071], -[0.045, 0.055, 0.049], -[0.459, 0.318, 0.316], -[0.069, 0.033, 0.026], -[0.035, 0.027, 0.020], -[0.019, 0.009, 0.010] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_c6a_metal.json b/website/benchmark/hardware/results/aws_c6a_metal.json deleted file mode 100644 index adaf3cce48d..00000000000 --- a/website/benchmark/hardware/results/aws_c6a_metal.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS c6a.metal", - "system_full": "AWS c6a.metal 192 vCPU (96 cores) 384GiB RAM, 200 GB EBS", - "time": "2022-06-25 00:00:00", - "kind": "cloud", - "result": - [ -[0.031, 0.044, 0.001], -[0.050, 0.013, 0.012], -[0.061, 0.017, 0.017], -[0.117, 0.019, 0.021], -[0.280, 0.099, 0.121], -[0.802, 0.113, 0.225], -[0.020, 0.001, 0.001], -[0.024, 0.014, 0.013], -[0.243, 0.116, 0.115], -[0.886, 0.133, 0.129], -[0.325, 0.102, 0.107], -[0.369, 0.093, 0.098], -[0.883, 0.186, 0.178], -[1.532, 0.220, 0.221], -[0.727, 0.208, 0.196], -[0.215, 0.181, 0.184], -[1.676, 0.419, 0.413], -[1.324, 0.306, 0.300], -[3.088, 0.880, 0.765], -[0.119, 0.038, 0.026], -[8.946, 0.218, 0.135], -[9.994, 0.162, 0.134], -[19.043, 0.603, 0.616], -[14.138, 0.257, 0.222], -[1.821, 0.061, 0.048], -[0.856, 0.044, 0.046], -[2.364, 0.064, 0.050], -[9.074, 0.303, 0.291], -[7.130, 0.315, 0.307], -[0.430, 0.400, 0.412], -[1.321, 0.119, 0.114], -[4.649, 0.207, 0.183], -[3.786, 0.861, 0.848], -[8.971, 0.681, 0.652], -[8.974, 0.661, 0.645], -[0.251, 0.230, 0.223], -[0.112, 0.066, 0.073], -[0.053, 0.029, 0.031], -[0.069, 0.031, 0.028], -[0.230, 0.168, 0.171], -[0.044, 0.018, 0.020], -[0.035, 0.015, 0.020], -[0.021, 0.008, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_c6g_16xlarge.json b/website/benchmark/hardware/results/aws_c6g_16xlarge.json deleted file mode 100644 index 364b40f657a..00000000000 --- a/website/benchmark/hardware/results/aws_c6g_16xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS c6g.16xlarge (Graviton 2)", - "system_full": "AWS c6g.16xlarge (Graviton 2) 64 vCPU, 128 GiB RAM, EBS", - "time": "2022-05-24 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.002, 0.002], -[0.051, 0.024, 0.026], -[0.037, 0.021, 0.028], -[0.102, 0.065, 0.061], -[0.243, 0.080, 0.080], -[0.976, 0.138, 0.138], -[0.003, 0.002, 0.002], -[0.044, 0.040, 0.039], -[0.204, 0.145, 0.146], -[0.799, 0.165, 0.165], -[0.306, 0.095, 0.095], -[0.523, 0.101, 0.096], -[0.973, 0.226, 0.224], -[1.520, 0.282, 0.277], -[0.645, 0.239, 0.236], -[0.260, 0.312, 0.280], -[1.535, 0.660, 0.629], -[1.426, 0.470, 0.427], -[3.456, 1.372, 1.138], -[0.147, 0.119, 0.079], -[9.101, 0.406, 0.358], -[10.117, 0.330, 0.323], -[19.495, 0.756, 0.748], -[16.173, 1.500, 1.532], -[1.832, 0.105, 0.094], -[0.836, 0.092, 0.090], -[2.363, 0.108, 0.099], -[9.269, 0.367, 0.363], -[7.317, 0.422, 0.414], -[0.918, 1.020, 1.058], -[1.347, 0.210, 0.209], -[4.535, 0.343, 0.335], -[4.288, 2.411, 2.501], -[9.310, 1.240, 1.172], -[9.301, 1.209, 1.205], -[0.446, 0.428, 0.421], -[0.245, 0.207, 0.202], -[0.107, 0.091, 0.098], -[0.112, 0.095, 0.101], -[0.546, 0.485, 0.444], -[0.061, 0.049, 0.037], -[0.041, 0.035, 0.033], -[0.006, 0.005, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_c6g_2xlarge.json b/website/benchmark/hardware/results/aws_c6g_2xlarge.json deleted file mode 100644 index e970e73b919..00000000000 --- a/website/benchmark/hardware/results/aws_c6g_2xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS c6g.2xlarge (Graviton 2)", - "system_full": "AWS c6g.2xlarge (Graviton 2) 8 vCPU, 16 GiB RAM, EBS", - "time": "2021-11-21 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.001, 0.001], -[0.035, 0.022, 0.022], -[0.084, 0.045, 0.045], -[0.745, 0.056, 0.056], -[1.397, 0.126, 0.125], -[2.237, 0.498, 0.499], -[0.063, 0.052, 0.052], -[0.025, 0.023, 0.023], -[1.295, 0.544, 0.542], -[1.655, 0.656, 0.655], -[0.769, 0.317, 0.316], -[1.043, 0.362, 0.360], -[1.938, 0.861, 0.854], -[3.563, 1.131, 1.104], -[2.152, 1.036, 1.014], -[0.967, 0.917, 0.900], -[4.259, 2.678, 2.724], -[3.342, 1.741, 1.704], -[8.303, 4.812, 4.794], -[0.680, 0.079, 0.074], -[18.753, 1.239, 1.224], -[20.694, 1.362, 1.333], -[38.987, 2.967, 2.937], -[31.357, 1.420, 1.404], -[4.471, 0.421, 0.413], -[1.633, 0.360, 0.358], -[4.554, 0.423, 0.417], -[18.076, 1.133, 1.118], -[15.164, 1.762, 1.747], -[0.678, 0.656, 0.651], -[3.504, 0.829, 0.817], -[9.359, 1.104, 1.088], -[8.794, 5.886, 5.848], -[19.039, 4.025, 4.007], -[19.061, 4.015, 4.053], -[1.289, 1.194, 1.194], -[0.287, 0.217, 0.208], -[0.113, 0.094, 0.092], -[0.111, 0.084, 0.086], -[0.539, 0.447, 0.439], -[0.072, 0.033, 0.025], -[0.042, 0.019, 0.039], -[0.005, 0.011, 0.004] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_c6i_32xlarge.json b/website/benchmark/hardware/results/aws_c6i_32xlarge.json deleted file mode 100644 index 5ff4bb5bfa8..00000000000 --- a/website/benchmark/hardware/results/aws_c6i_32xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS c6i.32xlarge", - "system_full": "AWS c6i.32xlarge 128vCPU 256GiB 200GB gp2 EBS, Xeon(R) Platinum 8375C CPU @ 2.90GHz, Amazon Linux", - "time": "2021-11-27 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.001, 0.001], -[0.034, 0.006, 0.005], -[0.031, 0.008, 0.008], -[0.079, 0.013, 0.012], -[0.495, 0.070, 0.070], -[1.063, 0.122, 0.124], -[0.015, 0.007, 0.007], -[0.042, 0.006, 0.006], -[0.397, 0.143, 0.140], -[0.870, 0.151, 0.147], -[0.404, 0.072, 0.074], -[0.663, 0.069, 0.067], -[1.052, 0.186, 0.174], -[1.688, 0.225, 0.213], -[0.836, 0.206, 0.207], -[0.315, 0.202, 0.201], -[1.812, 0.514, 0.502], -[1.282, 0.303, 0.312], -[3.392, 1.072, 1.051], -[0.078, 0.052, 0.028], -[9.317, 0.324, 0.259], -[10.195, 0.246, 0.210], -[19.674, 0.493, 0.490], -[15.990, 0.304, 0.308], -[2.081, 0.062, 0.061], -[1.022, 0.054, 0.052], -[2.507, 0.076, 0.062], -[9.436, 0.239, 0.241], -[7.490, 0.273, 0.260], -[0.440, 0.408, 0.413], -[1.283, 0.142, 0.139], -[4.740, 0.214, 0.204], -[3.855, 0.947, 0.892], -[9.066, 0.734, 0.725], -[9.062, 0.749, 0.681], -[0.273, 0.244, 0.244], -[0.171, 0.132, 0.138], -[0.074, 0.059, 0.055], -[0.088, 0.055, 0.053], -[0.406, 0.312, 0.316], -[0.054, 0.022, 0.015], -[0.034, 0.017, 0.012], -[0.005, 0.008, 0.002] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_c6metal.json b/website/benchmark/hardware/results/aws_c6metal.json deleted file mode 100644 index 83e75506ad9..00000000000 --- a/website/benchmark/hardware/results/aws_c6metal.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS c6.metal (Graviton 2)", - "system_full": "AWS c6.metal (Graviton 2) 64 CPU 128GiB 2x1.7TB local SSD md-RAID-0", - "time": "2020-09-23 00:00:00", - "kind": "cloud", - "result": - [ -[0.004, 0.003, 0.001], -[0.085, 0.030, 0.032], -[0.029, 0.028, 0.026], -[0.047, 0.068, 0.070], -[0.090, 0.075, 0.079], -[0.140, 0.126, 0.124], -[0.018, 0.013, 0.012], -[0.032, 0.021, 0.032], -[0.154, 0.139, 0.138], -[0.204, 0.155, 0.156], -[0.101, 0.091, 0.090], -[0.104, 0.104, 0.100], -[0.223, 0.203, 0.203], -[0.273, 0.255, 0.253], -[0.232, 0.212, 0.213], -[0.230, 0.223, 0.223], -[0.506, 0.484, 0.483], -[0.334, 0.330, 0.316], -[1.139, 1.085, 1.088], -[0.065, 0.077, 0.054], -[0.484, 0.315, 0.315], -[0.545, 0.295, 0.291], -[0.980, 0.661, 1.476], -[1.415, 1.101, 0.675], -[0.150, 0.086, 0.085], -[0.094, 0.077, 0.078], -[0.150, 0.087, 0.086], -[0.478, 0.348, 0.346], -[0.424, 0.403, 0.399], -[1.435, 1.388, 1.417], -[0.215, 0.178, 0.178], -[0.378, 0.294, 0.289], -[1.669, 1.590, 1.596], -[1.105, 1.007, 1.010], -[1.074, 1.041, 1.014], -[0.339, 0.323, 0.323], -[0.210, 0.199, 0.204], -[0.096, 0.091, 0.092], -[0.084, 0.080, 0.079], -[0.425, 0.405, 0.423], -[0.034, 0.025, 0.022], -[0.022, 0.019, 0.018], -[0.007, 0.007, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_c7g_16xlarge.json b/website/benchmark/hardware/results/aws_c7g_16xlarge.json deleted file mode 100644 index 91230ecceee..00000000000 --- a/website/benchmark/hardware/results/aws_c7g_16xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS c7g.16xlarge (Graviton 3)", - "system_full": "AWS c7g.16xlarge (Graviton 3) 64 vCPU, 128 GiB RAM, EBS", - "time": "2022-05-24 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.002, 0.002], -[0.031, 0.022, 0.023], -[0.066, 0.025, 0.025], -[0.240, 0.061, 0.059], -[0.328, 0.073, 0.076], -[0.955, 0.101, 0.098], -[0.002, 0.002, 0.002], -[0.035, 0.030, 0.030], -[0.499, 0.113, 0.115], -[0.704, 0.127, 0.127], -[0.452, 0.070, 0.070], -[0.613, 0.074, 0.072], -[1.060, 0.147, 0.144], -[1.749, 0.190, 0.187], -[0.933, 0.176, 0.175], -[0.408, 0.206, 0.188], -[1.714, 0.476, 0.464], -[1.391, 0.349, 0.307], -[3.271, 0.876, 0.719], -[0.375, 0.079, 0.071], -[9.094, 0.270, 0.293], -[10.251, 0.236, 0.222], -[19.763, 0.783, 0.839], -[16.380, 1.164, 1.192], -[1.861, 0.112, 0.114], -[0.863, 0.062, 0.060], -[2.499, 0.103, 0.113], -[9.448, 0.257, 0.245], -[7.546, 0.288, 0.285], -[0.822, 0.837, 0.837], -[1.352, 0.151, 0.142], -[4.743, 0.224, 0.214], -[3.807, 1.236, 1.366], -[10.096, 0.805, 0.780], -[9.191, 0.830, 0.792], -[0.320, 0.304, 0.294], -[0.209, 0.143, 0.175], -[0.099, 0.066, 0.068], -[0.141, 0.073, 0.064], -[0.499, 0.386, 0.372], -[0.061, 0.030, 0.032], -[0.035, 0.030, 0.028], -[0.016, 0.016, 0.004] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_i3_8xlarge.json b/website/benchmark/hardware/results/aws_i3_8xlarge.json deleted file mode 100644 index f95ca13b454..00000000000 --- a/website/benchmark/hardware/results/aws_i3_8xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS i3.8xlarge", - "system_full": "AWS i3.8xlarge 32vCPU 244GiB 4x1900 NVMe SSD", - "time": "2020-01-15 00:00:00", - "kind": "cloud", - "result": - [ - [0.009, 0.002, 0.002], - [0.053, 0.040, 0.021], - [0.043, 0.028, 0.027], - [0.109, 0.036, 0.035], - [0.147, 0.108, 0.100], - [0.296, 0.239, 0.239], - [0.017, 0.013, 0.015], - [0.013, 0.010, 0.011], - [0.524, 0.460, 0.445], - [0.589, 0.519, 0.510], - [0.186, 0.142, 0.140], - [0.210, 0.167, 0.164], - [0.659, 0.584, 0.529], - [0.781, 0.679, 0.665], - [0.709, 0.630, 0.613], - [0.642, 0.590, 0.588], - [1.723, 1.564, 1.557], - [1.027, 0.925, 0.909], - [3.618, 3.432, 3.411], - [0.123, 0.037, 0.049], - [1.318, 0.587, 0.570], - [1.368, 0.655, 0.646], - [2.847, 1.518, 1.495], - [2.431, 0.812, 0.764], - [0.366, 0.213, 0.193], - [0.237, 0.167, 0.158], - [0.374, 0.204, 0.211], - [1.310, 0.590, 0.597], - [1.260, 0.877, 0.870], - [1.966, 1.952, 1.967], - [0.692, 0.571, 0.566], - [1.080, 0.823, 0.827], - [5.017, 4.816, 4.843], - [3.072, 2.661, 2.726], - [3.006, 2.711, 2.688], - [1.071, 0.999, 1.024], - [0.231, 0.221, 0.221], - [0.094, 0.090, 0.086], - [0.093, 0.085, 0.075], - [0.488, 0.432, 0.451], - [0.046, 0.029, 0.030], - [0.030, 0.023, 0.022], - [0.012, 0.007, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_i3en_24xlarge.json b/website/benchmark/hardware/results/aws_i3en_24xlarge.json deleted file mode 100644 index e4e063fa3e2..00000000000 --- a/website/benchmark/hardware/results/aws_i3en_24xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS i3en.24xlarge", - "system_full": "AWS i3en.24xlarge 96vCPU 768GiB 8x7500 NVMe SSD", - "time": "2020-01-15 00:00:00", - "kind": "cloud", - "result": - [ - [0.010, 0.002, 0.002], - [0.067, 0.009, 0.009], - [0.040, 0.014, 0.013], - [0.120, 0.017, 0.017], - [0.159, 0.076, 0.077], - [0.240, 0.116, 0.119], - [0.020, 0.010, 0.009], - [0.015, 0.010, 0.009], - [0.279, 0.195, 0.197], - [0.299, 0.230, 0.258], - [0.199, 0.088, 0.111], - [0.185, 0.094, 0.094], - [0.327, 0.212, 0.206], - [0.439, 0.271, 0.267], - [0.370, 0.281, 0.280], - [0.367, 0.306, 0.312], - [1.092, 0.931, 1.022], - [0.533, 0.599, 0.413], - [1.629, 1.921, 1.572], - [0.130, 0.031, 0.026], - [1.451, 0.264, 0.269], - [1.714, 0.273, 0.261], - [3.668, 0.636, 0.669], - [3.837, 0.472, 0.402], - [0.378, 0.107, 0.079], - [0.199, 0.070, 0.088], - [0.381, 0.104, 0.086], - [1.426, 0.284, 0.272], - [1.246, 0.363, 0.360], - [0.737, 0.708, 0.741], - [0.426, 0.246, 0.284], - [0.877, 0.420, 0.384], - [2.698, 2.390, 2.375], - [1.918, 1.223, 1.122], - [1.909, 1.234, 1.217], - [0.486, 0.482, 0.473], - [0.235, 0.187, 0.200], - [0.083, 0.069, 0.072], - [0.111, 0.063, 0.062], - [0.473, 0.433, 0.406], - [0.050, 0.028, 0.027], - [0.038, 0.022, 0.021], - [0.012, 0.006, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_lightsail_4vcpu.json b/website/benchmark/hardware/results/aws_lightsail_4vcpu.json deleted file mode 100644 index ab55813b8e8..00000000000 --- a/website/benchmark/hardware/results/aws_lightsail_4vcpu.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS Lightsail 4vCPU", - "system_full": "AWS Lightsail E5-2686 v4 @ 2.30GHz, 16 GiB RAM", - "time": "2020-07-02 00:00:00", - "kind": "cloud", - "result": - [ - [0.002, 0.001, 0.001], - [0.046, 0.026, 0.025], - [0.156, 0.077, 0.078], - [0.746, 0.098, 0.095], - [1.383, 0.233, 0.218], - [2.161, 0.646, 0.626], - [0.041, 0.037, 0.038], - [0.032, 0.029, 0.026], - [1.494, 1.190, 1.159], - [1.843, 1.354, 1.357], - [0.841, 0.375, 0.375], - [1.254, 0.446, 0.448], - [2.235, 1.792, 1.746], - [4.175, 2.354, 2.315], - [2.602, 2.075, 2.042], - [2.258, 2.085, 2.058], - [6.402, 5.909, 5.895], - [4.178, 3.618, 3.670], - [12.978, 12.037, 11.764], - [0.754, 0.107, 0.102], - [19.615, 1.888, 1.868], - [21.740, 2.208, 2.171], - [41.009, 5.277, 5.245], - [38.068, 2.475, 2.435], - [4.739, 0.693, 0.680], - [1.766, 0.549, 0.542], - [4.730, 0.684, 0.672], - [19.010, 1.849, 1.811], - [15.999, 3.086, 3.099], - [3.655, 3.609, 3.593], - [3.967, 1.768, 1.836], - [10.566, 3.036, 2.963], - [20.065, 19.091, null], - [21.474, 8.597, 8.501], - [21.484, 8.563, 8.533], - [3.850, 3.487, 3.477], - [0.408, 0.240, 0.239], - [0.125, 0.087, 0.084], - [0.132, 0.073, 0.073], - [0.685, 0.471, 0.480], - [0.089, 0.028, 0.025], - [0.044, 0.027, 0.018], - [0.007, 0.007, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_m5a_4xlarge.json b/website/benchmark/hardware/results/aws_m5a_4xlarge.json deleted file mode 100644 index 48d7168eec5..00000000000 --- a/website/benchmark/hardware/results/aws_m5a_4xlarge.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "system": "AWS m5a.4xlarge", - "system_full": "AWS m5a.4xlarge 16 vCPU, 512 GiB RAM, EBS GP3", - "time": "2020-08-13 00:00:00", - "kind": "cloud", - "result": - [ - [0.002, 0.002, 0.002], - [0.028, 0.017, 0.016], - [0.074, 0.040, 0.042], - [0.758, 0.061, 0.062], - [1.380, 0.151, 0.147], - [2.204, 0.394, 0.385], - [0.002, 0.002, 0.002], - [0.019, 0.018, 0.018], - [1.385, 0.751, 0.740], - [1.754, 0.873, 0.870], - [0.817, 0.222, 0.218], - [1.219, 0.268, 0.263], - [2.303, 1.092, 1.057], - [3.892, 1.431, 1.420], - [2.388, 1.199, 1.188], - [1.439, 1.329, 1.339], - [5.108, 3.816, 3.799], - [3.549, 2.273, 2.271], - [9.976, 7.023, 7.043], - [0.706, 0.068, 0.066], - [19.187, 1.101, 1.076], - [21.210, 1.315, 1.276], - [39.984, 3.065, 3.043], - [32.102, 1.328, 1.295], - [4.599, 0.341, 0.343], - [1.762, 0.294, 0.287], - [4.786, 0.345, 0.338], - [18.596, 1.190, 1.175], - [15.549, 1.973, 1.931], - [3.643, 3.628, 3.625], - [3.658, 0.781, 0.777], - [9.786, 1.383, 1.381], - [11.912, 10.221, 10.227], - [20.664, 5.942, 5.954], - [20.707, 6.092, 5.920], - [1.847, 1.758, 1.756], - [0.292, 0.221, 0.216], - [0.109, 0.086, 0.085], - [0.118, 0.084, 0.074], - [0.542, 0.452, 0.445], - [0.060, 0.026, 0.025], - [0.031, 0.019, 0.020], - [0.007, 0.005, 0.007] - ] - } -] - diff --git a/website/benchmark/hardware/results/aws_m5a_8xlarge.json b/website/benchmark/hardware/results/aws_m5a_8xlarge.json deleted file mode 100644 index 8a231be0ede..00000000000 --- a/website/benchmark/hardware/results/aws_m5a_8xlarge.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "system": "AWS m5a.8xlarge", - "system_full": "AWS m5a.8xlarge 32 vCPU, 128 GiB RAM, EBS", - "time": "2020-08-13 00:00:00", - "kind": "cloud", - "result": - [ - [0.015, 0.001, 0.001], - [0.038, 0.015, 0.015], - [0.072, 0.033, 0.033], - [0.198, 0.052, 0.051], - [0.338, 0.166, 0.120], - [1.036, 0.252, 0.264], - [0.034, 0.020, 0.020], - [0.020, 0.015, 0.015], - [0.521, 0.414, 0.391], - [0.563, 0.496, 0.497], - [0.242, 0.143, 0.143], - [0.261, 0.168, 0.168], - [1.067, 0.574, 0.567], - [1.537, 0.772, 0.759], - [0.802, 0.736, 0.752], - [0.904, 0.797, 0.742], - [2.229, 1.795, 1.867], - [1.314, 0.987, 0.962], - [5.216, 4.149, 5.540], - [0.208, 0.057, 0.036], - [9.238, 0.660, 0.609], - [10.105, 0.685, 0.668], - [19.544, 1.851, 1.766], - [19.690, 0.943, 0.817], - [1.834, 0.228, 0.217], - [0.757, 0.177, 0.176], - [2.331, 0.224, 0.214], - [9.174, 0.728, 0.688], - [7.330, 1.130, 1.108], - [1.834, 1.810, 1.818], - [1.429, 0.547, 0.550], - [4.518, 0.879, 0.895], - [6.157, 5.540, 5.547], - [9.846, 3.033, 3.044], - [9.847, 3.061, 3.016], - [1.157, 1.086, 1.117], - [0.238, 0.169, 0.175], - [0.094, 0.072, 0.072], - [0.041, 0.037, 0.041], - [0.453, 0.364, 0.345], - [0.054, 0.015, 0.019], - [0.024, 0.010, 0.010], - [0.012, 0.006, 0.007] - ] - } -] - diff --git a/website/benchmark/hardware/results/aws_m5ad_24xlarge.json b/website/benchmark/hardware/results/aws_m5ad_24xlarge.json deleted file mode 100644 index e4e60ef91e1..00000000000 --- a/website/benchmark/hardware/results/aws_m5ad_24xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS m5ad.24xlarge", - "system_full": "AWS m5ad.24xlarge 96vCPU 384GiB 4x900 NVMe SSD, AMD EPYC 7000 series 2.5 GHz", - "time": "2020-01-17 00:00:00", - "kind": "cloud", - "result": - [ - [0.013, 0.002, 0.002], - [0.055, 0.020, 0.025], - [0.054, 0.027, 0.026], - [0.154, 0.035, 0.035], - [0.221, 0.117, 0.118], - [0.325, 0.171, 0.166], - [0.042, 0.021, 0.017], - [0.025, 0.017, 0.018], - [0.353, 0.253, 0.253], - [0.477, 0.610, 0.720], - [0.257, 0.154, 0.139], - [0.251, 0.130, 0.114], - [0.513, 0.293, 0.286], - [0.618, 0.360, 0.350], - [0.468, 0.336, 0.329], - [0.390, 0.333, 0.411], - [1.112, 0.936, 1.497], - [2.434, 1.350, 0.886], - [2.590, 2.069, 2.331], - [0.160, 0.048, 0.036], - [1.638, 0.334, 0.312], - [1.841, 0.423, 0.373], - [3.673, 1.122, 1.078], - [3.808, 0.912, 0.494], - [0.480, 0.112, 0.120], - [0.248, 0.107, 0.099], - [0.470, 0.118, 0.114], - [1.648, 0.544, 0.469], - [1.418, 0.583, 0.624], - [0.966, 1.231, 0.999], - [0.539, 0.311, 0.370], - [1.159, 0.712, 0.716], - [3.755, 2.772, 2.973], - [2.748, 2.033, 2.242], - [2.842, 2.150, 2.019], - [0.784, 0.616, 0.641], - [0.304, 0.273, 0.235], - [0.106, 0.086, 0.093], - [0.117, 0.073, 0.075], - [0.604, 0.453, 0.502], - [0.050, 0.036, 0.034], - [0.043, 0.023, 0.027], - [0.013, 0.008, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_m5d_24xlarge.json b/website/benchmark/hardware/results/aws_m5d_24xlarge.json deleted file mode 100644 index fdb0b3b6875..00000000000 --- a/website/benchmark/hardware/results/aws_m5d_24xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS m5d.24xlarge", - "system_full": "AWS m5d.24xlarge 96vCPU 384GiB 4x900 NVMe SSD", - "time": "2020-01-15 00:00:00", - "kind": "cloud", - "result": - [ - [0.012, 0.002, 0.002], - [0.061, 0.017, 0.008], - [0.043, 0.014, 0.014], - [0.160, 0.017, 0.016], - [0.193, 0.074, 0.075], - [0.300, 0.120, 0.118], - [0.023, 0.009, 0.009], - [0.015, 0.009, 0.009], - [0.321, 0.206, 0.203], - [0.351, 0.238, 0.244], - [0.205, 0.113, 0.112], - [0.211, 0.106, 0.091], - [0.394, 0.213, 0.211], - [0.519, 0.270, 0.259], - [0.439, 0.292, 0.286], - [0.394, 0.301, 0.296], - [1.195, 0.829, 0.806], - [0.561, 0.743, 0.418], - [1.841, 1.660, 1.650], - [0.163, 0.041, 0.026], - [1.632, 0.251, 0.269], - [1.885, 0.265, 0.265], - [3.425, 0.644, 0.620], - [3.839, 0.431, 0.367], - [0.486, 0.092, 0.086], - [0.256, 0.081, 0.091], - [0.493, 0.107, 0.106], - [1.646, 0.275, 0.255], - [1.445, 0.332, 0.332], - [0.768, 0.702, 0.721], - [0.509, 0.280, 0.268], - [1.071, 0.382, 0.374], - [2.800, 2.452, 2.389], - [2.159, 1.134, 1.181], - [2.153, 1.145, 1.200], - [0.516, 0.457, 0.493], - [0.256, 0.182, 0.188], - [0.091, 0.073, 0.070], - [0.121, 0.063, 0.064], - [0.506, 0.399, 0.421], - [0.055, 0.030, 0.027], - [0.041, 0.019, 0.023], - [0.016, 0.006, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/aws_m6g_16xlarge.json b/website/benchmark/hardware/results/aws_m6g_16xlarge.json deleted file mode 100644 index a0d15a0d384..00000000000 --- a/website/benchmark/hardware/results/aws_m6g_16xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS m6g.16xlarge (Graviton 2)", - "system_full": "AWS m6g.16xlarge (Graviton 2) 64 vCPU, 256 GiB RAM, EBS", - "time": "2020-02-13 00:00:00", - "kind": "cloud", - "result": - [ - [0.012, 0.003, 0.003], - [0.047, 0.028, 0.028], - [0.102, 0.025, 0.025], - [0.847, 0.061, 0.062], - [1.487, 0.078, 0.079], - [2.404, 0.149, 0.149], - [0.032, 0.016, 0.016], - [0.035, 0.030, 0.030], - [1.597, 0.146, 0.142], - [2.192, 0.162, 0.163], - [1.307, 0.102, 0.102], - [1.524, 0.102, 0.101], - [2.387, 0.217, 0.216], - [3.773, 0.267, 0.266], - [2.206, 0.231, 0.224], - [1.163, 0.227, 0.225], - [3.870, 0.517, 0.513], - [3.416, 0.348, 0.340], - [6.786, 1.266, 1.231], - [0.673, 0.058, 0.096], - [18.821, 0.346, 0.313], - [20.684, 0.319, 0.312], - [38.918, 0.684, 0.653], - [39.697, 0.990, 1.000], - [4.472, 0.107, 0.096], - [2.260, 0.092, 0.089], - [5.153, 0.106, 0.103], - [18.709, 0.341, 0.334], - [15.166, 0.398, 0.399], - [0.849, 1.224, 1.272], - [3.447, 0.209, 0.205], - [9.573, 0.327, 0.315], - [7.621, 1.857, 1.841], - [18.441, 1.167, 1.070], - [18.453, 1.208, 1.072], - [0.430, 0.329, 0.334], - [0.258, 0.215, 0.222], - [0.122, 0.101, 0.100], - [0.120, 0.081, 0.084], - [0.553, 0.449, 0.448], - [0.059, 0.026, 0.030], - [0.035, 0.031, 0.019], - [0.010, 0.007, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/azure_ds3v2.json b/website/benchmark/hardware/results/azure_ds3v2.json deleted file mode 100644 index 718279dbac1..00000000000 --- a/website/benchmark/hardware/results/azure_ds3v2.json +++ /dev/null @@ -1,106 +0,0 @@ -[ - { - "system": "Azure DS3v2 Standard SSD", - "system_full": "Azure DS3v2 4vcpu 14GB RAM 1TB Standard SSD", - "time": "2020-01-15 00:00:00", - "kind": "cloud", - "result": - [ - [0.709, 0.004, 0.004], - [1.052, 0.028, 0.025], - [2.075, 0.077, 0.080], - [2.700, 0.104, 0.101], - [2.858, 0.267, 0.259], - [4.058, 0.737, 0.718], - [0.597, 0.038, 0.049], - [0.598, 0.025, 0.024], - [3.786, 1.324, 1.313], - [3.982, 1.579, 1.562], - [2.995, 0.395, 0.395], - [3.279, 0.467, 0.470], - [4.301, 1.674, 1.690], - [6.499, 2.126, 2.132], - [4.774, 1.886, 1.927], - [3.484, 1.872, 1.818], - [7.813, 4.801, 5.006], - [6.032, 3.162, 3.106], - [13.991, 10.573, 10.665], - [2.750, 0.118, 0.101], - [25.608, 1.978, 1.960], - [29.117, 2.297, 2.303], - [53.220, 5.367, 5.325], - [51.767, 2.669, 2.465], - [7.509, 0.890, 0.865], - [3.827, 0.666, 0.653], - [7.574, 0.918, 0.899], - [25.753, 1.904, 1.898], - [21.624, 3.269, 3.192], - [5.454, 4.966, 4.975], - [6.569, 1.870, 1.912], - [14.536, 2.844, 2.863], - [18.908, 16.591, 16.820], - [27.527, 7.790, 7.738], - [27.556, 7.694, 7.695], - [4.168, 3.568, 3.426], - [1.185, 0.307, 0.252], - [0.483, 0.096, 0.093], - [0.519, 0.086, 0.088], - [1.274, 0.525, 0.496], - [1.048, 0.033, 0.034], - [0.379, 0.027, 0.036], - [0.599, 0.010, 0.009] - ] - }, - { - "system": "Azure DS3v2 Premium SSD", - "system_full": "Azure DS3v2 4vcpu 14GB RAM 1TB Premium SSD", - "time": "2020-01-15 00:00:00", - "kind": "cloud", - "result": - [ - [0.047, 0.004, 0.003], - [0.078, 0.023, 0.023], - [0.312, 0.077, 0.077], - [1.202, 0.105, 0.103], - [1.216, 0.260, 0.264], - [1.896, 0.751, 0.726], - [0.122, 0.041, 0.038], - [0.095, 0.028, 0.025], - [1.848, 1.304, 1.375], - [2.104, 1.534, 1.535], - [1.298, 0.394, 0.397], - [1.363, 0.469, 0.479], - [2.296, 1.728, 1.650], - [3.540, 2.320, 2.177], - [2.542, 1.863, 1.847], - [2.047, 1.861, 1.873], - [5.203, 4.830, 4.882], - [3.466, 3.131, 3.197], - [10.795, 10.396, 10.516], - [1.244, 0.111, 0.105], - [13.163, 2.019, 1.932], - [14.969, 2.346, 2.340], - [27.664, 5.259, 5.309], - [26.819, 2.589, 2.464], - [3.795, 0.902, 0.866], - [1.867, 0.665, 0.672], - [3.822, 0.919, 0.903], - [13.173, 1.916, 1.886], - [11.168, 3.253, 3.214], - [5.126, 5.290, 4.982], - [3.465, 1.866, 1.875], - [7.902, 3.009, 2.803], - [17.132, 17.154, 17.387], - [15.132, 7.755, 7.678], - [15.054, 7.779, 8.068], - [3.598, 3.590, 3.501], - [0.483, 0.279, 0.263], - [0.183, 0.094, 0.095], - [0.174, 0.084, 0.096], - [0.693, 0.480, 0.503], - [0.237, 0.038, 0.031], - [0.108, 0.029, 0.028], - [0.096, 0.010, 0.009] - ] - } -] diff --git a/website/benchmark/hardware/results/azure_e32s.json b/website/benchmark/hardware/results/azure_e32s.json deleted file mode 100644 index 5053f6028ad..00000000000 --- a/website/benchmark/hardware/results/azure_e32s.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Azure E32s v3", - "system_full": "Azure E32s v3 32 256 GiB 512 GiB", - "time": "2020-03-23 00:00:00", - "kind": "cloud", - "result": - [ - [0.003, 0.002, 0.003], - [0.114, 0.014, 0.013], - [0.230, 0.031, 0.029], - [0.893, 0.043, 0.042], - [0.915, 0.123, 0.143], - [1.475, 0.263, 0.264], - [0.055, 0.016, 0.017], - [0.056, 0.013, 0.013], - [1.467, 0.523, 0.523], - [1.661, 0.614, 0.608], - [0.999, 0.169, 0.176], - [1.058, 0.188, 0.190], - [1.839, 0.658, 0.697], - [2.753, 0.892, 0.881], - [2.034, 0.895, 0.895], - [1.425, 0.860, 0.879], - [3.401, 2.070, 2.091], - [2.573, 1.183, 1.208], - [6.376, 4.374, 4.442], - [0.922, 0.044, 0.043], - [10.137, 0.653, 0.691], - [11.589, 0.711, 0.805], - [21.234, 1.841, 1.827], - [22.035, 0.973, 0.940], - [2.983, 0.238, 0.237], - [1.493, 0.201, 0.186], - [3.016, 0.262, 0.259], - [10.139, 0.696, 0.676], - [8.723, 1.017, 1.008], - [1.561, 1.439, 1.563], - [2.688, 0.713, 0.728], - [5.942, 1.075, 1.063], - [7.803, 5.871, 5.826], - [11.131, 2.860, 2.798], - [11.089, 2.898, 2.847], - [1.550, 1.276, 1.256], - [0.851, 0.303, 0.280], - [0.376, 0.100, 0.108], - [0.571, 0.084, 0.082], - [1.654, 0.560, 0.533], - [0.356, 0.029, 0.027], - [0.232, 0.022, 0.024], - [0.032, 0.009, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/cavium_4core.json b/website/benchmark/hardware/results/cavium_4core.json deleted file mode 100644 index a7cb96b2cd3..00000000000 --- a/website/benchmark/hardware/results/cavium_4core.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Cavium ARM64 CPU (4 Core, 1.5 GHz, NVMe SSD)", - "system_full": "Cavium ARM64 CPU (4 Corem 1.5 GHz, NVMe SSD), 16 GiB", - "time": "2021-12-27 00:00:00", - "kind": "server", - "result": - [ -[0.004, 0.004, 0.004], -[0.196, 0.178, 0.180], -[0.495, 0.437, 0.426], -[0.715, 0.499, 0.499], -[0.992, 0.798, 0.795], -[3.958, 3.750, 3.751], -[0.288, 0.274, 0.273], -[0.236, 0.231, 0.239], -[3.129, 2.936, 2.918], -[4.221, 3.924, 3.934], -[2.395, 2.285, 2.226], -[2.832, 2.703, 2.644], -[6.510, 6.301, 6.262], -[7.933, 7.669, 7.704], -[7.397, 7.122, 7.146], -[4.692, 4.537, 4.540], -[15.194, 14.835, 15.051], -[10.446, 10.036, 10.072], -[26.472, 25.655, 25.809], -[0.879, 0.669, 0.694], -[14.614, 13.755, 13.726], -[16.876, 15.675, 15.703], -[34.715, 33.204, 33.250], -[18.850, 15.387, 15.332], -[4.455, 4.025, 4.016], -[3.667, 3.415, 3.457], -[4.507, 4.057, 4.049], -[14.344, 13.394, 13.390], -[17.519, 17.052, 17.067], -[8.606, 8.611, 8.545], -[6.936, 6.491, 6.496], -[10.020, 9.260, 9.233], -[39.793, 39.631, 39.553], -[30.310, 29.604, 29.572], -[30.485, 29.557, 29.649], -[8.539, 8.337, 8.342], -[0.931, 0.912, 0.912], -[0.523, 0.516, 0.507], -[0.460, 0.448, 0.450], -[1.880, 1.817, 1.884], -[0.141, 0.119, 0.117], -[0.116, 0.095, 0.092], -[0.021, 0.017, 0.014] - ] - } -] diff --git a/website/benchmark/hardware/results/core_i5_3210M_lenovo_b580.json b/website/benchmark/hardware/results/core_i5_3210M_lenovo_b580.json deleted file mode 100644 index a6259138138..00000000000 --- a/website/benchmark/hardware/results/core_i5_3210M_lenovo_b580.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Lenovo B580", - "system_full": "Lenovo B580 Laptop (i5-3210M)", - "time": "2020-01-11 00:00:00", - "kind": "laptop", - "result": - [ - [0.035, 0.003, 0.005], - [0.093, 0.064, 0.060], - [0.265, 0.170, 0.167], - [0.880, 0.251, 0.266], - [0.954, 0.593, 0.561], - [2.140, 1.506, 1.525], - [0.148, 0.096, 0.105], - [0.064, 0.048, 0.044], - [2.727, 2.330, 2.280], - [3.386, 3.210, 2.951], - [1.218, 0.787, 0.749], - [1.293, 0.915, 0.904], - [3.713, 3.224, 3.190], - [4.943, 4.338, 4.310], - [4.503, 3.999, 3.918], - [4.001, 3.686, 4.144], - [10.714, 10.011, 10.035], - [7.456, 6.556, 6.675], - [20.201, 19.238, 19.135], - [0.888, 0.217, 0.209], - [9.685, 4.144, 4.023], - [11.201, 4.648, 4.636], - [21.037, 10.712, 10.571], - [18.186, 4.743, 4.743], - [2.844, 1.379, 1.358], - [1.623, 1.138, 1.130], - [2.861, 1.394, 1.417], - [9.691, 4.191, 4.129], - [10.285, 7.381, 7.379], - [6.879, 6.871, 6.829], - [4.131, 3.336, 3.240], - [7.157, 4.666, 4.616], - [29.371, 36.392, 29.946], - [17.929, 14.223, 14.127], - [17.058, 13.998, 14.055], - [5.667, 5.460, 5.408], - [0.325, 0.230, 0.217], - [0.115, 0.101, 0.094], - [0.148, 0.093, 0.084], - [0.585, 0.464, 0.459], - [0.078, 0.042, 0.035], - [0.057, 0.038, 0.032], - [0.024, 0.011, 0.010] - ] - } -] diff --git a/website/benchmark/hardware/results/core_i5_9600K_asus_z390.json b/website/benchmark/hardware/results/core_i5_9600K_asus_z390.json deleted file mode 100644 index cf36dd37d04..00000000000 --- a/website/benchmark/hardware/results/core_i5_9600K_asus_z390.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "ASUS Z390-Plus Server", - "system_full": "ASUS Z390-Plus Server with Intel I5-9600K and DDR4 16GB", - "time": "2021-11-05 03:00:00", - "kind": "server", - "result": - [ - [0.001, 0.001, 0.001], - [0.021, 0.008, 0.008], - [0.104, 0.029, 0.029], - [0.404, 0.047, 0.045], - [0.409, 0.103, 0.102], - [0.647, 0.313, 0.312], - [0.024, 0.018, 0.020], - [0.034, 0.009, 0.009], - [0.710, 0.618, 0.612], - [0.826, 0.700, 0.700], - [0.439, 0.195, 0.195], - [0.461, 0.233, 0.231], - [1.148, 1.073, 1.065], - [1.508, 1.366, 1.373], - [1.167, 1.085, 1.080], - [1.237, 1.207, 1.203], - [3.149, 3.082, 3.079], - [1.808, 1.726, 1.737], - [6.510, 6.457, 6.477], - [0.412, 0.057, 0.057], - [4.543, 1.149, 1.133], - [5.162, 1.291, 1.299], - [9.613, 3.176, 3.177], - [7.753, 1.212, 1.179], - [1.306, 0.318, 0.315], - [0.635, 0.277, 0.280], - [1.303, 0.319, 0.319], - [4.542, 1.299, 1.287], - [3.893, 1.934, 1.925], - [3.651, 3.642, 3.645], - [1.252, 0.815, 0.813], - [2.869, 1.534, 1.543], - [8.087, 7.939, 8.006], - [6.254, 5.169, 5.208], - [6.390, 5.248, 5.248], - [2.077, 2.040, 2.051], - [0.136, 0.127, 0.119], - [0.059, 0.051, 0.048], - [0.061, 0.045, 0.044], - [0.261, 0.248, 0.240], - [0.046, 0.015, 0.014], - [0.035, 0.019, 0.014], - [0.020, 0.003, 0.002] - ] - } -] diff --git a/website/benchmark/hardware/results/core_i7_11800h_lenovo_p15.json b/website/benchmark/hardware/results/core_i7_11800h_lenovo_p15.json deleted file mode 100644 index f42be93e1e3..00000000000 --- a/website/benchmark/hardware/results/core_i7_11800h_lenovo_p15.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "ThinkPad P15", - "system_full": "Lenovo ThinkPad P15, i7-11800H @ 2.30GHz, 16 cores, 32 GiB RAM, NVMe", - "time": "2021-11-19 00:00:00", - "kind": "laptop", - "result": - [ -[0.001, 0.001, 0.001], -[0.013, 0.008, 0.007], -[0.036, 0.025, 0.024], -[0.098, 0.046, 0.040], -[0.139, 0.102, 0.100], -[0.368, 0.279, 0.278], -[0.018, 0.015, 0.017], -[0.025, 0.008, 0.009], -[0.586, 0.527, 0.533], -[0.690, 0.579, 0.554], -[0.224, 0.171, 0.162], -[0.244, 0.201, 0.191], -[0.996, 0.854, 0.871], -[1.339, 1.199, 1.159], -[1.116, 1.073, 1.045], -[1.177, 1.060, 1.084], -[3.307, 3.236, 3.182], -[1.958, 1.789, 1.835], -[6.079, 5.883, 5.895], -[0.109, 0.051, 0.048], -[1.429, 0.898, 0.819], -[1.626, 1.023, 0.937], -[3.390, 2.296, 2.381], -[2.249, 0.997, 0.992], -[0.422, 0.260, 0.299], -[0.285, 0.230, 0.213], -[0.419, 0.256, 0.268], -[1.411, 0.777, 0.830], -[1.828, 1.305, 1.283], -[3.556, 3.725, 3.603], -[0.805, 0.643, 0.650], -[1.369, 1.049, 1.033], -[7.665, 7.623, 7.601], -[5.305, 4.513, 4.691], -[5.370, 4.686, 4.874], -[1.756, 1.492, 1.579], -[0.143, 0.132, 0.131], -[0.058, 0.057, 0.056], -[0.068, 0.048, 0.051], -[0.339, 0.298, 0.304], -[0.032, 0.022, 0.023], -[0.018, 0.011, 0.015], -[0.010, 0.002, 0.004] - ] - } -] diff --git a/website/benchmark/hardware/results/core_i7_6770hq_intel_nuc.json b/website/benchmark/hardware/results/core_i7_6770hq_intel_nuc.json deleted file mode 100644 index 40fcdbf1482..00000000000 --- a/website/benchmark/hardware/results/core_i7_6770hq_intel_nuc.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Intel NUC", - "system_full": "Intel NUC, 4 cores (Intel i7-6770HQ), 32 GiB RAM, 1 TB NVMe SSD", - "time": "2020-04-15 00:00:00", - "kind": "desktop", - "result": - [ - [0.003, 0.002, 0.001], - [0.025, 0.016, 0.018], - [0.084, 0.058, 0.057], - [0.158, 0.092, 0.085], - [0.273, 0.211, 0.190], - [0.671, 0.555, 0.539], - [0.031, 0.033, 0.033], - [0.026, 0.019, 0.017], - [1.183, 1.110, 1.090], - [1.330, 1.246, 1.254], - [0.352, 0.297, 0.296], - [0.441, 0.375, 0.352], - [1.611, 1.491, 1.439], - [2.130, 2.022, 1.976], - [1.903, 1.795, 1.819], - [1.927, 1.851, 1.861], - [5.282, 5.155, 5.172], - [3.246, 3.313, 3.189], - [12.059, 11.378, 10.562], - [0.146, 0.092, 0.090], - [2.103, 1.496, 1.477], - [2.447, 1.777, 1.734], - [5.123, 3.999, 3.955], - [3.733, 1.808, 1.775], - [0.685, 0.530, 0.523], - [0.525, 0.446, 0.438], - [0.755, 0.545, 0.547], - [2.052, 1.416, 1.403], - [2.976, 2.441, 2.423], - [2.197, 2.189, 2.164], - [1.748, 1.596, 1.607], - [2.773, 2.481, 2.466], - [18.903, 19.166, 16.563], - [7.457, 7.116, 6.943], - [7.311, 6.957, 6.958], - [3.036, 3.005, 2.991], - [0.247, 0.186, 0.162], - [0.100, 0.063, 0.065], - [0.098, 0.061, 0.056], - [0.434, 0.344, 0.331], - [0.040, 0.025, 0.025], - [0.049, 0.026, 0.026], - [0.022, 0.008, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/core_i7_8550u_lenovo_x1.json b/website/benchmark/hardware/results/core_i7_8550u_lenovo_x1.json deleted file mode 100644 index 239d88a95b0..00000000000 --- a/website/benchmark/hardware/results/core_i7_8550u_lenovo_x1.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Lenovo Thinkpad X1 Carbon", - "system_full": "Lenovo Thinkpad X1 Carbon 6th Gen i7-8550U CPU @ 1.80GHz 4 threads, 16 GiB", - "time": "2020-01-18 00:00:00", - "kind": "laptop", - "result": - [ - [0.006, 0.002, 0.002], - [0.031, 0.019, 0.020], - [0.082, 0.078, 0.080], - [0.157, 0.093, 0.092], - [0.274, 0.214, 0.206], - [0.601, 0.513, 0.513], - [0.038, 0.045, 0.041], - [0.023, 0.018, 0.018], - [1.394, 1.378, 1.323], - [1.567, 1.496, 1.483], - [0.406, 0.328, 0.327], - [0.468, 0.414, 0.397], - [1.846, 1.753, 1.737], - [2.492, 2.423, 2.404], - [2.136, 2.064, 2.078], - [2.038, 1.971, 1.971], - [5.794, 5.679, 5.708], - [3.430, 3.498, 3.356], - [11.946, 11.738, 11.700], - [0.158, 0.105, 0.091], - [2.151, 1.551, 1.593], - [2.581, 1.990, 1.985], - [6.101, 5.390, 5.320], - [3.528, 2.341, 2.322], - [0.772, 0.699, 0.701], - [0.606, 0.583, 0.587], - [0.877, 0.723, 0.728], - [2.398, 1.916, 1.924], - [3.634, 3.272, 3.247], - [4.102, 4.082, 4.078], - [1.885, 1.784, 1.741], - [2.994, 2.691, 2.707], - [19.060, 18.852, 18.929], - [8.745, 8.476, 8.553], - [8.685, 8.406, 8.946], - [3.416, 3.426, 3.397], - [0.238, 0.234, 0.210], - [0.080, 0.071, 0.072], - [0.078, 0.066, 0.066], - [0.470, 0.407, 0.396], - [0.034, 0.030, 0.029], - [0.025, 0.021, 0.021], - [0.010, 0.007, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/dell_r530.json b/website/benchmark/hardware/results/dell_r530.json deleted file mode 100644 index 6637be26022..00000000000 --- a/website/benchmark/hardware/results/dell_r530.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Dell R530", - "system_full": "Dell R530, 128GB DDR4, 2x480 GB SATA SSD, Perc H730 RAID-1", - "time": "2020-01-14 00:00:00", - "kind": "server", - "result": - [ - [0.027, 0.002, 0.002], - [0.147, 0.017, 0.016], - [0.328, 0.034, 0.033], - [1.059, 0.050, 0.044], - [1.334, 0.123, 0.118], - [2.579, 0.239, 0.264], - [0.057, 0.020, 0.019], - [0.036, 0.019, 0.018], - [2.079, 0.648, 0.569], - [2.012, 0.631, 0.634], - [1.454, 0.158, 0.160], - [1.502, 0.178, 0.185], - [3.095, 0.722, 0.661], - [3.675, 0.816, 0.809], - [2.900, 0.903, 0.810], - [2.005, 0.861, 0.842], - [4.103, 1.983, 2.004], - [2.948, 1.200, 1.160], - [7.687, 4.411, 4.239], - [1.087, 0.054, 0.062], - [14.186, 0.651, 0.757], - [16.497, 0.739, 0.676], - [23.165, 1.703, 1.700], - [22.803, 0.898, 0.919], - [4.247, 0.317, 0.267], - [2.519, 0.214, 0.246], - [4.115, 0.316, 0.274], - [13.759, 0.805, 0.827], - [16.473, 1.215, 1.062], - [2.034, 1.870, 2.016], - [3.152, 0.677, 0.697], - [6.630, 1.216, 1.019], - [9.651, 6.131, 6.017], - [23.506, 3.416, 3.294], - [23.271, 3.547, 3.411], - [1.763, 1.344, 1.308], - [0.317, 0.215, 0.227], - [0.122, 0.090, 0.087], - [0.168, 0.074, 0.090], - [0.565, 0.419, 0.450], - [0.079, 0.037, 0.030], - [0.059, 0.032, 0.032], - [0.025, 0.015, 0.010] - ] - } -] diff --git a/website/benchmark/hardware/results/dell_r740.json b/website/benchmark/hardware/results/dell_r740.json deleted file mode 100644 index 21242e5400a..00000000000 --- a/website/benchmark/hardware/results/dell_r740.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Dell PowerEdge R740xd", - "system_full": "Dell PowerEdge R740xd, 256GB, 2 * Intel(R) Xeon(R) Silver 4214R CPU @ 2.40GHz, 48 vCPU", - "time": "2022-06-18 00:00:00", - "kind": "server", - "result": - [ - [0.004, 0.003, 0.070], - [0.086, 0.019, 0.021], - [0.220, 0.038, 0.037], - [0.596, 0.051, 0.050], - [0.189, 0.149, 0.148], - [0.991, 0.233, 0.230], - [0.004, 0.004, 0.004], - [0.022, 0.018, 0.017], - [0.519, 0.315, 0.305], - [0.469, 0.341, 0.337], - [0.252, 0.158, 0.166], - [0.252, 0.201, 0.184], - [0.532, 0.500, 0.479], - [0.642, 0.613, 0.596], - [0.635, 0.506, 0.508], - [0.579, 0.556, 0.560], - [1.587, 1.532, 1.518], - [0.813, 0.752, 0.737], - [3.990, 3.826, 3.737], - [0.114, 0.073, 0.054], - [4.866, 0.513, 0.514], - [3.822, 0.580, 0.569], - [7.784, 1.550, 1.535], - [17.171, 1.168, 0.834], - [0.511, 0.184, 0.185], - [0.190, 0.181, 0.169], - [0.214, 0.182, 0.183], - [4.611, 0.620, 0.616], - [4.234, 0.793, 0.779], - [1.823, 1.767, 1.737], - [0.813, 0.412, 0.371], - [2.306, 0.772, 0.737], - [3.995, 4.061, 4.041], - [5.142, 2.523, 2.562], - [4.803, 2.595, 2.482], - [1.172, 0.982, 0.990], - [0.454, 0.248, 0.275], - [0.200, 0.145, 0.153], - [0.232, 0.135, 0.134], - [0.621, 0.548, 0.478], - [0.130, 0.088, 0.047], - [0.112, 0.044, 0.049], - [0.048, 0.023, 0.011] - ] - } -] diff --git a/website/benchmark/hardware/results/dell_xps.json b/website/benchmark/hardware/results/dell_xps.json deleted file mode 100644 index 5c51d5c5c4c..00000000000 --- a/website/benchmark/hardware/results/dell_xps.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "system": "Dell XPS 15", - "system_full": "Dell XPS 15, 6 cores, 32 GB RAM", - "cpu_vendor": "Intel", - "cpu_model": "i7-8750H CPU @ 2.20GHz", - "time": "2020-07-15 00:00:00", - "kind": "laptop", - "result": - [ - [0.004, 0.004, 0.002], - [0.039, 0.019, 0.021], - [0.097, 0.058, 0.055], - [0.164, 0.103, 0.105], - [0.335, 0.291, 0.283], - [0.537, 0.496, 0.500], - [0.042, 0.033, 0.032], - [0.026, 0.030, 0.024], - [0.728, 0.642, 0.634], - [0.855, 0.844, 0.794], - [0.375, 0.344, 0.332], - [0.396, 0.329, 0.318], - [1.169, 1.094, 1.077], - [1.480, 1.374, 1.365], - [1.185, 1.082, 1.079], - [1.267, 1.279, 1.248], - [3.061, 2.842, 2.849], - [2.167, 2.293, 2.571], - [7.328, 7.258, 5.765], - [0.223, 0.114, 0.105], - [1.981, 2.030, 1.545], - [2.193, 1.687, 1.687], - [4.669, 4.379, 4.936], - [4.293, 1.761, 1.873], - [0.581, 0.432, 0.414], - [0.431, 0.385, 0.380], - [0.586, 0.422, 0.423], - [2.083, 1.784, 1.764], - [2.486, 2.272, 2.284], - [3.622, 3.644, 3.656], - [1.054, 0.899, 0.883], - [1.667, 1.376, 1.400], - [8.512, 9.455, 9.136], - [5.712, 5.536, 5.505], - [5.688, 5.485, 5.475], - [1.882, 1.933, 2.043], - [0.272, 0.157, 0.158], - [0.070, 0.086, 0.075], - [0.091, 0.069, 0.068], - [0.399, 0.349, 0.343], - [0.045, 0.027, 0.025], - [0.030, 0.021, 0.017], - [0.010, 0.007, 0.011] - ] - } -] diff --git a/website/benchmark/hardware/results/do_storage_optimized.json b/website/benchmark/hardware/results/do_storage_optimized.json deleted file mode 100644 index 6c6cee5423b..00000000000 --- a/website/benchmark/hardware/results/do_storage_optimized.json +++ /dev/null @@ -1,380 +0,0 @@ -[ - { - "system": "DigitalOcean Storage-opt 8", - "system_full": "DigitalOcean 8 CPUs, 64 GB RAM, 1.17 TB NVM SSD, Storage Intensive, Frankfurt, Intel(R) Xeon(R) Gold 6140 CPU @ 2.30GHz", - "cpu_vendor": "Intel", - "cpu_model": "Xeon Gold 6140", - "time": "2020-12-13 00:00:00", - "kind": "cloud", - "result": - [ -[0.003, 0.002, 0.002], -[0.020, 0.014, 0.014], -[0.060, 0.048, 0.049], -[0.089, 0.064, 0.062], -[0.194, 0.177, 0.178], -[0.521, 0.502, 0.497], -[0.031, 0.026, 0.026], -[0.020, 0.018, 0.018], -[0.782, 0.738, 0.730], -[0.874, 0.830, 0.834], -[0.288, 0.265, 0.261], -[0.340, 0.316, 0.316], -[1.144, 1.016, 1.000], -[1.423, 1.359, 1.348], -[1.296, 1.234, 1.217], -[1.458, 1.393, 1.392], -[3.573, 3.374, 3.335], -[2.143, 2.065, 2.083], -[6.179, 6.153, 6.110], -[0.101, 0.069, 0.069], -[1.611, 1.383, 1.362], -[1.659, 1.409, 1.388], -[3.642, 3.124, 3.104], -[2.399, 1.690, 1.652], -[0.501, 0.434, 0.412], -[0.382, 0.345, 0.346], -[0.490, 0.425, 0.420], -[1.401, 1.192, 1.165], -[2.132, 1.956, 1.979], -[1.777, 1.775, 1.770], -[1.134, 1.033, 1.043], -[1.611, 1.475, 1.451], -[9.319, 8.830, 8.826], -[5.214, 5.071, 4.960], -[5.464, 5.079, 5.113], -[1.862, 1.810, 1.800], -[0.223, 0.173, 0.167], -[0.074, 0.058, 0.060], -[0.039, 0.037, 0.038], -[0.430, 0.354, 0.355], -[0.034, 0.015, 0.013], -[0.016, 0.010, 0.011], -[0.006, 0.006, 0.006] - ] - }, - { - "system": "DigitalOcean Storage-opt 16", - "system_full": "DigitalOcean 16 CPUs, 128 GB RAM, 2.34 TB NVM SSD, Storage Intensive, Frankfurt, Intel(R) Xeon(R) Gold 6140 CPU @ 2.30GHz", - "cpu_vendor": "Intel", - "cpu_model": "Xeon Gold 6140", - "time": "2020-12-13 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.002, 0.002], -[0.017, 0.010, 0.010], -[0.039, 0.029, 0.027], -[0.055, 0.039, 0.038], -[0.125, 0.107, 0.112], -[0.302, 0.281, 0.271], -[0.021, 0.016, 0.016], -[0.012, 0.011, 0.011], -[0.418, 0.398, 0.397], -[0.473, 0.446, 0.442], -[0.172, 0.153, 0.155], -[0.202, 0.184, 0.185], -[0.605, 0.543, 0.538], -[0.753, 0.693, 0.682], -[0.687, 0.645, 0.635], -[0.708, 0.701, 0.693], -[1.703, 1.642, 1.655], -[1.037, 0.994, 0.997], -[3.563, 3.314, 3.364], -[0.085, 0.047, 0.039], -[0.857, 0.745, 0.742], -[0.900, 0.770, 0.752], -[1.931, 1.679, 1.672], -[1.537, 0.937, 0.914], -[0.272, 0.230, 0.219], -[0.212, 0.192, 0.187], -[0.271, 0.237, 0.225], -[0.782, 0.659, 0.661], -[1.169, 1.063, 1.068], -[1.193, 1.182, 1.180], -[0.625, 0.590, 0.572], -[0.936, 0.822, 0.800], -[4.796, 4.587, 4.589], -[3.061, 2.715, 2.700], -[2.819, 2.709, 2.708], -[1.078, 1.048, 1.064], -[0.210, 0.163, 0.150], -[0.073, 0.057, 0.058], -[0.037, 0.036, 0.035], -[0.387, 0.357, 0.324], -[0.030, 0.015, 0.017], -[0.014, 0.010, 0.015], -[0.012, 0.006, 0.005] - ] - }, - { - "system": "DigitalOcean Storage-opt 24", - "system_full": "DigitalOcean, 24 CPUs, 192 GB RAM, 3.52 TB NVM SSD, Storage Intensive, Frankfurt, Intel(R) Xeon(R) Gold 6140 CPU @ 2.30GHz", - "cpu_vendor": "Intel", - "cpu_model": "Xeon Gold 6140", - "time": "2020-12-13 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.002, 0.001], -[0.017, 0.009, 0.009], -[0.033, 0.022, 0.022], -[0.046, 0.029, 0.028], -[0.114, 0.101, 0.113], -[0.228, 0.215, 0.209], -[0.018, 0.013, 0.013], -[0.012, 0.010, 0.010], -[0.316, 0.294, 0.294], -[0.350, 0.328, 0.330], -[0.142, 0.129, 0.124], -[0.157, 0.141, 0.138], -[0.452, 0.401, 0.403], -[0.550, 0.508, 0.503], -[0.883, 0.455, 0.451], -[0.539, 0.535, 0.533], -[1.278, 1.206, 1.207], -[0.771, 0.746, 0.736], -[2.776, 2.543, 2.571], -[0.084, 0.065, 0.029], -[0.666, 0.539, 0.586], -[0.703, 0.559, 0.566], -[1.404, 1.232, 1.222], -[1.243, 0.695, 0.701], -[0.215, 0.173, 0.164], -[0.162, 0.143, 0.140], -[0.207, 0.169, 0.169], -[0.606, 0.506, 0.505], -[0.849, 0.788, 0.774], -[1.085, 1.074, 1.072], -[0.426, 0.392, 0.394], -[0.718, 0.616, 0.600], -[3.579, 3.372, 3.428], -[2.161, 2.006, 1.980], -[2.491, 2.026, 1.991], -[0.839, 0.818, 0.817], -[0.220, 0.154, 0.163], -[0.074, 0.058, 0.063], -[0.037, 0.038, 0.038], -[0.391, 0.353, 0.329], -[0.034, 0.012, 0.014], -[0.014, 0.012, 0.011], -[0.005, 0.005, 0.005] - ] - }, - { - "system": "DigitalOcean Storage-opt 32", - "system_full": "DigitalOcean, 32 CPUs, 256 GB RAM, 4.56 TB NVM SSD, Storage Intensive, Frankfurt, Intel(R) Xeon(R) Gold 6140 CPU @ 2.30GHz", - "cpu_vendor": "Intel", - "cpu_model": "Xeon Gold 6140", - "time": "2020-12-13 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.002, 0.001], -[0.016, 0.011, 0.010], -[0.029, 0.020, 0.019], -[0.044, 0.025, 0.025], -[0.103, 0.094, 0.091], -[0.202, 0.181, 0.182], -[0.021, 0.014, 0.012], -[0.012, 0.010, 0.010], -[0.259, 0.240, 0.237], -[0.291, 0.268, 0.267], -[0.128, 0.114, 0.113], -[0.137, 0.121, 0.120], -[0.351, 0.315, 0.315], -[0.435, 0.403, 0.401], -[0.389, 0.377, 0.370], -[0.473, 0.459, 0.450], -[1.076, 1.019, 1.034], -[0.673, 0.632, 0.653], -[1.804, 1.773, 1.779], -[0.055, 0.048, 0.030], -[0.555, 0.472, 0.462], -[0.575, 0.465, 0.478], -[1.245, 1.087, 1.073], -[1.206, 0.561, 0.572], -[0.178, 0.137, 0.137], -[0.133, 0.116, 0.116], -[0.168, 0.139, 0.133], -[0.533, 0.434, 0.440], -[0.727, 0.676, 0.668], -[1.037, 1.029, 1.029], -[0.343, 0.308, 0.308], -[0.503, 0.448, 0.449], -[3.128, 2.888, 2.937], -[1.772, 1.605, 1.586], -[1.653, 1.591, 1.606], -[0.754, 0.718, 0.703], -[0.198, 0.156, 0.143], -[0.074, 0.056, 0.057], -[0.040, 0.040, 0.035], -[0.395, 0.333, 0.320], -[0.031, 0.017, 0.014], -[0.013, 0.011, 0.017], -[0.008, 0.009, 0.006] - ] - }, - { - "system": "DigitalOcean Memory-opt 32", - "system_full": "DigitalOcean, 32 CPUs, 256 GB RAM, 4.8 TB SSD, Memory Intensive, Frankfurt, Intel(R) Xeon(R) Gold 6140 CPU @ 2.30GHz", - "cpu_vendor": "Intel", - "cpu_model": "Xeon Gold 6140", - "time": "2020-12-13 00:00:00", - "kind": "cloud", - "result": - [ -[0.003, 0.002, 0.002], -[0.016, 0.009, 0.009], -[0.030, 0.019, 0.019], -[0.045, 0.026, 0.025], -[0.103, 0.091, 0.090], -[0.200, 0.180, 0.175], -[0.017, 0.012, 0.012], -[0.012, 0.011, 0.010], -[0.264, 0.237, 0.234], -[0.291, 0.270, 0.259], -[0.124, 0.111, 0.111], -[0.136, 0.118, 0.117], -[0.352, 0.316, 0.303], -[0.439, 0.394, 0.392], -[0.391, 0.370, 0.372], -[0.466, 0.450, 0.449], -[1.075, 1.014, 1.008], -[0.654, 0.628, 0.619], -[1.810, 1.801, 1.787], -[0.053, 0.032, 0.030], -[0.550, 0.443, 0.461], -[0.553, 0.461, 0.465], -[1.237, 1.064, 1.065], -[1.163, 0.589, 0.548], -[0.178, 0.132, 0.135], -[0.131, 0.114, 0.112], -[0.164, 0.133, 0.132], -[0.530, 0.442, 0.433], -[0.720, 0.664, 0.659], -[1.032, 1.024, 1.023], -[0.346, 0.311, 0.314], -[0.502, 0.439, 0.438], -[3.146, 2.869, 2.891], -[1.757, 1.615, 1.589], -[1.642, 1.571, 1.580], -[0.743, 0.711, 0.722], -[0.208, 0.151, 0.140], -[0.077, 0.057, 0.056], -[0.037, 0.037, 0.040], -[0.378, 0.310, 0.315], -[0.031, 0.014, 0.017], -[0.019, 0.010, 0.010], -[0.005, 0.005, 0.005] - ] - }, - { - "system": "DigitalOcean CPU-opt, 32", - "system_full": "DigitalOcean, 32 CPUs, 64 GB RAM, 3.52 TB SSD, CPU Intensive, Frankfurt, Intel(R) Xeon(R) Gold 6140 CPU @ 2.30GHz", - "cpu_vendor": "Intel", - "cpu_model": "Xeon Gold 6140", - "time": "2020-12-13 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.002, 0.001], -[0.016, 0.009, 0.009], -[0.026, 0.018, 0.018], -[0.040, 0.025, 0.024], -[0.097, 0.083, 0.088], -[0.190, 0.167, 0.171], -[0.020, 0.011, 0.011], -[0.011, 0.009, 0.009], -[0.253, 0.228, 0.231], -[0.277, 0.257, 0.258], -[0.117, 0.104, 0.107], -[0.127, 0.111, 0.111], -[0.346, 0.313, 0.310], -[0.435, 0.396, 0.392], -[0.391, 0.366, 0.361], -[0.469, 0.458, 0.455], -[1.070, 1.026, 1.033], -[0.649, 0.626, 0.627], -[1.859, 1.808, 1.803], -[0.065, 0.032, 0.025], -[0.513, 0.402, 0.420], -[0.525, 0.418, 0.417], -[1.167, 1.004, 0.988], -[1.118, 0.531, 0.538], -[0.171, 0.126, 0.122], -[0.124, 0.108, 0.105], -[0.158, 0.126, 0.124], -[0.501, 0.407, 0.409], -[0.691, 0.624, 0.619], -[1.106, 1.095, 1.094], -[0.337, 0.301, 0.302], -[0.504, 0.447, 0.453], -[3.187, 2.934, 2.963], -[1.771, 1.583, 1.570], -[1.661, 1.584, 1.573], -[0.748, 0.733, 0.736], -[0.198, 0.152, 0.145], -[0.072, 0.055, 0.053], -[0.039, 0.035, 0.035], -[0.395, 0.332, 0.322], -[0.032, 0.012, 0.012], -[0.019, 0.014, 0.009], -[0.005, 0.005, 0.005] - ] - }, - { - "system": "DigitalOcean General 40", - "system_full": "DigitalOcean, 40 CPUs, 64 GB RAM, 1 TB SSD, General Purpose, San Francisco, Intel(R) Xeon(R) Gold 6140 CPU @ 2.30GHz", - "cpu_vendor": "Intel", - "cpu_model": "Xeon Gold 6140", - "time": "2020-12-13 00:00:00", - "kind": "cloud", - "result": - [ -[0.003, 0.001, 0.001], -[0.019, 0.009, 0.009], -[0.045, 0.017, 0.017], -[0.103, 0.022, 0.021], -[0.131, 0.086, 0.088], -[0.210, 0.167, 0.163], -[0.018, 0.011, 0.011], -[0.014, 0.010, 0.010], -[0.246, 0.204, 0.204], -[0.297, 0.231, 0.238], -[0.138, 0.103, 0.102], -[0.148, 0.106, 0.107], -[0.352, 0.313, 0.309], -[0.453, 0.394, 0.392], -[0.392, 0.349, 0.349], -[0.452, 0.429, 0.429], -[1.060, 0.972, 0.966], -[0.668, 0.591, 0.606], -[1.721, 1.645, 1.640], -[0.102, 0.039, 0.024], -[0.973, 0.386, 0.381], -[1.105, 0.405, 0.388], -[1.996, 0.986, 0.969], -[2.381, 0.475, 0.463], -[0.294, 0.112, 0.111], -[0.167, 0.096, 0.099], -[0.296, 0.114, 0.114], -[0.961, 0.432, 0.424], -[0.840, 0.611, 0.610], -[1.100, 1.090, 1.088], -[0.374, 0.292, 0.283], -[0.691, 0.432, 0.429], -[3.111, 2.863, 2.922], -[1.857, 1.579, 1.553], -[1.762, 1.548, 1.542], -[0.573, 0.552, 0.542], -[0.221, 0.140, 0.144], -[0.079, 0.059, 0.058], -[0.038, 0.034, 0.035], -[0.405, 0.342, 0.354], -[0.039, 0.018, 0.013], -[0.015, 0.010, 0.010], -[0.005, 0.005, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/do_xeon_6140_4.json b/website/benchmark/hardware/results/do_xeon_6140_4.json deleted file mode 100644 index 284d2b5bacc..00000000000 --- a/website/benchmark/hardware/results/do_xeon_6140_4.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "system": "DigitalOcean CPU-opt 4", - "system_full": "DigitalOcean CPU-Optimized 4CPU/8GB RAM Intel(R) Xeon(R) Gold 6140", - "cpu_vendor": "Intel", - "cpu_model": "Xeon Gold 6140", - "time": "2020-11-14 00:00:00", - "kind": "cloud", - "result": - [ -[0.001, 0.002, 0.002], -[0.039, 0.046, 0.025], -[0.116, 0.087, 0.086], -[0.250, 0.120, 0.109], -[0.391, 0.313, 0.321], -[1.035, 0.946, 0.960], -[0.058, 0.047, 0.047], -[0.030, 0.026, 0.026], -[1.498, 1.368, 1.371], -[1.708, 1.568, 1.568], -[0.568, 0.480, 0.478], -[0.652, 0.568, 0.566], -[2.200, 1.968, 1.924], -[2.739, 2.561, 2.531], -[2.358, 2.208, 2.206], -[2.544, 2.407, 2.405], -[6.307, 5.914, 5.927], -[3.838, 3.608, 3.589], -[null, null, null], -[0.251, 0.121, 0.120], -[3.337, 2.447, 2.441], -[3.785, 2.669, 2.602], -[8.053, 6.082, 6.054], -[6.301, 2.976, 2.931], -[1.109, 0.816, 0.811], -[0.791, 0.693, 0.681], -[1.111, 0.821, 0.817], -[3.162, 2.162, 2.090], -[4.601, 3.854, 3.825], -[3.590, 3.560, 3.582], -[2.114, 1.847, 1.823], -[3.559, 2.851, 2.797], -[null, null, null], -[null, null, null], -[null, null, null], -[3.620, 3.446, 3.397], -[0.231, 0.196, 0.182], -[0.079, 0.066, 0.066], -[0.095, 0.059, 0.069], -[0.447, 0.382, 0.386], -[0.050, 0.034, 0.021], -[0.042, 0.016, 0.015], -[0.006, 0.008, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/efs_vs_ebs.json b/website/benchmark/hardware/results/efs_vs_ebs.json deleted file mode 100644 index 8cbc89f58da..00000000000 --- a/website/benchmark/hardware/results/efs_vs_ebs.json +++ /dev/null @@ -1,107 +0,0 @@ -[ - { - "system": "t3.xlarge, EBS", - "system_full": "t3.xlarge, EBS 1 TiB gp2, 3000 IOPS", - "time": "2021-11-08 00:00:00", - "kind": "cloud", - "result": - [ -[0.001,0.002,0.001], -[0.053,0.04,0.038], -[0.254,0.136,0.121], -[0.524,0.194,0.174], -[0.71,0.375,0.376], -[1.575,1.27,1.225], -[0.088,0.076,0.075], -[0.082,0.031,0.032], -[2.326,1.976,1.973], -[2.751,2.244,2.183], -[0.953,0.704,0.669], -[1.284,0.831,0.771], -[3.409,2.972,3.072], -[4.786,4.218,4.286], -[4.686,4.061,4.058], -[3.539,3.287,3.191], -[12.088,11.476,11.371], -[8.679,7.746,7.719], -[24.023,22.915,22.783], -[0.777,0.489,0.238], -[8.885,4.196,4.088], -[10.116,4.387,4.215], -[19.825,9.816,9.468], -[15.399,4.741,4.668], -[2.502,1.261,1.121], -[1.358,0.972,0.982], -[1.865,1.136,1.132], -[8.788,3.68,3.472], -[9.6,5.466,5.405], -[4.733,4.742,4.798], -[3.531,2.568,2.431], -[5.217,3.306,3.327], -[21.862,21.028,20.748], -[19.793,15.297,15.241], -[19.678,15.219,15.316], -[5.943,5.372,5.428], -[0.284,0.256,0.213], -[0.102,0.085,0.1], -[0.115,0.095,0.079], -[0.602,0.549,0.722], -[0.063,0.023,0.023], -[0.064,0.02,0.02], -[0.008,0.005,0.004] - ] - }, - - { - "system": "t3.xlarge, EFS", - "system_full": "t3.xlarge, EFS 1 TiB gp, prov. at 1000 MiB/s", - "time": "2021-11-08 00:00:00", - "kind": "cloud", - "result": - [ -[0.001,0.001,0.001], -[0.074,0.036,0.043], -[0.482,0.13,0.142], -[1.623,0.205,0.209], -[1.823,0.425,0.361], -[3.075,1.274,1.237], -[0.11,0.076,0.076], -[0.152,0.039,0.037], -[3.837,1.944,1.959], -[4.251,2.517,2.168], -[2.232,0.835,0.711], -[2.263,0.936,0.802], -[4.947,3.078,3.026], -[7.207,4.084,4.07], -[6.074,4.452,3.919], -[5.349,3.191,3.12], -[14.559,11.097,11.063], -[11.058,8.01,7.762], -[28.227,22.692,22.459], -[1.688,0.35,0.259], -[16.214,4.229,4.039], -[18.284,4.339,4.233], -[31.926,9.819,9.475], -[30.797,5.062,5.247], -[4.744,1.308,1.122], -[2.785,1.022,0.957], -[4.473,1.235,1.135], -[15.194,3.561,3.443], -[15.267,5.481,5.387], -[5.145,4.8,4.686], -[5.523,2.581,2.531], -[9.329,3.263,3.452], -[25.753,20.411,20.543], -[26.907,14.767,14.783], -[26.541,15.051,14.838], -[6.637,5.235,5.491], -[0.493,0.237,0.217], -[0.171,0.095,0.094], -[0.298,0.091,0.085], -[0.901,0.555,0.547], -[0.171,0.042,0.035], -[0.12,0.035,0.033], -[0.032,0.014,0.015] - ] - } -] diff --git a/website/benchmark/hardware/results/equinix_metal_n3_xlarge.json b/website/benchmark/hardware/results/equinix_metal_n3_xlarge.json deleted file mode 100644 index c9fdb07c31c..00000000000 --- a/website/benchmark/hardware/results/equinix_metal_n3_xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Equinix metal n3.xlarge.x84", - "system_full": "Equinix metal n3.xlarge.x84 503 Gib local NVMe ", - "time": "2022-07-12 10:25:52", - "kind": "server", - "result": - [ - [0.010, 0.029, 0.003], - [0.387, 0.245, 0.198], - [0.089, 0.044, 0.049], - [0.128, 0.084, 0.070], - [0.210, 0.161, 0.173], - [0.321, 0.216, 0.272], - [0.011, 0.004, 0.004], - [0.109, 0.074, 0.064], - [0.353, 0.284, 0.287], - [0.437, 0.310, 0.302], - [0.194, 0.311, 0.280], - [0.194, 0.180, 0.148], - [0.351, 0.276, 0.263], - [0.381, 0.326, 0.313], - [0.353, 0.270, 0.271], - [0.324, 0.300, 0.306], - [0.839, 0.782, 0.786], - [0.608, 0.521, 0.548], - [1.387, 1.317, 1.339], - [0.090, 0.081, 0.087], - [0.623, 0.474, 0.428], - [0.581, 0.404, 0.395], - [1.137, 0.879, 0.878], - [0.794, 0.565, 0.577], - [0.211, 0.119, 0.116], - [0.162, 0.107, 0.100], - [0.230, 0.135, 0.132], - [0.606, 0.411, 0.406], - [0.724, 0.499, 0.491], - [0.638, 0.595, 0.609], - [0.289, 0.209, 0.222], - [0.469, 0.274, 0.313], - [2.042, 1.748, 1.697], - [1.484, 1.332, 1.262], - [1.347, 1.230, 1.175], - [0.516, 0.470, 0.471], - [0.142, 0.090, 0.099], - [0.078, 0.056, 0.045], - [0.084, 0.041, 0.057], - [0.248, 0.190, 0.218], - [0.057, 0.063, 0.048], - [0.059, 0.047, 0.026], - [0.018, 0.017, 0.009] - ] - } -] diff --git a/website/benchmark/hardware/results/gcp_c2.json b/website/benchmark/hardware/results/gcp_c2.json deleted file mode 100644 index d9e6e1e4c90..00000000000 --- a/website/benchmark/hardware/results/gcp_c2.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "GCP c2-standard-30", - "system_full": "GCP c2-standard-30, 128 GiB RAM", - "time": "2021-11-25 00:00:00", - "kind": "cloud", - "result": - [ -[0.001, 0.001, 0.002], -[0.050, 0.007, 0.006], -[0.140, 0.019, 0.018], -[0.426, 0.026, 0.026], -[0.348, 0.072, 0.071], -[0.610, 0.197, 0.197], -[0.074, 0.013, 0.013], -[0.030, 0.007, 0.007], -[0.542, 0.278, 0.284], -[0.617, 0.331, 0.312], -[0.429, 0.109, 0.110], -[0.431, 0.130, 0.130], -[0.651, 0.401, 0.402], -[1.031, 0.499, 0.520], -[0.787, 0.512, 0.518], -[0.583, 0.451, 0.446], -[1.579, 1.349, 1.289], -[0.980, 0.745, 0.766], -[3.189, 2.722, 2.722], -[0.336, 0.037, 0.032], -[4.048, 0.494, 0.499], -[4.609, 0.593, 0.572], -[8.568, 1.306, 1.303], -[8.766, 0.842, 0.586], -[1.142, 0.152, 0.161], -[0.540, 0.138, 0.137], -[1.144, 0.157, 0.161], -[4.062, 0.518, 0.513], -[3.470, 0.812, 0.797], -[1.095, 1.086, 1.073], -[1.042, 0.387, 0.391], -[2.361, 0.545, 0.532], -[3.121, 2.727, 2.749], -[4.681, 1.981, 1.953], -[4.635, 1.943, 1.902], -[0.799, 0.766, 0.774], -[0.187, 0.171, 0.187], -[0.073, 0.063, 0.063], -[0.071, 0.059, 0.059], -[0.404, 0.390, 0.354], -[0.039, 0.022, 0.025], -[0.025, 0.015, 0.019], -[0.016, 0.004, 0.003] - ] - } -] diff --git a/website/benchmark/hardware/results/gcp_n2.json b/website/benchmark/hardware/results/gcp_n2.json deleted file mode 100644 index e99bdc41b82..00000000000 --- a/website/benchmark/hardware/results/gcp_n2.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "GCP n2-highmem-32", - "system_full": "GCP n2-highmem-32, 256 GiB RAM", - "time": "2021-11-25 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.002, 0.001], -[0.097, 0.010, 0.010], -[0.218, 0.020, 0.020], -[0.373, 0.031, 0.032], -[0.186, 0.093, 0.088], -[0.390, 0.209, 0.216], -[0.076, 0.016, 0.015], -[0.034, 0.011, 0.010], -[0.433, 0.291, 0.286], -[0.501, 0.355, 0.325], -[0.328, 0.123, 0.117], -[0.309, 0.139, 0.139], -[0.496, 0.374, 0.392], -[0.656, 0.487, 0.488], -[0.790, 0.586, 0.570], -[0.556, 0.493, 0.543], -[1.814, 1.515, 1.511], -[0.931, 0.773, 0.757], -[3.767, 3.250, 3.531], -[0.168, 0.036, 0.031], -[2.036, 0.544, 0.496], -[2.315, 0.593, 0.557], -[4.409, 1.429, 1.388], -[4.968, 0.771, 0.665], -[0.591, 0.185, 0.171], -[0.285, 0.164, 0.158], -[0.595, 0.183, 0.177], -[2.044, 0.585, 0.521], -[1.778, 0.802, 0.753], -[0.773, 0.742, 0.753], -[0.691, 0.429, 0.421], -[1.333, 0.594, 0.540], -[3.207, 2.846, 2.940], -[2.842, 2.141, 2.024], -[2.730, 1.886, 1.833], -[0.782, 0.736, 0.759], -[0.209, 0.200, 0.193], -[0.083, 0.079, 0.078], -[0.081, 0.071, 0.072], -[0.435, 0.454, 0.422], -[0.046, 0.034, 0.024], -[0.046, 0.032, 0.017], -[0.006, 0.004, 0.003] - ] - } -] diff --git a/website/benchmark/hardware/results/gcp_n2d.json b/website/benchmark/hardware/results/gcp_n2d.json deleted file mode 100644 index 068eba114eb..00000000000 --- a/website/benchmark/hardware/results/gcp_n2d.json +++ /dev/null @@ -1,106 +0,0 @@ -[ - { - "system": "GCP n2d-16-highmem", - "system_full": "GCP compute n2d-16-highmem, AMD EPYC 7B12, 16vCPU, 128 GiB RAM", - "time": "2021-11-18 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.001, 0.001], -[0.017, 0.013, 0.012], -[0.046, 0.032, 0.031], -[0.062, 0.048, 0.046], -[0.122, 0.112, 0.103], -[0.365, 0.313, 0.312], -[0.026, 0.029, 0.024], -[0.028, 0.014, 0.014], -[0.516, 0.473, 0.477], -[0.591, 0.545, 0.542], -[0.210, 0.178, 0.183], -[0.224, 0.208, 0.205], -[0.682, 0.629, 0.609], -[0.862, 0.804, 0.812], -[0.854, 0.769, 0.778], -[0.769, 0.771, 0.768], -[2.147, 2.171, 2.166], -[1.439, 1.380, 1.355], -[4.099, 3.974, 4.048], -[0.118, 0.053, 0.048], -[0.873, 0.785, 0.786], -[1.022, 0.908, 0.891], -[2.278, 2.079, 2.042], -[1.714, 0.962, 0.950], -[0.400, 0.287, 0.267], -[0.336, 0.248, 0.228], -[0.379, 0.273, 0.268], -[0.889, 0.816, 0.802], -[1.474, 1.391, 1.455], -[1.358, 1.355, 1.342], -[0.723, 0.628, 0.613], -[0.914, 0.756, 0.741], -[3.916, 3.967, 3.962], -[3.194, 2.998, 3.016], -[3.097, 3.050, 3.073], -[1.099, 1.111, 1.087], -[0.184, 0.168, 0.175], -[0.072, 0.066, 0.065], -[0.067, 0.063, 0.055], -[0.373, 0.374, 0.376], -[0.032, 0.027, 0.020], -[0.021, 0.015, 0.015], -[0.006, 0.008, 0.006] - ] - }, - { - "system": "GCP n2d-standard-8", - "system_full": "GCP n2d-standard-8, AMD EPYC 7B12, 8vCPU, 64 GiB RAM", - "time": "2021-11-23 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.002, 0.002], -[0.054, 0.019, 0.023], -[0.109, 0.053, 0.061], -[0.282, 0.102, 0.098], -[0.261, 0.183, 0.185], -[0.812, 0.697, 0.699], -[0.058, 0.034, 0.036], -[0.048, 0.023, 0.020], -[1.164, 1.191, 1.206], -[1.332, 1.223, 1.354], -[0.378, 0.388, 0.314], -[0.448, 0.464, 0.376], -[1.593, 1.719, 1.699], -[2.137, 1.940, 2.271], -[1.957, 2.048, 1.895], -[1.797, 1.711, 1.998], -[6.016, 5.934, 5.659], -[3.573, 3.571, 3.396], -[11.690, 11.552, 11.673], -[0.251, 0.124, 0.125], -[3.106, 2.289, 2.330], -[3.490, 2.253, 2.563], -[6.666, 4.699, 5.062], -[8.078, 2.300, 2.726], -[0.883, 0.639, 0.604], -[0.513, 0.534, 0.507], -[0.879, 0.626, 0.625], -[3.068, 1.940, 1.656], -[3.389, 3.633, 3.075], -[2.576, 2.445, 2.623], -[1.431, 1.464, 1.441], -[2.365, 2.076, 2.067], -[10.904, 11.111, 13.093], -[8.407, 7.915, 8.475], -[8.005, 8.124, 9.201], -[2.701, 3.199, 3.036], -[0.244, 0.239, 0.204], -[0.118, 0.092, 0.089], -[0.103, 0.093, 0.060], -[0.539, 0.457, 0.445], -[0.048, 0.022, 0.022], -[0.038, 0.067, 0.016], -[0.011, 0.004, 0.004] - ] - } -] diff --git a/website/benchmark/hardware/results/gp1_s_16x.json b/website/benchmark/hardware/results/gp1_s_16x.json deleted file mode 100644 index 1353fc87d00..00000000000 --- a/website/benchmark/hardware/results/gp1_s_16x.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "scaleway GP1-S 8x x86", - "system_full": "scaleway GP1-M 16x x86 64bit 64GB ram 600gb NVMe", - "time": "2022-02-16 00:00:00", - "kind": "cloud", - "result": - [ - [0.005, 0.005, 0.036], - [0.039, 0.026, 0.026], - [0.092, 0.046, 0.046], - [0.172, 0.056, 0.055], - [0.166, 0.126, 0.123], - [0.364, 0.272, 0.265], - [0.005, 0.006, 0.005], - [0.028, 0.027, 0.029], - [0.581, 0.49, 0.486], - [0.69, 0.549, 0.553], - [0.248, 0.178, 0.175], - [0.266, 0.208, 0.208], - [1.584, 1.017, 0.868], - [1.717, 1.113, 1.145], - [1.144, 1.084, 1.048], - [0.991, 0.92, 0.895], - [4.121, 2.639, 2.621], - [1.447, 1.348, 1.354], - [6.802, 6.466, 6.433], - [0.142, 0.057, 0.052], - [1.252, 0.743, 0.715], - [1.389, 0.823, 0.791], - [3.143, 2.225, 2.159], - [1.795, 0.871, 0.837], - [0.361, 0.236, 0.229], - [0.264, 0.211, 0.214], - [0.37, 0.24, 0.225], - [1.449, 0.967, 0.876], - [1.605, 1.206, 1.16 ], - [3.412, 3.388, 3.397], - [0.783, 0.628, 0.65 ], - [1.419, 1.134, 1.112], - [6.983, 6.843, 6.852], - [5.466, 5.082, 4.955], - [5.632, 4.972, 5.22 ], - [1.639, 1.604, 1.571], - [0.285, 0.298, 0.269], - [0.115, 0.115, 0.101], - [0.098, 0.1, 0.092], - [0.563, 0.562, 0.512], - [0.058, 0.039, 0.042], - [0.039, 0.039, 0.025], - [0.029, 0.012, 0.012] - ] - } -] diff --git a/website/benchmark/hardware/results/gp1_s_8x.json b/website/benchmark/hardware/results/gp1_s_8x.json deleted file mode 100644 index 2bc008af54c..00000000000 --- a/website/benchmark/hardware/results/gp1_s_8x.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "scaleway GP1-S 8x x86", - "system_full": "scaleway GP1-S 8x x86 64bit 32GB ram 300gb NVMe", - "time": "2022-02-16 00:00:00", - "kind": "cloud", - "result": - [ - [0.026, 0.004, 0.004], - [0.038, 0.026, 0.026], - [0.071, 0.058, 0.059], - [0.118, 0.072, 0.069], - [0.190, 0.151, 0.155], - [0.465, 0.438, 0.401], - [0.002, 0.004, 0.004], - [0.028, 0.029, 0.026], - [0.751, 0.672, 0.676], - [0.897, 0.845, 0.798], - [0.291, 0.234, 0.254], - [0.371, 0.297, 0.296], - [1.208, 1.041, 1.005], - [1.445, 1.400, 1.414], - [1.406, 1.317, 1.342], - [1.414, 1.242, 1.244], - [4.179, 3.849, 3.878], - [2.320, 2.275, 2.201], - [7.499, 7.424, 7.196], - [0.135, 0.077, 0.068], - [1.465, 1.075, 1.063], - [1.700, 1.221, 1.198], - [3.731, 2.959, 2.905], - [2.283, 1.401, 1.342], - [0.474, 0.377, 0.367], - [0.371, 0.314, 0.337], - [0.483, 0.357, 0.356], - [1.565, 1.194, 1.181], - [2.226, 1.815, 1.746], - [2.990, 2.971, 2.947], - [1.003, 0.815, 0.842], - [1.386, 1.127, 1.108], - [8.174, 7.690, 7.735], - [6.171, 5.802, 5.933], - [6.201, 5.774, 5.972], - [1.758, 1.642, 1.639], - [0.288, 0.273, 0.253], - [0.121, 0.125, 0.107], - [0.096, 0.082, 0.088], - [0.490, 0.461, 0.476], - [0.041, 0.037, 0.035], - [0.035, 0.031, 0.025], - [0.008, 0.011, 0.015] - ] - } -] diff --git a/website/benchmark/hardware/results/hetzner_epyc.json b/website/benchmark/hardware/results/hetzner_epyc.json deleted file mode 100644 index 4ced699a56d..00000000000 --- a/website/benchmark/hardware/results/hetzner_epyc.json +++ /dev/null @@ -1,106 +0,0 @@ -[ - { - "system": "Hetzner CCX22", - "system_full": "Hetzner CCX22 (AMD EPYC 7003, 4 cores, 16 GiB RAM, NVMe)", - "time": "2021-11-17 00:00:00", - "kind": "server", - "result": - [ -[0.001, 0.001, 0.001], -[0.036, 0.023, 0.039], -[0.130, 0.102, 0.092], -[0.304, 0.148, 0.141], -[0.431, 0.298, 0.291], -[1.492, 1.359, 1.357], -[0.088, 0.087, 0.091], -[0.058, 0.039, 0.042], -[1.612, 1.477, 1.473], -[2.017, 1.805, 1.809], -[1.044, 0.925, 0.926], -[1.167, 1.050, 1.048], -[2.621, 2.447, 2.447], -[3.426, 3.176, 3.193], -[3.545, 3.475, 3.431], -[2.958, 2.805, 2.816], -[8.547, 8.320, 8.321], -[6.395, 5.992, 6.081], -[16.542, 16.407, 16.057], -[0.404, 0.166, 0.156], -[4.338, 3.419, 3.373], -[5.042, 4.102, 4.052], -[10.231, 8.420, 8.304], -[6.121, 3.904, 3.804], -[1.582, 1.297, 1.279], -[1.316, 1.183, 1.171], -[1.565, 1.305, 1.296], -[4.098, 3.290, 3.246], -[5.999, 5.242, 5.205], -[2.247, 2.198, 2.183], -[2.581, 2.336, 2.242], -[3.269, 2.806, 2.744], -[14.252, 14.052, 13.956], -[11.730, 10.638, 10.632], -[11.418, 10.659, 10.572], -[4.170, 4.086, 4.092], -[0.208, 0.173, 0.159], -[0.082, 0.075, 0.069], -[0.082, 0.062, 0.065], -[0.413, 0.392, 0.375], -[0.046, 0.021, 0.029], -[0.032, 0.016, 0.017], -[0.005, 0.004, 0.007] - ] - }, - { - "system": "Hetzner CCX32", - "system_full": "Hetzner CCX32 (AMD EPYC 7003, 8 cores, 32 GiB RAM, NVMe)", - "time": "2021-11-17 00:00:00", - "kind": "server", - "result": - [ -[0.001, 0.001, 0.001], -[0.021, 0.018, 0.017], -[0.078, 0.057, 0.063], -[0.178, 0.083, 0.076], -[0.229, 0.191, 0.182], -[1.141, 1.063, 0.977], -[0.071, 0.051, 0.068], -[0.056, 0.022, 0.035], -[1.043, 1.288, 1.272], -[1.757, 1.003, 0.996], -[0.554, 0.492, 0.555], -[0.931, 0.698, 0.582], -[1.471, 1.364, 1.310], -[2.284, 2.040, 1.720], -[1.852, 1.749, 1.710], -[1.551, 1.496, 1.482], -[4.852, 4.310, 4.964], -[3.384, 3.353, 3.015], -[10.150, 9.422, 10.005], -[0.230, 0.091, 0.089], -[3.525, 1.731, 1.721], -[2.939, 2.325, 2.077], -[7.716, 5.046, 4.394], -[3.927, 2.023, 1.951], -[0.848, 0.732, 0.874], -[1.005, 0.627, 0.606], -[0.968, 0.725, 0.687], -[2.771, 2.453, 1.815], -[3.536, 3.283, 3.020], -[1.661, 1.690, 1.761], -[1.511, 1.213, 1.205], -[2.002, 1.715, 1.518], -[8.160, 8.943, 8.982], -[6.999, 5.827, 6.024], -[7.777, 6.634, 6.338], -[2.391, 2.285, 2.284], -[0.221, 0.182, 0.196], -[0.114, 0.072, 0.069], -[0.096, 0.063, 0.065], -[0.423, 0.382, 0.405], -[0.077, 0.022, 0.024], -[0.030, 0.022, 0.018], -[0.011, 0.004, 0.008] - ] - } -] diff --git a/website/benchmark/hardware/results/hetzner_ex62.json b/website/benchmark/hardware/results/hetzner_ex62.json deleted file mode 100644 index 2a23b3749ff..00000000000 --- a/website/benchmark/hardware/results/hetzner_ex62.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Hetzner EX62", - "system_full": "Hetzner EX62 (Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz, 128 RAM DDR4 2666, NVMe)", - "time": "2022-04-28 00:00:00", - "kind": "server", - "result": - [ - [0.006, 0.001, 0.015], - [0.012, 0.033, 0.018], - [0.038, 0.030, 0.033], - [0.065, 0.044, 0.044], - [0.133, 0.117, 0.109], - [0.330, 0.281, 0.296], - [0.002, 0.001, 0.001], - [0.010, 0.011, 0.011], - [0.596, 0.564, 0.552], - [0.676, 0.629, 0.629], - [0.175, 0.154, 0.148], - [0.214, 0.190, 0.182], - [0.932, 0.891, 0.882], - [1.191, 1.131, 1.122], - [0.996, 0.877, 0.874], - [1.144, 1.062, 1.075], - [2.787, 2.687, 2.691], - [1.502, 1.455, 1.457], - [4.826, 4.703, 4.727], - [0.071, 0.050, 0.047], - [1.179, 0.934, 0.922], - [1.242, 1.021, 0.989], - [2.876, 2.389, 2.411], - [1.629, 1.050, 1.034], - [0.314, 0.254, 0.253], - [0.250, 0.231, 0.226], - [0.328, 0.269, 0.249], - [1.219, 0.975, 0.993], - [1.686, 1.517, 1.485], - [3.458, 3.455, 3.443], - [0.771, 0.654, 0.677], - [1.208, 1.067, 1.053], - [6.915, 6.738, 6.770], - [4.771, 4.656, 4.669], - [4.851, 4.701, 4.671], - [1.451, 1.341, 1.345], - [0.153, 0.126, 0.120], - [0.053, 0.056, 0.047], - [0.054, 0.046, 0.043], - [0.276, 0.250, 0.258], - [0.021, 0.018, 0.019], - [0.018, 0.015, 0.015], - [0.004, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/hardware/results/huawei_taishan_2280_3.json b/website/benchmark/hardware/results/huawei_taishan_2280_3.json deleted file mode 100644 index c31b055dbee..00000000000 --- a/website/benchmark/hardware/results/huawei_taishan_2280_3.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Huawei TaiShan (2)", - "system_full": "Huawei TaiShan 2280 (AArch64) 2280 (2x64 cores, SSD)", - "time": "2020-11-03 00:00:00", - "kind": "server", - "result": - [ - [0.001, 0.003, 0.002], - [0.009, 0.024, 0.032], - [0.023, 0.051, 0.027], - [0.035, 0.063, 0.027], - [0.103, 0.145, 0.096], - [0.121, 0.191, 0.109], - [0.009, 0.026, 0.023], - [0.014, 0.025, 0.022], - [0.209, 0.157, 0.119], - [0.254, 0.182, 0.132], - [0.153, 0.101, 0.078], - [0.151, 0.099, 0.097], - [0.289, 0.238, 0.137], - [0.342, 0.255, 0.164], - [0.302, 0.216, 0.142], - [0.291, 0.202, 0.163], - [1.269, 0.483, 0.309], - [0.457, 0.313, 0.229], - [1.376, 0.942, 0.597], - [0.073, 0.055, 0.023], - [0.555, 0.321, 0.193], - [0.913, 0.308, 0.191], - [1.713, 0.668, 0.421], - [0.894, 0.837, 0.387], - [0.238, 0.091, 0.061], - [0.153, 0.081, 0.055], - [0.174, 0.091, 0.059], - [0.933, 0.361, 0.233], - [0.793, 0.366, 0.237], - [0.682, 0.551, 0.549], - [0.321, 0.213, 0.154], - [0.593, 0.293, 0.173], - [4.436, 1.481, 1.003], - [1.544, 0.997, 0.774], - [1.981, 1.006, 0.841], - [0.539, 0.339, 0.247], - [0.194, 0.186, 0.141], - [0.075, 0.072, 0.066], - [0.072, 0.087, 0.053], - [0.393, 0.398, 0.356], - [0.032, 0.042, 0.031], - [0.023, 0.023, 0.022], - [0.005, 0.006, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/huawei_taishan_2280_v2.json b/website/benchmark/hardware/results/huawei_taishan_2280_v2.json deleted file mode 100644 index b4a1a6ccfff..00000000000 --- a/website/benchmark/hardware/results/huawei_taishan_2280_v2.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Huawei TaiShan", - "system_full": "Huawei TaiShan 2280 v2 (AArch64) 64 core (2-die), one physical HDD", - "time": "2020-01-15 00:00:00", - "kind": "server", - "result": - [ - [0.356, 0.002, 0.002], - [0.333, 0.018, 0.017], - [0.608, 0.021, 0.021], - [1.885, 0.032, 0.032], - [0.598, 0.099, 0.097], - [2.884, 0.165, 0.167], - [0.356, 0.016, 0.014], - [0.349, 0.015, 0.015], - [0.981, 0.283, 0.296], - [0.783, 0.326, 0.328], - [0.580, 0.135, 0.136], - [0.511, 0.142, 0.142], - [1.060, 0.434, 0.438], - [1.069, 0.569, 0.566], - [1.116, 0.479, 0.479], - [0.825, 0.478, 0.486], - [1.899, 1.574, 1.590], - [1.260, 0.874, 0.849], - [5.456, 2.869, 2.903], - [0.418, 0.037, 0.034], - [19.336, 0.478, 0.494], - [22.442, 0.595, 0.595], - [45.958, 8.735, 1.363], - [41.321, 0.675, 0.706], - [6.074, 0.167, 0.159], - [0.925, 0.133, 0.133], - [1.151, 0.153, 0.152], - [19.627, 0.607, 0.622], - [16.496, 0.792, 0.787], - [1.770, 2.045, 1.242], - [4.827, 0.471, 0.466], - [7.695, 0.701, 0.647], - [5.246, 4.741, 4.676], - [20.496, 2.676, 2.628], - [20.338, 2.559, 2.557], - [1.696, 0.701, 0.724], - [0.665, 0.294, 0.302], - [0.402, 0.140, 0.137], - [0.366, 0.082, 0.086], - [0.867, 0.575, 0.552], - [0.334, 0.025, 0.025], - [0.333, 0.023, 0.022], - [0.340, 0.007, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/huawei_taishan_920.json b/website/benchmark/hardware/results/huawei_taishan_920.json deleted file mode 100644 index 61a37d338d5..00000000000 --- a/website/benchmark/hardware/results/huawei_taishan_920.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Huawei TaiShan", - "system_full": "Huawei TaiShan Kunpeng 920, 96 vCPU, 256GB RAM", - "time": "2022-06-18 00:00:00", - "kind": "server", - "result": - [ - [0.018, 0.001, 0.002], - [0.106, 0.019, 0.019], - [0.835, 0.027, 0.026], - [3.314, 0.031, 0.030], - [1.034, 0.130, 0.131], - [3.221, 0.166, 0.166], - [0.002, 0.002, 0.002], - [0.061, 0.023, 0.022], - [3.989, 0.191, 0.189], - [3.233, 0.215, 0.213], - [2.774, 0.112, 0.106], - [1.999, 0.111, 0.109], - [3.377, 0.259, 0.262], - [5.308, 0.324, 0.326], - [3.505, 0.267, 0.272], - [2.382, 0.316, 0.311], - [5.633, 0.648, 0.656], - [5.505, 0.446, 0.449], - [8.911, 1.509, 1.487], - [2.116, 0.042, 0.032], - [21.597, 0.308, 0.287], - [28.341, 0.347, 0.354], - [53.912, 0.907, 0.901], - [52.521, 1.923, 0.904], - [7.845, 0.100, 0.100], - [3.213, 0.082, 0.083], - [6.985, 0.102, 0.099], - [21.502, 0.404, 0.406], - [20.771, 0.505, 0.498], - [0.739, 0.666, 0.664], - [6.025, 0.228, 0.224], - [12.865, 0.410, 0.408], - [10.248, 2.236, 2.222], - [21.545, 1.226, 1.232], - [26.602, 1.218, 1.247], - [1.696, 0.435, 0.431], - [0.353, 0.232, 0.227], - [0.193, 0.103, 0.100], - [0.305, 0.086, 0.087], - [0.640, 0.476, 0.483], - [0.190, 0.028, 0.030], - [0.137, 0.023, 0.024], - [0.064, 0.005, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/i3_2xlarge.json b/website/benchmark/hardware/results/i3_2xlarge.json deleted file mode 100644 index e716b99e8a2..00000000000 --- a/website/benchmark/hardware/results/i3_2xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS i3.2xlarge", - "system_full": "AWS i3.2xlarge Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz", - "time": "2022-01-02 03:16:35", - "kind": "cloud", - "result": - [ - [0.002, 0.002, 0.002], - [0.040, 0.023, 0.027], - [0.153, 0.084, 0.090], - [0.682, 0.113, 0.120], - [1.218, 0.227, 0.225], - [1.972, 0.708, 0.700], - [0.066, 0.052, 0.052], - [0.086, 0.037, 0.030], - [1.609, 1.123, 1.119], - [1.784, 1.231, 1.241], - [0.782, 0.444, 0.392], - [0.929, 0.504, 0.476], - [2.273, 1.649, 1.633], - [4.022, 2.181, 2.214], - [2.459, 2.022, 1.925], - [2.015, 1.621, 1.677], - [6.344, 5.439, 5.625], - [4.450, 3.724, 3.678], - [12.221, 10.922, 10.933], - [0.674, 0.139, 0.132], - [18.758, 2.164, 2.152], - [20.902, 2.440, 2.367], - [39.396, 5.476, 5.427], - [31.640, 2.759, 2.755], - [4.498, 0.647, 0.646], - [1.709, 0.627, 0.540], - [4.488, 0.665, 0.656], - [18.286, 2.023, 2.013], - [15.375, 2.896, 2.959], - [2.962, 2.899, 2.974], - [3.663, 1.299, 1.304], - [9.731, 1.922, 1.915], - [11.575, 10.394, 10.514], - [20.617, 8.121, 8.097], - [20.558, 8.088, 8.049], - [3.059, 2.780, 2.678], - [0.322, 0.244, 0.217], - [0.122, 0.082, 0.092], - [0.146, 0.073, 0.072], - [0.652, 0.473, 0.502], - [0.097, 0.025, 0.034], - [0.052, 0.025, 0.019], - [0.007, 0.004, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/ibm_cloud_baremetal.json b/website/benchmark/hardware/results/ibm_cloud_baremetal.json deleted file mode 100644 index 46d319c6c7b..00000000000 --- a/website/benchmark/hardware/results/ibm_cloud_baremetal.json +++ /dev/null @@ -1,210 +0,0 @@ -[ - { - "system": "2x AMD EPYC 3.2 GHz, Micron 5100 MAX 960 GB", - "system_full": "2x AMD EPYC 7F72 3.2 Ghz - Total 96 Cores - IBM Cloud's Bare Metal Service - 512 GB RAM - Micron 5100 MAX 960 GB SSD", - "time": "2021-01-13 00:00:00", - "kind": "server", - "result": - [ -[0.002, 0.002, 0.002], -[0.027, 0.013, 0.012], -[0.116, 0.017, 0.016], -[0.509, 0.020, 0.018], -[0.535, 0.071, 0.069], -[0.155, 0.111, 0.108], -[0.028, 0.016, 0.015], -[0.016, 0.013, 0.013], -[0.700, 0.124, 0.123], -[0.832, 0.144, 0.149], -[0.567, 0.074, 0.075], -[0.571, 0.078, 0.076], -[0.241, 0.176, 0.171], -[0.728, 0.223, 0.223], -[0.333, 0.227, 0.221], -[0.634, 0.235, 0.241], -[0.983, 0.577, 0.566], -[0.692, 0.340, 0.319], -[2.222, 1.081, 1.117], -[0.500, 0.034, 0.033], -[5.333, 0.263, 0.193], -[5.484, 0.221, 0.200], -[9.963, 0.623, 0.614], -[11.277, 0.331, 0.299], -[0.940, 0.075, 0.070], -[0.124, 0.060, 0.056], -[0.942, 0.072, 0.066], -[5.445, 0.307, 0.269], -[4.305, 0.313, 0.301], -[0.613, 0.558, 0.573], -[0.732, 0.190, 0.186], -[2.433, 0.301, 0.293], -[3.473, 1.907, 1.782], -[6.202, 1.048, 0.976], -[6.193, 1.013, 0.991], -[0.499, 0.304, 0.300], -[0.192, 0.134, 0.129], -[0.072, 0.054, 0.055], -[0.030, 0.029, 0.029], -[0.295, 0.263, 0.300], -[0.052, 0.017, 0.022], -[0.021, 0.016, 0.011], -[0.006, 0.005, 0.008] - ] - }, - { - "system": "2x AMD EPYC 3.2 GHz, Intel P4610 NVM SSD", - "system_full": "2x AMD EPYC 7F72 3.2 Ghz - Total 96 Cores - IBM Cloud's Bare Metal Service - 512 GB RAM - 1.8 TB Intel P4610 NVM SSD", - "time": "2021-01-13 00:00:00", - "kind": "server", - "result": - [ -[0.002, 0.001, 0.001], -[0.023, 0.013, 0.013], -[0.036, 0.018, 0.018], -[0.078, 0.021, 0.021], -[0.113, 0.070, 0.070], -[0.171, 0.106, 0.103], -[0.026, 0.017, 0.017], -[0.016, 0.015, 0.015], -[0.174, 0.119, 0.120], -[0.190, 0.135, 0.142], -[0.128, 0.075, 0.087], -[0.132, 0.078, 0.077], -[0.254, 0.172, 0.169], -[0.303, 0.215, 0.210], -[0.275, 0.221, 0.218], -[0.251, 0.223, 0.220], -[0.787, 0.550, 0.536], -[0.373, 0.298, 0.307], -[1.149, 1.050, 1.061], -[0.081, 0.025, 0.019], -[0.820, 0.193, 0.163], -[0.923, 0.204, 0.179], -[1.723, 0.639, 0.625], -[2.089, 0.300, 0.282], -[0.239, 0.068, 0.064], -[0.126, 0.063, 0.055], -[0.237, 0.073, 0.063], -[0.829, 0.267, 0.254], -[0.721, 0.284, 0.270], -[0.463, 0.450, 0.453], -[0.281, 0.177, 0.172], -[0.573, 0.276, 0.272], -[2.388, 1.731, 1.699], -[1.254, 0.927, 0.992], -[1.280, 0.975, 0.963], -[0.315, 0.285, 0.285], -[0.188, 0.126, 0.136], -[0.070, 0.049, 0.051], -[0.031, 0.029, 0.030], -[0.326, 0.304, 0.337], -[0.034, 0.021, 0.015], -[0.017, 0.025, 0.020], -[0.010, 0.005, 0.005] - ] - }, - { - "system": "2x AMD EPYC 7642, 7.2 TB NVM", - "system_full": "2x AMD EPYC 7642 - 512 GB RAM - 7.2 TB NVM", - "time": "2021-01-13 00:00:00", - "kind": "server", - "result": - [ -[0.003, 0.002, 0.002], -[0.056, 0.057, 0.041], -[0.081, 0.051, 0.059], -[0.080, 0.039, 0.034], -[0.149, 0.109, 0.105], -[0.192, 0.124, 0.125], -[0.031, 0.026, 0.027], -[0.029, 0.022, 0.025], -[0.181, 0.122, 0.126], -[0.206, 0.136, 0.136], -[0.133, 0.079, 0.081], -[0.140, 0.079, 0.080], -[0.207, 0.150, 0.148], -[0.281, 0.182, 0.180], -[0.228, 0.150, 0.149], -[0.183, 0.151, 0.151], -[0.431, 0.348, 0.349], -[0.316, 0.237, 0.244], -[1.091, 0.794, 0.787], -[0.081, 0.037, 0.024], -[0.833, 0.183, 0.152], -[0.950, 0.203, 0.164], -[1.735, 0.503, 0.491], -[1.656, 0.290, 0.270], -[0.251, 0.063, 0.057], -[0.134, 0.050, 0.056], -[0.250, 0.061, 0.063], -[0.844, 0.244, 0.238], -[0.735, 0.224, 0.225], -[0.532, 0.505, 0.512], -[0.259, 0.128, 0.134], -[0.523, 0.215, 0.199], -[1.380, 1.031, 1.029], -[1.122, 0.665, 0.638], -[1.115, 0.651, 0.646], -[0.261, 0.222, 0.227], -[0.151, 0.147, 0.134], -[0.063, 0.060, 0.063], -[0.035, 0.038, 0.035], -[0.372, 0.310, 0.351], -[0.031, 0.025, 0.026], -[0.019, 0.016, 0.016], -[0.008, 0.007, 0.012] - ] - }, - { - "system": "2x AMD EPYC 3.2 GHz, 4x1.8 TB Intel P4610 NVM SSD", - "system_full": "2x AMD EPYC 7F72 3.2 Ghz - Total 96 Cores - IBM Cloud's Bare Metal Service - 512 GB RAM - RAID 0 4X 1.8 TB Intel P4610 NVM SSD", - "time": "2021-01-13 00:00:00", - "kind": "server", - "result": - [ -[0.002, 0.002, 0.001], -[0.052, 0.037, 0.043], -[0.045, 0.047, 0.034], -[0.074, 0.024, 0.025], -[0.102, 0.073, 0.065], -[0.135, 0.106, 0.101], -[0.021, 0.017, 0.017], -[0.020, 0.014, 0.014], -[0.140, 0.115, 0.116], -[0.156, 0.132, 0.131], -[0.113, 0.074, 0.071], -[0.106, 0.075, 0.080], -[0.221, 0.194, 0.192], -[0.270, 0.247, 0.240], -[0.253, 0.236, 0.237], -[0.265, 0.247, 0.246], -[0.748, 0.576, 0.583], -[0.349, 0.318, 0.335], -[1.101, 1.096, 1.103], -[0.083, 0.024, 0.025], -[0.613, 0.209, 0.225], -[0.606, 0.208, 0.191], -[0.803, 0.666, 0.648], -[0.647, 0.324, 0.304], -[0.137, 0.069, 0.068], -[0.118, 0.055, 0.052], -[0.138, 0.064, 0.064], -[0.597, 0.270, 0.258], -[0.560, 0.302, 0.293], -[0.607, 0.629, 0.620], -[0.212, 0.176, 0.181], -[0.393, 0.290, 0.281], -[2.137, 1.832, 1.779], -[1.125, 0.990, 1.007], -[1.193, 1.016, 0.985], -[0.309, 0.294, 0.327], -[0.149, 0.135, 0.150], -[0.058, 0.057, 0.055], -[0.037, 0.037, 0.029], -[0.346, 0.315, 0.323], -[0.020, 0.024, 0.021], -[0.020, 0.019, 0.015], -[0.009, 0.009, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/im4gn_16xlarge.json b/website/benchmark/hardware/results/im4gn_16xlarge.json deleted file mode 100644 index 6db4f08021f..00000000000 --- a/website/benchmark/hardware/results/im4gn_16xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS im4gn.16xlarge", - "system_full": "AWS im4gn.16xlarge Neoverse-N1 4x7,500 NVMe SSD", - "time": "2022-01-04 01:04:37", - "kind": "cloud", - "result": - [ - [0.002, 0.001, 0.001], - [0.046, 0.017, 0.021], - [0.044, 0.021, 0.022], - [0.850, 0.064, 0.066], - [1.423, 0.076, 0.075], - [2.368, 0.141, 0.139], - [0.022, 0.013, 0.013], - [0.037, 0.038, 0.036], - [1.434, 0.138, 0.138], - [2.173, 0.159, 0.158], - [1.253, 0.089, 0.091], - [1.481, 0.102, 0.093], - [2.377, 0.211, 0.206], - [3.850, 0.272, 0.253], - [2.180, 0.276, 0.239], - [1.030, 0.242, 0.228], - [3.966, 0.564, 0.526], - [3.549, 0.404, 0.377], - [6.940, 1.389, 1.267], - [0.741, 0.225, 0.126], - [19.135, 0.398, 0.371], - [21.322, 0.330, 0.322], - [40.018, 0.727, 0.697], - [33.059, 1.592, 1.565], - [4.599, 0.098, 0.092], - [2.270, 0.089, 0.088], - [5.238, 0.098, 0.095], - [19.201, 0.358, 0.349], - [15.661, 0.430, 0.412], - [0.896, 0.876, 0.863], - [3.579, 0.223, 0.200], - [9.826, 0.344, 0.314], - [7.844, 2.085, 2.183], - [19.018, 1.143, 1.036], - [19.009, 1.203, 1.046], - [0.531, 0.325, 0.331], - [0.262, 0.221, 0.218], - [0.137, 0.101, 0.090], - [0.116, 0.099, 0.079], - [0.531, 0.468, 0.468], - [0.070, 0.025, 0.043], - [0.034, 0.020, 0.020], - [0.007, 0.004, 0.018] - ] - } -] diff --git a/website/benchmark/hardware/results/im4gn_4xlarge.json b/website/benchmark/hardware/results/im4gn_4xlarge.json deleted file mode 100644 index c3024c8dff2..00000000000 --- a/website/benchmark/hardware/results/im4gn_4xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS im4gn.4xlarge", - "system_full": "AWS im4gn.4xlarge Neoverse-N1 1x7,500 NVMe SSD", - "time": "2022-01-02 06:59:48", - "kind": "cloud", - "result": - [ - [0.002, 0.002, 0.002], - [0.023, 0.013, 0.013], - [0.061, 0.026, 0.025], - [0.841, 0.033, 0.032], - [1.530, 0.086, 0.084], - [2.362, 0.291, 0.292], - [0.038, 0.029, 0.028], - [0.016, 0.015, 0.014], - [1.341, 0.302, 0.301], - [1.845, 0.376, 0.360], - [0.888, 0.184, 0.181], - [1.343, 0.215, 0.210], - [2.185, 0.469, 0.459], - [3.662, 0.603, 0.580], - [2.150, 0.587, 0.561], - [0.875, 0.458, 0.449], - [4.079, 1.425, 1.343], - [3.451, 0.927, 0.859], - [7.646, 2.890, 2.877], - [0.710, 0.107, 0.042], - [19.321, 0.696, 0.677], - [21.321, 0.740, 0.726], - [40.051, 1.625, 1.598], - [32.154, 0.842, 0.819], - [4.681, 0.240, 0.221], - [1.976, 0.197, 0.195], - [5.062, 0.241, 0.223], - [18.972, 0.643, 0.628], - [15.676, 0.978, 0.957], - [0.524, 0.505, 0.518], - [3.589, 0.460, 0.461], - [9.647, 0.674, 0.642], - [8.330, 3.414, 3.354], - [19.314, 2.296, 2.286], - [19.278, 2.311, 2.273], - [0.799, 0.753, 0.717], - [0.288, 0.222, 0.222], - [0.118, 0.101, 0.099], - [0.126, 0.085, 0.084], - [0.542, 0.480, 0.446], - [0.065, 0.025, 0.031], - [0.046, 0.021, 0.020], - [0.006, 0.010, 0.017] - ] - } -] diff --git a/website/benchmark/hardware/results/im4gn_8xlarge.json b/website/benchmark/hardware/results/im4gn_8xlarge.json deleted file mode 100644 index 117812b0162..00000000000 --- a/website/benchmark/hardware/results/im4gn_8xlarge.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "AWS im4gn.8xlarge", - "system_full": "AWS im4gn.8xlarge Neoverse-N1 2x7,500 NVMe SSD", - "time": "2022-01-03 22:23:27", - "kind": "cloud", - "result": - [ - [0.002, 0.001, 0.001], - [0.034, 0.010, 0.010], - [0.044, 0.016, 0.016], - [0.862, 0.020, 0.020], - [1.500, 0.069, 0.071], - [2.454, 0.174, 0.172], - [0.025, 0.017, 0.017], - [0.023, 0.023, 0.023], - [1.329, 0.182, 0.181], - [2.167, 0.216, 0.212], - [1.159, 0.125, 0.119], - [1.483, 0.127, 0.122], - [2.313, 0.268, 0.260], - [3.788, 0.361, 0.329], - [2.043, 0.343, 0.308], - [0.872, 0.321, 0.309], - [3.921, 0.879, 0.840], - [3.460, 0.587, 0.543], - [7.272, 1.517, 1.447], - [0.707, 0.078, 0.064], - [19.314, 0.425, 0.385], - [21.332, 0.414, 0.405], - [40.030, 0.945, 0.921], - [32.867, 0.513, 0.477], - [4.640, 0.130, 0.124], - [2.227, 0.115, 0.107], - [5.223, 0.134, 0.126], - [19.179, 0.371, 0.367], - [15.658, 0.557, 0.545], - [0.541, 0.558, 0.552], - [3.548, 0.273, 0.250], - [9.772, 0.384, 0.357], - [7.896, 2.431, 2.661], - [19.149, 1.389, 1.268], - [19.103, 1.342, 1.282], - [0.583, 0.530, 0.541], - [0.238, 0.233, 0.243], - [0.114, 0.098, 0.102], - [0.124, 0.092, 0.089], - [0.552, 0.471, 0.481], - [0.053, 0.025, 0.025], - [0.047, 0.057, 0.020], - [0.022, 0.032, 0.004] - ] - } -] diff --git a/website/benchmark/hardware/results/intel_core_i5_4440.json b/website/benchmark/hardware/results/intel_core_i5_4440.json deleted file mode 100644 index b70b9e08fd4..00000000000 --- a/website/benchmark/hardware/results/intel_core_i5_4440.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Intel(R) Core(TM) i5-4440 CPU @ 3.10GHz", - "system_full": "Intel(R) Core(TM) i5-4440 CPU @ 3.10GHz", - "time": "2022-01-06 08:48:45", - "kind": "server", - "result": - [ - [0.002, 0.001, 0.001], - [0.136, 0.021, 0.020], - [1.102, 0.061, 0.055], - [2.669, 0.089, 0.084], - [2.646, 0.198, 0.192], - [4.018, 0.606, 0.600], - [0.115, 0.034, 0.044], - [0.210, 0.018, 0.018], - [4.655, 1.002, 1.004], - [6.715, 1.139, 1.150], - [3.235, 0.351, 0.352], - [3.850, 0.410, 0.408], - [4.446, 1.579, 1.570], - [7.112, 2.031, 2.061], - [5.658, 1.812, 1.804], - [3.528, 1.600, 1.599], - [9.216, 5.029, 5.031], - [7.023, 2.968, 3.362], - [17.412, 9.705, 9.695], - [2.717, 0.110, 0.100], - [28.586, 1.907, 1.870], - [34.064, 2.178, 2.172], - [67.172, 5.105, 5.101], - [79.885, 2.579, 2.540], - [9.176, 0.572, 0.560], - [4.050, 0.496, 0.492], - [8.918, 0.575, 0.568], - [28.731, 2.089, 2.058], - [24.174, 2.956, 3.043], - [5.103, 5.010, 5.007], - [10.075, 1.188, 1.197], - [18.485, 1.966, 1.954], - [19.455, 10.855, 10.917], - [31.320, 7.848, 7.831], - [30.794, 7.871, 7.877], - [3.360, 2.777, 2.778], - [0.371, 0.166, 0.180], - [0.259, 0.064, 0.083], - [0.275, 0.060, 0.058], - [1.024, 0.380, 0.378], - [0.198, 0.025, 0.025], - [0.162, 0.023, 0.015], - [0.059, 0.006, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/intel_core_i9_11900kf.json b/website/benchmark/hardware/results/intel_core_i9_11900kf.json deleted file mode 100644 index b94ce7c8f0e..00000000000 --- a/website/benchmark/hardware/results/intel_core_i9_11900kf.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Intel(R) 11th Gen Core i9-11900KF", - "system_full": "Intel(R) 11th Gen Core i9-11900KF, 64 GB RAM, 1 TB SSD", - "time": "2022-03-27 04:04:00", - "kind": "server", - "result": - [ - [0.001, 0.001, 0.001], - [0.010, 0.007, 0.006], - [0.030, 0.022, 0.020], - [0.100, 0.033, 0.035], - [0.114, 0.079, 0.074], - [0.241, 0.209, 0.225], - [0.002, 0.001, 0.001], - [0.008, 0.007, 0.007], - [0.565, 0.519, 0.511], - [0.629, 0.590, 0.599], - [0.159, 0.130, 0.134], - [0.190, 0.149, 0.150], - [0.976, 0.927, 0.915], - [1.273, 1.208, 1.199], - [1.086, 1.044, 1.041], - [1.229, 1.196, 1.200], - [3.206, 3.491, 3.213], - [1.841, 1.774, 1.809], - [5.919, 5.897, 5.799], - [0.104, 0.039, 0.037], - [1.176, 0.639, 0.694], - [1.407, 0.814, 0.825], - [2.984, 2.391, 2.121], - [2.100, 0.770, 0.716], - [0.342, 0.220, 0.211], - [0.222, 0.211, 0.189], - [0.346, 0.222, 0.224], - [1.272, 0.832, 0.822], - [1.507, 1.306, 1.282], - [3.619, 3.573, 3.597], - [0.761, 0.695, 0.703], - [1.375, 1.217, 1.229], - [8.576, 9.686, 9.070], - [5.634, 5.699, 5.801], - [6.090, 5.789, 5.797], - [1.996, 2.057, 1.946], - [0.119, 0.105, 0.112], - [0.049, 0.040, 0.040], - [0.048, 0.038, 0.038], - [0.261, 0.237, 0.231], - [0.029, 0.013, 0.014], - [0.017, 0.013, 0.011], - [0.003, 0.002, 0.003] - ] - } -] diff --git a/website/benchmark/hardware/results/intel_xeon_scaleflux_csd3000.json b/website/benchmark/hardware/results/intel_xeon_scaleflux_csd3000.json deleted file mode 100644 index 929f0cc9a86..00000000000 --- a/website/benchmark/hardware/results/intel_xeon_scaleflux_csd3000.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Intel(R) Xeon Silver 4314 with ScaleFlux CSD3000", - "system_full": "Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz, 376 GB RAM, ScaleFlux CSD300 3.5 TB", - "time": "2022-07-08 03:58:35", - "kind": "server", - "result": - [ - [0.002, 0.021, 0.002], - [0.074, 0.032, 0.013], - [0.021, 0.016, 0.016], - [0.044, 0.023, 0.022], - [0.086, 0.078, 0.076], - [0.162, 0.150, 0.142], - [0.003, 0.001, 0.001], - [0.020, 0.018, 0.018], - [0.202, 0.200, 0.189], - [0.246, 0.215, 0.222], - [0.204, 0.086, 0.081], - [0.098, 0.086, 0.088], - [0.250, 0.230, 0.226], - [0.306, 0.280, 0.285], - [0.267, 0.250, 0.256], - [0.267, 0.245, 0.253], - [0.747, 0.681, 0.679], - [0.506, 0.438, 0.439], - [1.186, 1.241, 1.146], - [0.052, 0.027, 0.022], - [0.510, 0.384, 0.370], - [0.517, 0.363, 0.352], - [1.000, 0.916, 0.896], - [0.723, 0.487, 0.536], - [0.120, 0.101, 0.101], - [0.096, 0.076, 0.079], - [0.122, 0.095, 0.100], - [0.517, 0.371, 0.371], - [0.573, 0.448, 0.444], - [0.620, 0.621, 0.635], - [0.214, 0.195, 0.189], - [0.357, 0.273, 0.280], - [1.600, 1.483, 1.631], - [1.145, 1.154, 1.085], - [1.156, 1.082, 1.098], - [0.486, 0.458, 0.486], - [0.086, 0.080, 0.086], - [0.039, 0.037, 0.041], - [0.037, 0.035, 0.036], - [0.181, 0.163, 0.167], - [0.019, 0.018, 0.015], - [0.024, 0.018, 0.015], - [0.005, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/hardware/results/linode_cloud_amd_epyc_7601.json b/website/benchmark/hardware/results/linode_cloud_amd_epyc_7601.json deleted file mode 100644 index 78bc423d860..00000000000 --- a/website/benchmark/hardware/results/linode_cloud_amd_epyc_7601.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Linode 16GB", - "system_full": "Linode Cloud 16GB RAM, 6 AMD EPYC 7601 CPU Cores 2200 MHz, 320GB Storage, CentOS Linux release 7.9.2009 kernel 3.10.0-1160, KVM", - "time": "2021-11-05 18:00:00", - "kind": "cloud", - "result": - [ -[0.003, 0.002, 0.002], -[0.348, 0.017, 0.024], -[0.258, 0.060, 0.066], -[1.178, 0.091, 0.088], -[0.308, 0.192, 0.186], -[2.037, 0.604, 0.602], -[0.359, 0.037, 0.035], -[0.129, 0.025, 0.022], -[1.435, 1.026, 1.034], -[1.395, 1.159, 1.147], -[0.586, 0.405, 0.413], -[0.648, 0.472, 0.465], -[2.223, 1.604, 1.588], -[2.264, 2.140, 2.170], -[2.344, 1.802, 1.768], -[1.959, 1.873, 1.805], -[5.687, 5.096, 5.146], -[3.179, 3.017, 2.994], -[12.036, 11.416, 11.191], -[0.403, 0.114, 0.129], -[9.107, 1.949, 1.902], -[4.205, 2.061, 2.035], -[9.822, 4.721, 4.627], -[23.501, 3.074, 2.504], -[1.249, 0.586, 0.567], -[0.648, 0.489, 0.494], -[1.199, 0.599, 0.562], -[3.706, 2.056, 1.885], -[8.797, 3.413, 3.266], -[3.787, 3.730, 3.541], -[1.834, 1.373, 1.443], -[4.264, 2.227, 2.081], -[11.448, 10.761, 10.775], -[11.334, 7.668, 7.630], -[9.125, 7.593, 7.642], -[3.179, 3.042, 3.070], -[0.511, 0.311, 0.308], -[0.155, 0.133, 0.116], -[0.197, 0.113, 0.096], -[0.729, 0.566, 0.530], -[0.235, 0.028, 0.024], -[0.094, 0.028, 0.019], -[0.050, 0.040, 0.010] - ] - } -] diff --git a/website/benchmark/hardware/results/macbook_air_m1.json b/website/benchmark/hardware/results/macbook_air_m1.json deleted file mode 100644 index 33f15d02480..00000000000 --- a/website/benchmark/hardware/results/macbook_air_m1.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "MacBook Air M1", - "system_full": "MacBook Air M1 13\" 2020, 8‑core CPU, 16 GiB RAM, 512 GB SSD", - "time": "2021-04-11 00:00:00", - "kind": "laptop", - "result": - [ -[0.003, 0.001, 0.001], -[0.019, 0.014, 0.014], -[0.042, 0.034, 0.033], -[0.101, 0.043, 0.041], -[0.100, 0.102, 0.101], -[0.394, 0.283, 0.289], -[0.029, 0.027, 0.027], -[0.018, 0.018, 0.018], -[0.511, 0.489, 0.494], -[0.620, 0.615, 0.618], -[0.217, 0.200, 0.197], -[0.237, 0.235, 0.242], -[0.774, 0.762, 0.761], -[0.969, 0.982, 0.969], -[0.896, 0.887, 0.861], -[0.999, 0.943, 0.945], -[3.343, 2.426, 2.366], -[1.463, 1.414, 1.382], -[4.958, 4.268, 4.257], -[0.056, 0.050, 0.049], -[1.696, 0.851, 0.846], -[1.036, 1.104, 1.174], -[4.326, 2.224, 2.255], -[1.397, 1.038, 1.055], -[0.317, 0.310, 0.305], -[0.274, 0.284, 0.269], -[0.317, 0.316, 0.313], -[0.943, 0.952, 0.951], -[2.794, 1.427, 1.433], -[1.606, 1.600, 1.605], -[0.751, 0.691, 0.679], -[1.532, 1.000, 0.952], -[9.679, 8.895, 7.967], -[7.001, 4.472, 4.050], -[4.790, 3.971, 3.987], -[1.215, 1.204, 1.256], -[0.129, 0.125, 0.119], -[0.057, 0.061, 0.056], -[0.045, 0.043, 0.043], -[0.256, 0.247, 0.249], -[0.020, 0.014, 0.013], -[0.013, 0.011, 0.012], -[0.009, 0.009, 0.009] - ] - } -] diff --git a/website/benchmark/hardware/results/macbook_pro_core_i7_2014.json b/website/benchmark/hardware/results/macbook_pro_core_i7_2014.json deleted file mode 100644 index 011401ed3e5..00000000000 --- a/website/benchmark/hardware/results/macbook_pro_core_i7_2014.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "MacBook Pro 2014", - "system_full": "MacBook Pro 2014, 2.5 GHz Quad-Core Intel Core i7, 16 GiB RAM", - "time": "2020-04-04 00:00:00", - "kind": "laptop", - "result": - [ - [0.030, 0.003, 0.003], - [0.078, 0.020, 0.020], - [0.176, 0.056, 0.056], - [0.358, 0.082, 0.082], - [0.451, 0.254, 0.208], - [0.887, 0.582, 0.583], - [0.040, 0.004, 0.002], - [0.079, 0.023, 0.024], - [1.213, 1.100, 1.109], - [1.839, 1.250, 1.529], - [0.590, 0.304, 0.370], - [0.645, 0.502, 0.489], - [1.793, 1.418, 1.531], - [2.803, 1.953, 2.333], - [2.101, 1.871, 1.718], - [1.875, 1.508, 1.493], - [5.053, 5.682, 5.334], - [3.484, 4.643, 3.188], - [10.762, 10.994, 10.125], - [0.517, 0.241, 0.166], - [3.898, 1.701, 1.828], - [4.394, 2.155, 1.987], - [8.082, 4.622, 5.137], - [6.218, 2.413, 2.131], - [1.099, 0.531, 0.550], - [0.766, 0.436, 0.712], - [1.094, 0.585, 0.559], - [4.207, 1.628, 1.818], - [3.969, 2.775, 2.579], - [2.604, 2.441, 2.449], - [1.773, 1.262, 1.165], - [3.059, 1.803, 1.833], - [19.756, 17.851, 13.698], - [10.651, 8.640, 7.184], - [10.125, 8.230, 7.775], - [2.865, 2.256, 2.196], - [0.292, 0.226, 0.249], - [0.194, 0.084, 0.070], - [0.162, 0.063, 0.064], - [0.515, 0.404, 0.423], - [0.127, 0.024, 0.025], - [0.099, 0.021, 0.018], - [0.045, 0.007, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/macbook_pro_core_i7_2018.json b/website/benchmark/hardware/results/macbook_pro_core_i7_2018.json deleted file mode 100644 index fa7ff4d72ee..00000000000 --- a/website/benchmark/hardware/results/macbook_pro_core_i7_2018.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "MacBook Pro 2018", - "system_full": "MacBook Pro 2018, 2.7 GHz Quad-Core Intel Core i7, 16 GiB RAM, 1TB SSD", - "time": "2020-04-04 00:00:00", - "kind": "laptop", - "result": - [ - [0.002, 0.002, 0.002], - [0.028, 0.031, 0.025], - [0.060, 0.058, 0.047], - [0.125, 0.101, 0.070], - [0.164, 0.185, 0.168], - [0.672, 0.568, 0.557], - [0.072, 0.038, 0.037], - [0.031, 0.021, 0.021], - [0.849, 0.836, 0.820], - [0.941, 0.938, 0.942], - [0.423, 0.444, 0.457], - [0.617, 0.556, 0.555], - [1.761, 1.694, 1.641], - [2.190, 2.277, 2.226], - [1.964, 1.895, 1.934], - [1.956, 1.978, 1.884], - [6.029, 5.977, 5.975], - [3.372, 3.436, 3.439], - [12.883, 12.778, 12.572], - [0.116, 0.080, 0.076], - [1.874, 1.372, 1.467], - [2.321, 2.356, 2.238], - [5.304, 4.955, 4.912], - [2.474, 1.993, 2.033], - [0.744, 0.708, 0.719], - [0.562, 0.568, 0.602], - [0.737, 0.742, 0.719], - [1.547, 1.580, 1.583], - [3.074, 2.665, 2.697], - [5.466, 5.560, 5.693], - [1.658, 1.562, 1.543], - [2.935, 2.802, 2.743], - [19.141, 19.674, 19.212], - [8.738, 8.334, 8.302], - [8.268, 8.276, 8.364], - [3.311, 3.288, 3.243], - [0.182, 0.169, 0.169], - [0.075, 0.066, 0.066], - [0.066, 0.057, 0.053], - [0.353, 0.324, 0.327], - [0.030, 0.018, 0.018], - [0.018, 0.015, 0.015], - [0.011, 0.007, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/macbook_pro_core_i7_2020.json b/website/benchmark/hardware/results/macbook_pro_core_i7_2020.json deleted file mode 100644 index 8250c5f3c7e..00000000000 --- a/website/benchmark/hardware/results/macbook_pro_core_i7_2020.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "MacBook Pro 2020", - "system_full": "MacBook Pro 2020, 2.3 GHz Quad-Core Intel Core i7 (10th gen), 32 GiB RAM, 2TB SSD", - "time": "2020-06-25 00:00:00", - "kind": "laptop", - "result": - [ -[0.003, 0.002, 0.002], -[0.046, 0.025, 0.015], -[0.042, 0.042, 0.041], -[0.069, 0.068, 0.069], -[0.164, 0.166, 0.165], -[0.559, 0.550, 0.552], -[0.026, 0.026, 0.026], -[0.016, 0.015, 0.016], -[0.891, 0.880, 0.879], -[1.029, 1.031, 1.022], -[0.355, 0.358, 0.369], -[0.415, 0.416, 0.417], -[1.406, 1.355, 1.349], -[1.775, 1.787, 1.788], -[1.615, 1.633, 1.619], -[1.532, 1.525, 1.482], -[4.284, 4.203, 4.180], -[2.825, 2.782, 2.756], -[8.516, 8.328, 8.408], -[0.097, 0.073, 0.074], -[1.541, 1.557, 1.557], -[1.945, 1.920, 1.911], -[4.410, 4.375, 4.353], -[null, null, null], -[0.613, 0.616, 0.621], -[0.535, 0.557, 0.541], -[0.622, 0.622, 0.623], -[1.536, 1.548, 1.535], -[2.436, 2.430, 2.435], -[2.551, 2.542, 2.589], -[1.470, 1.426, 1.430], -[2.377, 2.248, 2.227], -[15.628, null, null], -[6.155, 6.397, 6.368], -[6.439, 6.392, 6.412], -[2.643, 2.337, 2.316], -[0.163, 0.174, 0.155], -[0.060, 0.064, 0.063], -[0.057, 0.057, 0.053], -[0.331, 0.314, 0.314], -[0.016, 0.023, 0.020], -[0.014, 0.014, 0.013], -[0.005, 0.006, 0.007] - ] - } -] diff --git a/website/benchmark/hardware/results/macbook_pro_m1_2020.json b/website/benchmark/hardware/results/macbook_pro_m1_2020.json deleted file mode 100644 index 52e62b06d61..00000000000 --- a/website/benchmark/hardware/results/macbook_pro_m1_2020.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "MacBook Pro M1", - "system_full": "MacBook Pro M1 13\" 2020, 8‑core CPU, 16 GiB RAM, 512 GB SSD", - "time": "2021-10-22 18:17:00", - "kind": "laptop", - "result": - [ -[0.001, 0.001, 0.001], -[0.020, 0.013, 0.012], -[0.046, 0.032, 0.032], -[0.083, 0.045, 0.044], -[0.099, 0.098, 0.099], -[0.361, 0.306, 0.297], -[0.031, 0.028, 0.026], -[0.017, 0.015, 0.015], -[0.530, 0.500, 0.497], -[0.621, 0.633, 0.634], -[0.229, 0.207, 0.207], -[0.259, 0.243, 0.242], -[0.818, 0.796, 0.767], -[1.090, 1.052, 1.055], -[1.053, 1.011, 1.071], -[1.223, 0.966, 0.864], -[14.533, 8.276, 8.041], -[6.470, 8.012, 6.991], -[38.097, 6.530, 8.532], -[0.058, 0.054, 0.052], -[1.341, 1.306, 1.141], -[1.313, 1.330, 1.311], -[3.156, 2.974, 2.919], -[1.665, 1.423, 1.401], -[0.421, 0.337, 0.338], -[0.289, 0.287, 0.300], -[0.348, 0.344, 0.341], -[1.149, 1.142, 1.150], -[1.855, 1.591, 1.984], -[1.691, 1.644, 1.646], -[0.921, 0.750, 0.771], -[1.485, 1.233, 1.011], -[91.560, 10.399, 8.895], -[8.034, 7.663, 7.372], -[6.836, 7.444, 7.235], -[1.263, 1.166, 1.150], -[0.125, 0.118, 0.118], -[0.055, 0.053, 0.053], -[0.043, 0.043, 0.043], -[0.248, 0.243, 0.251], -[0.016, 0.012, 0.012], -[0.011, 0.010, 0.010], -[0.003, 0.002, 0.002] - ] - } -] diff --git a/website/benchmark/hardware/results/macbook_pro_m1_2021.json b/website/benchmark/hardware/results/macbook_pro_m1_2021.json deleted file mode 100644 index 516940e1ef2..00000000000 --- a/website/benchmark/hardware/results/macbook_pro_m1_2021.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "MacBook Pro M1", - "system_full": "MacBook Pro M1 Max 16\" 2022, 64 GiB RAM, 1 TB SSD", - "time": "2022-02-27 00:00:00", - "kind": "laptop", - "result": - [ - [0.012, 0.001, 0.001], - [0.096, 0.012, 0.010], - [0.043, 0.022, 0.023], - [0.063, 0.031, 0.030], - [0.099, 0.070, 0.070], - [0.229, 0.197, 0.195], - [0.012, 0.001, 0.001], - [0.027, 0.012, 0.011], - [0.340, 0.301, 0.306], - [0.439, 0.383, 0.386], - [0.169, 0.134, 0.136], - [0.197, 0.160, 0.162], - [0.475, 0.435, 0.432], - [0.615, 0.557, 0.553], - [0.553, 0.502, 0.507], - [0.490, 0.445, 0.439], - [1.392, 1.260, 1.254], - [0.865, 0.833, 0.835], - [2.285, 2.180, 2.194], - [0.064, 0.035, 0.033], - [0.761, 0.650, 0.651], - [0.867, 0.715, 0.718], - [1.753, 1.478, 1.499], - [1.037, 0.737, 0.735], - [0.251, 0.201, 0.202], - [0.208, 0.172, 0.174], - [0.254, 0.202, 0.201], - [0.733, 0.598, 0.603], - [0.995, 0.882, 0.879], - [0.562, 0.545, 0.545], - [0.431, 0.371, 0.371], - [0.586, 0.490, 0.490], - [2.882, 2.664, 2.656], - [2.255, 2.147, 2.146], - [2.248, 2.137, 2.154], - [0.659, 0.638, 0.631], - [0.125, 0.108, 0.108], - [0.070, 0.052, 0.052], - [0.060, 0.042, 0.042], - [0.250, 0.229, 0.228], - [0.030, 0.013, 0.012], - [0.026, 0.011, 0.010], - [0.017, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/hardware/results/oracle.json b/website/benchmark/hardware/results/oracle.json deleted file mode 100644 index 6470b70e109..00000000000 --- a/website/benchmark/hardware/results/oracle.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Oracle Cloud ARM 4vCPU", - "system_full": "Oracle Cloud (free tier), Ampere Altra, 4 vCPU, 24 GiB RAM", - "time": "2021-07-28 00:00:00", - "kind": "cloud", - "result": - [ -[0.031, 0.008, 0.002], -[0.087, 0.044, 0.039], -[0.193, 0.068, 0.068], -[0.807, 0.110, 0.089], -[1.622, 0.230, 0.244], -[2.539, 0.765, 0.742], -[0.146, 0.087, 0.092], -[0.074, 0.044, 0.038], -[1.650, 0.973, 0.978], -[2.020, 1.139, 1.166], -[0.907, 0.530, 0.509], -[0.964, 0.618, 0.608], -[2.448, 1.500, 1.529], -[4.810, 1.994, 1.930], -[2.932, 1.814, 1.853], -[1.935, 1.577, 1.583], -[6.272, 4.697, 4.650], -[4.279, 2.832, 2.871], -[12.380, 9.137, 9.085], -[0.601, 0.167, 0.118], -[25.357, 1.873, 1.848], -[28.153, 2.274, 2.202], -[53.116, 4.946, 4.907], -[56.118, 2.229, 2.192], -[5.749, 0.732, 0.696], -[1.829, 0.601, 0.592], -[5.860, 0.748, 0.709], -[24.439, 1.954, 1.949], -[20.452, 3.093, 3.042], -[1.539, 1.448, 1.437], -[4.704, 1.362, 1.430], -[12.698, 1.997, 1.940], -[12.854, 10.336, 10.454], -[26.098, 6.737, 6.771], -[26.259, 6.679, 6.677], -[2.602, 2.305, 2.278], -[0.283, 0.182, 0.181], -[0.130, 0.101, 0.085], -[0.174, 0.068, 0.073], -[0.557, 0.374, 0.377], -[0.066, 0.017, 0.017], -[0.049, 0.014, 0.014], -[0.033, 0.006, 0.004] - ] - } -] diff --git a/website/benchmark/hardware/results/pinebook_pro.json b/website/benchmark/hardware/results/pinebook_pro.json deleted file mode 100644 index 7a79a299b7d..00000000000 --- a/website/benchmark/hardware/results/pinebook_pro.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Pinebook Pro", - "system_full": "Pinebook Pro (AArch64, 4 GiB RAM)", - "time": "2020-03-08 00:00:00", - "kind": "laptop", - "result": - [ - [0.021, 0.009, 0.007], - [0.195, 0.135, 0.144], - [0.439, 0.264, 0.273], - [1.266, 0.672, 0.706], - [1.337, 0.795, 0.790], - [2.706, 1.989, 1.947], - [0.246, 0.198, 0.197], - [0.157, 0.142, 0.133], - [4.150, 3.769, 3.617], - [5.223, 4.405, 4.234], - [2.391, 1.815, 1.785], - [2.534, 2.158, 2.042], - [7.895, 6.890, 7.003], - [10.338, 9.311, 9.410], - [8.139, 7.441, 7.312], - [8.532, 8.035, 8.011], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null] - ] - } -] diff --git a/website/benchmark/hardware/results/pixel_3a.json b/website/benchmark/hardware/results/pixel_3a.json deleted file mode 100644 index a79f3549676..00000000000 --- a/website/benchmark/hardware/results/pixel_3a.json +++ /dev/null @@ -1,57 +0,0 @@ -[ - { - "system": "Google Pixel 3a", - "system_full": "Google Pixel 3a, 8 cores, 4 GB RAM", - "cpu_vendor": "Qualcomm", - "cpu_model": "Snapdragon 670", - "time": "2020-07-15 00:00:00", - "kind": "phone", - "result": - [ - [0.034, 0.029, 0.035], - [0.283, 0.186, 0.320], - [0.447, 0.410, 0.359], - [0.808, 0.609, 0.534], - [1.162, 1.140, 1.230], - [2.149, 1.957, 2.019], - [0.336, 0.332, 0.404], - [0.316, 0.326, 0.243], - [2.671, 2.592, 2.692], - [3.138, 3.113, 3.122], - [1.870, 2.075, 2.012], - [1.673, 1.679, 1.698], - [null, null, null], - [null, null, null], - [6.340, 5.212, 5.476], - [5.184, 5.362, 5.213], - [null, null, null], - [null, null, null], - [null, null, null], - [0.818, 0.500, 0.605], - [8.266, 7.663, 7.208], - [8.109, 8.402, 8.925], - [null, null, null], - [null, null, null], - [2.655, 1.891, 1.916], - [1.609, 1.675, 1.607], - [1.873, 1.877, 1.882], - [7.922, 7.835, 7.648], - [null, null, null], - [10.069, 9.602, 9.614], - [4.542, 4.094, 4.049], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [null, null, null], - [0.962, 0.921, null], - [0.394, 0.344, 0.357], - [0.343, 0.312, 0.344], - [1.793, 1.617, 1.642], - [0.159, 0.134, 0.136], - [0.121, 0.110, 0.098], - [0.052, 0.033, 0.036] - ] - } -] - diff --git a/website/benchmark/hardware/results/powerpc.json b/website/benchmark/hardware/results/powerpc.json deleted file mode 100644 index f6701126697..00000000000 --- a/website/benchmark/hardware/results/powerpc.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "POWER9 x_large VM", - "system_full": "POWER9 x_large VM (8 vCPU, pSeries, 16GiB RAM)", - "time": "2022-05-10 00:00:00", - "kind": "cloud", - "result": - [ -[0.263, 0.004, 0.004], -[0.352, 0.044, 0.040], -[1.556, 0.087, 0.083], -[2.341, 0.129, 0.120], -[0.490, 0.264, 0.258], -[4.565, 1.059, 1.038], -[0.042, 0.004, 0.004], -[0.132, 0.057, 0.062], -[3.168, 0.915, 0.917], -[2.020, 1.180, 1.183], -[1.510, 0.430, 0.435], -[1.525, 0.552, 0.607], -[3.104, 1.500, 1.529], -[2.459, 1.995, 1.975], -[5.291, 1.847, 1.849], -[2.552, 1.842, 1.920], -[7.378, 6.412, 6.283], -[3.881, 3.592, 3.552], -[13.757, 10.763, 10.880], -[0.504, 0.156, 0.134], -[16.285, 1.777, 1.770], -[5.135, 2.305, 2.242], -[21.215, 5.129, 5.183], -[35.624, 2.645, 2.476], -[2.648, 0.676, 0.715], -[0.815, 0.576, 0.568], -[1.365, 0.746, 0.692], -[4.857, 1.922, 1.898], -[14.380, 5.401, 5.445], -[4.555, 4.535, 4.371], -[4.095, 1.366, 1.349], -[7.474, 1.816, 1.859], -[14.235, 13.406, 13.854], -[14.165, 9.465, 9.715], -[11.313, 8.640, 8.462], -[3.605, 3.009, 2.813], -[0.662, 0.394, 0.391], -[0.350, 0.196, 0.194], -[0.217, 0.150, 0.145], -[1.025, 0.918, 0.895], -[0.254, 0.057, 0.057], -[0.132, 0.045, 0.046], -[0.047, 0.015, 0.012] - ] - } -] diff --git a/website/benchmark/hardware/results/qemu_aarch64_cascade_lake_80_vcpu.json b/website/benchmark/hardware/results/qemu_aarch64_cascade_lake_80_vcpu.json deleted file mode 100644 index ed25794c77b..00000000000 --- a/website/benchmark/hardware/results/qemu_aarch64_cascade_lake_80_vcpu.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "system": "Intel 80vCPU, QEMU, AArch64", - "system_full": "Intel Cascade Lake 80vCPU running AArch64 ClickHouse under qemu-aarch64 version 4.2.1 (userspace emulation)", - "cpu_vendor": "Intel", - "time": "2021-04-05 00:00:00", - "kind": "cloud", - "result": - [ -[0.045, 0.006, 0.006], -[0.366, 0.201, 0.576], -[0.314, 0.144, 0.152], -[0.701, 0.111, 0.110], -[0.308, 0.259, 0.261], -[1.009, 0.642, 0.658], -[0.160, 0.087, 0.086], -[0.123, 0.079, 0.080], -[0.570, 0.458, 0.454], -[0.708, 0.540, 0.547], -[0.541, 0.460, 0.464], -[0.578, 0.524, 0.531], -[0.927, 0.908, 0.906], -[1.075, 0.992, 1.051], -[1.055, 0.965, 0.991], -[0.904, 0.790, 0.781], -[2.076, 2.134, 2.121], -[1.668, 1.648, 1.615], -[4.134, 3.879, 4.002], -[0.142, 0.103, 0.105], -[7.018, 1.479, 1.515], -[1.618, 1.643, 1.680], -[6.516, 3.172, 3.182], -[6.028, 2.070, 2.076], -[0.608, 0.559, 0.577], -[0.548, 0.515, 0.516], -[0.598, 0.564, 0.563], -[1.562, 1.529, 1.537], -[5.968, 2.311, 2.375], -[3.263, 3.239, 3.279], -[1.134, 0.903, 0.928], -[2.987, 1.270, 1.284], -[6.256, 5.665, 5.320], -[3.020, 3.148, 3.109], -[3.092, 3.131, 3.146], -[1.183, 1.140, 1.185], -[0.762, 0.704, 0.715], -[0.412, 0.380, 0.385], -[0.376, 0.330, 0.327], -[1.505, 1.532, 1.503], -[0.201, 0.133, 0.130], -[0.173, 0.123, 0.150], -[0.070, 0.028, 0.028] - ] - } -] diff --git a/website/benchmark/hardware/results/raspberry_pi_b.json b/website/benchmark/hardware/results/raspberry_pi_b.json deleted file mode 100644 index fff027f2bfe..00000000000 --- a/website/benchmark/hardware/results/raspberry_pi_b.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Raspberry Pi 4", - "system_full": "Raspberry Pi 4 Model B 8GB", - "time": "2020-11-14 00:00:00", - "kind": "desktop", - "result": - [ -[0.015, 0.005, 0.005], -[0.214, 0.176, 0.171], -[1.205, 0.535, 0.534], -[5.138, 1.320, 1.342], -[5.129, 1.489, 1.494], -[8.349, 3.230, 3.290], -[0.327, 0.234, 0.231], -[0.228, 0.183, 0.175], -[7.530, 5.440, 5.662], -[9.073, 6.912, 6.881], -[5.609, 2.995, 3.201], -[5.817, 3.245, 3.239], -[12.712, 11.231, 11.141], -[18.517, 14.798, 14.698], -[13.510, 11.171, 11.260], -[12.944, 11.576, 11.706], -[28.042, 23.958, 22.930], -[18.430, 13.992, 14.173], -[null, null, null], -[5.193, 1.342, 1.311], -[59.597, 19.483, 20.791], -[68.012, 24.377, 24.159], -[127.859, 49.266, 47.251], -[133.812, 25.078, 24.812], -[16.838, 5.128, 4.824], -[8.195, 4.025, 4.066], -[16.791, 4.911, 4.997], -[59.740, 24.009, 23.916], -[50.460, 25.922, 26.049], -[23.961, 23.536, 23.835], -[15.293, 8.960, 8.687], -[36.904, 14.905, 14.755], -[null, null, null], -[74.268, 74.887, 74.103], -[74.727, 59.369, 65.550], -[15.400, 14.807, 15.437], -[1.286, 0.836, 0.804], -[0.501, 0.341, 0.320], -[0.704, 0.299, 0.265], -[2.539, 1.756, 1.710], -[0.345, 0.085, 0.082], -[0.219, 0.070, 0.072], -[0.044, 0.021, 0.023] - ] - } -] diff --git a/website/benchmark/hardware/results/rock_pi.json b/website/benchmark/hardware/results/rock_pi.json deleted file mode 100644 index 210dc213a49..00000000000 --- a/website/benchmark/hardware/results/rock_pi.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Rock Pi 4, 4GiB, NVMe", - "system_full": "Rock Pi 4, 4GiB C, NVMe", - "time": "2021-12-23 00:00:00", - "kind": "desktop", - "result": - [ -[0.007, 0.014, 0.005], -[0.229, 0.132, 0.215], -[0.489, 0.351, 0.306], -[0.879, 0.774, 0.768], -[1.034, 0.966, 0.879], -[2.491, 2.249, 2.493], -[0.379, 0.212, 0.213], -[0.227, 0.140, 0.152], -[3.944, 3.823, 3.805], -[5.272, 4.985, 5.069], -[2.356, 2.193, 2.254], -[2.819, 2.595, 2.568], -[9.124, 8.306, 8.529], -[11.857, 11.412, 11.290], -[9.796, 9.477, 9.610], -[8.846, 8.867, 8.909], -[null, null, null], -[null, null, null], -[null, null, null], -[1.293, 0.887, 0.980], -[15.018, 14.928, 14.748], -[19.179, 17.889, 18.021], -[45.524, 46.927, 46.909], -[23.904, 23.197, 23.511], -[5.264, 4.891, 4.936], -[4.211, 3.940, 4.047], -[5.113, 4.615, 4.783], -[17.910, 16.800, 16.410], -[23.537, 22.249, 22.172], -[16.549, 16.388, 16.337], -[9.562, 9.006, 9.260], -[17.097, 17.676, 17.585], -[null, null, null], -[null, null, null], -[null, null, null], -[null, null, null], -[1.668, 1.469, 1.342], -[0.463, 0.442, 0.353], -[0.486, 0.410, 0.346], -[2.190, 2.014, 1.878], -[0.263, 0.097, 0.201], -[0.173, 0.082, 0.139], -[0.188, 0.024, 0.016] - ] - } -] diff --git a/website/benchmark/hardware/results/scaleway_epyc.json b/website/benchmark/hardware/results/scaleway_epyc.json deleted file mode 100644 index 54cf6eaf459..00000000000 --- a/website/benchmark/hardware/results/scaleway_epyc.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Scaleway GP1-XS", - "system_full": "Scaleway GP1-XS (AMD EPYC 7401P, 4 cores, 16 GiB RAM, NVMe)", - "time": "2021-11-17 00:00:00", - "kind": "server", - "result": - [ -[0.002, 0.002, 0.002], -[0.044, 0.026, 0.028], -[0.100, 0.076, 0.067], -[0.151, 0.101, 0.102], -[0.276, 0.218, 0.207], -[0.740, 0.693, 0.703], -[0.066, 0.054, 0.050], -[0.062, 0.035, 0.041], -[1.271, 1.124, 1.141], -[1.441, 1.279, 1.280], -[0.438, 0.382, 0.376], -[0.514, 0.485, 0.467], -[1.914, 1.664, 1.694], -[2.367, 2.277, 2.258], -[2.143, 2.066, 2.131], -[1.923, 1.826, 1.777], -[5.894, 5.653, 5.765], -[3.545, 3.464, 3.405], -[12.060, 12.893, 13.049], -[0.196, 0.121, 0.118], -[2.328, 1.841, 1.808], -[2.498, 2.100, 2.067], -[5.839, 5.094, 5.078], -[3.068, 2.255, 2.202], -[0.718, 0.611, 0.616], -[0.597, 0.531, 0.529], -[0.702, 0.615, 0.592], -[2.310, 1.991, 1.969], -[3.540, 3.222, 3.179], -[3.950, 3.977, 3.876], -[1.527, 1.319, 1.319], -[2.264, 1.950, 1.927], -[11.987, 11.644, 11.777], -[10.142, 9.150, 9.204], -[9.627, 9.298, 9.183], -[2.937, 2.812, 2.849], -[0.229, 0.226, 0.227], -[0.096, 0.097, 0.095], -[0.087, 0.074, 0.071], -[0.464, 0.447, 0.463], -[0.037, 0.027, 0.032], -[0.030, 0.046, 0.029], -[0.006, 0.017, 0.014] - ] - } -] diff --git a/website/benchmark/hardware/results/selectel_cloud_16vcpu.json b/website/benchmark/hardware/results/selectel_cloud_16vcpu.json deleted file mode 100644 index c7f734b8256..00000000000 --- a/website/benchmark/hardware/results/selectel_cloud_16vcpu.json +++ /dev/null @@ -1,158 +0,0 @@ -[ - { - "system": "Selectel Cloud 'fast disk'", - "system_full": "Selectel Cloud, 16 vCPU, 32 GB RAM, 'fast disk'", - "time": "2020-01-26 00:00:00", - "kind": "cloud", - "result": - [ - [0.017, 0.002, 0.002], - [0.047, 0.011, 0.012], - [0.070, 0.024, 0.024], - [0.350, 0.033, 0.031], - [0.353, 0.087, 0.091], - [0.623, 0.233, 0.226], - [0.040, 0.018, 0.019], - [0.021, 0.011, 0.011], - [0.628, 0.420, 0.415], - [0.725, 0.466, 0.468], - [0.389, 0.152, 0.151], - [0.416, 0.181, 0.172], - [0.819, 0.481, 0.495], - [1.332, 0.714, 0.719], - [1.044, 0.853, 0.796], - [0.865, 0.827, 0.846], - [2.010, 1.816, 1.798], - [1.097, 0.940, 0.927], - [4.414, 4.188, 4.310], - [0.336, 0.034, 0.030], - [4.807, 0.541, 0.527], - [5.494, 0.633, 0.608], - [10.233, 1.448, 1.469], - [9.897, 0.721, 0.676], - [1.322, 0.212, 0.213], - [0.594, 0.177, 0.175], - [1.319, 0.218, 0.216], - [4.804, 0.615, 0.567], - [4.093, 0.855, 0.801], - [1.428, 1.378, 1.300], - [1.299, 0.649, 0.646], - [2.921, 0.835, 0.809], - [5.717, 5.883, 6.368], - [5.655, 2.715, 2.666], - [5.943, 3.008, 2.795], - [1.091, 1.011, 1.089], - [0.264, 0.212, 0.221], - [0.097, 0.085, 0.081], - [0.083, 0.081, 0.075], - [0.498, 0.531, 0.446], - [0.063, 0.031, 0.033], - [0.029, 0.022, 0.022], - [0.006, 0.006, 0.005] - ] - }, - { - "system": "Selectel Cloud 'basic disk'", - "system_full": "Selectel Cloud, 16 vCPU, 32 GB RAM, 'basic disk'", - "time": "2020-01-26 00:00:00", - "kind": "cloud", - "result": - [ - [0.142, 0.002, 0.002], - [0.090, 0.014, 0.013], - [0.478, 0.023, 0.023], - [2.305, 0.032, 0.032], - [2.371, 0.090, 0.087], - [3.377, 0.228, 0.224], - [0.080, 0.020, 0.017], - [0.034, 0.009, 0.009], - [3.210, 0.425, 0.414], - [3.389, 0.471, 0.459], - [2.446, 0.157, 0.152], - [2.331, 0.187, 0.177], - [3.826, 0.503, 0.505], - [5.749, 0.725, 0.701], - [4.521, 0.752, 0.705], - [2.345, 0.572, 0.588], - [6.360, 1.697, 1.512], - [5.928, 0.949, 0.908], - [11.325, 3.598, 3.829], - [6.485, 0.126, 0.033], - [24.494, 0.550, 0.519], - [27.711, 0.615, 0.594], - [52.599, 1.455, 1.435], - [50.820, 1.001, 0.648], - [6.933, 0.206, 0.203], - [3.278, 0.177, 0.172], - [7.082, 0.216, 0.206], - [24.399, 0.564, 0.541], - [20.916, 0.812, 0.801], - [1.574, 1.323, 1.358], - [6.717, 0.558, 0.550], - [13.772, 0.830, 0.801], - [12.547, 5.084, 4.637], - [25.178, 2.601, 2.337], - [25.118, 2.460, 2.432], - [1.626, 0.901, 0.895], - [0.286, 0.206, 0.209], - [0.130, 0.081, 0.083], - [0.220, 0.075, 0.076], - [6.502, 0.495, 0.520], - [0.223, 0.024, 0.024], - [0.045, 0.028, 0.030], - [0.006, 0.005, 0.004] - ] - }, - { - "system": "Selectel Cloud 'universal disk'", - "system_full": "Selectel Cloud, 16 vCPU, 32 GB RAM, 'universal disk'", - "time": "2020-01-26 00:00:00", - "kind": "cloud", - "result": - [ - [0.016, 0.002, 0.002], - [0.055, 0.023, 0.009], - [0.252, 0.020, 0.019], - [1.337, 0.038, 0.031], - [1.342, 0.088, 0.086], - [2.192, 0.241, 0.222], - [0.047, 0.017, 0.016], - [0.017, 0.009, 0.009], - [1.946, 0.410, 0.412], - [2.262, 0.454, 0.455], - [1.427, 0.158, 0.150], - [1.487, 0.181, 0.172], - [2.327, 0.481, 0.464], - [3.794, 0.657, 0.633], - [2.582, 0.596, 0.602], - [1.708, 0.673, 0.670], - [4.431, 1.545, 1.622], - [3.661, 0.890, 0.871], - [7.950, 3.954, 3.844], - [1.327, 0.055, 0.031], - [16.200, 0.538, 0.523], - [18.455, 0.613, 0.599], - [34.059, 1.428, 1.436], - [31.534, 0.713, 0.653], - [4.576, 0.205, 0.201], - [2.155, 0.177, 0.172], - [4.574, 0.208, 0.206], - [16.201, 0.554, 0.534], - [13.787, 0.840, 0.809], - [1.443, 1.408, 1.331], - [3.815, 0.561, 0.536], - [8.831, 0.778, 0.761], - [9.031, 4.476, 4.428], - [16.938, 2.347, 2.287], - [17.093, 2.722, 2.257], - [1.133, 0.897, 0.888], - [0.240, 0.207, 0.202], - [0.101, 0.083, 0.082], - [0.086, 0.075, 0.074], - [0.467, 0.450, 0.440], - [0.060, 0.028, 0.024], - [0.028, 0.020, 0.020], - [0.006, 0.005, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/skylake_kvm.json b/website/benchmark/hardware/results/skylake_kvm.json deleted file mode 100644 index 7e05a95e0df..00000000000 --- a/website/benchmark/hardware/results/skylake_kvm.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Time4vps.eu", - "system_full": "Time4vps.eu VPS (KVM) Linux Ubuntu 4 Core (Skylake) 16GB RAM 160GB Disk", - "time": "2020-01-13 00:00:00", - "kind": "vps", - "result": - [ - [0.068, 0.002, 0.002], - [0.124, 0.021, 0.025], - [0.594, 0.089, 0.077], - [2.300, 0.133, 0.090], - [2.710, 0.205, 0.212], - [5.203, 0.603, 0.610], - [0.090, 0.029, 0.036], - [0.118, 0.021, 0.022], - [5.977, 1.295, 1.206], - [3.909, 1.415, 1.452], - [2.551, 0.336, 0.324], - [3.123, 0.446, 0.409], - [4.075, 1.743, 1.661], - [6.427, 2.499, 2.487], - [5.775, 2.156, 2.431], - [3.322, 2.288, 2.276], - [8.642, 6.463, 6.690], - [6.365, 3.852, 3.757], - [20.426, 13.849, 13.695], - [2.507, 0.105, 0.100], - [30.691, 1.747, 1.699], - [30.206, 2.010, 1.943], - [57.155, 4.699, 4.859], - [50.924, 2.173, 2.119], - [10.907, 0.660, 0.686], - [3.636, 0.505, 0.524], - [8.388, 0.683, 0.627], - [27.423, 1.650, 1.703], - [21.309, 2.824, 2.821], - [4.227, 4.053, 4.037], - [8.198, 1.797, 1.776], - [18.853, 2.927, 2.881], - [22.254, 21.156, 20.854], - [29.323, 8.728, 8.621], - [27.889, 8.759, 9.063], - [4.121, 3.837, 3.934], - [0.452, 0.292, 0.247], - [0.221, 0.093, 0.090], - [0.331, 0.069, 0.074], - [0.703, 0.469, 0.506], - [0.211, 0.026, 0.027], - [0.134, 0.021, 0.021], - [0.121, 0.007, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/ssdnodes.json b/website/benchmark/hardware/results/ssdnodes.json deleted file mode 100644 index 623f4b49687..00000000000 --- a/website/benchmark/hardware/results/ssdnodes.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "SSDNodes G6", - "system_full": "G6 Performance+ 48GB RAM, 720GB NVMe, 12x Intel Silver vCPU, KVM", - "time": "2021-12-27 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.002, 0.002], -[0.021, 0.017, 0.017], -[0.053, 0.034, 0.039], -[0.090, 0.053, 0.047], -[0.146, 0.123, 0.117], -[0.358, 0.325, 0.323], -[0.025, 0.020, 0.021], -[0.042, 0.015, 0.014], -[0.566, 0.511, 0.524], -[0.704, 0.626, 0.591], -[0.229, 0.174, 0.194], -[0.255, 0.210, 0.206], -[0.849, 0.725, 0.701], -[0.984, 0.907, 0.948], -[0.952, 0.886, 0.899], -[0.772, 0.741, 0.738], -[2.945, 2.667, 2.703], -[1.645, 1.646, 1.576], -[5.342, 5.042, 5.306], -[0.088, 0.052, 0.051], -[1.176, 0.825, 0.839], -[1.261, 1.001, 0.933], -[2.977, 2.190, 2.193], -[1.872, 0.991, 0.956], -[0.368, 0.264, 0.275], -[0.300, 0.247, 0.241], -[0.329, 0.272, 0.277], -[1.124, 0.870, 0.824], -[1.545, 1.270, 1.281], -[1.478, 1.399, 1.463], -[0.809, 0.696, 0.677], -[1.095, 0.875, 0.832], -[5.164, 4.841, 4.613], -[3.859, 3.435, 3.396], -[4.054, 3.479, 3.496], -[1.325, 1.274, 1.294], -[0.261, 0.248, 0.266], -[0.102, 0.096, 0.104], -[0.102, 0.090, 0.094], -[0.600, 0.550, 0.566], -[0.041, 0.031, 0.028], -[0.029, 0.021, 0.025], -[0.007, 0.006, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/upcloud_8cpu_32gb.json b/website/benchmark/hardware/results/upcloud_8cpu_32gb.json deleted file mode 100644 index 7eb1434a143..00000000000 --- a/website/benchmark/hardware/results/upcloud_8cpu_32gb.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "UpCloud 8CPU 32GiB", - "system_full": "UpCloud 8CPU 32GiB, AMD EPYC 7542", - "time": "2021-11-23 00:00:00", - "kind": "cloud", - "result": - [ -[0.002, 0.001, 0.001], -[0.024, 0.020, 0.019], -[0.123, 0.040, 0.048], -[0.448, 0.061, 0.058], -[0.517, 0.125, 0.119], -[0.849, 0.357, 0.350], -[0.037, 0.025, 0.033], -[0.038, 0.018, 0.018], -[0.804, 0.549, 0.551], -[0.920, 0.602, 0.612], -[0.542, 0.234, 0.231], -[0.573, 0.272, 0.274], -[1.032, 0.764, 0.777], -[1.617, 0.991, 1.001], -[1.219, 1.010, 0.994], -[1.056, 0.999, 0.992], -[2.643, 2.488, 2.498], -[1.659, 1.583, 1.533], -[4.957, 4.929, 4.960], -[0.453, 0.056, 0.055], -[6.116, 0.994, 0.951], -[6.914, 1.035, 1.028], -[12.878, 2.481, 2.450], -[9.791, 1.195, 1.139], -[1.682, 0.318, 0.319], -[0.784, 0.286, 0.273], -[1.706, 0.322, 0.318], -[6.060, 0.968, 0.948], -[5.184, 1.488, 1.450], -[1.151, 1.206, 1.219], -[1.520, 0.715, 0.723], -[3.469, 0.910, 0.911], -[5.128, 4.690, 4.771], -[7.003, 3.448, 3.492], -[7.022, 3.478, 3.430], -[1.266, 1.246, 1.257], -[0.225, 0.200, 0.199], -[0.087, 0.079, 0.070], -[0.076, 0.064, 0.063], -[0.450, 0.458, 0.411], -[0.034, 0.024, 0.026], -[0.024, 0.020, 0.021], -[0.006, 0.005, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_2176g.json b/website/benchmark/hardware/results/xeon_2176g.json deleted file mode 100644 index 4145d77d95f..00000000000 --- a/website/benchmark/hardware/results/xeon_2176g.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Xeon 2176G", - "system_full": "Xeon 2176G, 64GB RAM, 2xSSD 960GB (SAMSUNG MZQLB960HAJR-00007), ZFS RAID-1", - "time": "2020-01-14 00:00:00", - "kind": "server", - "result": - [ - [0.001, 0.001, 0.001], - [0.010, 0.011, 0.009], - [0.035, 0.031, 0.033], - [0.058, 0.056, 0.058], - [0.113, 0.126, 0.121], - [0.296, 0.300, 0.301], - [0.017, 0.016, 0.016], - [0.009, 0.009, 0.011], - [0.660, 0.659, 0.655], - [0.775, 0.746, 0.737], - [0.185, 0.181, 0.184], - [0.219, 0.237, 0.243], - [0.943, 0.933, 0.952], - [1.228, 1.185, 1.201], - [0.975, 0.963, 0.971], - [1.068, 1.086, 1.077], - [2.704, 2.713, 2.725], - [1.596, 1.564, 1.562], - [5.653, 5.571, 5.581], - [0.072, 0.065, 0.062], - [1.209, 0.958, 0.951], - [1.383, 1.222, 1.224], - [3.261, 2.771, 2.776], - [1.586, 1.210, 1.196], - [0.417, 0.392, 0.325], - [0.271, 0.268, 0.267], - [0.340, 0.338, 0.337], - [1.376, 1.160, 1.134], - [1.928, 1.643, 1.697], - [3.167, 3.135, 3.149], - [0.947, 0.859, 0.858], - [1.566, 1.446, 1.467], - [8.005, 8.065, 7.980], - [4.640, 4.322, 4.277], - [4.410, 4.330, 4.300], - [1.811, 1.749, 1.767], - [0.138, 0.142, 0.144], - [0.052, 0.047, 0.048], - [0.042, 0.043, 0.041], - [0.271, 0.249, 0.245], - [0.030, 0.016, 0.016], - [0.014, 0.013, 0.013], - [0.004, 0.004, 0.004] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_clx_6230r.json b/website/benchmark/hardware/results/xeon_clx_6230r.json deleted file mode 100644 index ee7c55a90f9..00000000000 --- a/website/benchmark/hardware/results/xeon_clx_6230r.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "system": "2x Intel CLX 6230R", - "system_full": "2x Intel CLX 6230R, 384 GB RAM, P4600 1.6 TB ext4", - "cpu_vendor": "Intel", - "cpu_model": "2x Intel CLX 6230R", - "time": "2020-11-13 00:00:00", - "kind": "server", - "result": - [ -[0.016, 0.003, 0.003], -[0.042, 0.023, 0.022], -[0.036, 0.031, 0.032], -[0.083, 0.037, 0.036], -[0.125, 0.111, 0.109], -[0.206, 0.171, 0.172], -[0.027, 0.028, 0.028], -[0.021, 0.023, 0.023], -[0.232, 0.196, 0.195], -[0.260, 0.213, 0.210], -[0.142, 0.100, 0.103], -[0.141, 0.120, 0.109], -[0.318, 0.255, 0.256], -[0.394, 0.316, 0.311], -[0.345, 0.301, 0.306], -[0.332, 0.333, 0.318], -[0.782, 0.734, 0.719], -[0.494, 0.423, 0.412], -[1.502, 1.379, 1.429], -[0.094, 0.054, 0.057], -[0.860, 0.295, 0.302], -[0.978, 0.343, 0.344], -[1.767, 0.823, 0.827], -[2.214, 0.468, 0.443], -[0.258, 0.116, 0.121], -[0.142, 0.101, 0.104], -[0.254, 0.113, 0.113], -[0.860, 0.354, 0.356], -[0.729, 0.449, 0.447], -[0.846, 0.847, 0.854], -[0.322, 0.247, 0.245], -[0.582, 0.343, 0.348], -[2.110, 2.008, 2.031], -[1.417, 1.158, 1.135], -[1.360, 1.069, 1.084], -[0.377, 0.371, 0.357], -[0.228, 0.160, 0.157], -[0.092, 0.077, 0.076], -[0.053, 0.043, 0.057], -[0.423, 0.343, 0.348], -[0.051, 0.032, 0.034], -[0.023, 0.033, 0.035], -[0.015, 0.010, 0.016] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_e5645.json b/website/benchmark/hardware/results/xeon_e5645.json deleted file mode 100644 index 0468ef07830..00000000000 --- a/website/benchmark/hardware/results/xeon_e5645.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Xeon E5645", - "system_full": "Xeon E5645 @ 2.40GHz, 2 sockets, 12 threads, 96 GiB, 14 x 2TB HDD RAID-10", - "time": "2020-01-18 00:00:00", - "kind": "server", - "result": - [ - [0.061, 0.003, 0.003], - [0.203, 0.026, 0.019], - [0.231, 0.056, 0.060], - [0.533, 0.080, 0.099], - [0.458, 0.202, 0.213], - [0.723, 0.468, 0.411], - [0.143, 0.034, 0.029], - [0.117, 0.025, 0.023], - [1.033, 0.810, 0.745], - [1.165, 0.916, 0.898], - [0.514, 0.249, 0.297], - [0.600, 0.343, 0.385], - [1.294, 1.156, 1.221], - [1.859, 1.459, 1.384], - [1.627, 1.349, 1.346], - [1.414, 1.269, 1.306], - [3.798, 3.774, 3.631], - [2.177, 2.054, 2.016], - [7.002, 6.187, 6.263], - [0.461, 0.081, 0.116], - [3.860, 1.296, 1.330], - [4.705, 1.587, 1.503], - [9.533, 3.887, 3.564], - [11.468, 1.932, 1.712], - [1.362, 0.451, 0.403], - [0.648, 0.374, 0.414], - [1.195, 0.437, 0.418], - [4.187, 1.686, 1.474], - [3.289, 2.146, 2.159], - [3.919, 4.242, 4.208], - [1.673, 1.084, 1.040], - [3.264, 1.496, 1.629], - [8.883, 8.965, 9.027], - [5.813, 5.225, 5.365], - [5.874, 5.376, 5.353], - [2.053, 1.910, 1.951], - [0.478, 0.324, 0.325], - [0.206, 0.132, 0.124], - [0.222, 0.105, 0.111], - [0.699, 0.599, 0.563], - [0.213, 0.041, 0.040], - [0.133, 0.032, 0.040], - [0.062, 0.010, 0.010] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_e5_1650v3.json b/website/benchmark/hardware/results/xeon_e5_1650v3.json deleted file mode 100644 index 8dba51d37bd..00000000000 --- a/website/benchmark/hardware/results/xeon_e5_1650v3.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Intel Xeon E5-1650v3", - "system_full": "Intel Xeon E5-1650v3 3.5 GHz, 64 GB DDR4, RAID1 2×480 GB SSD (INTEL SSDSC2BB480G4)", - "time": "2020-01-26 00:00:00", - "kind": "server", - "result": - [ - [0.005, 0.001, 0.001], - [0.025, 0.015, 0.016], - [0.085, 0.039, 0.034], - [0.302, 0.052, 0.057], - [0.313, 0.137, 0.136], - [0.639, 0.426, 0.434], - [0.036, 0.027, 0.027], - [0.021, 0.014, 0.014], - [0.871, 0.707, 0.707], - [1.060, 0.812, 0.811], - [0.521, 0.285, 0.290], - [0.548, 0.331, 0.339], - [1.358, 0.987, 0.983], - [1.832, 1.317, 1.326], - [1.464, 1.165, 1.134], - [1.311, 1.115, 1.111], - [3.426, 3.060, 3.069], - [2.113, 1.832, 1.849], - [7.000, 6.235, 6.309], - [0.293, 0.067, 0.074], - [3.325, 1.062, 1.030], - [3.765, 1.296, 1.311], - [7.438, 2.870, 2.831], - [7.820, 1.318, 1.277], - [1.139, 0.412, 0.405], - [0.704, 0.372, 0.362], - [1.154, 0.428, 0.427], - [3.557, 1.103, 1.060], - [3.249, 1.680, 1.666], - [2.411, 2.364, 2.312], - [1.365, 0.993, 1.003], - [2.696, 1.535, 1.523], - [9.226, 8.948, 8.734], - [6.053, 4.337, 4.321], - [6.020, 4.341, 4.300], - [1.919, 1.790, 1.759], - [0.220, 0.160, 0.161], - [0.085, 0.067, 0.061], - [0.116, 0.061, 0.061], - [0.431, 0.335, 0.320], - [0.050, 0.037, 0.025], - [0.083, 0.016, 0.019], - [0.029, 0.006, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_e5_2640v4.json b/website/benchmark/hardware/results/xeon_e5_2640v4.json deleted file mode 100644 index b24c5b03899..00000000000 --- a/website/benchmark/hardware/results/xeon_e5_2640v4.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Dell PowerEdge R730xd", - "system_full": "Dell PowerEdge R730xd, 2 socket 10 cores E5-2640 v4, HW RAID5 3TBx12 SATA", - "time": "2020-01-14 00:00:00", - "kind": "server", - "result": - [ - [0.225, 0.001, 0.002], - [0.534, 0.010, 0.010], - [0.229, 0.025, 0.026], - [0.530, 0.042, 0.040], - [0.265, 0.094, 0.090], - [0.685, 0.224, 0.219], - [0.172, 0.013, 0.013], - [0.181, 0.010, 0.011], - [0.908, 0.418, 0.424], - [0.725, 0.450, 0.462], - [0.517, 0.138, 0.139], - [0.445, 0.168, 0.161], - [1.065, 0.585, 0.584], - [1.325, 0.756, 0.747], - [1.184, 0.627, 0.637], - [0.905, 0.676, 0.699], - [2.101, 1.848, 1.775], - [1.275, 0.927, 0.988], - [5.285, 4.201, 4.088], - [0.465, 0.040, 0.071], - [7.380, 0.557, 0.538], - [7.636, 0.665, 0.718], - [13.905, 1.685, 1.645], - [18.739, 0.828, 0.790], - [1.950, 0.195, 0.185], - [0.549, 0.163, 0.156], - [1.384, 0.205, 0.190], - [7.199, 0.650, 0.611], - [6.514, 0.935, 0.885], - [2.154, 2.034, 2.031], - [1.538, 0.546, 0.525], - [3.711, 0.916, 0.936], - [5.993, 4.973, 5.183], - [8.215, 2.759, 2.741], - [8.162, 2.795, 2.772], - [1.347, 1.074, 1.051], - [0.478, 0.208, 0.204], - [0.147, 0.077, 0.074], - [0.197, 0.066, 0.066], - [0.694, 0.438, 0.453], - [0.217, 0.024, 0.033], - [0.137, 0.032, 0.020], - [0.058, 0.006, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_e5_2650_4hdd.json b/website/benchmark/hardware/results/xeon_e5_2650_4hdd.json deleted file mode 100644 index 478229badcc..00000000000 --- a/website/benchmark/hardware/results/xeon_e5_2650_4hdd.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Xeon E5-2650", - "system_full": "Xeon E5-2650 v2 @ 2.60GHz, 2 sockets, 16 threads, 4xHDD RAID-10", - "time": "2020-09-25 00:00:00", - "kind": "server", - "result": - [ -[0.040, 0.002, 0.002], -[0.698, 0.014, 0.013], -[0.533, 0.030, 0.030], -[0.700, 0.043, 0.046], -[0.749, 0.108, 0.102], -[1.350, 0.221, 0.259], -[0.168, 0.020, 0.020], -[0.096, 0.013, 0.013], -[1.132, 0.406, 0.386], -[1.279, 0.426, 0.440], -[0.842, 0.153, 0.146], -[1.042, 0.186, 0.182], -[1.149, 0.536, 0.533], -[1.734, 0.688, 0.683], -[1.481, 0.688, 0.651], -[1.100, 0.709, 0.700], -[2.367, 1.668, 1.682], -[1.687, 1.013, 0.988], -[4.768, 3.647, 3.783], -[0.599, 0.055, 0.040], -[5.530, 0.646, 0.622], -[6.658, 0.671, 0.648], -[11.795, 1.645, 1.574], -[19.248, 1.168, 0.906], -[1.826, 0.224, 0.232], -[0.964, 0.189, 0.187], -[2.058, 0.234, 0.215], -[5.811, 0.758, 0.704], -[4.805, 1.014, 0.995], -[2.272, 2.035, 1.838], -[1.827, 0.546, 0.547], -[3.643, 0.863, 0.834], -[5.816, 5.069, 5.168], -[6.585, 2.655, 2.756], -[6.949, 2.681, 2.795], -[1.325, 1.090, 1.072], -[0.460, 0.183, 0.179], -[1.000, 0.087, 0.091], -[0.142, 0.051, 0.038], -[0.808, 0.392, 0.391], -[0.256, 0.021, 0.015], -[0.132, 0.038, 0.012], -[0.054, 0.006, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_e5_2650_8hdd.json b/website/benchmark/hardware/results/xeon_e5_2650_8hdd.json deleted file mode 100644 index 9c23205fda4..00000000000 --- a/website/benchmark/hardware/results/xeon_e5_2650_8hdd.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Xeon E5-2650", - "system_full": "Xeon E5-2650 v2 @ 2.60GHz, 2 sockets, 16 threads, 8xHDD RAID-5", - "time": "2020-01-12 00:00:00", - "kind": "server", - "result": - [ - [0.101, 0.002, 0.002], - [0.196, 0.019, 0.021], - [0.486, 0.035, 0.029], - [0.413, 0.045, 0.043], - [0.368, 0.134, 0.105], - [0.563, 0.282, 0.269], - [0.078, 0.030, 0.025], - [0.070, 0.019, 0.014], - [0.751, 0.522, 0.558], - [0.856, 0.549, 0.547], - [0.458, 0.155, 0.163], - [0.439, 0.169, 0.190], - [0.929, 0.699, 0.608], - [1.494, 0.863, 0.902], - [1.379, 0.778, 0.794], - [1.032, 0.832, 0.851], - [2.364, 1.974, 1.914], - [1.284, 1.140, 1.043], - [4.745, 4.279, 4.294], - [0.713, 0.085, 0.071], - [4.133, 0.775, 0.729], - [3.485, 0.924, 0.880], - [7.568, 1.808, 1.853], - [9.496, 1.115, 1.119], - [1.130, 0.209, 0.243], - [0.643, 0.225, 0.211], - [1.338, 0.293, 0.233], - [4.353, 0.803, 0.759], - [2.667, 1.158, 1.070], - [2.612, 1.753, 1.721], - [1.370, 0.641, 0.704], - [2.348, 0.977, 1.015], - [6.154, 5.822, 5.696], - [4.553, 3.076, 3.232], - [4.647, 2.960, 3.249], - [1.441, 1.424, 1.285], - [0.560, 0.303, 0.245], - [0.223, 0.082, 0.084], - [0.275, 0.078, 0.076], - [0.929, 0.487, 0.416], - [0.362, 0.033, 0.049], - [0.179, 0.035, 0.022], - [0.075, 0.013, 0.013] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_e5_2650l_v3.json b/website/benchmark/hardware/results/xeon_e5_2650l_v3.json deleted file mode 100644 index 42d01f69720..00000000000 --- a/website/benchmark/hardware/results/xeon_e5_2650l_v3.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "DO Xeon E5-2650Lv3", - "system_full": "Digital Ocean, Xeon E5-2650L v3 @ 1.80GHz, 16 cores, 64 GB, SSD Disk 1.28 TB", - "time": "2020-01-21 00:00:00", - "kind": "cloud", - "result": - [ -[0.004, 0.004, 0.004], -[0.056, 0.037, 0.036], -[0.111, 0.079, 0.078], -[0.151, 0.092, 0.095], -[0.302, 0.262, 0.233], -[0.602, 0.541, 0.552], -[0.063, 0.049, 0.049], -[0.039, 0.036, 0.036], -[0.829, 0.762, 0.746], -[0.939, 0.883, 0.862], -[0.391, 0.311, 0.316], -[0.445, 0.358, 0.356], -[1.227, 1.024, 0.986], -[1.455, 1.276, 1.235], -[1.285, 1.146, 1.104], -[1.229, 1.119, 1.062], -[2.750, 2.555, 2.497], -[1.798, 1.587, 1.527], -[5.485, 5.167, 5.165], -[0.184, 0.142, 0.106], -[2.054, 1.350, 1.302], -[2.229, 1.399, 1.290], -[4.673, 3.013, 2.946], -[3.984, 1.656, 1.566], -[0.736, 0.492, 0.461], -[0.479, 0.382, 0.378], -[0.682, 0.456, 0.448], -[1.974, 1.296, 1.146], -[2.295, 1.847, 1.694], -[2.232, 2.199, 2.213], -[1.123, 0.953, 0.944], -[1.814, 1.385, 1.279], -[7.367, 7.127, 7.355], -[4.973, 4.595, 4.775], -[5.127, 4.639, 4.612], -[1.794, 1.630, 1.633], -[0.522, 0.420, 0.431], -[0.216, 0.193, 0.202], -[0.204, 0.171, 0.163], -[0.888, 0.823, 0.789], -[0.098, 0.077, 0.058], -[0.078, 0.042, 0.041], -[0.025, 0.015, 0.020] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_gold_6140.json b/website/benchmark/hardware/results/xeon_gold_6140.json deleted file mode 100644 index b31ea90ab68..00000000000 --- a/website/benchmark/hardware/results/xeon_gold_6140.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "system": "Xeon Gold 6140", - "system_full": "Xeon Gold 6140, 1 socket, 32 threads, 64 GB RAM, vda", - "cpu_vendor": "Intel", - "cpu_model": "Xeon Gold 6140", - "time": "2021-08-24 00:00:00", - "kind": "server", - "result": - [ -[0.002, 0.002, 0.001], -[0.017, 0.008, 0.008], -[0.029, 0.019, 0.018], -[0.043, 0.026, 0.025], -[0.101, 0.087, 0.084], -[0.213, 0.190, 0.187], -[0.017, 0.012, 0.012], -[0.036, 0.009, 0.009], -[0.264, 0.240, 0.245], -[0.313, 0.269, 0.270], -[0.139, 0.120, 0.123], -[0.149, 0.129, 0.134], -[0.411, 0.342, 0.342], -[0.490, 0.448, 0.445], -[0.433, 0.411, 0.403], -[0.459, 0.446, 0.445], -[1.223, 1.143, 1.133], -[0.723, 0.675, 0.685], -[2.121, 2.095, 2.049], -[0.058, 0.049, 0.030], -[0.560, 0.429, 0.430], -[0.653, 0.499, 0.501], -[1.379, 1.171, 1.152], -[1.153, 0.564, 0.562], -[0.200, 0.155, 0.152], -[0.150, 0.129, 0.127], -[0.192, 0.151, 0.152], -[0.583, 0.449, 0.459], -[0.752, 0.666, 0.656], -[1.073, 1.072, 1.073], -[0.373, 0.330, 0.336], -[0.525, 0.441, 0.441], -[3.795, 2.731, 2.908], -[1.918, 1.756, 1.787], -[1.828, 1.732, 1.735], -[0.725, 0.697, 0.711], -[0.188, 0.171, 0.179], -[0.072, 0.067, 0.065], -[0.077, 0.062, 0.062], -[0.435, 0.415, 0.391], -[0.027, 0.022, 0.020], -[0.023, 0.020, 0.014], -[0.014, 0.008, 0.003] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_gold_6230.json b/website/benchmark/hardware/results/xeon_gold_6230.json deleted file mode 100644 index 973c3ed41d6..00000000000 --- a/website/benchmark/hardware/results/xeon_gold_6230.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "system": "Xeon Gold 6230", - "system_full": "Xeon Gold 6230, 2 sockets, 40 threads", - "cpu_vendor": "Intel", - "cpu_model": "Xeon Gold 6230", - "time": "2020-01-01 00:00:00", - "kind": "server", - "result": - [ - [0.009, 0.002, 0.001], - [0.028, 0.008, 0.009], - [0.055, 0.014, 0.014], - [0.122, 0.018, 0.018], - [0.157, 0.069, 0.068], - [0.217, 0.124, 0.121], - [0.020, 0.008, 0.008], - [0.013, 0.008, 0.007], - [0.284, 0.213, 0.217], - [0.312, 0.241, 0.239], - [0.164, 0.087, 0.090], - [0.168, 0.092, 0.092], - [0.318, 0.256, 0.251], - [0.436, 0.323, 0.325], - [0.385, 0.327, 0.327], - [0.419, 0.402, 0.398], - [1.061, 0.918, 0.935], - [0.523, 0.474, 0.598], - [1.706, 1.621, 1.657], - [0.124, 0.037, 0.024], - [1.192, 0.263, 0.258], - [1.362, 0.303, 0.302], - [2.473, 0.730, 0.735], - [2.857, 0.451, 0.382], - [0.374, 0.106, 0.103], - [0.204, 0.079, 0.078], - [0.381, 0.114, 0.109], - [1.185, 0.327, 0.318], - [1.011, 0.408, 0.415], - [0.895, 0.925, 0.880], - [0.406, 0.277, 0.274], - [0.846, 0.386, 0.389], - [3.174, 2.500, 2.533], - [1.758, 1.311, 1.315], - [1.766, 1.332, 1.355], - [0.469, 0.449, 0.462], - [0.201, 0.169, 0.170], - [0.069, 0.061, 0.061], - [0.064, 0.056, 0.057], - [0.362, 0.365, 0.369], - [0.035, 0.022, 0.024], - [0.030, 0.019, 0.019], - [0.009, 0.005, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_gold_6266.json b/website/benchmark/hardware/results/xeon_gold_6266.json deleted file mode 100644 index 0e68466a633..00000000000 --- a/website/benchmark/hardware/results/xeon_gold_6266.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "system": "Huawei Cloud c6.xlarge.4, 4vCPUs, 16 GiB", - "system_full": "Huawei Cloud c6.xlarge.4, Xeon Gold 6266C, 3GHz, 4vCPU, 16GiB RAM, vda1 40GB", - "cpu_vendor": "Intel", - "cpu_model": "Xeon Gold 6266C", - "time": "2021-12-23 00:00:00", - "kind": "cloud", - "result": - [ -[0.001, 0.001, 0.001], -[0.034, 0.023, 0.023], -[0.168, 0.105, 0.104], -[0.745, 0.162, 0.160], -[1.512, 0.328, 0.327], -[2.408, 1.162, 1.155], -[0.069, 0.052, 0.051], -[0.074, 0.027, 0.026], -[2.314, 1.833, 1.796], -[2.749, 2.014, 2.011], -[1.424, 0.618, 0.579], -[1.494, 0.681, 0.677], -[3.208, 2.457, 2.529], -[5.071, 3.329, 3.411], -[3.968, 3.289, 3.330], -[3.142, 2.925, 2.827], -[9.473, 9.034, 8.850], -[6.768, 6.256, 6.115], -[18.388, 17.790, 17.892], -[1.105, 0.195, 0.194], -[20.310, 3.459, 3.416], -[22.772, 3.811, 3.773], -[42.554, 8.738, 8.640], -[30.747, 4.013, 3.967], -[4.707, 0.973, 0.965], -[2.003, 0.845, 0.839], -[4.978, 0.991, 0.974], -[19.726, 3.293, 3.264], -[17.151, 5.171, 5.134], -[3.620, 3.600, 3.600], -[4.693, 2.172, 2.115], -[10.842, 2.686, 2.750], -[17.857, 17.086, 16.907], -[22.926, 13.070, 12.808], -[22.803, 12.727, 12.867], -[4.189, 3.888, 3.893], -[0.227, 0.176, 0.177], -[0.085, 0.068, 0.067], -[0.101, 0.064, 0.067], -[0.493, 0.438, 0.399], -[0.042, 0.022, 0.021], -[0.029, 0.017, 0.015], -[0.007, 0.005, 0.003] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_silver_4114.json b/website/benchmark/hardware/results/xeon_silver_4114.json deleted file mode 100644 index e0de9fc2572..00000000000 --- a/website/benchmark/hardware/results/xeon_silver_4114.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "system": "Xeon Silver 4114", - "system_full": "Xeon Silver 4114, 256 Gb RAM, SSD RAID1", - "time": "2020-04-23 00:00:00", - "kind": "server", - "result": - [ - [0.010, 0.002, 0.002], - [0.047, 0.016, 0.016], - [0.080, 0.041, 0.044], - [0.236, 0.063, 0.059], - [0.266, 0.139, 0.133], - [0.507, 0.366, 0.346], - [0.045, 0.034, 0.035], - [0.027, 0.015, 0.017], - [0.716, 0.589, 0.565], - [0.771, 0.658, 0.635], - [0.320, 0.225, 0.251], - [0.358, 0.246, 0.248], - [0.934, 0.749, 0.764], - [1.252, 1.017, 1.020], - [1.144, 0.990, 0.997], - [1.106, 1.037, 1.030], - [3.004, 2.754, 2.779], - [1.865, 1.666, 1.687], - [5.054, 4.953, 5.174], - [0.229, 0.060, 0.057], - [2.609, 1.049, 1.058], - [2.968, 1.109, 1.113], - [5.592, 2.381, 2.401], - [5.589, 1.385, 1.241], - [0.768, 0.348, 0.340], - [0.431, 0.283, 0.282], - [0.774, 0.351, 0.349], - [2.591, 0.919, 0.842], - [2.493, 1.627, 1.536], - [1.849, 1.860, 1.823], - [1.088, 0.832, 0.842], - [1.862, 1.133, 1.177], - [null, null, null], - [4.641, 3.828, 3.872], - [4.580, 3.979, 4.150], - [1.471, 1.412, 1.406], - [0.250, 0.203, 0.212], - [0.097, 0.084, 0.085], - [0.121, 0.086, 0.074], - [0.541, 0.458, 0.448], - [0.057, 0.027, 0.037], - [0.035, 0.020, 0.018], - [0.017, 0.008, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_sp_gold.json b/website/benchmark/hardware/results/xeon_sp_gold.json deleted file mode 100644 index 936a85e9999..00000000000 --- a/website/benchmark/hardware/results/xeon_sp_gold.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "system": "Dell PowerEdge R640", - "system_full": "Dell PowerEdge R640 DX292 2x Xeon SP Gold 16-Core 2.10GHz, 196 GB RAM, 2x SSD 960 GB RAID-1", - "cpu_vendor": "Intel", - "cpu_model": "Xeon SP Gold", - "time": "2020-01-13 00:00:00", - "kind": "server", - "result": - [ - [0.005, 0.003, 0.003], - [0.035, 0.013, 0.016], - [0.043, 0.023, 0.023], - [0.076, 0.030, 0.027], - [0.109, 0.087, 0.098], - [0.184, 0.154, 0.151], - [0.030, 0.017, 0.016], - [0.018, 0.017, 0.016], - [0.346, 0.357, 0.375], - [0.467, 0.397, 0.410], - [0.165, 0.135, 0.137], - [0.166, 0.146, 0.143], - [0.452, 0.432, 0.415], - [0.543, 0.523, 0.527], - [0.508, 0.489, 0.472], - [0.638, 0.551, 0.549], - [1.280, 1.231, 1.272], - [0.680, 0.748, 0.611], - [2.380, 2.465, 2.351], - [0.073, 0.065, 0.040], - [0.724, 0.371, 0.376], - [0.805, 0.474, 0.450], - [1.547, 1.064, 1.117], - [1.798, 0.543, 0.507], - [0.217, 0.145, 0.142], - [0.139, 0.122, 0.133], - [0.221, 0.161, 0.159], - [0.730, 0.440, 0.449], - [0.875, 0.744, 0.721], - [1.307, 1.259, 1.318], - [0.457, 0.401, 0.404], - [0.716, 0.688, 0.617], - [4.147, 4.251, 3.844], - [2.082, 1.950, 2.187], - [2.109, 2.095, 1.930], - [0.875, 0.851, 0.848], - [0.233, 0.235, 0.221], - [0.103, 0.087, 0.086], - [0.087, 0.078, 0.078], - [0.452, 0.407, 0.403], - [0.047, 0.041, 0.054], - [0.036, 0.034, 0.035], - [0.013, 0.010, 0.010] - ] - } -] diff --git a/website/benchmark/hardware/results/xeon_x5675.json b/website/benchmark/hardware/results/xeon_x5675.json deleted file mode 100644 index e7dc40902d5..00000000000 --- a/website/benchmark/hardware/results/xeon_x5675.json +++ /dev/null @@ -1,106 +0,0 @@ -[ - { - "system": "ProLiant DL380 G7 (2 CPU)", - "system_full": "ProLiant DL380 G7, 12Gb RAM, 2x Xeon X5675 3.07GHz, 8x300GB SAS soft RAID5", - "time": "2020-02-18 00:00:00", - "kind": "server", - "result": - [ - [0.041, 0.005, 0.005], - [0.084, 0.020, 0.019], - [0.403, 0.046, 0.043], - [0.190, 0.081, 0.082], - [0.192, 0.127, 0.131], - [0.388, 0.324, 0.309], - [0.078, 0.028, 0.038], - [0.055, 0.019, 0.019], - [0.677, 0.614, 0.604], - [0.808, 0.706, 0.727], - [0.282, 0.190, 0.181], - [0.312, 0.223, 0.229], - [0.997, 0.895, 0.891], - [1.167, 1.155, 1.115], - [1.155, 1.088, 1.143], - [1.119, 1.090, 1.109], - [3.451, 3.222, 3.153], - [1.743, 1.770, 1.655], - [9.346, 6.206, 6.436], - [0.352, 0.108, 0.105], - [2.985, 0.993, 0.976], - [3.594, 1.211, 1.195], - [6.626, 2.829, 2.800], - [10.086, 1.331, 1.318], - [1.072, 0.348, 0.332], - [0.535, 0.298, 0.269], - [1.046, 0.362, 0.334], - [3.487, 1.221, 1.165], - [2.718, 1.742, 1.719], - [3.200, 3.158, 3.116], - [1.346, 0.901, 0.917], - [2.336, 1.285, 1.285], - [8.876, 64.491, 123.728], - [10.200, 5.127, 4.743], - [5.196, 4.783, 4.659], - [1.628, 1.544, 1.527], - [0.476, 0.296, 0.285], - [0.172, 0.127, 0.097], - [0.170, 0.078, 0.083], - [0.670, 0.529, 0.511], - [0.181, 0.065, 0.039], - [0.123, 0.029, 0.033], - [0.045, 0.011, 0.011] - ] - }, - { - "system": "ProLiant DL380 G7 (1 CPU)", - "system_full": "ProLiant DL380 G7, 128Gb RAM, 1x Xeon X5675 3.07GHz, 8x300GB SAS Soft RAID5", - "time": "2020-02-18 00:00:00", - "kind": "server", - "result": - [ - [0.048, 0.005, 0.005], - [0.092, 0.026, 0.026], - [0.167, 0.067, 0.073], - [0.200, 0.117, 0.116], - [0.263, 0.185, 0.203], - [0.587, 0.586, 0.586], - [0.094, 0.043, 0.043], - [0.067, 0.025, 0.026], - [1.371, 1.299, 1.298], - [1.638, 1.546, 1.548], - [0.441, 0.341, 0.337], - [0.482, 0.405, 0.385], - [2.682, 2.680, 2.630], - [3.189, 3.207, 3.167], - [2.634, 2.525, 2.556], - [3.181, 3.200, 3.213], - [7.793, 7.714, 7.768], - [3.802, 3.819, 3.960], - [19.101, 16.177, 15.840], - [0.320, 0.153, 0.134], - [3.108, 2.188, 2.115], - [4.515, 3.139, 3.069], - [7.712, 6.856, 6.906], - [11.063, 2.630, 2.567], - [1.015, 0.739, 0.723], - [0.738, 0.644, 0.623], - [1.048, 0.717, 0.736], - [3.371, 2.905, 2.903], - [4.772, 4.539, 4.518], - [11.700, 11.656, 11.589], - [2.217, 2.083, 2.072], - [4.329, 4.153, 3.889], - [21.212, 21.887, 21.417], - [12.816, 12.501, 12.664], - [13.192, 12.624, 12.820], - [5.454, 5.447, 5.462], - [0.376, 0.280, 0.288], - [0.152, 0.097, 0.113], - [0.171, 0.093, 0.100], - [0.594, 0.484, 0.464], - [0.129, 0.043, 0.036], - [0.098, 0.027, 0.045], - [0.033, 0.025, 0.011] - ] - } -] diff --git a/website/benchmark/hardware/results/yandex_cloud_broadwell_4_vcpu.json b/website/benchmark/hardware/results/yandex_cloud_broadwell_4_vcpu.json deleted file mode 100644 index c33feefb006..00000000000 --- a/website/benchmark/hardware/results/yandex_cloud_broadwell_4_vcpu.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "system": "Yandex Cloud s1.small", - "system_full": "Yandex Cloud Broadwell, 4 vCPU (2 threads), 16 GB RAM, 30 GB SSD", - "cpu_vendor": "Intel", - "time": "2020-01-14 00:00:00", - "kind": "cloud", - "result": - [ - [0.507, 0.002, 0.002], - [0.267, 0.035, 0.034], - [0.970, 0.120, 0.121], - [4.600, 0.200, 0.194], - [0.554, 0.469, 0.462], - [7.314, 1.276, 1.251], - [0.164, 0.062, 0.063], - [0.159, 0.035, 0.036], - [5.551, 1.935, 1.946], - [2.291, 2.170, 2.188], - [0.718, 0.653, 0.686], - [0.841, 0.796, 0.776], - [7.636, 2.906, 2.849], - [6.644, 4.234, 3.796], - [3.847, 3.080, 3.029], - [4.308, 3.285, 3.214], - [9.768, 8.793, 8.694], - [6.103, 5.225, 6.072], - [20.421, 17.609, 17.372], - [2.141, 0.182, 0.189], - [55.415, 3.527, 3.553], - [55.961, 4.545, 4.011], - [106.069, 9.063, 8.975], - [116.871, 4.638, 4.542], - [16.100, 1.818, 1.186], - [2.543, 0.950, 0.933], - [5.086, 1.199, 1.192], - [55.720, 3.259, 3.240], - [46.784, 5.170, 5.190], - [6.505, 6.229, 6.191], - [11.382, 2.817, 2.863], - [22.205, 4.495, 4.348], - [34.430, 27.314, 27.662], - [58.643, 14.066, 14.196], - [50.675, 14.220, 13.868], - [5.674, 5.107, 5.219], - [0.577, 0.293, 0.272], - [0.151, 0.098, 0.094], - [0.107, 0.094, 0.089], - [0.692, 0.582, 0.610], - [0.302, 0.040, 0.036], - [0.101, 0.027, 0.026], - [0.094, 0.006, 0.006] - ] - } -] diff --git a/website/benchmark/hardware/results/yandex_cloud_broadwell_8_vcpu.json b/website/benchmark/hardware/results/yandex_cloud_broadwell_8_vcpu.json deleted file mode 100644 index 1217adbbff5..00000000000 --- a/website/benchmark/hardware/results/yandex_cloud_broadwell_8_vcpu.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "system": "Yandex Cloud 8vCPU", - "system_full": "Yandex Cloud Broadwell, 8 vCPU (4 threads), 64 GB RAM, 500 GB SSD", - "cpu_vendor": "Intel", - "time": "2021-02-05 00:00:00", - "kind": "cloud", - "result": - [ - [0.004, 0.003, 0.003], - [0.047, 0.030, 0.021], - [0.129, 0.066, 0.067], - [0.873, 0.098, 0.095], - [0.869, 0.247, 0.257], - [1.429, 0.818, 0.768], - [0.055, 0.042, 0.043], - [0.034, 0.025, 0.024], - [1.372, 1.003, 1.051], - [1.605, 1.281, 1.209], - [0.942, 0.503, 0.483], - [0.980, 0.537, 0.558], - [2.076, 1.664, 1.635], - [3.136, 2.235, 2.171], - [2.351, 1.973, 1.974], - [2.369, 2.170, 2.133], - [6.281, 5.576, 5.498], - [3.739, 3.481, 3.354], - [10.947, 10.225, 10.271], - [0.875, 0.111, 0.108], - [10.832, 1.844, 1.877], - [12.344, 2.330, 2.227], - [22.999, 5.000, 4.903], - [20.086, 2.390, 2.278], - [3.036, 0.722, 0.673], - [1.420, 0.602, 0.578], - [3.040, 0.728, 0.714], - [10.842, 1.874, 1.783], - [9.207, 2.809, 2.705], - [2.751, 2.703, 2.714], - [2.810, 1.675, 1.568], - [6.507, 2.449, 2.505], - [15.968, 15.014, 15.318], - [13.479, 7.951, 7.702], - [13.227, 7.791, 7.699], - [2.811, 2.723, 2.549], - [0.358, 0.249, 0.273], - [0.157, 0.099, 0.101], - [0.189, 0.088, 0.080], - [0.758, 0.544, 0.525], - [0.115, 0.033, 0.027], - [0.063, 0.048, 0.023], - [0.014, 0.011, 0.008] - ] - } -] diff --git a/website/benchmark/hardware/results/yandex_cloud_broadwell_8_vcpu_s3.json b/website/benchmark/hardware/results/yandex_cloud_broadwell_8_vcpu_s3.json deleted file mode 100644 index ace2442c86e..00000000000 --- a/website/benchmark/hardware/results/yandex_cloud_broadwell_8_vcpu_s3.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "system": "Yandex Cloud 8vCPU Object Storage", - "system_full": "Yandex Cloud Broadwell, 8 vCPU (4 threads), 64 GB RAM, Object Storage", - "cpu_vendor": "Intel", - "time": "2021-02-05 00:00:00", - "kind": "cloud", - "result": - [ - [0.007, 0.003, 0.003], - [0.214, 0.111, 0.096], - [1.239, 1.359, 0.718], - [3.056, 3.366, 1.869], - [1.946, 1.552, 2.450], - [4.804, 2.307, 2.398], - [0.198, 0.108, 0.114], - [0.141, 0.104, 0.100], - [2.755, 2.749, 3.608], - [3.140, 3.905, 3.830], - [2.353, 4.996, 1.637], - [3.796, 1.536, 1.724], - [3.565, 3.016, 3.381], - [4.962, 4.263, 4.352], - [4.210, 3.974, 4.318], - [3.884, 3.434, 3.124], - [10.451, 9.147, 7.526], - [6.288, 5.882, 7.714], - [15.239, 33.243, 17.968], - [1.645, 1.870, 3.230], - [10.980, 8.984, 7.589], - [14.345, 11.503, 12.449], - [17.687, 17.764, 18.984], - [76.606, 65.179, 94.215], - [5.833, 3.347, 3.127], - [3.815, 2.574, 2.402], - [4.916, 6.169, 5.731], - [7.961, 9.930, 8.555], - [5.995, 7.382, 6.054], - [3.113, 4.176, 3.172], - [5.077, 5.221, 5.709], - [8.990, 9.598, 6.272], - [17.832, 17.668, 17.276], - [11.846, 14.692, 13.225], - [12.544, 12.502, 12.725], - [3.604, 4.811, 3.267], - [0.738, 0.751, 0.862], - [0.718, 0.611, 0.561], - [2.125, 0.688, 0.522], - [1.469, 1.546, 1.373], - [1.382, 1.069, 0.976], - [1.353, 1.212, 1.119], - [0.045, 0.031, 0.041] - ] - } -] diff --git a/website/benchmark/hardware/results/yandex_cloud_cascade_lake_32_vcpu.json b/website/benchmark/hardware/results/yandex_cloud_cascade_lake_32_vcpu.json deleted file mode 100644 index 5d2927c224d..00000000000 --- a/website/benchmark/hardware/results/yandex_cloud_cascade_lake_32_vcpu.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "system": "Yandex Cloud 32vCPU", - "system_full": "Yandex Cloud Cascade Lake, 32 vCPU, 128 GB RAM, 300 GB SSD", - "cpu_vendor": "Intel", - "time": "2020-09-23 00:00:00", - "kind": "cloud", - "result": - [ -[0.021, 0.001, 0.001], -[0.051, 0.011, 0.010], -[0.396, 0.025, 0.025], -[1.400, 0.035, 0.033], -[1.413, 0.095, 0.098], -[2.272, 0.222, 0.208], -[0.042, 0.014, 0.014], -[0.024, 0.011, 0.010], -[1.948, 0.311, 0.303], -[2.267, 0.379, 0.348], -[1.498, 0.138, 0.135], -[1.563, 0.164, 0.155], -[2.435, 0.544, 0.516], -[3.937, 0.661, 0.659], -[2.724, 0.727, 0.642], -[1.795, 0.683, 0.641], -[4.668, 1.682, 1.643], -[3.802, 1.051, 0.895], -[8.297, 3.835, 4.592], -[1.427, 0.100, 0.033], -[16.816, 0.652, 0.547], -[19.159, 0.650, 0.532], -[35.374, 1.538, 1.311], -[32.736, 0.854, 0.699], -[4.767, 0.203, 0.184], -[2.249, 0.166, 0.158], -[4.759, 0.207, 0.189], -[16.826, 0.584, 0.529], -[14.308, 0.920, 0.789], -[1.137, 1.041, 0.992], -[3.967, 0.545, 0.555], -[9.196, 0.872, 0.789], -[9.554, 5.501, 5.694], -[17.810, 2.712, 2.329], -[17.726, 2.653, 2.793], -[1.260, 0.955, 0.978], -[0.260, 0.171, 0.164], -[0.092, 0.065, 0.069], -[0.046, 0.041, 0.037], -[0.475, 0.391, 0.383], -[0.066, 0.021, 0.019], -[0.023, 0.024, 0.011], -[0.022, 0.005, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/yandex_cloud_cascade_lake_4_vcpu.json b/website/benchmark/hardware/results/yandex_cloud_cascade_lake_4_vcpu.json deleted file mode 100644 index 81c5ca2e324..00000000000 --- a/website/benchmark/hardware/results/yandex_cloud_cascade_lake_4_vcpu.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "system": "Yandex Cloud s2.small", - "system_full": "Yandex Cloud Cascade Lake, 4 vCPU (2 threads), 16 GB RAM, 30 GB SSD", - "cpu_vendor": "Intel", - "time": "2020-01-13 00:00:00", - "kind": "cloud", - "result": - [ - [0.621, 0.002, 0.002], - [0.288, 0.035, 0.030], - [1.023, 0.126, 0.132], - [5.152, 0.219, 0.194], - [0.458, 0.427, 0.447], - [6.848, 1.223, 1.232], - [0.271, 0.077, 0.058], - [0.130, 0.044, 0.032], - [3.722, 2.145, 2.159], - [2.571, 2.459, 2.490], - [0.764, 0.679, 0.721], - [0.892, 0.816, 0.816], - [5.743, 3.467, 3.294], - [5.177, 4.540, 4.596], - [5.294, 4.565, 4.510], - [5.109, 3.902, 3.845], - [14.256, 12.943, 12.882], - [8.741, 8.056, 9.738], - [30.649, 26.987, 26.702], - [2.063, 0.183, 0.239], - [54.740, 3.602, 3.559], - [54.077, 6.038, 4.264], - [107.285, 11.156, 9.986], - [114.734, 4.735, 4.673], - [15.581, 1.257, 1.249], - [3.779, 1.002, 0.992], - [4.864, 1.305, 1.305], - [55.450, 3.348, 3.230], - [46.372, 5.424, 5.263], - [6.437, 6.404, 6.179], - [11.933, 3.524, 3.546], - [20.803, 5.352, 5.216], - [43.065, 41.106, 41.870], - [58.396, 16.545, 16.610], - [51.752, 16.329, 16.221], - [6.722, 6.256, 6.391], - [0.533, 0.241, 0.237], - [0.113, 0.085, 0.077], - [0.093, 0.083, 0.074], - [0.624, 0.497, 0.492], - [0.286, 0.036, 0.028], - [0.088, 0.022, 0.021], - [0.099, 0.005, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/yandex_cloud_cascade_lake_64_vcpu.json b/website/benchmark/hardware/results/yandex_cloud_cascade_lake_64_vcpu.json deleted file mode 100644 index e593c47e4dd..00000000000 --- a/website/benchmark/hardware/results/yandex_cloud_cascade_lake_64_vcpu.json +++ /dev/null @@ -1,108 +0,0 @@ -[ - { - "system": "Yandex Cloud s2.6xlarge", - "system_full": "Yandex Cloud Cascade Lake, 64 vCPU (32 threads), 128 GB RAM, 400 GB SSD", - "cpu_vendor": "Intel", - "time": "2020-01-11 00:00:00", - "kind": "cloud", - "result": - [ - [0.037, 0.002, 0.002], - [0.113, 0.008, 0.014], - [0.275, 0.016, 0.016], - [0.448, 0.022, 0.022], - [0.472, 0.083, 0.082], - [0.808, 0.137, 0.136], - [0.063, 0.016, 0.010], - [0.055, 0.008, 0.008], - [0.753, 0.280, 0.327], - [0.850, 0.308, 0.305], - [0.540, 0.110, 0.105], - [0.533, 0.113, 0.112], - [0.930, 0.326, 0.314], - [1.463, 0.392, 0.374], - [0.994, 0.389, 0.386], - [0.763, 0.471, 0.499], - [1.890, 1.271, 1.280], - [1.335, 0.642, 1.776], - [3.886, 2.448, 2.370], - [0.452, 0.052, 0.029], - [5.834, 0.380, 0.374], - [6.645, 0.509, 0.385], - [12.401, 1.204, 1.146], - [12.727, 0.502, 0.513], - [1.628, 0.154, 0.128], - [0.739, 0.121, 0.144], - [1.632, 0.173, 0.127], - [5.856, 0.495, 0.568], - [4.960, 0.685, 0.784], - [1.619, 0.974, 1.638], - [1.420, 0.377, 0.361], - [3.379, 0.596, 0.580], - [5.797, 4.241, 4.280], - [6.864, 2.376, 2.224], - [6.834, 2.112, 2.118], - [0.996, 0.890, 0.947], - [0.286, 0.226, 0.218], - [0.110, 0.080, 0.077], - [0.146, 0.075, 0.068], - [0.531, 0.500, 0.438], - [0.076, 0.025, 0.027], - [0.054, 0.021, 0.022], - [0.033, 0.004, 0.004] - ] - }, - - { - "system": "Yandex Cloud s2.6xlarge 4TB SSD", - "system_full": "Yandex Cloud Cascade Lake, 64 vCPU (32 threads), 128 GB RAM, 4 TB SSD", - "time": "2020-01-13 00:00:00", - "kind": "cloud", - "result": - [ - [0.054, 0.002, 0.002], - [0.140, 0.009, 0.015], - [0.139, 0.017, 0.020], - [0.430, 0.022, 0.022], - [0.453, 0.083, 0.082], - [0.839, 0.160, 0.159], - [0.058, 0.010, 0.010], - [0.048, 0.009, 0.008], - [0.706, 0.307, 0.288], - [0.821, 0.328, 0.301], - [0.509, 0.108, 0.106], - [0.534, 0.117, 0.116], - [0.905, 0.318, 0.313], - [1.573, 0.429, 0.413], - [0.960, 0.410, 0.403], - [0.769, 0.619, 0.521], - [1.914, 1.335, 1.272], - [1.279, 0.657, 1.215], - [3.839, 2.264, 2.481], - [0.425, 0.064, 0.027], - [5.605, 0.344, 0.367], - [6.389, 0.382, 0.403], - [11.794, 0.894, 0.878], - [11.730, 0.536, 0.436], - [1.540, 0.120, 0.109], - [0.715, 0.091, 0.106], - [1.553, 0.132, 0.132], - [5.580, 0.375, 0.350], - [4.720, 0.511, 0.480], - [1.025, 0.953, 1.008], - [1.475, 0.359, 0.357], - [3.457, 0.504, 0.495], - [4.688, 3.581, 3.673], - [6.325, 1.913, 1.865], - [6.338, 1.933, 2.030], - [0.961, 0.785, 0.847], - [0.267, 0.221, 0.215], - [0.095, 0.071, 0.078], - [0.148, 0.065, 0.071], - [0.516, 0.471, 0.432], - [0.076, 0.028, 0.025], - [0.053, 0.018, 0.021], - [0.034, 0.004, 0.004] - ] - } -] diff --git a/website/benchmark/hardware/results/yandex_cloud_cascade_lake_80_vcpu.json b/website/benchmark/hardware/results/yandex_cloud_cascade_lake_80_vcpu.json deleted file mode 100644 index 565a5bd41c2..00000000000 --- a/website/benchmark/hardware/results/yandex_cloud_cascade_lake_80_vcpu.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "system": "Yandex Cloud 80vCPU", - "system_full": "Yandex Cloud Cascade Lake, 80 vCPU, 160 GB RAM, 4TB SSD", - "cpu_vendor": "Intel", - "time": "2020-09-23 00:00:00", - "kind": "cloud", - "result": - [ -[0.024, 0.002, 0.002], -[0.067, 0.012, 0.012], -[0.104, 0.017, 0.017], -[0.411, 0.020, 0.021], -[0.577, 0.069, 0.068], -[0.739, 0.123, 0.122], -[0.038, 0.015, 0.014], -[0.024, 0.012, 0.012], -[0.625, 0.169, 0.168], -[0.748, 0.216, 0.207], -[0.471, 0.089, 0.082], -[0.487, 0.092, 0.087], -[0.818, 0.256, 0.245], -[1.324, 0.352, 0.352], -[0.927, 0.333, 0.319], -[0.642, 0.376, 0.377], -[1.686, 0.983, 0.959], -[1.290, 0.588, 0.582], -[3.105, 1.793, 1.818], -[0.426, 0.031, 0.034], -[5.559, 0.415, 0.344], -[6.343, 0.435, 0.405], -[11.779, 1.151, 1.101], -[11.851, 0.537, 0.509], -[1.530, 0.125, 0.126], -[0.695, 0.103, 0.103], -[1.531, 0.127, 0.119], -[5.576, 0.541, 0.496], -[4.718, 0.740, 0.719], -[1.429, 1.467, 1.500], -[1.309, 0.335, 0.322], -[3.138, 0.505, 0.518], -[5.481, 3.475, 3.512], -[6.330, 1.877, 1.818], -[6.238, 1.843, 1.813], -[0.660, 0.626, 0.603], -[0.251, 0.152, 0.151], -[0.090, 0.058, 0.059], -[0.041, 0.038, 0.034], -[0.470, 0.376, 0.385], -[0.076, 0.015, 0.018], -[0.030, 0.018, 0.010], -[0.024, 0.006, 0.005] - ] - } -] diff --git a/website/benchmark/hardware/results/yandex_managed_clickhouse_s3_3xlarge.json b/website/benchmark/hardware/results/yandex_managed_clickhouse_s3_3xlarge.json deleted file mode 100644 index 55e2a19ea85..00000000000 --- a/website/benchmark/hardware/results/yandex_managed_clickhouse_s3_3xlarge.json +++ /dev/null @@ -1,107 +0,0 @@ -[ - { - "system": "Yandex Managed ClickHouse s3.3xlarge", - "system_full": "Yandex Managed ClickHouse, s3.3xlarge, Cascade Lake 32 vCPU, 128 GB RAM, 1 TB local SSD", - "time": "2020-01-14 00:00:00", - "kind": "cloud", - "result": - [ - [0.039, 0.041, 0.046], - [0.079, 0.084, 0.081], - [0.069, 0.068, 0.060], - [0.067, 0.065, 0.073], - [0.114, 0.116, 0.115], - [0.176, 0.171, 0.166], - [0.055, 0.058, 0.065], - [0.060, 0.061, 0.057], - [0.290, 0.290, 0.289], - [0.323, 0.315, 0.309], - [0.128, 0.124, 0.137], - [0.144, 0.136, 0.136], - [0.344, 0.326, 0.325], - [0.402, 0.392, 0.431], - [0.430, 0.436, 0.414], - [0.509, 0.485, 0.485], - [1.233, 1.151, 1.167], - [0.652, 0.651, 0.631], - [2.078, 2.076, 2.046], - [0.070, 0.071, 0.070], - [0.358, 0.327, 0.355], - [0.428, 0.403, 0.405], - [0.974, 0.986, 0.976], - [0.532, 0.549, 0.500], - [0.164, 0.169, 0.158], - [0.128, 0.130, 0.138], - [0.166, 0.169, 0.159], - [0.428, 0.449, 0.471], - [0.586, 0.598, 0.568], - [1.115, 1.115, 1.147], - [0.342, 0.344, 0.342], - [0.481, 0.473, 0.470], - [4.436, 3.273, 3.320], - [1.661, 1.542, 1.545], - [1.573, 1.875, 1.576], - [0.600, 0.566, 0.586], - [0.261, 0.255, 0.251], - [0.134, 0.136, 0.120], - [0.135, 0.133, 0.132], - [0.525, 0.531, 0.521], - [0.073, 0.071, 0.068], - [0.060, 0.071, 0.071], - [0.051, 0.057, 0.050] - ] - }, - - { - "system": "Yandex Managed ClickHouse s3.3xlarge", - "system_full": "Yandex Managed ClickHouse, s3.3xlarge, Cascade Lake 32 vCPU, 128 GB RAM, 12.5 TB local HDD", - "time": "2020-01-14 00:00:00", - "kind": "cloud", - "result": - [ - [0.049, 0.049, 0.045], - [0.297, 0.079, 0.068], - [0.350, 0.059, 0.065], - [1.099, 0.068, 0.064], - [0.120, 0.123, 0.117], - [1.847, 0.202, 0.191], - [0.124, 0.056, 0.060], - [0.062, 0.058, 0.055], - [0.681, 0.321, 0.312], - [0.346, 0.349, 0.368], - [0.200, 0.140, 0.145], - [0.227, 0.142, 0.152], - [0.447, 0.410, 0.414], - [0.499, 0.570, 0.487], - [0.658, 0.467, 0.465], - [0.587, 0.576, 0.580], - [1.376, 1.340, 1.290], - [0.746, 0.748, 0.717], - [3.608, 2.427, 2.455], - [0.073, 0.106, 0.076], - [9.138, 0.422, 0.386], - [0.615, 0.463, 0.449], - [12.166, 1.067, 1.094], - [13.856, 0.735, 0.548], - [0.194, 0.187, 0.179], - [0.145, 0.140, 0.151], - [0.185, 0.180, 0.182], - [0.493, 0.507, 0.498], - [9.379, 0.618, 0.617], - [1.182, 1.187, 1.268], - [1.088, 0.402, 0.407], - [3.943, 0.646, 0.538], - [4.547, 3.858, 4.407], - [13.240, 1.776, 1.808], - [1.760, 1.823, 1.918], - [0.719, 0.693, 0.729], - [0.346, 0.309, 0.279], - [0.151, 0.143, 0.134], - [0.191, 0.141, 0.149], - [0.804, 0.550, 0.686], - [0.147, 0.093, 0.086], - [0.099, 0.085, 0.084], - [0.056, 0.059, 0.057] - ] - } -] diff --git a/website/images/backgrounds/bg-card-pattern-blue-1.png b/website/images/backgrounds/bg-card-pattern-blue-1.png deleted file mode 100644 index b15958adcb13c9dcb6316e634df2c3a5e324b43f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^6Brnn92l8_tO(88zd(vDz$e5NNI(AgFEa7QMIei@ zB*-tA!Qt7BG$6;()5S3)Alr=vYJ^PXODpR{Bu%js7W xRWub3PU%_hHc6#>o`B$D1 zk7{^KdZP8gUuKd@smF&0`=>A2$tmdGasB_~;s5{t zKXnxc14S5^@dt?<~_XAZ=`m-La7sB!KO>ao}vT;>qDbNWPsS%!OzP=1vKsE;h zV|yk83y{SK#8N=az`(SC2`0jLWdSpseE=k>x8wjDkWwvijVMV;EJ?LWE=mPb3`Pb< zM!E(@x`rkpMg~@<7FGtv+6D$z1_s+-${a+|kei>9nO2Eg!?7((YJnPrKsKaSWTsiU z0tx@LwBpnf1|zdz=|!nPSqZpoKxRd1PJ~NlZfag}W_})niJ65&#abgwHIYbaOf0rL S>Ky~BVeoYIb6Mw<&;$UTI;|)G diff --git a/website/images/backgrounds/bg-footer-cta.svg b/website/images/backgrounds/bg-footer-cta.svg deleted file mode 100644 index d3a89339800..00000000000 --- a/website/images/backgrounds/bg-footer-cta.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/backgrounds/bg-hero-home.svg b/website/images/backgrounds/bg-hero-home.svg deleted file mode 100644 index 5df2ea819d6..00000000000 --- a/website/images/backgrounds/bg-hero-home.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/backgrounds/bg-hero.svg b/website/images/backgrounds/bg-hero.svg deleted file mode 100644 index a3dd0df7beb..00000000000 --- a/website/images/backgrounds/bg-hero.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/backgrounds/bg-quick-start.svg b/website/images/backgrounds/bg-quick-start.svg deleted file mode 100644 index c68b8f94788..00000000000 --- a/website/images/backgrounds/bg-quick-start.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/backgrounds/bg-quotes.svg b/website/images/backgrounds/bg-quotes.svg deleted file mode 100644 index 2bab82c0d75..00000000000 --- a/website/images/backgrounds/bg-quotes.svg +++ /dev/null @@ -1 +0,0 @@ -Artboard \ No newline at end of file diff --git a/website/images/curl.svg b/website/images/curl.svg deleted file mode 100644 index e694f5d5e82..00000000000 --- a/website/images/curl.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/dots.svg b/website/images/dots.svg deleted file mode 100644 index 39272c1470d..00000000000 --- a/website/images/dots.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/flags/en.svg b/website/images/flags/en.svg deleted file mode 100644 index b1dab887bcf..00000000000 --- a/website/images/flags/en.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/flags/es.svg b/website/images/flags/es.svg deleted file mode 100644 index d859aa650b2..00000000000 --- a/website/images/flags/es.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/flags/fa.svg b/website/images/flags/fa.svg deleted file mode 100644 index 35cd83dded2..00000000000 --- a/website/images/flags/fa.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/flags/fr.svg b/website/images/flags/fr.svg deleted file mode 100644 index 9733f5c270c..00000000000 --- a/website/images/flags/fr.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/flags/ja.svg b/website/images/flags/ja.svg deleted file mode 100644 index ad8286de8f8..00000000000 --- a/website/images/flags/ja.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/flags/ru.svg b/website/images/flags/ru.svg deleted file mode 100644 index 1733cc42728..00000000000 --- a/website/images/flags/ru.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/flags/tr.svg b/website/images/flags/tr.svg deleted file mode 100644 index 7f9f90a079d..00000000000 --- a/website/images/flags/tr.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/flags/zh.svg b/website/images/flags/zh.svg deleted file mode 100644 index 903b1eca640..00000000000 --- a/website/images/flags/zh.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/icons/icon-arrow.svg b/website/images/icons/icon-arrow.svg deleted file mode 100644 index 22f67781c6e..00000000000 --- a/website/images/icons/icon-arrow.svg +++ /dev/null @@ -1 +0,0 @@ -icon-arrow \ No newline at end of file diff --git a/website/images/icons/icon-blog-black.svg b/website/images/icons/icon-blog-black.svg deleted file mode 100644 index 42d5c1a3010..00000000000 --- a/website/images/icons/icon-blog-black.svg +++ /dev/null @@ -1 +0,0 @@ -icon-blog \ No newline at end of file diff --git a/website/images/icons/icon-facebook-gray.svg b/website/images/icons/icon-facebook-gray.svg deleted file mode 100644 index 6c0e3190e0a..00000000000 --- a/website/images/icons/icon-facebook-gray.svg +++ /dev/null @@ -1 +0,0 @@ -icon-facebook-gray \ No newline at end of file diff --git a/website/images/icons/icon-facebook.svg b/website/images/icons/icon-facebook.svg deleted file mode 100644 index c7c66a5cb5d..00000000000 --- a/website/images/icons/icon-facebook.svg +++ /dev/null @@ -1 +0,0 @@ -Group \ No newline at end of file diff --git a/website/images/icons/icon-github.svg b/website/images/icons/icon-github.svg deleted file mode 100644 index 79f936ad51b..00000000000 --- a/website/images/icons/icon-github.svg +++ /dev/null @@ -1 +0,0 @@ -icon-github \ No newline at end of file diff --git a/website/images/icons/icon-google.svg b/website/images/icons/icon-google.svg deleted file mode 100644 index 732c960a052..00000000000 --- a/website/images/icons/icon-google.svg +++ /dev/null @@ -1 +0,0 @@ -icon-google \ No newline at end of file diff --git a/website/images/icons/icon-linkedin-alt-gray.svg b/website/images/icons/icon-linkedin-alt-gray.svg deleted file mode 100644 index eb5931c3a65..00000000000 --- a/website/images/icons/icon-linkedin-alt-gray.svg +++ /dev/null @@ -1 +0,0 @@ -icon-linkedin-gray \ No newline at end of file diff --git a/website/images/icons/icon-linkedin-gray.svg b/website/images/icons/icon-linkedin-gray.svg deleted file mode 100644 index 684f35e8f47..00000000000 --- a/website/images/icons/icon-linkedin-gray.svg +++ /dev/null @@ -1 +0,0 @@ -icon-linkedin-gray \ No newline at end of file diff --git a/website/images/icons/icon-linkedin.png b/website/images/icons/icon-linkedin.png deleted file mode 100644 index 1facc9e76df1c16a1d3933c190e9c9b9c571ceb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3247 zcmds3X;f3!7QRlWkwJpUC^CiI6i}wfAcBakG$34R5>!yJ zfGMCd*eD1hf`VQX5RpOwMMMbm9EOJkly_^_YhTw}???Z|>H8rkb1ksU z%~6M`F2Lsf5og(te^&9&egBF$eQ5hccoBxJ3UD|;-qKfYR)rbTQFe|t(u1ov%8`sW zPe|PdfDK-DWJ}lBHxu2jp@Ql>sKGD%g85m^Ro9;+S6|HyH}t)<{j zyEwbg=Q~q<#%^oX5z2imsx_Ng$2-iQ-?&Lh)}2L1#d-8mUhB z(&dLn&doLy3y+XQI>UU=$5YklUL^q z9`C2~)9~let;kDe%h8Gm-c#q6_|{C5qOgiFULH~SDxkw6tdyiGc|BF(d);Af?DPys znTQ?s$O)_79>yE#8LVUHg-gj=v$jji7Uf7DCAwRgJHqvK_wQ)yxd$Hweiq z2_#J=>4aMmjpVd$t;k^hg=eKj^|`u+Q&9B=OTVSc-cgMpT;?9H(&*qD&+H*GORyPK zwT4qITCW8;0u%d7KiY!#@|39<*G} zEktjvfgr_XahY8Im34 z#GKqTyW@c%E3FdpO5`1c+i)l36MxczpYDVXT19fUBnXH>{W$z5Hm!h@j5ye`b9TTg zJV#vpRaDGCaCT>A<6=R#9pGSYa%M{br#Ec5g|VA-CCT4T^d8c=1y_>xRU${f2CrMR z@fmWr;H)5}qv&Stz^3RRupj%+trE}MT3 z*=O#7i%r%Ut}?4c;BB%NA((a@6PCJ+M{>=?a_w?$`5#3>o;v zm;Q2}>_LI8o^*7M;RaBgI6v;lKP5=92vUF`j-DJ#25PS~Wzx9mNagVzJ7r(mQnt-{ z2Q2F2LA_@6$t+k+6q>rOUh%np#+ES&e8Pu&1+l#_-Kk)10WyvJ=Ul#%VLZ*Hso@>K zJFNiT1-$bMfIkZAwrIT2gD#7W%oD_Ks=gL_pWuj0o}N(_Qd}sPu&^9+$R%10-&|VJVT^ku z1H5giT zm;Hb^G7M-HvT%qdulo+72zCiOPs)ODu?6-`{Zf#?^W#jC%sD9#bkDFvXRI_pY`0^x z05vSeR9a2+-VJ8+JsqUL#&m2lI+7$)xDCpt3L;3jbBJrLu1bA?JUDrs%`6T3Ef70` z1m6lS$5I|E9e%@-weAE|jJ@i9DLn2$alSl%prc^rL!nm3=?RJrs>3+wRy)@vdhp<_ zd!I9xZvuwSFK|v5yPs-W4K8t=qZW$w3<)7=5Sxz&LldX2-DUL~VU;M9rB})?^A0YB z9)n_3d9fdk`(#il;qbUMW%21Lvym9-ZQw(;xL>*W;yqa?Mn$yPeS7PfdmN;RaZuMA zSJ0JKf}%cG(yRXTRmrfe>buFv01cLBHDw|6XiI7CJZ zpn&DO_?8Yl-2r4$2ZLX#>tp{irL32BH9>C&bU&kIeD(_tzZ?nDUECQ{7T`ol8spOL zBx^JsmIoD;By#%csYp6Z?j9vRxtFH~Dhg9bUVZ|>0ZLn7#L(KzHaG;2pP97&GOU=x zPe%le>&(C_f)q3NW*An*OALF<7M#m%eu_?u3of>@&sOeAk8ygjP`cSTxFdD8q zu>>D#3?7o(U?pG5bu#)rQr~C`txB&kr=k*wf-U3JFS{iS0$B99)0UTuFRX_)>hK(P z^LAqm{1_H6ZiTJ7P9Qnail>5?2l=(?;PG8z4%P>~hgHwXK!VP>ZwMfvQRA>MNU--l z!o^%Fzxn^C!srrhmm$=?_~Y*v#pm1N|5;=oKs(8yu^%?bFl!%Y*`w7{Q%4GxEb^=m z{0E;=wf>W{{K<9x#diJ;WB-BXJ!;TUTC3g0dan|cx4CXuW7whoA@Z35v$bJO#nD@t&q(8QNpoiaY`1aK zXi0U8WQ&(-&vrp|{kg-BG*1idZM(KVxc1Oc7#XCE`uLKMqjhy3D*x(qf|BP@t1V`ogiP+ z!M^9+9Suv{PJ|byPP>MljE*$#VAUM2Cw#?l^HUsf_4630IQMJ?u7*TrimxR!8IQW}W|2+XM S`zZlS0PJk2 \ No newline at end of file diff --git a/website/images/icons/icon-performance.svg b/website/images/icons/icon-performance.svg deleted file mode 100644 index 9fcd2a6558a..00000000000 --- a/website/images/icons/icon-performance.svg +++ /dev/null @@ -1 +0,0 @@ -icon-performance \ No newline at end of file diff --git a/website/images/icons/icon-reliability.svg b/website/images/icons/icon-reliability.svg deleted file mode 100644 index add18fe0e76..00000000000 --- a/website/images/icons/icon-reliability.svg +++ /dev/null @@ -1 +0,0 @@ -icon-reliability \ No newline at end of file diff --git a/website/images/icons/icon-scalability.svg b/website/images/icons/icon-scalability.svg deleted file mode 100644 index 9b015300f99..00000000000 --- a/website/images/icons/icon-scalability.svg +++ /dev/null @@ -1 +0,0 @@ -icon-scalability \ No newline at end of file diff --git a/website/images/icons/icon-security.svg b/website/images/icons/icon-security.svg deleted file mode 100644 index da341224ed3..00000000000 --- a/website/images/icons/icon-security.svg +++ /dev/null @@ -1 +0,0 @@ -icon-security \ No newline at end of file diff --git a/website/images/icons/icon-slack-black.svg b/website/images/icons/icon-slack-black.svg deleted file mode 100644 index 03420c85534..00000000000 --- a/website/images/icons/icon-slack-black.svg +++ /dev/null @@ -1 +0,0 @@ -icon-slack \ No newline at end of file diff --git a/website/images/icons/icon-slack.svg b/website/images/icons/icon-slack.svg deleted file mode 100644 index 42e33c4d65f..00000000000 --- a/website/images/icons/icon-slack.svg +++ /dev/null @@ -1 +0,0 @@ -icon-slack \ No newline at end of file diff --git a/website/images/icons/icon-stack-overflow.svg b/website/images/icons/icon-stack-overflow.svg deleted file mode 100644 index c8244ac9e4c..00000000000 --- a/website/images/icons/icon-stack-overflow.svg +++ /dev/null @@ -1 +0,0 @@ -icon-stack-overflow \ No newline at end of file diff --git a/website/images/icons/icon-telegram.svg b/website/images/icons/icon-telegram.svg deleted file mode 100644 index 3bf90eea1a4..00000000000 --- a/website/images/icons/icon-telegram.svg +++ /dev/null @@ -1 +0,0 @@ -icon-telegram \ No newline at end of file diff --git a/website/images/icons/icon-twitter-gray.svg b/website/images/icons/icon-twitter-gray.svg deleted file mode 100644 index b209f7b07cb..00000000000 --- a/website/images/icons/icon-twitter-gray.svg +++ /dev/null @@ -1 +0,0 @@ -icon-twitter-gray \ No newline at end of file diff --git a/website/images/icons/icon-twitter.svg b/website/images/icons/icon-twitter.svg deleted file mode 100644 index ef11bb2f0ba..00000000000 --- a/website/images/icons/icon-twitter.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/icons/icon-youtube-black.svg b/website/images/icons/icon-youtube-black.svg deleted file mode 100644 index bb20f823a06..00000000000 --- a/website/images/icons/icon-youtube-black.svg +++ /dev/null @@ -1 +0,0 @@ -icon-youtube \ No newline at end of file diff --git a/website/images/icons/icon-youtube.svg b/website/images/icons/icon-youtube.svg deleted file mode 100644 index 224fc1c49cf..00000000000 --- a/website/images/icons/icon-youtube.svg +++ /dev/null @@ -1 +0,0 @@ -icon-youtube \ No newline at end of file diff --git a/website/images/index/blog.svg b/website/images/index/blog.svg deleted file mode 100644 index 871e5c753bc..00000000000 --- a/website/images/index/blog.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/flash.svg b/website/images/index/flash.svg deleted file mode 100644 index ddb8f911c3c..00000000000 --- a/website/images/index/flash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/github.svg b/website/images/index/github.svg deleted file mode 100644 index fc532813365..00000000000 --- a/website/images/index/github.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/google-groups.svg b/website/images/index/google-groups.svg deleted file mode 100644 index fc879fcdf0f..00000000000 --- a/website/images/index/google-groups.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/hackernews.svg b/website/images/index/hackernews.svg deleted file mode 100644 index 0ee57d73c40..00000000000 --- a/website/images/index/hackernews.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/hardware-efficient.svg b/website/images/index/hardware-efficient.svg deleted file mode 100644 index f7f06d5a962..00000000000 --- a/website/images/index/hardware-efficient.svg +++ /dev/null @@ -1 +0,0 @@ -215 \ No newline at end of file diff --git a/website/images/index/heart.svg b/website/images/index/heart.svg deleted file mode 100644 index 590d8b96ae0..00000000000 --- a/website/images/index/heart.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/intro.svg b/website/images/index/intro.svg deleted file mode 100644 index 85db6c7c2ff..00000000000 --- a/website/images/index/intro.svg +++ /dev/null @@ -1 +0,0 @@ -114 \ No newline at end of file diff --git a/website/images/index/linearly-scalable.svg b/website/images/index/linearly-scalable.svg deleted file mode 100644 index b2cd41338ec..00000000000 --- a/website/images/index/linearly-scalable.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/meetup.svg b/website/images/index/meetup.svg deleted file mode 100644 index 836f00ee9a5..00000000000 --- a/website/images/index/meetup.svg +++ /dev/null @@ -1 +0,0 @@ -412 \ No newline at end of file diff --git a/website/images/index/reddit.svg b/website/images/index/reddit.svg deleted file mode 100644 index f991ed9e00e..00000000000 --- a/website/images/index/reddit.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/safe.svg b/website/images/index/safe.svg deleted file mode 100644 index 9dbdab5fe93..00000000000 --- a/website/images/index/safe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/scale.svg b/website/images/index/scale.svg deleted file mode 100644 index ab188a58de6..00000000000 --- a/website/images/index/scale.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/shield.svg b/website/images/index/shield.svg deleted file mode 100644 index e48b824909f..00000000000 --- a/website/images/index/shield.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/slack.svg b/website/images/index/slack.svg deleted file mode 100644 index d15621d00d7..00000000000 --- a/website/images/index/slack.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/stack-overflow.svg b/website/images/index/stack-overflow.svg deleted file mode 100644 index f5db34bf532..00000000000 --- a/website/images/index/stack-overflow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/telegram.svg b/website/images/index/telegram.svg deleted file mode 100644 index bd7e2b0720b..00000000000 --- a/website/images/index/telegram.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/twitter.svg b/website/images/index/twitter.svg deleted file mode 100644 index 3166e01e3bf..00000000000 --- a/website/images/index/twitter.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/index/youtube.svg b/website/images/index/youtube.svg deleted file mode 100644 index 46ac9355064..00000000000 --- a/website/images/index/youtube.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/logos/logo-almaz-capital.svg b/website/images/logos/logo-almaz-capital.svg deleted file mode 100644 index 20cf10bd205..00000000000 --- a/website/images/logos/logo-almaz-capital.svg +++ /dev/null @@ -1 +0,0 @@ -AC Logo \ No newline at end of file diff --git a/website/images/logos/logo-altimeter-capital.png b/website/images/logos/logo-altimeter-capital.png deleted file mode 100644 index 7c0d3b25b8d90a3b65a4ba792eac46ad88a12c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3600 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 B!Xf|w diff --git a/website/images/logos/logo-benchmark-capital.png b/website/images/logos/logo-benchmark-capital.png deleted file mode 100644 index 626599c6b35bf68388df4ab09ed0e26aaa878e8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3154 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 RQ2X88NGl<8lK{sX)hHthfa diff --git a/website/images/logos/logo-cloudflare.svg b/website/images/logos/logo-cloudflare.svg deleted file mode 100644 index 9c6b408c388..00000000000 --- a/website/images/logos/logo-cloudflare.svg +++ /dev/null @@ -1 +0,0 @@ -cf-logo-h \ No newline at end of file diff --git a/website/images/logos/logo-coatue.png b/website/images/logos/logo-coatue.png deleted file mode 100644 index c9331141f5a6476339cb38b5c8209ab87b4ef8e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6128 zcmdT|Wmgmo(LoxINa;y&aBwKp-zpp6;NYSE`LV?K|1^o?k@!DT zp{He}^3VQ%5fRaP<$qYkeGPOBac-C!F{B4FbOi5u5hK3O&i)hVc3k7gfodotCl)$p zdC+0;5;h_OwHBVPDSBo843!y01(4mX?`?0N;o#7;sw*oP`7a&iEq?kg|DXH_0aF|? zM^qTu294Kh<-6kDVaXsCHkq|=h!TL+~2rqJvpnNO6zK(Be7-Q*nP0n3IHYC>K!~ZeYa&0(>QWn zbaC!Fe#rn2dAQw_w*IQ&U*O`{08Km^Sv|Fy>|O9(ddVW|Z@q>acS`G{H^3{$xaws4 zXT*iLXXi$e$ia(84llSm9oQn1w%G?R*nHYMCFY4InU!0~F7t~l;QZ6$Imv6hnhTpG zZ6*B5t{O6!mfmg%&24w3EqCxQXC5iBI=#@T50oBsVqq-z9LDrluOlo;{)m}8P0QZg z{LtR_C(>MtC7u0X5^B9bDI1n+SB$K=2{6Oe#O)9d{KA3xZvJj{8pW9Thw@!%42&EP z=lzDq__yx*#HE=hgHRea`wMGKur@|D?(_F)epupg<6U2YN5(F3b89DIgYScZf^34H zskLz(M>e0pfQpfdY&iE;Xv84hgB5~7bjFSX=Bo$ovPhc#ZjyxF=GuB(z<|~DxCFvI z;!or8-_~s=wpeLX_(Pe1z@(A6kWrFV=<&%E(;Q^`7RW?yO`oJ;cgbW_IS9}Gol#G5pHK6G{BgPNc(bT&JE{^(#Nyh z_6rN({H|vXV!!%ZlHG*#n=kE~?rg4*Dq^&x5mgt+OolZgD~w%{GGA$W%2el2Ol(klz*J3>+1|oO?9*%J7Ne}=fWw+CZ{S=>W^oT}ms+Tqr2PSpcNf=sUJC=M1Xh^NbrE&p4q z3WOEBkxv-Ywg91}Z^-R*vikMPlbXG~tpOZaXFSsYhe5I+o9t+G2H7JNfT9K+CtNax zqkRC`^2X&(F*OAB>lYvy|aY@Y$92;Qu_=S=x|;JpL&w{lEbv^3{+Ne;C^vU8-D<{&XV1)$`NR7;Ah)EcQ|_D*Rd#q>g%&n8RN^tix=MaT46h+ z41EhaP2$qC4`<}{Tc-V{^+I5kcFbg;y%Gy?|W_+cYp@MN3x7a z&k@8<>c z2qSbV5R*yni(pyd5==NvYmXHLw%l!-WxF?Y?+n!GHbn2_&Ca{_C)-nU_@ji+I3J3 zw8;qrYZgnj9i7MgW*Qelc84rLeMaHRf4tsJnfgk4olSIt>%recX9LU<AG(f9!2;XTKcKyV;yPXT4~^LUNg-Z_+9Y<|OL&vC64%{TaZ zpx8t0U1L3kthc|utlsmpxw~e0lo)T4%7Obz7FsC3#kQZJ;`30xNdg7nSM`STT(Nom zh(McXRXLUigtFDoL$l^$VrG~UV8PKYQrAY4>@uL*gMwrQiYhT2Eqw9}*>bA8Oh zm^DV*3o((c5OKh4Gwa0|r=^hM*pmcU(GA<28E4K_O$Od)Sp| zN7kr~LiG+@T#_&jIbvKy6YP&#rcl>Ra%-+DFS)q-y^!9ps z$omog%U$y6yU=So6a_{~6&GRh@DW8d$Qq*ct70D^HtQjLW1JbYqe>jB@2Z=Z4(8yB@)ipbG2*h-qmd_5|Fp8sN!_nhYypPpn`O(QsrBxZNGb=1su%)d2fu(v19d<%XIi-+$BeU;Lz~w@K+(*ZH^aJI9RbC4iXuEVu`&(C(cE$ z2q+zv8B~%i8vlK_iUbU)+ai0`+C}>zt2m#0LY{QTeCcOawu$s8v&?LIw>}>SCPq5< z!xM=YK<`%8PH<85eaHB%W3bwTS|iC!0O$dZ8KT30zKKv&l|xJ`OFC_1s1EvcZwrY# zoRIiyK_UZfk6dV@D7PH%r0`Rcj7`2Kh`p1D^}-~8r~ZStL_k(l{9N6;S*wI47<}BT z_%3}SDigdgiP*(kDh9DoNsbvX-a(Q)^)D5nW9)3%QHIDrQvbqHqh2RZad#u^NRCXY zd3+rpcIWqtfakRxvn`??RISh@v4Hu~fOnI8OR{<;2?+(OO4?APdauf*Lq6*HC7 z0ciNw_}9InoUl2Xiu;JZDw|O_whvN}LV>2myG;tAc|<ro%Nfgv~8fT-R169MT z85|<8A;fY6#05DtPraYL@{*zSD6G& z4201v385Q;TLw<;Q%(?$)9u~2k8~$-&CZv8saSeVk2&YX=DLZ#d~J`c=xTZm|cEW|x zPmp)@O{BC)tW!7(K)v1$2>=Nrm-V*~#Mk)a9J!!E?*0F=B$0iykpotisKKseA6sMv zpE8YZpGkw{c|E3J{T>Evfezd~?}Lq03pyhY(d8?0oSVB{WFalPi)}L|j!x(htir`} zOU0LJaV^g$wA%4JF-uN4U-*YLPbAZx-kxMKvC3|H1z-c6S??5byJop(-7Q*HG=(hDOtCQ{c0(dmp6{FV}u<@TAg zF>Z$bLzQYUn;X;%0|iSrbhfG3D)uHAov4elag0g6sGYgvg5ar*!@)f=!?+z%^^yIh z+3w$C(vvHR+=E>S-1s3mbf93knwk%CLbeoGrYT0*T|WLdsFmzb6(sn! z;$dZa`tmAz%1BzIzQ_H8fGb!>cmIjj26NN#nc@j*p6c<6vs^|#)^X``@a)FAL* z=#vw5!OO~ut%+Ff1kPt@f(`WNfi{s`)!^UFS&^7{2j;V3HH7j1>fm_>5_O7q4d-7$XJ3jo_m0Nf!Agwaw9pnR@myFIjm^`$mqJ$| z_LU01XJDQJlo(Cz|1g?&RDNTOI7Hfbu}vgmnjUU%O5lM%n>fn;C4hvs*TzY94){{) z$U994u5|j4PE!(!g04pEKt{BQK9V-%hO_;={Y4q+4@->buE5q(2=p1cZyg=I5mX%f z9;%l0KuC4Wo$hGWDzh3i$QWexZ~LKZ_H@FlRLtm;TKxzeOcS=@vI`=g$yq6=N_REOUnAU6~q-fArTGSmf%cFN35iMyQ#dv7j@zL^o z2)yD!8V&f76{_PsCxIE#u+PRUgdVlUW|`bzJ*o6Vc$m<40j1Lcz2qJxaAX{r=L+s_ z5???#tM<=MtkT1wrPByQix%O3SFl#9f6v7Gy1L$Jw4uM5%lUyc+L~a1-KB&|Kc(9 zN2)Te(u1xd*&32z6u#C^OPv2Ut?O8gCYBpgB0;bh+TkLy{+^o4?q`V|rjI#}xs`~j zdYaXa-pfAt!36}*ZQUh2se&bdzON+X3EI8cji}^<9MVR}j9{?~=5l<(Xl&jAvy)_S zF*RJw5ES8-{bwRXwnnKCztoUXM!;vcoBq3d?4@QLmUOOYKwHw6gUBdCkCzOP$ERx zcvUE+q#Rf4nXu`E#REwpg4wU&F+_%66RGBKT&zFaP4D5+QJ~I1LvgRVSwRLrdFt zl+TweGWvRO_r+md%niL<#S>eq2D1!Wkdv>E(z&CS0Unb5)5uEBjWtZ=)p*kt<8~( z(7*JiR6%BV$ayntbpx*B!D2+pfW+;K^7SU%Wr}PO!$+oU_gOhsAH1A1Y2Ot~7;|#t zqAmL60+}isYZNn0*@|ig3gjngJ;esDWUP$%1kkUBr|3&b8VvD}Y68f5DaS*cv+aKJ zrmT~3Q_{c8v#C)kh+Qwsk4FER+ZXGEY#HV2rx0z()+KxE;)ZJ-g_^pBrb?K{l?JIW zf03M1P7$w=mON^k+W7kTm#Cn(A+xcFeh15s-N4oXvQ=+E>KoOovUv`wW`X)4!QM%k zk{XoHM5#9rmIVH=dz)W_G7?`j5iAj#$?R#QB4Z15gVheitjCkck_;%pl$>}B+J zjop%K{iViCb>3cU5+6X8E4z!+{8zM;D-PAONfsSAK1aQz!VA{jd;+VB{kaIcM6>(} zPb39L9cc6F?<8#y>u-}KRi9Pu=H&N+o1r#D!R(WPr>}ahpeEcQL{wLO{2NWG1%K?z zE+`^Eu`h)FYEy}Gv&!>dbGWRGOT;(svj*`nhP_Iy6+vrEHz<_#Nkz#Fi6!@Y|4IFqjwhvYTJ0EJG8JwwdaqgbUzDk^Q?-awoj|nVB^My$j znVAnE{jC~3ZFNnVr2go6y<#rU_hB$-h}<@8ez~rjTvzNK;PBhXk5)|Z;Aujy%cT_C z5P0E<=m{!7qAW{;nf6Of9+r z!rS|7n&>u+{v_SbM|({NA*YnPpQVf?ELmw|EoAhm__g`Bp3us#Yf=Yt$vm8|eztyd z*WeHlO?7gaTgUZ~;~j6)@QbtPR=8K1nsy_Q$kJ(2T`a%;MFlQj%wee@s&UtT>RM4_ zr=Qno>C8*JRq(#?XWy&y+V*)m!l%^v6n=#T;_08YUU6nw5U7H0zTt)vdyr6?>$h4^ zVH*G_0_xbmd(qyc&9VmEhNK;%Lm7+F3oE^!@hV?3o9%q;qoR3YdazuuusRE2oavKnNRb>fqSZUg_l8lJ~tZi0UT$@4D4ZCyWoqTAAV=j zb77Nny=|oi1}?9q$|#d(U)WfUu~Zk6{}^BXecO}Tt_NwcKCG>Hb{vU6JbmO*Fsp{4E~YX4a591(Lnn(+iLFd_aNp-U7oVQJ%EnM!{w@FUP%oG?ee{G@ftAqS?72o0#8YmlN_Lz!Ai28a@NJ(1ln2%WItm3rq3CvCo&rN5ZhT^U_)X zPp(Qnh)Dxwa=H~S+JucI_-rn*;P6qI<{AGHSdj<0nl@eJv0sv0yBWl#c=p3&Cdc)W z!tgKEAEHry2J4iQfmp&LK1Gd0tl||(o&!AhuU{BP3p-%!i|lnt3KmUfxr$dZJqeCT1O;C)ktO}p=}TJNrSZ)?xW|^ej7ooZ7o>g zXHOMIT=P{=2At==n&*WT<+;|F1SED|WIVD)1T&n6N9gWG8Fi=bJTF0N{T-Vh?0DrS zvg+hdYd z`0iZsEVIpWEYJ=H{HQL4tZ`UmuQtnwYT*1*KB4gHtWzWcq~eB(P|PqX)@ UHvtD5|NGmktLP|KDcXenAE`#v*8l(j diff --git a/website/images/logos/logo-deutsche-bank.png b/website/images/logos/logo-deutsche-bank.png deleted file mode 100644 index ba1de3a28730897afd204dd5f04fcb1f354c3825..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2792 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 diff --git a/website/images/logos/logo-firstmark.svg b/website/images/logos/logo-firstmark.svg deleted file mode 100644 index 9f372958295..00000000000 --- a/website/images/logos/logo-firstmark.svg +++ /dev/null @@ -1 +0,0 @@ -logo-firstmark \ No newline at end of file diff --git a/website/images/logos/logo-index-ventures.png b/website/images/logos/logo-index-ventures.png deleted file mode 100644 index 5bf804bc8b7a0791332d0a40440636b024fe84c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5475 zcmZX2XFQx=(DrJpt-e?#y6D{O>b=A3M6OJfE)ZaN4A!eD|nu!TU#VW6x>LjnF_ z#fdba;kUK9y&;KvZIy*l%-UH>z z%95IjGG1O@Zf@?==4#L)Dk|#j?X9As1c$@z?QOs%M@Rd*>MC#vlzn~iM?;> z?$_VCl{HEn1cPg9Ydz`f*myYzk^#dO7Z)Ot3Py$oS()iol_e+?s;RNw$;lC9E+Zq8 zo|=@ClT}h&(AD|K#|IC>Sy@_qursi=wek1&jf%RKmYUSqP}|+nURqLAUso+7BMs6( zqmAust(BA%B_zb{Y;9tqBje+)OGrpGHq`0pXq%fD-?(w3x3dE*$dxO+AU#=GS&)e$ z3IzZ_#I-OrHC3=0g@t*sF;O|UGW&ZvK&m`E-1>UDCTL?E)&tDm(ZSB#)C9~O0OS

G&(9B10=>Y}fqhX>kQWjX1oJu&hNDnOMMXtX5fQN0 zva&K@XM=+Ryu7e!X^HCUsyI&%NpbO@Apgk7FdrWuFuQ$W_?{4(l$4aRveK0+S4>UO zZf-6ZjDxkcrIM0TPEID?%j0cXn44}2Kf7UVLi=EO@e{~Jl$Qhv(wep)j>+q z($ZjAz&I|>7(qcnXN;4gf`XC~(g%+Nt05sS4weGrvndg^ILQaWFbYy}s2zdz;9XmJA9CMR^q&wRR#on`Tn z!GD7|309Wp4RFTEamLniD`g2mftTG5W(qvq#Da7ejH#5UfB<5+?@mrmWT3aZn~bP1 z2jh>S`*pSHH~sDOb%3iJ^qn<#Zv^Af<3l}-w^E|QoNYCfUboce=4RsDZLGABQdi(K z2Wu~1JbnDQ{b6l+X;NHNSQuJITUCV{c6s)tw=gd?IXNmqU4CQsetb-1q&L>ZP(^97 zrX)S}hLeTFtGOA=@`PYVJ^7;5zi)962#cVJfv$b*`bl-1+g(G%i^I(|nP9>3dXf&U zSxU2Yu48vm{%sYi0XacdN)14;_+`$Jt-RUU=q%DgeR+0asHgj3I`T0@# z^>+z=2MIr!-SD@6{$x5;Kb;+vfA=%)p!|?auT*U{=#{WVh+gU69sAQjpob*@@L#}^ z8i}^72PZmEim?zIG;LSNtGABS-wt9n8Jcz1w#HvFO|k~AW~6^PV%B< zMK7Y8%O&~l$P8YX?s-}K=0%M zF8oo`oiC$W*Vhx?QeZJ>N()O@u{zhuVZu%8aTqPfyy%Vk{2BOgSP&3cImnDHyQG_Z zrQv(H!}bJwVM-ZMJk+55gQlNvpiVZREAdi$6u^_05u9y)JR5)8JLPb8lH3_T8h*rY zvGk1Tf*&W4NdADSPcv@Z>?TJGk>rbA(}M3NdcL`dVjUWeGLN66i-MuOa%zjYQHJfEemlnA`dh$;$4Wk&&X=z)QrUbHk)jhgIGk zechas3H*A2bMY*@6W;LK5?@b{VooX-V){G7!4Wq2nGtK;d{ywa%PpgFBn8{acdL}g zJoi3?0*Ln;&h`wRGJ&sQ#H`LtfyQ};Uft39om9rOZ!J7dZ&CyUkIO~O_yt2^8M_~2 zHZ4~I9TrDT8<)nIQt!nA<9($FRB|`FUm>ODBu1=pPR6(5(Y;~%P;Q8&;R@oQ*tG+F#I@>pK)%)wEMo4Fp$Z<>y@%NkC$;sDG0U}c-@+r0)`8<;&cPyhn z1KMb>TP1+aXMO_tJbSYK#ueuI7JnIK35?Hgd zq^nKRn(0Vk?lrr%^TkCrm$+xk3>MveTW=Yh6=#{)rja$}l8WC3t}JIdx33DA9EcgR zHnKdWK2y1SNRMINCb1FSt(NE_U}Kt*xof+-jZsBd5me)|38oz8cfaOq7GG$7N&}|MEMhvo3Qc-#RxEvza3@2mMXsbul|E)# z6q9ipVdna(5LK?_RjaaEESy69I9tE5C4{r*b*6c|HHj{u^1p|*wH_iS)mviC(&GfX z3d4)>v0E$R??r1GHfL5ol4WQPQ{~8Q{{1kISry2rzOO5wGZD*-0sd+v-)uS!1z3_Z zdvg2Bqsrf~pWRDHm3E=8Mh8VWF%kynNF{q>W6Z-;pOG360#(bl;6p8GQ*)eiY;?x2 zi3aW$aihC|B2s1|wJtqng3ICO$}#*1gXm$h-RpnxO~Zecqg8jc%2f8|bxA|18ylA(g}PSgdStQ=PbH*U1?|2nI_HR(WFlS`$S=?Jjx z2u@QbV|d0jc5Sg2@$WxIzKWplGc&d+xe2T4t~y)GhUqDGVNB!{4g!$#Q%{wfQ1xFl zh+9tar-i4SmOsbz>dgBg;v$(-PaMc&j1eR7*vZ8bi0w(f(7Q|+ImZgWjy(l5L*ke# zP+j+)-^`lBWKAB%Oj}};`eu4c`DefWEZyos`#;&r*g^tlr21kL&4KWrBvScC1_YsV z9T}S&pI!##Btq!!{YTE@s9}P^x^mMptc=w0LCcsM@X8rG>$`2DkVTS@V8W3nVIw=s z;p75fBurgV3qQLTp3pTs;UO$pt|(ZVxK4YAf*hLJGLwC=%lux{cZN_4AJSPmOU(yeFYbha5Nn%8A; z{l969Kbc}s8d4kC6Ihk!219y@G~T$Z57bKk?8ZNS^y^qn7mRwclTh|g*{+e3zkSt0 z?}vklZRv%_E=}3SKyPpvSB&k~)>mYUkRbp-m<>3)lUa~$x;J`mVO8IIa}R>->%piVkK~ZV3R8AIZ}cCEVeO4_Td`{q z>Kprs0Ib!1!@R~Z-dYDL?jgbIGKi-xJl?EtEIG-kn&mxuo0#PfV<<(efKz@(m~fU z-an&sm$#mqz~tj-bcWut5ZcX7Mj_pnnvhd5aahBl&g>H!8yjsR2eJuGpK(`t=kQNj zb(8GCrcs%TlKmGxdZ1aVCgKdJah@FsALst?`caeW&we^+=rLz<>DoEDZ6MVMv$(8B z06-iO=)VH%z;8!wY{Cihf1~Q14Vl3i_Kb*%b|1BSKgqTn_*y8PnZ7`KBQBf1G_%ZT z$iO)CRUIz3!X`^L(-#Tw9*QZ#u^*Nd0O>To`&BK4rF7BnTLmB-@$9PYx9Ijl0XJ7~ zdMG#Mpje%-gFH6DSY%K9Vm5%?n#)kV_I{lhcU(AQ-A*aDV-;rhXEYwu%J)6WPlu3# zi?^R4GS3h@h)lZ@frtqF>2_WUq5P%)`U&=f!W%8vmzCWmc%yAh6-<9=6}4qO$~+U7 zhRzbwK|Ktw9amP>zP7(F61Yd_eJHAuLHu>QZXcW+QoSy-s9PiR_Yu3WnflSfa&)^y zUX&j~?U!0A+13r7cZpGhF#RubBKki1{4EiA#cVfe=pQr*9NuV_LtYwsXP0hE@r-JO zpvlG_HBVrZ4T@O_PMXW3*w^{ZdStF5G&vvXCG+*&SKKt;EdT<&lc*^m{bs5|y{gH+ zFYBE9!$wQ@$veJpM|0g|Qv$54felk&t!P>;%eRA;r8395b?Q5iyEQ_r$vIq&*yr*}A)Q`{UF>?Gr)Y;pu&zbX(@|#J`?m(A%K6@}gGHKp z=`7W*7o<&Bb>1lK*xMYxAd7+aal7hAQ%8tb z)R1S#Tg@w~xP(2*@!WFS(ymoJ|HC%V57Qo`gg*RLp^`$%&z|>q#FU0`IZ#O_izu1L z)gHA-AI4iQDWmBjN;rwd&b^a!>M=n34?yC}fqnSoqr}2p0n~oSO(oLph9&8n8dmQq zSAW~R0wtj|slH3MR-YQ#d6EKT;_@bs_ury6t1Ra=lYqCUaDv=OjC2&GeskA+>|KG+ zmP)#h81zFGCgq9+@3S6w)9H^(4Sl!Hv9i)d?}%6#29*CSeAnx3vjwEBQB>Gm`cF~A_0xlG`#AJkSfp`4lp2NpXBfn{b@2V>YH)kw z6O<;qL^;iaV?j~L=~p4p&=|CxG0#QW+&*!RkkX{oI(YtP(!CRj;I0#OuCAm}s+=oW zfbIp1OR;J7Y+1}(RI$TsHqaqv12?8eM)Cn$dmh#y5WgDl zMIz|z_8T1Rv&ko;6=1h`Nj5MhIdQ~@70JuVg>R0?tdLbkNO}2|JeIcoLx>k$qyjme z7gLC_rM;=p)TqDaX6eBXfq-<~pq+#E5ONrnazq$2BW!km8Je=QCaYx|PD629g&f|r z1J$WTr|LSqVy0&@cDDxdS8Ga$i|iCBDN^~%d8e;L<&=KKPIq4dGMPS>w!cQ|q+Up- z{`~h?`(Hhm)We!_)a*e`otIYiA2U*iSV->${eI}5r5nqbY8wMT}fXa zls`AJvxoz}kCoFMGZ!9gd2ZY#u`=f`ycOg7p0BQ4c7zBlf~MxqOW1SK z07bWSTZDmj0pcXZZ)%ZAc}>MwE!&~`nA4+3x9@HQR-mWAlSLw;(pmN|pYT93IpYfY zqez5aWlb|VA}f8u@%)Xkz!PFKJ0-_R^96mUyqQ1S>|e+CFK7JOrnO#^3aT14df$&{ zvzuv-eQQysqJSCv?o>7Be};YNV%?98gw`fM6A=UcL>|1AVUW>bEOP9*%B_D{Y2kGh z!hk95YisHd5Fcywv)uZ@{0&nwalvnOK4SlUp%6>e!+lASI+d4p)&orqNWRt?Pp|Ot zoO1X}A?FRVv@uSu*>P*@az4KyMv@MrXV|oKn^yH-P2C1Q8^quLDrb}a9;K$vA*slo zZ2fKc{fer{Ztc@78)C57OA$6Q)hP10{}Lx(0WU5U=1D{O*H_yfGUy&MQrsQ4ldD@e z6Vdc-bnPM-vFEl60YlL;2iV#sUA+V74-G1kfE4dV3MGY!_J~9<70f}v07mv_mr?98|>^RJ{lIUD%=HpGd4~@?AW^($Fv?V_c8H?C>{%!0D z%JPm(A3rPJ85b*U8EP608++*gfubnb)iBSzxbe+~p}-!3R`A#a5~*GN1Y%)7+H*X3L3l5~>?9&`E+*pUrl*{dR* z5+Z_4tab0CX+4jAfb`<|v4XE2w)3X=_`|2oY|C}A%u8o(VNZtZ$btUW-3GSqK=SR# z*4@hAotN}!54q3G`rzw)0a`Bm{H_j8h&)ulLZeAh)4{RV;qMl0@G2k0p`L#l@PD5o lt~&h-%T8Rz{u(~ghS4hyGIe#p!S^DFiJ_%IyB;?6{{edyk!Jt^ diff --git a/website/images/logos/logo-leadedge-capital.png b/website/images/logos/logo-leadedge-capital.png deleted file mode 100644 index 2c7671bfd67545d26216a8cbf78f886f434153b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3029 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 W@0(u>PGJ81N*Y7&8P@5$Joyh$5RDN4 diff --git a/website/images/logos/logo-redpoint.png b/website/images/logos/logo-redpoint.png deleted file mode 100644 index baf5a9a739240319a07e720a5601cc2b92dca5a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6109 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 diff --git a/website/images/logos/logo-spotify.png b/website/images/logos/logo-spotify.png deleted file mode 100644 index bc7c102e6ae52a19ddc3f54e91d1bb449119fa78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3965 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 Q0DLAO5`pYge<~pDe_{O^3jhEB diff --git a/website/images/logos/logo-yandex.png b/website/images/logos/logo-yandex.png deleted file mode 100644 index 02ea52f2c7f508676b528d1ef240873918ccaacd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7266 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 dz%SsD*W<|lKcK&*GKK^I= \ No newline at end of file diff --git a/website/images/mkdocs/edit.svg b/website/images/mkdocs/edit.svg deleted file mode 100644 index e12ba890f2e..00000000000 --- a/website/images/mkdocs/edit.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/mkdocs/multi.svg b/website/images/mkdocs/multi.svg deleted file mode 100644 index 2ae85a1d227..00000000000 --- a/website/images/mkdocs/multi.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/mkdocs/pdf.svg b/website/images/mkdocs/pdf.svg deleted file mode 100644 index c85df2f41ca..00000000000 --- a/website/images/mkdocs/pdf.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/mkdocs/search.svg b/website/images/mkdocs/search.svg deleted file mode 100644 index 2d678d62213..00000000000 --- a/website/images/mkdocs/search.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/mkdocs/single.svg b/website/images/mkdocs/single.svg deleted file mode 100644 index 077561b8c58..00000000000 --- a/website/images/mkdocs/single.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/website/images/photos/aaron-katz.jpg b/website/images/photos/aaron-katz.jpg deleted file mode 100644 index 3e03047a24719a8e730552c657892ccc90c0f7af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44550 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 Uf2Zg9_Q8y*tVg3)kGG8f+1U4L{r~^~ diff --git a/website/images/photos/alexander-sapin.jpg b/website/images/photos/alexander-sapin.jpg deleted file mode 100644 index 8f132c6f4065fc9e33c5c5e77e657e19e872e43b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91455 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

JJA<3xPBqNK2sv1TR#4 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 diff --git a/website/images/photos/alexander-tokmakov.jpg b/website/images/photos/alexander-tokmakov.jpg deleted file mode 100644 index dcee6b9d55119ef8140bd64c45d75abc57404039..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103648 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 |<#~1}S#3a>t(N+2v4G9ql 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 xa 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 diff --git a/website/images/photos/anne-carlhoff.jpg b/website/images/photos/anne-carlhoff.jpg deleted file mode 100644 index 4bbc9265585843637f2d6e0702d34caa2d082992..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25133 zcmb5VgOldm7d85{ZF}1GwC!ozw#}z)<89lvZQHipJ#FjG?|$F?1MW(yPE~g5r0OJD z$zE&kpS7Po0J4<0q&NTs1ONc}w*h`O0U`i!FtGpa{{R624gm!R1qBHSg$N4^4Tp?~ zf`W{QjD(7gi;0ScjfRAbNrZ)shetp_fP(ppgb1Gm7oPzCf0KYfKtMo2LLopwA>gAT zqvHSn#?Jr%1sX&UL=X%F1ptZy0)_(eGX%f~0Kh>0UjY6uz#$;Ppg^HP0RN)6Apa-& z|Hb}|AfRCX&iL5?z=MGRK#{?a{~f#f>?SOGCaQW;-s3m6QeNDnW$4L-qi;ltlbj-~ z7CCN+Zf;+pfK|*MszWvkc83_#ShR#~9uM_on^@6#7ZblS{2~21nVrC6>-sPJxMrTn~J!)uw&Ea2+m+)M-50~MhV4WtYg%Q$F*9n-ePE_ zAMxatHKo(E!Uw15fg}{dpjMe-ZUyz14#zrC6@)B8N|(7LVJpVkoOZ(+X_#>jo-dU6 z^*Bqxm2&*E7Fa8?VE6hM1^g0g%@jX;v}2o`vjBq(*drw=ASnpr$*z$>nUySOQDX^Z zI(eGh#3Aw0Fvir;u(c96@P-fe<>L^N}lv9M=i>iP~YY*L^a$1)Cv)`rd{g^*nvPrlJ zF?WD;AdWm#)K%IvAfjg(f`?4#?h_?1~x@{wj~IHXkNFu^fiFMi6_y)ORH zv_`&R3DzGhyh^W3@cs_iLOL~7 z=CwI^rLl~9#)^{JE;)LWJ&0qu&Z8eht>FgiTvLDbxlh|K;dA;bBs*Zh4|4D>qyrf> ztz*;Vy5kai^J*e18l~oX$07VVSwIHIN!a*9#0aTsW(u5sdyXiE%@mRbG^p|#H4@f#*YvrXl& zoLp7pc6ljeDW&?4HBU=lUX3_j43!F24%|p0P`K;u2Oyt2`FJ{_opn?CXsoY162EQb zS=3$93qap{YYA`xOCAPAd7;Mjp_4*Yl$czeYwvhmpzy5Ql@sH-ah%-P>SWE&+QASQ zz>beVEQ%1Qz&MY?IeR#`WKTbrovwc_;|9K0DZ!4kA|y_;&-+PhvXN~0`~hfl9nXf^ z|H4G!a6f%{f>;zWz}-jd#ZM^F>8@FRoS6SSXVxTPiSjTt4)bXDCC@N?sQCse+B7a) zCVFQZoHpC0y*pQ@R+PjSlp6`qUi7k|o;V&uW!4u| zR3+?JW`H_VH%Ld?r(IHC< zRtW)Tl=@5qJ<+WZjP;9nZnea=7r}Ac65?n3o_Jh)a~K}LKyjWPR96;@77VvZQSX4 zIdiz=Axa8VX4K^xBfk+uCp9e*4!%?No~m~hXC^cB6b?Y=0l#&w4e}q@X3dzbT?S!i z;U+U&9PhiiG|c@X%f|VI?zc<0$*vFoG7YOX&5yq&7+}t07|3WV8wod*jHbj#`N_Y1 zcMElb$&=^Bpf-egIIwA2rOE+MhgD~{oQ>3gT^N>OP-hRxs3`n_#TZ7Ygjv}nIxjo1 zz_Z?CzlDWT9ujHf4WvJ%meT1D;waACN3ukUZ{LH=7Z&)I*PArtu7p@8^mbWWANbr@ z0?xX7<_TZG|IWuBX0|fSx>c?s$y_bjqwbQcQy1SIVD0?)KvVf)EtKYeGcL0H4u^$_ zvd>A(m_14NF+KINgC&%=?XoACUyN-klz}20#eC9m6SzFZvvW3IHr9%x+LW&SpOfQz$%~7c^&_^32Dr`O3FTS(eF1#3)1L zm6xGb4uRmz_NLAKjaBI2+HXGE_i|crx^kk{Sy%ZTf27T8^Vvkg7WBy35SgqR-Ap$| zapq(Fs%Q&S<%i*yEMv*cr}G1lx_$3f&uognQt@pSa1ljZf^K}`^8JM>(Y0tl+us2M zl}gMtbJAMUK)tYdH8;&~Ka+Ev?Ixu9PfhJkqC$g72gx4VVsk!C)1WbEUG`?$G3N)M z`qr~v@rettN;Ri|t6=r8&7pr7|1bG6mu@$Q{PYO-WJgko0Anyj>{whZJv%x37D;}V z-+J70xIA)^|Yg(4VrEA%vV6V@&4Uw=0a9+tso%FQfJ1Pt}&YC zm;1@_hUt5t!US~_qVRn|q&RbaytujPumh6xtRwrrPL2xnww5U8wJ~)?h~_w-cq~>! zqRd;VICk56V%X+6wr5zSXoyMmY8Dv2%q?)jdUllk)HFLALxR%ZnXQ7>o_icIil1ug ztm@{tS30Dkm1!x)mUkSJk(~(v@*qOzv02_(fX&u)UYf(;hp(Vs88+aO+0SCV9zOPmEK1zm412=#RXn{j%Z&3DvRK$+zNT z+r|DwaO}K#=q9C4tbed?6gO27KTJRhC^7+Y@3h=sZVd)TI%OXzHOtwq6 z#-&Htg2J*`B)%>2mBuiW(be?iTjI%sheFFaFJgg_X`>ebReA{ZEa`7$gMDM$3rW~x zaENw|WtE^Ozx|m7WF@h>xfNq(i32labme&Iq%2Mv8&rT?7y}NCeI_yANe|1@=ZnT_ zy3zVZJ4cKWQ^lyfnzLTkU&qG$5I{=OBv*7vpLOE#<{xN;k%G^eQr{Kt4|Tvpm#HE%2Xr3cs`)l}kx zbrYQ&{8?TLM{k{tX|IfT8ztwssiC(wkr*x>C|=ri#?*}JE56cIu!-x80n zl=N5_^9~z@nT9n^d0b6yT#!#8wOdozI6sw2DEzNsGW@X5n{7+1l6)xe*rQg}u z!*2KP{CYX2H8OXd9BSA%6UPe*2YCirGfP2#s32pHeQGaaXw-DrGQ4wTM>3^JEYfQu z`SgD$JZBV-1n0N7f6&1qMVeHET4y}&jS}FRdmqjnsFs+<)TpT_EcuDIc)!nMTxtD| zC7j!qmM*~DM_k|kVR!WDV#O1w@Ja(p(G zW4oN3%Kld1-kM_e@CAy&G?TjEnDYgdDYQ&q-VQT$`Al5~QkE)X#xW|mBCm3N-(%ZC z8FeP7d)C;!?5%#`07M$`06ThynEqngz<0|%UEqEguA{fYOyt$B%w6*Zv#gISnz;tL zY3U%TQ!D-DdT~uIY5p9GtPIIbI>;}H5w*uZ31IA z39Pnh&^b(X-1T@std=y<__#x#j$~pESww>cvQ!NKBlAK2O6a*ELjzf-zc;SEBa#P0e(sxrbRyy z43t;l5xy8wyLQeWic3gX+t1_L*^d7zsduxfA{S+`j8`G_2`-!@F&%M03p$uIW&Llw zk1js+s5Z-~2$qS$0O|CXN75{(@qNi3BRiCm3`5%DM&GXFhGL@_S*mo^$OhwV-wPJvT@J7;ufIF&a}2Nw zlcIsHVTT~l)$!8H1A&ZCxH3-uT`W3p8Y)DQ?WGP^7SBPJjP@4;*#-t#>exRG0Pc=% z^?~&2apxB{VpRhD6pDS}j6D(Z9}8LB3+YF3{gDV)jCG}T3V%r%n$!hD>47*u2@BKu z5u-gz#~w3gboGukSaD&j9lEGY5GZYSLd%%p8ljDvPS&;w99`Fz;txQtE2;EXi71al zbJ79rn^Q?^4M`I`>^CoPKRRRGDjJg|T23dQHeDqa-_d^@KHtvBLRCZz$*;6j$gIvL z@4LG_JjXF)$#V#Wg?Y|Qx5R!D+22S&^{a{LYd+gaz@lVyxI_{I?Gg`is_h&8u%0J@ ztSSmi4+=%0sCmg#IB^8@p~iKlol_)6oy(}#(N&IR*|+U1?Kc)$*`^pK80bV==~DA~ zoT}9}w^etq@pO_yUR{<&4=jFSwh5VP25h<0afmHX z$D~XAV^4hywt4KxIwqKLy+5KyaX4I`1B1~@yP%1zqUiHSZ!CXX4eL3qyDzcS`J@u%>PL9y!7k3+^Ir0BKFl~>mJ7v#Tx0Gq$* za-}^};G*^6(+^oIwQTRw#p>W8LH?^Zppx2HWbgWF)0JD=$?*mV#^1 zDnLg+h%O){=?;_QKQ7Gst`*4iw_Hj(MMvOrF>Sg7ICsoNyiUh zOr5t^mECbkU!P68-K75^LI2;gHam$a$N+K~uJ7^q_g>j}xg~~MR=m{h z;{Ez~0!Q5?$ph{AE=H8weAQ=wQbC&*`)`7W9BzN<}Nler=Xvf&la z+Dct7dpkP{*9&@WWphhNX>)Qyf#nE&EPNK#8-)<9fawZcqC?N^l=2XdtE>IE8;v>Q zZQ}AjY~0O?rK_Bsnu;XVT1`N~OAhxB;D`V_W^)$F_{30CWxCaE60(kgtT8;eJ9`~v z28@D4;8E$qRJfH@DevGsKxpYaQ;Pq4SXRE3mKLT7!NsgwH>4kw@{XPEaZauTH`gQU=J{9L+Em}%>4tJC>W?aMKodPE8-v>9%Bw)aCC3N9I-Y5G9xjtM^bSC z-}_|2G~{}=e*Vv;n;87?c{NE*tpz<+YLdGe#m)$>BKl!dIq$*hD!Pu?N6$2WT1~f) z;zu8GOepD~ZgzOwoW~NeZ2bl&bjwT|T?qB^39ClUX){&FE)v~ZZm16r{?(;X z`jp7OcmB?36g%Iv}F3guuts$ zmkjqBazvc{CPgaewKqD%*o|@c=+v_@3B=5sHE-I?c96e#O_sLreP09q4VenqxlVqN zg&9_<39YRZe>W+PewHi*B_PkC$7$KoPOFkQcqA4sbN!qnTlf-L!xRPFF{g62 z@oltmhe-D*yX#tp(|_P(M8UWK87~`|uI)$=WU3xf*NSDwI*k^3)6^PWZ$8rb9A#+5 z&rHru_&fK;t_ab#JHL&9D^pqXY!RM>e@RTArzvbq~%}8bjBW|HrV=-I@F@)uN+AGa88tu58%U zdr^cgf@vky*V_~S1l6fP#Oa7i!FwoJ$bT1%!U}`kcR+&88tabJwk}2vMK?w`fFhkm$tbxL4q>u~b)4TXioRtAe63y!K!*%gpMwRjuy3 zlKc2)Sw+#tKJ?wy2KKPSFx=klJ-VwMa;pGxoFr1#u`Inf3&zk5nhWfi3VDduHtbME z#NH-aq*|-vpe0UF(KRzeHKb5rS@8HkT?d65&`WAL9$S3GZEH+B{LjodaGDPXIv|W} zG7AL)9buqkx~~0d5_j}TOm!8~vG*sTS=I zC&XoHnx?ivm-ez`I?%FDaAwGpQG-CE{VusSqoK|iTNyw(rRGvpwIxl<#nP1<9UX8@ zw~yUC9-F$DC|t7^x*d^mm+&@iOUomThbO7f|0ii^iYiMuWf#c>Gx ze9o958uvYg*reGTlRqHUEHkvKQP`la>AYrB7zgiw9J; z@b#JnH=6flk1y-9nhSq?S$n$k)GM9qpxsQzGEEF?A#yg+T!+n2XaC%S={`> z<)argy+&IiS><%LIo%;RNC(H%5Q}RDb3*UOD#G?oE7_($OGM4NmV(L-q3rPYS%qQN zSct~x;^!$+)_UYMi-l2m9BLsqJ|eoF`fn6-(=oYk6PY(+l5(vJg)+9B76eANotqg6 zZkME4!r;W)Xc(Iebdol5w&u_-6fQJaG+@Vwae2&qSglP#Aw1J{;+1fcToTZx zcUo(k`&6&ns1k|YK5dc=vofcBhAABy|E_n&(U=}JWeK6g{3U~mjoB7y5DsEiJ67K6%CQmNLUI+vhLrCC_9lc z1twtpzAN~@Dhs3l$g`D7y$!6^VwBaC17k)hYk4cbQsixUP>4*OH)VQ+7luwm(rpe= zTV+BubMA9sD%zo(@1s^zRx|P6m}r$DrC9Zk>B$8{UNtyNmj`UCIX8UqAf)6`#y^3D zNq7NZioY*Nj_mAl=KZ^Fqlf5@>Q^2fQfKkLz`3XP7t1m-WloIF;fN=74Tl|>^%*9y zJ+A@wB0P^m+YYw>akCDW|Jg$GtpLi=^ul5X9^}i~a(ZH%D~;B1=8Iz`3UlNZw$g?t;z?Oh|DiH1Hnyj5;(*9>0l+qA`STA6K5>ymIQg5s9vH z2Q180%IfWM?*)WF7X`xu$D#@XS{uDi+5#Tnhewk0BTaoXiEsN)ECY1za0*39QICoGa6UK^&ICx?mUYkED=%3j`hkgK;td73Q zN-`%L8Bv3NR68be7=d%PJ|urT#EGb*s22>k&f#}?OCSiTywz0~%QGg0awIgQEeRSY zd8E3a5R{P+HB`$LfGWj^?-SDD)INjtQ z1>9l={sPtcMgg8Rja?K^LKOKux)3EtFj3H>bzy`W?kQ@JM(yXk5}e{@_!0vh&<=ev zT=3FGLK9p&+)rJm`~|d5Vd)EawieoSw*RGxwzIBi2(}*=$?tbz@OsO-wP+)ZpCcg* zX4!i!SGbn>+5I;#I_Cvd#fo?v+UnB#1CU6fTarULKKrGL6Lhk(m2d23yr6e$*h6Vi zL7$R{aiearW*-d9{QS%xn$AfC)4Au!jITFlo6I7WQwYeiWeMq(Y@V6#ct$gR6}Dwf ziFgrhX@Ax-Wm4+oF+ms6ylutMr*!;_6fFHwozf{>vTov&u+lEXptSW?MQj_srSB1y zEH%~X6-XHkuO+QqujTKZxniK~{*QBuJ{A%_#tPnKB-q!SvNzsxskfCdg>LMw8CJn8 zJ57N$kBx?6Q;*kTr$Fh4)raX@_$TIhel-z-P?LrPG^3@eN5<2 zR;HW^ldR^gB^{&R6!luaMwYS196ahdRd2$4EQVoz=XUap2zjKugSNK~L>c`E6T+oKN7r?cRq? zUNUV>TA0=V^(<_EUv1!bGFsTir~9&g2pXP3ny=uybGEJkC9pm@yt^z&b70$e30y|Q z_L<@gfiXb&-nM`S_zX~%q>2(%<_uG9Lkq3yVHsdPk9@PE9C9|uGUPmH;x?#_J4-1- z=St}v7p~q77s_VMdDLjk$gN8^AmFW(j?iP)B8*#6SLnMU)X-S zcm9gCMQ9$q)Z4Hx&LkAa_Xr2pCmrH*TDKr?%VU~A@i1jZ`WGpEc&lj@@y@#5rr1a+ z>~T^L>P2Ca`Tf2hlE4U*)Iqbck*YH+cbeBJhHd>n6=rN=j1?1f1Ewr3cWR+vS8_zd zDqo^{ACDfMiDSrcg8q5|zn|4Ia;QIPk*n1(Y=bFcZW1St#GR{Jzi`yF^?@tm`5Wxb zCJaUF1&Nb06!U;O3qwM0Dxm8Q>{YlG`y21eb5J#_l*w=a%3acKN?c)nKT3HYQZUBq z2VhY%u-1zcf=GFcAui$ErCuf7acZa7a2a`*7PYmrZA1%}mW;k}P@1n-N>HwKC0}YF~@dZQic$%g)sv)uN#W$$)VSYyshnx&5MR^?;lwE{y(vG*$o_bQ^5 zMwv33F=+OOEB-tEkEEezHiajR1_gvHm2WJe-^8SM*3)~bT(W&~vcoppB4App%3}L? zPGGo2c9kICQJ6STaRY$}_DttKr>^>O#Zm?AWUe;nInG`pwbl7s+51+-s$6#SJdxl^ zGM{=+9b%rYR~fj#o&h469~a577?6CG05Y%8-2k&`i-AIb^65 zeB+Ly--_kQ8z~xBk)%G;#8VL4EulL?<^EImr|Y?OUPw)5jT%Yy`?A{4;g7d*h+=*W zb)|6nxZd;!Sm!+7{PiVxJpATA0Nvu;h%z5y-hv~Cha3!!Ifxx&vmS_9dU-0<2vxYO zqIBYXg?s|ws8#18JMRXwP8gR4-g%N(&swoCx|vf zw57N^etiDKAwF=ffFiH>y;8sIMA2Kqt zq3|AYoa)%08r#2rERz>5x@Y>;BQ&y&dk77ed@Gw*;eWA;I+zQHD;>`?dQCOQGm zm`mXP+f=h*(zgcZJIr<}dcb6QMBl=pt=6h}dBScI?T!wq#;{t8C!t>x#&5g-w9K!7 z!4GgB5SVM0rVlAEB}PWln8& zb3<|o)n(?)E0(NPF2v)e0?;QSg-FyJp6r|TRQsGINmoQA9Jc7fX68U zi}S=_;|=yIRt}m07mQLRmjtb`4>#U56Fw(QXZK&*2|g`7wI$zrjAkZ1!o|ST}Y)eerXX?6QlAGF--tgwut4Sr+E$WfFU@Dbjut!>nfCFL~6UNa}&z*I^R{s1-c1yJ)pA*i@g*?LSt{^)2qNqT@mr zW&mfVi$%k4=QI$S%xq>qi^6T54c9rTKdlcs!_UqXCIz1#NPq01Kj!Fpi6KAIhYbs( zqWx&myj!yv5hQ|OwvT2CXD>N(7(;8Ns=0B?>^XOlG}1KIj)Sw71{x~8%J(!a7(LL1 z8RM^pZ!DlR^J8x3Uh#G2yX8K#(#m}3Kq%6U!<=eOT0#EqgA}bCN(h*vIgYiNN1xHgfaJ{Tx zW{gbLy_W&Zs)BRWIxYDOKy-==TDny8ki2)M{ME;vTb5Sa-y zS;--}-pt!CV9ovjsK*ezq+3cSc}4XMAvLrhFVoXFh7Hg0xAxgd>GNIgkr*dPkzCGk zP}eGeng1PAzY7|K)um_O4r{nx_ow*whq{Z53u?UIu$}P;_jCwD>`(YncL{(jU>ztX zR9J}j#!<16w`2ud=>7m;PUu(I34BS}ZXZsG_IKQ^t&IkeP_)KnUCV%v_=k+}2n_hg zFS+FcMeaj9h4I=ZjmRx%%+5(+Rgc#zI*Fmk=}ZeKTIu4-2-Psb$5e#wGe}0&ZtT#E z56Lv`cS~gHw$Bm&zFC^01dM(Ll>iA6V)IV+07DF>=3ABsxo8NAKB4KCbl znKJDCg32A9N4yyxAf`!5e-*YNdet-2p==Rn}0SDD77I6B22TTFX<0 z(iIdZVgzTD#s-UD1Q~;9fzpOJy|oO}_w12#)`8m41FXI0AlFRY((YF*AB8V5spmd(%eaj;Rs{~jSWo|kxeE=Jz{qfT5CY=ucUmxF#Eh@Mj+bLWG5F+N;w_* znJ4f?cRaMPy>vIpWDP6qZNrGyWE$;}>FaTNn`3hW3#(HzVx8*5=!Caia~Q$cD>~lw zbd*-pyJ*ASviyGltRXOcuEy0JGV;TLK=3YFK@@wRm??4P+BXZQDQis3?%Ljg-md*N zPiYgY)Yfgh_lZ2dJ|o7d8+h2GNVZSP{Iw5AwyM5sXnw*1)Z_>P^DJQ|D9r;I-V=&Y zpOp055X%>wLWc$0gVJj0$8F~_nHt-v+ChMNH zDQmt}zYN{UvyLcGj%7XnQ8n4|jDafps$8Q0)rJw_)llb@+MOMbbE{>alxuhK$L#m4Oi41bgBsaM{q%#859(!#Y9 z?5+ahsN&Jl8R|KQ7hRqvduV;9w#@xy=OZ89i2|PcW%jGZDD+)Ud3UlQ*Z)E^!`Syr z`=_h_BQ2Vg&t7QZs8;HbKSKrd46((K)y_^sbvHDOpMru)QXbi#aXJG5n6oqHnq*ye_(qK%t&TGHKc?g>k;k}$qmAe)X6*C z(>b;5cvD(xjE-LsiLS?~gyN-v&xrd?E}p<=YiKB)#Y9bc_yh~N(w^CAY>ET^x(;Pk z%8IUnLN`VGfC~hHHwuZlNBx+e;lCdM@;9-=^DXs@#0`RYGq*OvHSe$91rM=~4cOml z&Q@V!_{aRXsgWUc_1?NNCy1eVI47VB>xRfoPFM`E^ZTvkm^pSWM*y9xGIX%^eA;YE~A*pb-{orqp@(I`&~Vy zQUsObvNPbj@a4k{U)N+7*4WgeA(XSjN4jD%!gZ>KYQGxfkki!=Qj{^ERS;bNfY%A} zgH$fct6N^Vc9kmvw*2j zz2uYvIS**9BDmC}F#s{X`}{@l#Oy8~Esgmu6cRvbLHk0jRJKSMwX*p3G|j7}AzJbY z%!BIOPU&j6+kfbN97~G>s=YznhgwPU5*qu*75@KMo9)a0qT z6SL*pNLZL8p}ZfGe03<7cun=!B0lmJ3{LJQPU;`ox}rUxSk9OKS@^}%&00&Aba|_Z zWy4TiT2;BDCYAiCqWBb|011q&Of>DXs&E?9!fdgEA5Ou*d7rwDg!pvIL82P>1G=ry zZ^5yX(qc>w(ZkhqDwyXkP|<)|U>WjaKr}%W+2KHJR6S5>Z_` z@no2{R0K&fkvPQ$hTJQVp!RTtb`X1d;#t)r zg6?{fwwvl!PnL2fL%mC!!vIqKKx4XPe&3~#t$ois9G4|C>3GO!sdPm=@dccz{NBw- zt5!$rmgr=AVy@Rlm{DsFL3TIx=FZJ(pLj$1FKW&pDEkcRo1>cRYm zb&1=!=7ek_-6I5);Zmubd1K@Xf%&_7VP>~`$5dBmZD#E5t7mRZV{Gs`R{uqUzdk-B zL9G`e;wBRA=>@rC=ODK9MT`UL5|X7g=V2Ql*l;~TI}d?V``>3_hE)@%m8MGd>VIe< z2Oeac7naI9T zoemD>wQvqy!viZFkYnB{h6VjkoBN*k^; zrF1ZJKyI9E9V^^hbg+1j(A5WfeXqT#;7_y)$8y zE1s{{o0_MaT%HjD3rS^5LY`Hx@XuOD9O?|7-~g=C!uKQ7!aTT&xzPazP_`6ULOf4^)e#+8jk9!eh0|a}C}{{pXuaj5{MF(W$NCMYeFrubiqB-3$_h;N zswhxa*SKZLbAHky-{#Y{p-M+~rqd?39;%x*t;3Rt<4sHE|20)5+FM}KvYfQ(ctfA9 zI_co9$QZ1c*R@Twb{m^q2RDTA6tafS$5%YIFQq7kZ_`804P}3?Y!87=#DL`R=TamR z&$7>zx;|Lcd8tX!)rpDQr4f=;1?;~QT&QCv+K%IIA+MwD>>`JT%s1Z(BhHb|2Z%zo zwxuG@CHE!mND=qAZ^KkPq!0S#Q`FSKDEEcGau;c4;L4c0I?i!?a)ZJ$QxvSanG~bd3=ztsJU1^|K>LRlUfRY{#u5Y8up0`iC5=|B?YZdgR0Al2&Gx z8cCzelIdg66RuH&|3;T*%+jAy=RlJeh-vXSD3Hx`k$k~p9AHq3Zct5U;68Yl0CT}# z%Nc*7vT$#i|qk8HN{B|@C`lHP+PYy4iv7xGveUsAy*5KzodMjLu|NoCK+cA6|lPf>QUj> z1i&()FF*mzYP_40_QDgP}HwN%O|t zo)Bt_STYnc@E(ay#BzWr!Yz)8pJPQ}sj!W?s{SXZ0jF9MyteSjps%s5oR>`fqj60Sq`)fpI;xzy8Dzv0<3 z;IQq~Rp*zDr$zJEgjp7c|3uxRAKE-cQ)XL%>yGx!!tTgXP~6p)3SQgSl`u3Wx8Uio zCa5;}xT8)wnR?vQUSv{0v5H--eYA;Jq>-^+4GyfGrvw4V_}q5( zUaz<&LvJ))W4i0gOocHOHT1L;Cvwm-_zsK_kt=2m5-}<&)?dBnn0OlJg*xbuTU2=n zEl@D4t6%VT1aybpd)mf%_ys>qnm{cLdiI-ER{i1hg!@`1gW-W`SL>N3t|RSGC{zm1 z=6sCrXVI!o&CggLq&}WdhCQ&x5^*p;MN>ErMcs5+C)Tj`9VTI@84lu|RfO zp^Q?XK}P5EFb6hEoqv}-*4Tm`7Wq9q2o5zL#{)xq)FBoK7shd1=;>Zq%re7XwY38C zJRu}eTc>pu?o5mktGp9B6ryLSviqPjql&g6n9Trjj?9qt^ydrGK=(cAe4$tq5G{?q zWe~P{ho1Zm#~V@5Ngms6a&gGDO0;YDf<7$pt+7M}Ilqu{$`4e}fpOXzd5EW?%v`uOifa$2zS7v00mW>|1D#A;19N z`rGosQ9%YB(fM0ewFu$r2SCE#aFZU`>ZA`_qgVa)b9{ zcbzH=n9F1qt3QXgP}f1m=qJP~+ViIPo1vi6>@Ei&(8URi6n?>pL`5{+6JIUCcAIIi zbw$%cw9ClGdr%E$CuX4O{YfQr_IUxzakXR&TSWs;L$o#aHLz}a54G047aV^49a9=? zliInLIud4B@R4TKP}T1<*>8w!C}E5Y9pWIoEp5q0=-Qh@j$#cu!tk%q!Hhs-JGa$F z1RZXpdzUo_POe((P+9m;%VPQNEbMq>p~OAbK;25)z^M2fHS)17bTvKJjii<3nKX?I z7gwU>cIY{|HLH#Q648`eyZDKd{5#LFXU!#BxlnfaF!g{FEDjBkbko5YLHK2eC(cw@ zxSg_r>aE`CGL@9^s;26mr~NfDh|M_9eM--Q)w5dR0dj}0(%R-38!JGk;rYG-8YnNv zBPvW<;>D>~0*DLybA#iJI##BEn!*ojibzhI5PjaqsG!X=d>z#-^-l_f+by$%4)`Ek z_bx-y(Z7c}2~5%FM)~!^2@Voa&E`~mrKyddpL$(J{QGg9*95U@*)zSUQDt@HmqH2E zYo4i$?Gmy1lY zMatT-{!s7Vq}ai)Nhkoz8rbH*9nJchg9~`R@7c7Dj1&UmUjT9~3Qb&ue!GmD6?EGF z6LKMq-m@FCF&#zuV#8OMGvXcihXbx<^usNZ@w}`1HhO)*!^;a-K+0;ZSHf4HXg1#g z&H96A*cP7y5VMKiq(d|txTUV8x_%&=M?kxy`zx))y(IM_eGN~W*BOPS-SB|I_EkZ; z#A7*)?unDcs(E;dV1d}~^&OZk`LiF1dnOfIgO+dA)~Eu1CVH&0)I!B*_vSSg*Mh$K zxCc19Co9Sr13C>w!IB!?uvb90o2X_yW)D6|k>7D`_)Ra9fQXgObuq>#-!J|}^A+IK zB}@?Xb#oRoTvw@0l}{LD3Y0T#TKP+Qydcz_}*VF`16v8J$AWp-z|PKjP4| z7yDwFm(DCx4J2OYIjGs=ECP3r!-4h87MTjNe34YqUh{(M?T3n^<1`32N?$WVy%N|e z({MSJ1!`Cq6j;;3qu?Ok#pjO_{>6ve5sqhgHO))=mj|zYV5;VoExE-pdmxqbUa!Qd z+M}OMLA#n%2@=NU?)GDT*KAOO^8tK4K>M8tu;n8KMbC%O5Vs>Iw?-7!|Da zW)kHFJOE5^xnQXb%r_1!C6gpYs*16u(=7W8|l3 z`#WB6*RJMAcW`PSaCv4Sb-8oI>5agb6WpzMyi79F<^tp#x#f>zhQE+CbG`Q|)f*ov z#HFeUcq`XeJwW8TJ*8$0sOpt<&O2o$7_&yRaje)P7<$C9%}CW^D#Gg*h32B10okHk zm!6-P8JPIzh-u-dRXp^S_E`Af38vLr>ltO$MKt1AJ|4VZ24#Ooe!r0|jXwc16(@G$ z##7v<0|o+Psr-UreBk<*#_e`jjwKgAo}#E_dWSp)@ zr=<{677I9pd0cw)iIw_*eZ50&peVS$ph^RL&sJs<-2*YTEuuSHW@OV6|C3Wg&L&sG@4kzvN(d z1LYmzaC+tS3>OQ=n~Nci4=v<+meFmlaFv$FE8Gykb_KiOAa0vQ77mLPPYIoE3?XwnYH{tGo$kc zqP2(>SJWCwd_`#sYc0+bJWM-7ad;O7x1@$+Qqj-0ClSEkeKOPA9)O1W#eO7dgtME5pGZ`m0n!RD*&djr%Wl;8kM#*J51}usueuG9-tKfH_9@a4oG{hxY-NI1_f26pw3S+FocJ%=V z9LCx*IA%SPSHv6g#Pnv!$r`;&(_-Li{L9$!ofpXmAqqXYl^hr9_4$C0DxWn(wVblA zzF>q<%5f=9G#&^!k>iT#wp*>wa?z*A7P~ED)U~e3JUGP!8j8!`;Ep#Up$RJi>!f8E zXsq(D6Oa7F7dc%LhTbfDj9x=NX@wJJk3B{QCf8=FX%$t~@%bgVELSt0W)Y7EcQ(IZ z)l3S{s^IYa%QeXmKCv*=LFy5G#95}&dbUNHoQ|p%qGM9GQ2j}yp;l!X4(%E3sP<8s)}Pl zrU~JDfUP{T{COgupg!&j7-8+{x}s6ymj;4SJFNL-xUkl?^&0En1JNw|+~xemcai1K z#3nfhhi}wEWWy^07Jg+`^Hu19xOVVvqfwn8sG(Aks(4QlruJVAWMY}kdF~kPwE3xd z_)9+!P4&2kMqXuI19jX!@f=*Gxzy4eky5GC6KYt?OfdI}#KZa^^F)na_?l{Ere3Yp z^T777`)_$Vftlr{FzSD$}Ro0dx>B(wy@pTPF=&( za4Lq3-FGN!#vU(4{$odz8)6!p5clkADX>-ZiG+7EMHykVZjuTRlQ0Y0rGi?*OgE!8fI}uURMPQaDNz5J2~$47nlVf8Gxjww(32pLB`j| zk1~{!3v8MCa~9u6!v?+2jS8x7h5PbzU7_KbxhA}pQ!E9+m1R#q9X$~BBGW^nRGEcf zaVhn=G}Oz@76&i(T1#>ggSx(H7?+VXOzs~F#N`kkgZ;RU%J>AO`^4ojXPpF2V=%pf z7%~yw8<+HG_D;!>N-l`Aneta+%4`PK0Yw^OviD63qi* z?f(F>m?Lz5;VQB;>%_l=%)Xf+CWKq}*S5 zevE$NMlwDMIsVNm6!09-?N$n-M13khs7kit@v)Zsg){L6kBOW!sL2PT45E^OYl}I8 zAuQEqRjbY~8-V_ju>Sz!Xqe_Gw_L(kL069wpN|d2qG#>EU*fTZBI{c3#drJkZFcvEnWNELS01s%H=wkd|#H;cA0qM2Cr52 z)GbDg5~IX-SLA|>u5Ks1Fl{=O&O|Gy$nSn~S189eA+m>2cLw)k1-E!^VKO{;A<%n{ z8RacJVKhxTlpWS}7v?@0yeTnzi&g^s)0VwUL3`NuI(^G|gGEBa4!1y51&R+2B9{*q z=wr&6j&B2ycb4L8UFh9?V3+GOrQ|%*++l5$?JVKqC1EcghAqTedLR9nY^+pz*?-7% z)+)7T8ut%)Pe(D9>2y4W3mD!pm};eYxIW#8TYG{G)|ABz*US%P#T)xRGxkb!_9rL? zl2%&p{{TLzlHSRc1@{shy731bL~BwQ7mUnp^c@|zZB%XT<*zFi@pYqm)Ch&R=a?qS ztX(G(ongga?o;_Gje}3lqg2RX!Qc#kzfvtJRx)H@2}Kd7~WX5go<> z=-;c{w2G?7?Y%-*9IL~u7?~1+m7=ghwDA$3%B z9bCrsM59*6*An=2#y@I@R?dK<2HN6XFn81O>K?kSm^>HU5e=EK!-Q zFw;LSyvLhWn&rEeW2~sl1NA7z8|7Hn;!>Lhc^DsE%<4SW;$R9Yy+L;I*KDu5+V1PQ zbb+IYFjGx$#2RVqP^}AUe>~1Uwf!RyR&8Lyw(6xBt`+-^XHv!(^|-8ml^qbkWnP(p zEki}!6#iLDHkMw5ls1g1+(a_&xtnyY=0GcS*ygy@19Gjt#I~11(SnY6>E<8&T&Doy zbhTH4^#|E#m^VWwmJ3V~Xko)$v4kjD;k<$BP&Fk#I`q!v`+3jsoV?2?JeU{Ow=HN3 zvuZ2#Fy&FhcBK46ZW7u&rUhYzF`r=XQm@nZ+c@HCIOYa5K`M>vh-{i84DBxcH;c{gMgZUo2$f_ zAU1m%;^Fx2-9A}+UqZL;9kuj-Y9!b8CHYFS(sZm^8ea0OTmlt%cJQVCB{TfQr;36`wI@U>B9BG}_P; zRsg%UTXb^s7Ab_Yj(-)nCzb={w!iGy8Cp55R68oxUt_#C(bwHfXH#}8<1u~Hvw^&P$MU6NV@X~mKPbhF zJr{_3*06THLavJ~oqPHmLr!!UMg!o=jL~T8!7VR19al4XnUFj{v2mQnIYeYIF5Bl( z4{<0sW@{X=y-vKvbdGP_HRSK14aHF7n&wpQjZZN7)dJhxNjSQP9CDlVPFgs>EfLQ| zu&m0jFLP^}Cm`48OSC$&w`ah>xS$RD&&(DLPt9gB#t7odbK&I)&*1HPa^klg^uS&% z4A;1wqTa~0pEBC#RaQ77DVPU#6$FYGsQCDB%h^FizOSjW`n{1~(vL#eJN>+3Q>YbZ zbv144?>v)@3$b)#k5zK6U@erq*QakRcI`Md=NO368$vJvQI|kBRsLwU1G-c`hVshi zh!P+DmQgio0&RbjFD5{Kpio^M`G&=pUnd9dQP(Oy9FlZ-aV`w6Er$;Qsn5$N8p^T0#)5>m-Id;@Ih+N8!cu0_{F9#2%QAdB>W}Dgd|V4k5SrgC2)oNJ_sFuHtxCE$Eeae=(dZ zO?V8qb9aex_opTL@hx$OBU8(&IgoK02b(cD`;?!@0oE&k$g)1&pym zb61N6{Y~tNBY+M*jWl`8wBFjP_r?Nee^rwEFt51Y92L+xzEh3d6`WP-8S|Gb!*$es zv-9mZm_pA_R|`ZkyC}=^D2{-47B}FCDzZ){ZSw9ns#T<|8Uzb!8?8pxyu*;N!QEp% zBP}X%UG6(eMXI}7WhdzsM4HaSnz=ozxOod9YdW=t<>z(NwauV&dqgakE!0gmKE2U3 zfIxip1lz>*!cII6=iKt#){Fy)+GRs^vGPbIXLaG z29NE`?K{O`W%rg_(G;Nh=kXL89>j2cuxXvmVc^z1B@yW(n&OId;76=%mA;5;PpH~z z#=`s^MgTw!7_)m{q7$&NI;UQ4HJU4fTQP~119oWl)U0leivTeEw=h;9UPYVscMr8~ zX$_CVxn1XHOD&bHCeB@CcNHuHajG&Oaow;AY`w(|z0X!YqGruK_bEFlsM8!ViKWh8 zsJaliY&IhC4a+=M`Py>f+o(oi(`~$6N)xNJxoy?H)T&zZ5DDJpyI3}OQ?=hW{EGeB z(_Y{@4t+ZR0EAx|)7f>7*l5_wvW^|sEDL2)(mxjxhP+nLyz%{uHI2J%+2%Xr=dSw4 zaQQ388MD&nN}-y*mbUavxz~4WG<9(M_YGs~{{SR5+uot;{fp0L%YGtY*=t$@w6EL? z?v%K~RY5SeO<#YAO}1oitYgxvX||LtSBX`euWmMWnOd?trl8rJ^<1*WI7;iS0?jz7 zU&CPCxVZUMbU5XXy4rOKmibeMbohVq9-tx09zb&|+h)T}lD;LEN5pitf-B1Scz_NS zC;tFVMhgfhS?;=(C^XB!IQ1w;T4~GVhDVWf#ok4@){0xKGX)D2T}+d0oUYff*O)y^ zR5);Y^D24;OW=9)DK@ckPcqXz+DFF`+B~bM$Kox&ua6^Kc!GYk_?A0bZZH$sC~!9X z;_I`R^Jo5d&PPP-JNuM!+iq_Y&r_!DUPzR4)j6=EUlQl@jgz_`)NmquFpTqQP%LQU zXSs%-pO8CtTz1(F74mdu4$AcDYm)fh>N%99FGpA9gIxnHCe!K*sRr0=ZCaxw;aUFx zz^AApfWp}DA5$Bkx@5mFabCX+_OQXG!C&f9(v&pU6TO?mZG>acU9wW;W~h~}66m+o zI;sG4GZdS*oYMFuQLv$mtwsyAY+UY`8gYx$dpY)aiZsGH{i`MFqc=NyH$JC)6#`5cUK|HJ?v5dZ=M0RaI30|5X400000 z009C35D^0)5(E=46#v=)2mt~C0RjNLtm5z40Y?PuzuK65npmNdVM3n-AIs<6NfCW0 zDf<+w{_dR?IrftbS*m;+Jk%`O%*y;=?n;$%`b;&x7AZEZzKy8%3!2h*>0Fvo@o3-7 z(!f7~Fp_*MCM*5ujapq_b}y3@D2wEsC209t$(8g^?0$;>0AFhAcF2fV(S$=r8e36{ zG8kbc%-Os4oj+nam6+qFP9KrJkAilTMT<&Vi(HcJ6VLE;46NMJYNPM;Io-p{Dk{? zz+1x)@KX#hlS?qs^NK5#D1K*xas5A$2o$H=2DC0dVrW{+lWt$zK3feysYua zEeNEas9~F~d?>Y@2`g(9xc(~;Lq3tW>-K)aW0HI?o{y;(EOaw`W@J#IDa(*UId>v2 zPrdNIIxD6-a~6fe9ieqS=k|_o4XCH zRhno~hL4T4qc)LQkj~u5r7>ojP^$^jV$q`yol%h;yeoYvQ=NI`>@yKzB>322hQ5r? zjL!yc#41CTbft?k;W-y4$+HYTau|Ptlw%RyACJZ;P9}){X0XKsLpNg!G^dM|qtEq# zY{TPd(Xt42O*UF5?oA}+ni~i5pVLPyxwVCiiqgj))sr#j$P}a^|&kmo25qg*UQ}kMmtod1x z!$yl2C`aqh8lZ{E1nzfXCcgxL$rFNkcb8^g3MGL8B13q3*m#_+2e;e;hUZ8rV*QtE^~Gt`>eWoZ0- zBNP>aT~J4-_`!Gmdr)XR5rf@3vfEaLp&I>#3zj==qS}AdAM!8UAC7x+wO0W`UsSW( zm3pc8t-BhYp-+(dL7vv&v21Ff{o>~CmbVy6DLGGw>N!Cnw?EU|+il#Vyh>^bDCAoG zJZsHu5*`P4C2`%!w_=y)GV!ga$Y{giTVmbIyI}WXUkub?SiAkxwl^_j3k@0&?Ds() znq9zRmgQS#%Tl2R2=I!ROK>jc+_x0E7Yg0COK@Gcx-osYD;k#NS#(=Ur3koQ__1J? zMw&22?I=-tqj5?RRd=lzsHGSL=XAGYMd7v!X>{%?2}K>n$Y?8W#+N)2z}cEECEBAx zZOcoyU4^Uv!~h==00II70RaI40RR91000000RjLJ0}&D+1TYg6|Jncu0RsU60s!}u z7{Fqcikd{{QDO99D*K95tD~pO{!EzR@@SPRgnvKgsA3OqH)IbBN^xe!GvLUF#`>9J z;ZQ*uAx5=1qeOBnj(uS`$$YL&Z^Bl@=7<8fxwuEM;buIU{)d0TMX#P=8qtY0t~l1T zd@NbFe|)GgQkbUqH(V%OX@mIx0274cDg5l5oR<{w!X|U*B~Mw!@MTk>J7ri?S5DQ!oC zYe*3mciEid$-Ca@!!nq&PMoT!Ds6oq2X zsrR*u=YDa_aYW~yNY;!nO!(Ymb#h$vkz$3JG7}UCw0#;g0ml})qXj)EnKALFrA3NV z%#X^_itEXmd4gh!RL*H)jP!t^#&ML+R8flm0My`%7BApp&19b}Sf^`5(#-K`Axi+pFlL1@Wm7+`U2r!o}b2^Em zYZhzpFolaxHio%T7Kr5>yE~kj7R`vUZD(7)p;cOVXT_2%(v4`t3GC!9nM%d*LxL!mGgN6}(%QxFN0*v#XUX&-Uj&zi67T4Uw7$BXA0%=?3Vtl& z$S}f5cbf8Pd}(WS&pi0$!>LSf^Jk+B$mQQKn)k}m#fuiV7xCr7$4nXV{eRfz$(JY6 zzlz%VSYW06@+B$%07Cr*)-5ltZEa@My9!$|eW+pQYAa&7@wK(3p%!gSGi&K%sqjuT znjuq_{MK)*VwNc;Eo{X*Q>?goR8;;cWcmvfTzvD-qh!UK zi%JwJQ~ee9YY9^_KJbz|SL%saz71{|vwOYQd*1rm!gi3%hAC2MEZ0kWIXAIJA*J%9 zl|7?~@nD8JQo|Ij?v~yY)Kd&nrf<-kCNoPrF7INJjCiGMqDiH$jM^2i|HJ?%5dZ=K z0|WvA1Oos70000000I&KA}~M$5Fk)8LQ)eWF>xVLk^kBN2mu2D13v)eOlha4D+hs3 zmL`s;tkYsR#Xw^rVz_}O+ZY+sB~gQzh{Y*a9Z%XZ1CQxs)ZSI3)0#`TO@MR8+g zH%`%b3>ys>8L(Cf4F3Qk;MIn^v|rR-nQ15_GwG7yRfay$N-WVKsIaQSrV~OR7~Wsa zihEMox?zoiybA=O4~!_*2ZdY*QYml}gMiSQEt1(rHwK05F>lp)MhRN=%pw|g{qDud zXSMpRQ^ATZ)bnDEeykc-i40+kDltl{{{VK~T$s6#!6B;u0N%F6#YZ7^hRJhkauzVR zP}*z#USi7ndYwyBU!I_bsr5R7(yu6O!ENf=o2^#`=vHcZM)KLQhU$*$T{`p)#+bOv8vR>SmH*kGD;jM8 diff --git a/website/images/photos/anne-krechmer.jpg b/website/images/photos/anne-krechmer.jpg deleted file mode 100644 index 1333ec4774b3f79a32627c29833097d18582e632..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22606 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�>sB)xL7xW@(%9GH7L=j1>k#p{i&vrLP z=(Tz;rU~@$PieNnJ(ed&8(^K8ZgEDUsI){KZN{fh<_v6$U2c@@#^@GMdB?VMA;R7 zqYrFY*Sa1(W?DXnx%EbttWrikx}NNAeVEymjk0&OvC)C)k@XuycG|K?)N!{ujbv4a POQz``DtM4MsdxX``Vqul diff --git a/website/images/photos/anton-popov.jpg b/website/images/photos/anton-popov.jpg deleted file mode 100644 index 86fefbba27a779feac7ae113ff6b3582a2ccde0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92054 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 z 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 KbZ@Twx&PT+UCj#s diff --git a/website/images/photos/arno-van-driel.jpg b/website/images/photos/arno-van-driel.jpg deleted file mode 100644 index 878ab2c336d8a33d4db662d82d0d4a0abe3c1ff2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18278 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 PYTNxGxk#H9hROfg6%H** diff --git a/website/images/photos/artur-filatenkov.jpg b/website/images/photos/artur-filatenkov.jpg deleted file mode 100644 index 4fdc7758268197d0f2479406f62863bd507ccacf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12541 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 z 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 diff --git a/website/images/photos/bastian-spanneberg.jpg b/website/images/photos/bastian-spanneberg.jpg deleted file mode 100644 index 95c9e5b73ac263cf99bc095a965d4bfadfb89bb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40832 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 q>X#Z^AM2#j06XVhJb+n0S^oR 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 diff --git a/website/images/photos/caryn-marooney.jpg b/website/images/photos/caryn-marooney.jpg deleted file mode 100644 index fa0dd29ad1f22eb54be9bc100150ed56e5865f6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27016 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�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 AZvX%Q diff --git a/website/images/photos/christoph-wurm.jpg b/website/images/photos/christoph-wurm.jpg deleted file mode 100644 index 8a9adac2fb0ce2b45b3d3217bf5aa7594202b65d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20591 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 Z000000001>|Jncu0RsU6KLAJ{|Jj0_uO$Eg diff --git a/website/images/photos/claire-lucas.jpg b/website/images/photos/claire-lucas.jpg deleted file mode 100644 index 4ee456304965ed5fe23b850ba66faa822da05641..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22886 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�Sn*GIyIYw1o)&_Mv#nd=690qYFhwi4Xan}MK z&4uwW<~p0EXSReHmQbT4QvH@v)*)v-WgvVdK2S0O{u=Gq#}cC*tb9eec2#Iym4k!} zXUNm_$C340oRlWs>$=H|Qm-oJEcU1iT2eC`3=gS(r8mGBgRyB}jZ9#zP5&2+jE@6RRb^s&iE%=O21?f_Qumg@_|F%-oUUyyB29sA7q2KBOCW$v6Jg8G+HA;mJe z0+HZ@8XjU2lBd{Sy319o$&7etp9iovvAi60MOY@qWy(3Q*Q30ay-&2zDFP-;Rm}1> zrc8uda4|zct&XAS`)NFmhEv8%gYyGSU!Ykcl) zKhwvHDpi(FzX7h`R>-fPDb^WI4Gc>Lh);C-lzVAU_Q|d|Q0AUW!11!&fLnZ*3Z92f zBRbRepoDzf9>1JH!5Q2G_X=ZO(X?;6g-%%9DMV=DPg0}6cX&DEci{HT1GDq)x&W%`%4YE*p zI!m6;MG6l3vrGm#uC3=UscovEo&Zd7Gi!2(Qb}_4@X>i@Rk?Ice>{8c+mAz?p_tNA z#U&IXTJeYGN#=I4w5Sdy>xzr%xeO)deQHKq?O2g-fE31%VMQwXS~H2Ks4lS7*u>8y zl5Gw;%EVwb`ZNyo<(pd1kYoPIfi*W-CxKlLUsJc#Pv0-TWm$fUZ&GX$I?mIVS`-P_ zN$1mBokp~U!nZ&%U6Zut9EMyHtL4~ZAwbWI0kbtM=LmKIuH7MpFjJcB~%GD*G-VQ4#SdMIjiyo zCmtFdfi04wPtaSvk!3#>G2@eQttoL*%aT5Zo?z)-LTw2#&S(ri+NlbG3yRC_=F&g8ts3H3xCD?vr>21}lW zHFdjLl57%VB8^7dfKDwpWPq2Sq!>t)!wfni&06RkCpP&MonzvoXx?=4f{ z_YCTPjZ*(KQK6DB3!#IR{!N@fKp+SZ&~4YWnbbV9S^6Rf`qN?rK;>1r%vC|9h(v+P zGti7p%SV^(O_m{#MuljRFohf`Q=&_`25*2+yk!_+KH6=^CKbW)sCci&HI3{{ghjb= zv2;YJ+I2Wuh1U6}2fiRxb5=Jto5)#!q)6rt{~$B6$WaZ+*+t13r7RNoW581@-U^|$$uIp2XCAAw6&IyXBpNF#i%XhYx;1a^>h%GaGAOlaoMS{iPD+I<6$~;p4fq@~ zDh4$siwi946@yWgblxHD{=l*uO*5p`g^GihFE9bj?3DN%F zHaIi@nUWEykRy8F|1~dI5U?E=1@|28z!ljq>JAf5Mu7ZfUvyqb2q9M(%F2SY6O+7O z@O>^~wD(;59WMU^%X{LDhDriPXBJYz_;(pWE?XbP!!Zkc-f7#`?zP=um$YtCImwJ+TfBp}3Pa?bIFXdU7Vwl71+3%Ii=jbXAQP@h%F(i6v;=!Pd>{Zm>M#`Rs2{%;=QFMb~{Bg zX!C$9y=$E)9@>n1xtALG!pghwIY>$buH?otb=@VwF)_it}+FD8Ibj+{3)c3x<#EZ_SXmtAyzft5h9pk5dny09^8=aYjSG0VER z$jnWOD?@G&yK3-v*Y<^#$5Pm0#*SWhsorM29mnJ3?Ck7RD`V1S*WBXCXLZEF271@S z!*inr+NIOHkQ6>n>$1HMcVZI0y}b?EWk;xjF^*fA6 z-ZvNk%=@nMUqT}VqP|1O2tb1F_#ae)f$ylir!o29$%a)-D7-hGGF$csCZt0j3zpNC mstm6^<*Qa&eqT&$(`8>IudID diff --git a/website/images/photos/dmitry-novik.jpg b/website/images/photos/dmitry-novik.jpg deleted file mode 100644 index d688407e5e4ac96c33624d37c0319af5f6c8baee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25601 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 diff --git a/website/images/photos/dorota-szeremeta.jpg b/website/images/photos/dorota-szeremeta.jpg deleted file mode 100644 index 1e7c0aa2471c80e9c6eb1a9131f4704ca4c7fcb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21198 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 X!51!NIPJXs>%?PC;6bnK-@^X@*#^If diff --git a/website/images/photos/geoffrey-genz.jpg b/website/images/photos/geoffrey-genz.jpg deleted file mode 100644 index 6d86aca47f7a8b7fc4a2cee98a237f678914128b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16233 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 v^|R{?>bMl9j0___GG}Z#<)l86{ diff --git a/website/images/photos/ilya-yatsishin.jpg b/website/images/photos/ilya-yatsishin.jpg deleted file mode 100644 index 23d10a86c7b3c45bc26860387e42a2cce8723da9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35219 zcmb@tbyOBz7dJfDMT2xmOLsR?0@B^m(v5V7B8VU$E!~~csYo|UH`3iD%{zGCdOz!3 z??2yK-x+4+H~Z|f&))m&Q^Q>MQ}>GihODHFBmjXx0BP_5?w63vB|NOm0YE_kU;qFB z5kP~W15gmbgJ&%S<2ObIaWe!9fC29i@Bko$0Nig309_EGzjz2j_b(490mA;f>;vBf zgyT2H0r4C}@UP56h!_C-j~plf5&sWEvHzm?=e-8i|&_7;B|CRys z2c!JRdi-y_U~6apV+$Bq0Fd)9N>uro^b!K_Wpm-7}+?Vvatcc|9uA_ZF>Nq zvjeEZ}1as5OWIORx=QKrjB`fq)kO!Bb$r`-8_o?mrj;`tl(@z#a%I21$%be|Z3a z>^BcJsDlSU{+A~Wp#07Apj#e5{f8eC3efz)pbfNt`O5&hzjO$|_*Z5v!2E}faS5>e z!JtoB|6(10=P&LDc>iKpK;SP10KvcbVWa_!9c- zY)BBq6Ji5_hr9&v0Bq2iE08_N5o8TA4;hDaL5d+CApsCN2s!9ZOwjGfz&7L%as}Ci ztb#K7AO(>35KRa-SQhPfqrd}OVD2?2VHcFq0Evb8Ldw7#6!1U7_3<0YEHz*$+%tLx84G=)7pg$oBKqpvx zK9B^2fDMBR9_XM3NMKOnK`FsQ3E~d~10LY0m-^eWkH8v`LG9qdd@3kC^f5Faq6nD> zegIv7Cs^*G?1OYf00&A2r3Ftos6J#D_zBDa1E2;E`49Ht0LV}xC7uO9R1y zU_zK6Vh}7yJvbOV0&YS7VM6i2d;r=G@rUSw?PCQA0ozF!k_}n`D)Bo(AA0IT`g8y1 z34lKO6D?!l5Dg0lkASFQZfj)Wtl?&4>uRR$Vr6IMY+__O>jGc#BjGa%ESyYT&lueYI zTa;al|8F%9wsCG{<3<(yzK8)m0i3d9DUpg^}9<=_aRp5WqL5#!&HtlbIxd(pK zzjQ&+YeNmo5*EXAe3&$oyRd#JvAJgJbw_I*b8$Hw91Xf9c@- z^S}=${hJPjX$MWf0`WtF0I=|#;1pW?_YN*le$#>bWhj@7xVWLRs*)|`!2hN5i3e=}V3hem zRv%$p0JT4c0$(Eb`0zGvz<+%IPlRj=MlYCi1D<9k4o)th2@keXy1O_$;P)WLaRQYB zamoX>_y>P}z{daJj^8}$s^TEe6o|=8ER8@JkP8qq8UGL6;(zeJ^@1z_%5y7c2U{ag zGG)$($Nztmiu{U91BI96V2V48W=l|4tqVn695WIo^ zXm7#ZCj|D!`=V>h#y2Jk0Q59C7&#gJmInh~q2}Pq`Zym9Qf&ah z{sETJbvEaGSf_w4dL#zM(Zc=x2@M!CF#vEId4GSMb$@@G4aO9>PWxtmKMROItkwTZ zP$&%aA;H4HKw;ou;o#t4VIRgNcsMu&cvx6?Byfm&NO15-$ViCD4-XGQ9t1p;0{;-f zQomXLKV9zI08B*4GsrU-2n7Jeguq}z?mGZd&Q1)+w9xgCYP#XvWA~*|x`NZH} z0q%dZ!EOloXB@qs0Up7C!vO{iI3z(%3up{EfB3}W2R-jn^KnS*u#l8VF|{CoK1WN!&T|rawJJY?#jww#9~d zjsq9HY+zbx%5v_mX?@h^xTwH4e7&LR@yp_wroFG72Q@6hMj-{x-h(uBi4}O)-$ka^ zgX?&Bq2ILMj^YW5)=|IBveyZ{_DUTe6q^*}?+Y*qOGo1ACHfF$`+i6-(6&QPEvuYz zccRgjAQN&DDIjo-BvymC|9KwKE#KL#dt;6{T)fEo3jcdGcUgIXe52VU-)X#WU35xo zU8X?S^+rTfnR@EhKwY@)*R)wdsc{_~-gmBh*GfVJhTViZsR?Q5QAmXi@1ig&s>FC) zLMP)n!aG8B^qhkNli9GZjw#Y#kM261y6_b?PQrMr59vR3sj2b&l@g&O(a%E~R%AG| zzO!W;v!Uj-W2jfQta_SvIL1(w7PH<@Ws~DYo4Dr-k1if)IyvC`4#a6QbZg(5;PUYxb1HvxVFEN_YD`=CR~1n8QuNl z1=o6@Ki4fY|Awk}SFFou;A@_b`J0`WHl|LwSH`Kba$4MHEz~-#!&<&1Ev7^7PkG;Z z6V9_l(xEnm{XF;@mi%!w>>UAYx)x8k+4qCyN6%fTEnewi#1(dmtd^q{tVIj(VxIaF zZEU`(5$ML=LMwA{x51;@vrVxcaVtbZ87JtI+0f+%W#c_QWW zeNdo=OP9}#&w-7)G*6;0J`vAVJ4L+oarsqpGP=La*U2aWPrr|+`BYcsabG%!kG6wT zQzg-vc99ldW!+BJ^KIn(;&cyRCS7i0vEH=zQn|DcYT`Am;hUZMPA-%=vX1LyUVJ%{ z@ogn`3)47?{PLxqBsNT+FAl^B(=lTZavTGRjD21e!J~y#!sYu$>-$~}vH7Cj$hivdmzJccquA(Q-g6qd&Wcex=}XQebaOEDyTA-L}#C4xVY>43`#|Mm=8-; zUkEL}*NL$6lhE2c<@iJSGnhwCPU04PM?V!1iK%3w(J-V5)FYD{v7U*3j*AF#oQOOh z#&)z1cv0F}lzX+{x7Sn?vAykhSxHprDUz_TNfOnYyuYi9q$69j6YJ zU%qNF>mz-H@|Z+DVPfyOokhSc!Igf5eL_E;j&o-2&nHb|>=YkWWii;v(;N70KD#S$ za7x5J-;P(iUS-SrfTyhEXL&jAlab_$??sv~WUPiw2}S2JjY~z3Nh4k{&v*(6{Y>*q z#_s>ZH#W~pWS2zRt(RdZa3Q_d4Cov&kf%t?|X5uFdo4l^a5~g$8q%7G>62G4ZH^_dqSTcoOfS z8v;SshpH8fX`8X0>W)6i`u=Gfx9kWhCrc?x+RwKiE=y1SnyxGo5|wde*!>NhP#1T! zH(aw1P|pVH_@;S3`VuBG$qdGbS6sY}wIQkFwj5W~c1f@|C3;~NUZXQXApXPdvTc58 zEv-(v(4Iw?ccKw)j-o~{6+qVMlbl( zn{susI8KgbMxc~c0I5e%U}9n_taDbfX^DvYTr1&a(XX3GijEZZjV=`zWADs>719GRcGZnM{{Fi&EBfLOzZQDLN@4l}qpKhkLu^qq+E z%@>-{r0<$=&7OEgZVUMR=&ldD{52&$B!s(06Ne`ADcpOjS={YoyZPsVPu%$dD0wHH z969C$gn%b|ouCD4LlSi?Y!`m&hdP39aIr}2B^6}ZOand(9_`VS2{D+gaAIw>8bmfP zLPfPL%G2%vc9skgaACrP0j@(H(!=WI_u}PYe+R%}!efzRQ?Mg&h+^Pyis4dOjPrE!S?!4=M7N!F$a z&K`F*sgsucn zzh?H=UYW?AT={7Q`E7cZb7X{T1UT9TV&WwvlHlqf^Kkm-ln(T?TsD2|H818$8)R+H zo_t!O`Rf0}QBfMnMtzP-utC;T?R&RtZMs~UT>QH&ub>?C4IM|saTyV?^K0$72XvG- zRthB~?Byf##tWif#5kqRat$sCS#D#dHw^{uCa{XvYfZN*yNKITP%^rqgo>4hh^hUG zkno!?NROXmjP$*h&lSuaIjXVVQ;m?SqxzBKC>4a=80`+XMvw6JIaC2NHf+s6sczMQ z1C?16y5wRHQ^!vuVO`hnxHn1~akCTHon%iuLeB7*}3Dr@YD!%BVf$dA|lvQi!mnNC`d_huxb zIfw26jPJ8m7LFhG@scGo>UilJ!USr&ZQPg=4t!4(r{Xp2U0=0738yx$8nS7Aq`6le z7YB9Jtqh#tqo@k)AtTB0u9}~*vO%;d*(qNzNTlKVg{<%AKigngn8=xv!Dbv`b!l-HhfJv-hC8(-aw+ZM~9&9!-=1yss3I{h zN|bmnKN+Xs9+<~q*kJ* zWgbth`%U0YM$~hh;=Tw8?5L#0VJ$~@<^woI*3`?a4RYU06}1l(DLz!&m&t206s|ec zpC6fy;^Tx4(KD=`7+T}xh`&OtqgMEN+Fv3QxNA|wM5MwhCJ!l~qHnW{ALafcsN-MC zs$fvv6rz^vA-K^tVVT0(Bi!kPe4$qF?1q(Z)$O_S^3!ozor8n@_Sn#iF*>zZv-q20 z6xsD^3angxC;CsPB&d*WYLjJ3OL2r}4DDURn|Hk!K7{zeU5TjX4?bEbnZ#k+JEbwz zN1it4BgiR~jO&YA(zvs5l&uQlzXs8-Ph7}w{qiFT3Eon^Ee_!^g zowIyCb^M|Bvp(!@rHDB46jhB-B&uAB(AR|(x~}X_#h=s3Y;(1~FAlyf{sn*ZydA9c|xk;J#lZuVqu z0{*<&oXF@mZEDg|$d<~g&ar_dVU4Ay@vjNrIec^*cWT+GYpk=8?*8D~)E1Ud64NS3 zx72Cj-@RRqhO9b>d#IPSPWL5Ab*F%?JhmZ})hXYd=Az)pm2rr=(OwKy%9StdLdI%! zWKJfN9+jGNmP~KTw#7R{;b#_~c%}jzEn~snYhRfxG%JA z?{xS2gOO*WC6${u+~K~_HH9)}N#Hu=t_Eq|!Mtls`NTId$d9eQjWRLqm zS94H4QN44JF`Ez1VtyhiwJiu*!QMddQx5r0 zM)CTm=jRl9r8<2JIUQfbV{;1dV%fBM5So%SJQy8T8+x57-E7>Di)o^>=o$A>_*E~O zW|sA{7MNNb9h?^0wp|RX^&2nVf7f16uNJH7iz401TcyaaBV|`VYdovfd_G{j(HRu?3?O_-abRd`eyz-omp7V@|XIW=D zAtd`*+|*H91JV*Rj6V(@@z{Oh(Ii@svqCCl;G1)|f&bMloBQpHf?3@jA+1zaJFZZH z`0ISK9EPu|{9?5u(i$+|$F5(u#oOSMY4U5~zO4s}VmTG7qhZ|&G7$;QN?UI@k*aDV zx0UCnG%t(pr~?ZV-XqKTP(f7V9fU^h?7qJ;tR$%08l9{$srDx1!VRSbQhUTIWfgag zJX<%Oz^a_A769l?XTs?8!rH%Df>hsGKGIax8zrOu@HQu{0GGwB z%XP*%eH5iF)0*0Lxryc7p4F0bEQ(yCGGFNn*;xcS2;NAQ1tFwL7b0+F&hFFOr7ogo z3kc)!*~drXOma6h5#(IgF8OFhdaE4@-V}esiIVN2H@FnPnsdk5+HRn)%a(Xs{KU?B ztZ0iZRf}6?Nm`YgBvbik?VOv&S2wE$^Tye&&XHzAd-Pf3>WCw_t?O!2l(Nn(Z)iAEd5vnhF$rRqNqNb`b*Y1sXvO__d@c1z|e4HCLp5?*2 ztiamw8Dv_i2JPHu`gytyW`nJDRC+zGh3ijUAa?g2)8sv6I54MaqQ6hib7EXx` znVIPPQMo|*^Tpj0>x8?U%F2ulc=1g;;dF!X0+(yU5Ru3LdMP_Tc()~Sffn;mw^ps9 zOZ0*&BvV<8r(WGCM@tm*>S4L4^Fh z1Pom`e9#EaL&30^2;Z@qF}p%XKi zEIG87${DBUL2~sk8ysa-_Xb~;>)78`K|5QQK+Uv$;%_@lA~iux=S&eN&0)hvG|MN8B}w#v3w7F=``KN zjsBu4*fr2dO1<)yU`R{5mtnl|He4_SYr%7VHa=28Mp;2OR>Cq}dq3XMY#a|&l*B+a zBR8#ED4RHKTZ=16HiVY1gb-Svi$QNNPDnCV0^8`IC%9j{P&<0%RSgXm$U>EA=1*A{^0$hm$Ak;FQ92)+7WA2T zv&ZVhSPv(u{_YS-3U^(yb}Su5yW7wSwb_a_j*2WZ68kNuLw&$Uy270T6L^!yfe zhhc>m&6Y9cnLtw4ZL&re2UMvk6sJr$l-4tsSmQudyLwl5qr{y6xzUitom==SB`*mH zv$DvWoRi{oGBYm(ulohE+##yGr=#u{ePW#B_~pqJemb0}Bv~LUY}BfI)YnxYyO>iM z*+y}@X(1^t>sGmH+Z7TZsjfI)FJQT1?Zb#KjN?h0>R2{4%B;_#x{oE>E;=c@Mb{g* zP~vdlMENvn7#p=w-95Uxmz;k5asBx)g?OcA?l7D>A=f)gWKy2R8+$5TUrGjX7T^BZ zPl!2uRXR_`lXIgSSEUk0(@uK0kCqsgfHk0J(CWOE64FE z6?kluR=+b^SGyrOu9GK}M07k!e->wwk_#Wnrpl)>NKF%K!=1!fy6q;W5xIOmx*a>~ zHoYI4t6MyB52&KPX{}K&p(14@E+56-jz71;)_t&il(r&+NYJ^jnh6rK9NA2jb1q z<=8oRscef(6`H`k0FoRQ3-!D*i*fUrD$a{iG>`?Z-anO*m7FM&f2H}n? zMYjtjaHh!iG8nm}V21@wle7^LajqVL{}npq&u_Q;f&*)MU|-DP$n3yV2WjFG@Iug1wu6tGTRAy}XX@p$b zuF7)KqI1)e^IKAFX}cHntMDhXov7pW=$XCQF)vn&d^ zg<9Z=Q_tp_5utg|lSY%zxnWd#TR2CtzK_^baQ80yaEsEJN);IdA6xah$bUC~`ZDbE zv96xvtLV1xWB!>;OsEmIrMLmPXdVo+f)46>i;haM`orCH@*nl{TE9*4C7Cgeacz4W z%iG#E6!c73YtvHQ;O5>2DL#SwIX#S$^}aAPWHTZpceLYJH^y|DA%Rg-zKBY{M9yJj zpPRr#zaoc0D$h$iOI4Rt>B^d#XiY63hfPmoM!ob?%(mz1&qHez6o-u=xfxZT%o5Mt zFgR?hx6qFixit=kIZCgDJRC4R{>rdX#F0Gik^w5l*tBv$6Vxk}ojz?@*nUPnp)x(?llkfmE{Rs@UxF z7KT%bE7WT~y%KTzd1)f>so;*$bWn6@HO}y@#+Wa%V*YCLn2ny`MEvvTVS$}2_3RUE z<)7>F(xYa}2jsT7oDZHCj;=i~b(nLj%=zq3(=aq+Y;258);V*ft7rR37#Ww3#kyCR zKxOS%tMGk9gR4tcom{K4##nMnix5VjXh_1IZNVVxCH%}?kkVnH4)f}ll9)agMjpj7 zJnaHQ+2Zb%oF9P_k(5(EWouT@O#4gCJo03-&tmTZ(?T=TdKnhEchPefl9|5S-X@Ed z1~M$qLsTv>F$u5Zb=r5nccY2P1)*64-d3MyM*PdN{QXZ zlkA&yEMAuFx~;L&#?T|7^ajhvN!ElX6dn#j;na(W>o+VN)?w{VMAtG+BNgwVO%Py! zFbe<3WC^&FbDA|mD zcQEKf2V<|iW@Tati#ZRi0RYl zcPhBC;k5f<=;pE(E(avXPh$#a`}kPSt(LV6kc${IvnsnJncJTrygGX7BUt;=#yn(B z_T;Pk&}Dym011DOj+9P6Ly~zK6-`9|*TyhIxS-Q#w-an};}6zS!JFPgtv##G{GnYM zuUMl8xkv;rNjM_3_-+VGVT}x_tO~={-W>{Sn5^N+a4Jz~aakIDr?o*rjFqf)A|m=p z%fxw!FnVP^nuvJ(5$TFFCa?uhOw|wmb10_1-E{jk!>AnHAsJafm;UN^dk(85xTu&T1xnx^(T6s+-riyDvUG zTDFE|xiuqYdx5iMWcZ#!{P`l6jP2bla%pfOA-?#>5FE|u+i>$NWvlMqoKo7@mzz31 zwB{o-($Ul;Y18MoM8TGLP?Lg%tEI z1oLUP+?~9ph)K4Kr4{){Cx0r+s;Gh3j!QuTe`wWB0%5PMgS3V84>vlQYrnqC6-qO2 zQM1Ux{LN5)wD!nVe%{5Ruhg|m9$X9NlGJg>F9?QRQs-rrMvkeYZ4kS=j?T51GOMW4 z(4<-r!yL2)@i@gFHSc~0JKl5fWi%`R3;sJF9svR7pAS+VIvy4|JBFwV95#iKBMyhy z8%ouv%!*ccDo$gkr9(1tHIsnhBV6i{=g!etZU4Tch8KalW9-F5Znf^1Lg^ykB^?T3 z*q6IU*BYhiZdVRGdFMMWlAfu1p;4WIF>YB3tyU03rlWOmg5xF;q%X+eQ_VqUl+Z)X z%eUzo{a}G_i@`5<*?dVp*N&s&9}?ngerio2Jo;_@szC=~K`ZQK1!k0~gvtK28nutY zRnBfRTn^dt80oK@_|1U2uK;E196FSb^AmhlLeko58n#-T1KN6HUMc1{fD*=Y}uz2X_yj*RK%5s+i!BLxY5wHWgQesFYsbr6*hyPpDxbJ+CQ>c zs(ifTo;WYHn1ji8OIq_ub(Y~64|K1R$d_{Cv4ki28iDI*)d>D%CG!Tr6 zQDbD)2<*S#Xw9K)U;D;qpA_&FRe>%-re0nEHX+z`I=fKuz!BB^GLmsMl^m7hc2lS( z`o?5Ch&bAcbfYtWCPF6D=3uVQI)da!C)IV@-kpe!f{VvAp=3}r*Je{|GS(r-ntiCe zenYfS+L8eBY8XM0Owt89nzQ@khu{^D@BiqYTevV@5R6+>t!^uRuNEz1W#q@QHjswJ=_p}PfR zlDF-J%UO+l#ItWR1Ygm)QzWQ5CNg$zVm}X}(O{>m#kaHWkji%Tj390m5hHMEC7p<8 zaevDTL=63nJeO;A&AGl=XkHSz-2ooNvRDImEDG*uELZUu{lHRZl1|bFrs1 z3^U9$`2Hc9z+mNeVQ@kHWFL&YjiWk8X1tr-r7Hpxacg z@GeQ``E8-61w*MjGA27Q^?Vep29&QA=r?g@m)p`XO5@-2&lHNAQ28)ohWIy*#*()y zl3?ltCGhke@_bZ(aX_-29P#F@2sm`}!UT1Q6ekujol8%lOC+NtqnkLSv8z0%c7X40 zU_G^#Nsew?Hu=P2F>hy*)eYY`vphLjeu}B<@akN*btW%T znWx%`L`BOw%E3)6Ws8cV_ku22U9nk0S?|-AUk;n4Bvu6*j;u9tgJ_I`Vxfg_K}2ur zXr+={J!5|eY(Y%y;`V74H=@#dDJ7*+z4HUxX{to&V9|?YJ7N~x?P8_y?An20!9!TG zw++RJq|kaS+>C&q?{-WV^B_wH2Rc8~#*)Ufpf%!2v>Lt6OFlpBbuO7ES3?Ll3zs|d ztwfC}2NO#eJA8-=4K9i?x~e4?eUHYjXoIVv^EL_FIryLIUskc_>|0OZwzIxBTumv} z;#yQz6vga+w^I&$S!;Z{U8j#Hf*thrvI1*nZM}&HKRpRs5Dj6+kVq7F7?oS6c-~xN z&|;FCkx?St`v!F^{aMqc-gDp<|7xzgmy_V}s8Ei3etM$_?oX-IJ@?n4>rEmRiRAWJ zTL;8*Ss?)laImYh3OTY6e)bo-rNiTvt~!}g5!;i>O(Limy*M5Tx&}Fn-ft!QrQkAa z;xC!z#wS8gerTkSr)l~Yyz4*F7U*j5-eXp;mf{rsS|k;@zcx5}b^0RWN9))pe@R6x zHWvrAS|1$Ln0Ys4ooR&hq}>LSgKXyoQv9W29p1s6Ui)-Q8p>f+2ClPwXq`qc4pS-j zBCc)IW9;Q~&k9Ve{WNMWyI(qTl7+b=F{C~0BM3BapJgsD#cxvdoFEwLPgt%c`1WRL zQ$cUeE@|GgbJV1qbbdM%%`+c-BHGJ+YP(-lFNZR_+;2xU(qUm`qT|E5Dj52KfOuq? zyFaRQxQO4G|9G`W|BJsaIUb9WX?k`iQPWCr2nCnmQ z^Gt^^n+%YXv;h-mQd#MY)@!4U{6L?+yd?pGh$ra&<#Wk+ECg(-PP}&VE{bRdTS#59 zeQA0VQ&Q93e6k1~3M|LcpCPwGg`duGCYD!{k^Bxkv-LOC^n-1}FUbo}Yl=2Mw@1O^ ztgG*SVz(;u?-sx(T(Lw?qd|Elr)>J0u= z?dc14T12Jcu0y|N4FO)K^MD_FZGg+TP*#l^UN_Ybt85}Oc(=e;rj+JBB7??;)3y0fL^rW6GkQVTQA7*2cC@j>233xo{6DKozZ^v)H`Dbaqt{Qh;S%dPs-#^gYB zxHcGo)8Z!Yv&)vCVz1||rhH0Ngo4UL!}(M8Rp$-W&$;aGUVN4dIJ@6FTrH9ZgxM*bTRddre{>#~#Ra#ULfCk10?THCdMF zM*O1<-R-8l{9X6)3pLt_T%VYUOxO`TlW(yd*DGdNp~%CFe7r%F-ZWg!^j!t()$L_E z8jUYYGPaR_#lM?~uSSkz3BuOQ+PyT6O7Pi>+TYQo`+Uef_~!LZAK4+0PZKCbWo0HR z@P1LOzuN%9h1QHDz|6#I6?qBU6U4-6a#+t1O3HU2s zZxa(~^FHux0j(K9@M8q8rQD0kdmxA9Ha(|ub&gA{tJ=>h8$0B@p?G16^8Ko7HC!xx zk4GqCMwTBMx@DKJxdah^<`ZiZS?0(R@->0&@NpKa(sN&>j%?8rMb zF9_Fkw_JrYq)uFE%Sf)e;dg(cEw+I$3^DNP&(G?ow)us-&@uUxi*UzEv}45C^-M}4 z>%8y<@w(slUrJmgjghplW7j%aV&hn8qM9keP|T$w+)CQl26`M>nx3Y-qeV6q2-v~? zA+QH}+(bPo=zQ%hgZOsSr25?c^iiEGOxt`atO? zv*>7G)iBgCMJ>fRq`UarjXzYvjJay@ARzuqFZotJ#Rh4)7UcvVx53Xd%<<&~o??8% z(-bAL^(~xgM%;H!b|BG^5&Kb!twXF4lfRtbF1g)hBBBu*F?cb5{A&5JdFR`>k4d!B zbKs|RVmp(oVqPIKMg6Nug@VrNZi+~Y1*44)b`(MRWWmvn3fiGuse@cNILO zYNn>Jb;2ne0`It5s6fNKX2cV=x|;bat+e-d;paYQGxhKMJ}=#NNco=$97?eU(~b6` zrP-juj<_e>pl?^kG$kKCsZ#m`m7GJ=$@j|3E5sIk{_~tTxwSBpk5z)uNl-wbs>1W7 zIRo+Sk}$^ZxYRfTf{pjg&W1G`e}+=C-|Ep3>x{N|$Io-^N9pzjQV1g?1vtB;=#5=| z(XxWf7cXx&{6v+6DJ+)Rotgxr1*g5^&ctP3MD%wI^nAXU6P8?eMLWuyJrO2LOX7Dp z$*pue5#?#y>d=CwBo&aic4T?LZYzw-y(8Vlka#?wd=HGYpCefQN(kX;AN2Zhp?|j- zJVk25TkSM6Z0{ERhC^bwo1=used@=t#D1)$<#rI?+IIT%{D=8vZ@o~~Iq{pZ`ovtH zi~Q&f=@qk1o;|epT=rF5H=_c)r{U3V-z~gLlkU=3Z@SA0W6lk&m)Lc@8w&T1nV)9% z_ER>}j5zAhIFwUMz!lLQI%@UWex%NaFHb7_(akj^((s|1cDjGPNxar5^o3D}Olqm$ zqB7y_hsr$Sd~5Xb$Liu)MksSx<@Gk9kKUA_eCcRmOAxS=w0A=l$Z~cd|J|8Jpt0>TV zyqIO5mPgk}Q#40veB0{&+Mz(q9zOR=88J-o=b2SEo$t>(YiYKVrYg4J#kb-oZkqzl zLOVkp`^mV`yF~~mwy$ruc~N!5#_j>`Wt=JxO$+XZD_U{lArm1L=U+OinWu=CTY6@ zeFQo(LFnG6+L_U6${ms0*n|{&3O!8@CC`0^@68S5;}SteKV7q!H=7clxlpME#K8Va zTRYvHABRtk*KhMtWJh*EZ#U?!JI<)5h#!bFm7pzrPv^++UDB^Oho)zBu zoUpsVb&<{NZ`JHUbSC2YbE5G0%eoA)ZGEaz(VUB>7}1)6oP;-7#A%7@uuX<&MKI!J zNw9LI_pu-2SI%z4O>Tyq*q)#4&lzhT8@r9=oHM2_dV-xT{YJ~Dn@4(%3Bwj=X8wgv zW>K{!XJw1z^uqFtykh4Q*WfhK@8iNU2A39|1HZ7r@3QSJ#8_zDFH69$~pUWoiwp2tsQtT-P=W_Um-u8uX`rg*<~(_*}B@ zciN+)0LvV&X=MMnvR1#qU!uv)e~j2^iN^8v?bl2Z_1hjp7Q6tbb9u^Rw>&od6-tlk zBavhMjj5V5Mg!bI0-Tp8?tm1OWNE|F-K2A}Xt+mnE{;!?h}lI~MeM@9Aq|$&hNoB~ zW7t_@NRX@1uwFCCMf}ve=7^w470(wo;^}w%wHxt62~P(WN5EOXct+lJiQww%W5fM! zk+iMwW2q=XX5@KzIIOj{Jr~Fee$A`PQaQbvT=Roqv9i7s@uJO-8tWl3lBeoiIz>1& zj9IkmTza1pUijGKX^FoNS=w{J^%~bH@ic z2og+Vw(vZG9f-R#8vhxo%w`SqTC=VU=PiHUmXVyl2CNddg$qUo=3J@DuoJbV%ETm> z7o)!ZFjC&U)>QLw4$!EN9iyW>Qtz4B3kdBOgKr*bFu-5nfG_=j|Ay!Hcd!ru6MWaN zV&wQH>JWU}IJ|U(@#h0r5%N*el_*m^ZaU@7f$)yrXU5B(ak!#hoJ0s5Qc+lp%O5r( zu147$&cerPzPhX88(Ui5ND^e)d%sirtY47ZBaWfh1R;u#8{>y-l@eRdmF0y&HMe!< zfZMlhqOE!VCNiK6=e=!1VJ5j#a&8#D^yX~_wQ&MKnq^MUU~QCG{XyR#`ygtNg}kCC z0P7;;0A+RAOgeW1p-gbV6oOGXWwAyfTGH?xUgP}-*D1l`FmLxrR)QOO=bQQO!5>)i z&@#!pcvJc+gx3>}&oE5hs~d3^yySe|lQ}>$_pPG$DTm&paI%Wx*!p|mnEzLj<|~eC z3oDmO4`d%T0-tz7AMrLk?JufyH1A2Kv$1~|v+xj+4Omw&E^xuWL;K>+j^R{ROH!|xJbaE~rsLn+F?i-`C;({SGuSqLMNG^K%FmXM!Q&HpC>>cvD2RwV>Ks_-v zTLP^ygfN*P<0XozBS8i#TR2iu>L7EdIuQp=#2STnK>Jt$3R4P4Z%|X7MjlW6Y~cn` zJ^o8|y&AVTYg_p@{uiUxTJkFktS`2qvVr7XO;eNSR*ma;$JB%`^;s@!E|N3g7TY86 zC);6Y)d8KH*W-707H0d5o#51-O@J3&UdAQH1FWgu;NC) z_APp2O`bHn%NERvw>4kh8l0ifHFIcfQ-5L_Qe3FK+N82%WRzk?Th zU|he4lSzkM^h-C#csR=vzQcZ%_QZVrY-L944%|6*)|rH()w!_;G}0Jq>*gTwET6oN@a9S{3o$i`(;ockWxgACBUxrosqbM}?ecO! zv5QFH(D7j37IvprFAop;Cx554FNnv6`%2OhV#^*0Y;{|VCxx2Vtv~lQ@rqwxnXDUi z=3`XV2dRruRJ6l(*=MNvh%J0wMi~&43s|8e)Qh6S%6qY`q9D~{&BrB4z*w1~u|~eX zG7)I~Vq`XU8#^8{Kvtsclwnt6;FJ{0L6o38JzAJ8pKSMWJrLe>=1{J9`JkiD0_h`U z{gr$Kuj8bM9uvMfrp(qT;UT$3_S?>GShQ8CSHgEuqATP?iRiG~KI+*mt)#-ywlXtc zJ0vNxIaC}E9+SZMs(mN^Ie_VJ&C(uxj1uXdb$Jh_X$c|TZaHyQ^G)F& zr3Su>a5U@hUR{Z~W16&6tOcM?sb^>xekZuCOfOVTKxUV{?eC}>aXymhow_h89@)vi zAb9--z5et{vI*G%VC`CK&NX*(*!!N+e+8BCmLr`wrCeVpK}b5%S+`);bLJz|B8gb! ze668#>2xJ4VHc-}m_yCtcvz5oL7p`Z2BjHnm>!5^tENN*9 z6I}IPgx*0N*Uy>;+Qvue#kbf#VV2I4a?&X!Qg+LEFd(%Ij3D*zNe(u7 zV#pX^n3sU+ATInS>V!yak!aB)s7`;l9BYWGg6o8yZWyl6o1IIy&FAIiyAbCXzHnul zxgu1`e9?s1B}Ctsv*&qj(z%md<}!1hTIfHOW^{u;MGwYri#?Xz{&U3~2h$ul*iZ-uwun&ulYdP{2;7hjI0V0q#)# z?e;di&Ky%+{cD4^_WwiFS9Zk_ZQC}kjRk4k-Q9z`ySsZskcI$h+$}(GcY=FxcZcBK z0g^z_;F8NZ@80o#K#fr!YSx;2Pg&Re6~?EzkF))NKJoq?V;;*U-JQTZDM)GEmzv&T zx}#kEp0=-U1Uj+WJP?Dx5dheWeO331nWZg>>1y}ehvFf6err*mS7$Nm9?;L2mR z2+{TT#w&u{jpv>xd7gE_TXA*7wR$rarSNbCJcU!8&2jeU&$ zxu{!$1HZ?qxhHStni*qOaI1{) zNDmPtNS2*(j5^AGmiUHo4rec?nKKI4Tp^A7v{l;SgaSozQaXP19$4(iDz(Ysgw;eI zJx0S7d8;1@5V|$&YOT4R*CNPOu8IEdvPwCZGzB_Y+BsD_l_0xA6~SkP4Y+AKKPmaNL+rO zLiTmp2i1A8+HN1(=pXLQ)&H57AD3ncKk}ohF6>nqdbCHDMIB~Lo4AU;D)y8~q6)Gk zla-UTs7!mOBg0+gtgPF7kdEy;bVObA*Wf*Up&YvB3Gb}3&p$v@|E*dbAi!w1(eu@knKq1Am>`J5KnnmNvr=75%op51+vV zyxMTjw6OQx`lh+?inaIU#&BAd2wte{#l~FRu^bdfp=OV0|1ra2Nb& zs3DlE{Gn`FJ|z0yGk7!oAD~rYNt7R8v2P0pGyIzgI~*?O?KA!DbVt?BbafWZBGodk zN>*j3#eSq3S^Ld?(7tTTX9AC*nL}Ss*GT?z_%+X82G5i*uRXmHrcCjsuwi3~$i3ph zSd_r2K1er+9FhCCx>1J#QBXB+2gz({k)IS=oca9se{oO}rE+o3tk`XqFa9kd{V;I0K7g>iarI z>jeBjTI5@l76x~^x|xNFA%KQ&taV^$2*hJX8irEwh1d8eeIr3y?P>o#lh^rOF8>TI+47mGD}`Ln|Il_>|8jkuVC{*?MP=@Ba;t`&W06U{nPlqlUn|jgjrCy zrlN{3mYgvkM^lG&|0I~AnHA#_4Hkx)5V4j>`e*Ode-BO-oB~6%js0+f>0bsj;hQ+{ z;U=V=+K-%FveFG?l5l^*^VxCn4I$HGrCcLrB!o>E;KLazNNrDfx>Ih|E;66wb*q%$ z=kYc6vz764a*3l_wN8(taHHOFp-mfw!~eEahYdv*2f0?d=z$(DBJH8YuXY@fRQi+y z%mhg968D9ezF!K}GfgB=_q}m(PT;#q4$EAMETDfCluQLBBRWM#ijySLu;WL#-XqPo z0g9TwV!eEfDoEZGZOs&y?Q%!HJw}@+)0YgL65ga`9B?UWS)C&t3hy*Z)Y z27)Pz<`8k{u=DTm-%#a+YbDeTSmopg89r4SM zo~c9CPj!3d^p(pk74a)B>6H}SdEa5K8O`E))Nkm@2%rPYm6iQ3mY-5mmn!3S+A~6s zt@Y2eBc^eiX{JNXOVlq$p>)+ep|y~lBCwWsW5?5vkL$%Z6z>GFRGX>eKUMjpX`Xcq zv!CNi?>Pk{>))o$m2?2uznjT)VAWCenh&5(}RmqhHWQgm@8r4~P z@q5yOx<_Jc{Rv$*j>gFnIJ)F-FwjcVYL|t7WrjNNA|uoP>V^4DtvT>U(7_#y`BCxc zG60E-BkNM^8}wpFp*)%BXJ%Ay%Ul@hrWRT*c6o@G6hKbH|%^og^>fX2bl49=t z1EkP)3HOCB9VPtcQy^3;`0@8%8;rZvJFhQR$U*MFOqwd&Tko1*_@W$Arh6Ak>-c%| zgy;0?Mc#VpB`d3g$uN`nn9H)5i<2w#4x^an(DlZ(=_iDWdKFZ=h)Q?vkIied_Sv|~5vypSA#(1HGo!N{`%><@0?wfMH zOC}~GVwb}IIE8xa5D>ePZe&HJ7qHd`KY3f#ffP(Rt(zzGrOr8dSGUl=;+9d(u~=*i z9GgKVCv1QMFK>Tzc=M1R`?Zp+9iAG6S7d;>HU4NuT~k(3x1SXg_|BEaaTw!Xzj$U1 z%YLBIHCiYyJBlpOO^Z3!c<4DRDlF_e#v<{s|32|@UwW9mXd zpDbq&7oen*>aW<08@za-s*sM*I2Wy^+#;m=rkU`vBco90!GSs4LsSS&k%4sK$8Rf# zv416|b;AfuG7pKeJ+%js@0Q2pw#ZkxYt1J0?Ltg>HIIq~kiL3qSL|wq`~#>US2&$& z`nv~~5Xl%O#B6n@{VAYkw&~2YMfstIWO0j*k*h*I_ma_g0|nPd3$(&#gc^5*FP#V^ z0;tU2vep+;iboh7(!F|E%AVYl5mO+gy)jMg@G+5 zxS2tEb*ApAKP~h96iSoCeNS?WtG84MRyB>EV!9^9?sAc_YHkpUNg^(me}gMp{ID7t z2z#6IG%yG{nnum)7|>>TNn+!F*|WhFCvB;4m%O9{`+pu0KsNJay0KXHajT;pze`qw z7`XFOc#zSZqXY5{n-?|fsK$3_#|jBzehvjqVf+K^DN{>!>^TEl%0|&2Ny24R>SpbY zs|`vev;zVsFAoW#TF?~~vH9;TCF-~S#ybeM90vabbVT$-rBvnl3CH$ zAXBbNZW>+dJ7uS0*K&Iq5}w1c_Xf$N%> z1wP(nijYsH`Oj|mgx$IR+>ag4r21>vKfvdOPYj}OFPG)6S|YAEGn#o=p<1Vz=1UEL zl35r~V0$4tVG;Il*_m)U)cpA2Lz^jpMTpPIWk>K<8VHu|I#!Zg(^pPgXa;w8kCEz0 zDzMmeNFSB#JgUfedMkG$P{0gcdB+uX7=TO8Vfnizc(LU6EvcA!&zY$Z!hEUX^4B{vJ>d~O%*%b2TKln_)apU`>g*R;zty?`vSemfYN zs5X}ELi}4{Tk-j%Q8=Yn{@y4;?emO;B6=MXm7cP-V?!JVTV*~qnfIiFafCg0`5D<4 zy=b^2VL!Co6@BNNIDT&N>zJy#u1~C#M7+!Nca)+V6*#yyZx`fEC0|32b+mf;rBSMp zDz5Qv%@JceYp3A{YkrR+RiE;+o#}8(GyCs9-L(_eoY(h368Lq`hxrvS`3aEJtZmAT z-hOXAf|bfhqS38)=8X%sA~wgI;KH)kW`s%jhTqLQ-qH$l6yQvi z7$Ai6-(FJ!4Y$Ve_MhyjTKX^p;+|E}^5=tw@06L$uIYL{%CMu1Z5T*#h8aKIRkyBe zc(OIeH9}+Nc`kSjv!tWg$S_=hLBlHL9%l^9h$kVu-@g}RdEf43Y69U_lJuXa(yYFT zT^v}OE|!U}eN#1xF~y}6aGNe(O;fjzpRy-wZhaPhl&vIkeT0aS#LGfd zT4YG(*R#GmcfbD(Mba0qkSyEH3hJVbqggH{i6abD|u6`F-F) zJbz;06nRH>e!d&f4Zo0jzB}Ek6GRQ?JS&nU6xtvxHh_@u1(wk|8NM(UU1!8Wf3d_- z(y&CN%)5=tIHSGJ-lwGI9y1Hs&JH+a8Xb-;ESP!?5LlC1ytWtag! z{m0EUFaC^TfP?~59JRg4;oS|Oi8^Zb{XamT>W9*7wmTsg`cvwztihFH%P{gKX7u{p|lcgH9!BLi-p85E!J>1`ioDN%-Z-Kq) zf?Fku-w%E$dudqOW)f?xjZwD#$1wxI^U`~%D+_teG`NtI2iXAi6EB6G%twps?;dHR4> zcG&+|R(;B`J{6D=V*_6)?Zwv6-uq%9Uedwt&dU{c{F|J@LgW%xpP1?y_AXaVZPo%u3L+$udCY!Ij{b7s!m~b41fHWk4D-t2uFU7$CM(!> z;-tO)X`|vaGgZxxCM%Cal=f3J8d1;D#Qe`w(LL8OB%?%v;&Z~a!7^>d6PwBntEV@c z(2?U=gf&-3IXP$GpdhBc#T{Y_dA2YI?oUpEtm(ZwPT81K{R5dH zWyS6{Qmy{(JZe*m4E&q+{hw0w** zWw$qp`6ruD20s~hWTit2Bll#6j@bmiaAAp6*%;|{K;5PtFj*OhpUwj=TRXjZ6u3NV2%!a9VV3rQ| zMsKjh?UiY5K!wE4{48kSQq|0M?#x6$!64sFUZve^T$R2 zoLfwqOM0Yw&o~D7?U`E|x7@7}6`5CGtK->uBth5w>GU_iLe{#HCYFG7ewf}6SN;zM zOW``}ey^WTcTZ5A#Nd~gPKZ%2Lzvy`>*Wm`CJ++*av-3mfBtp|E(lEF8q9lndWwww z_E-FElr?N3ct718F#tD#3s}v5?YMI5hfFMY`Sx(^_vsyjf1+h|b^U*1QY`a~)W7re z#WSYjTsab8d2)EuIR4v4_MbXJ0FDjN+BoEgpAw3MevJbd z&hM|dPvGJ)nn!~jX%eWC`M{fm;7*Wh+ATOWM7OJ2*=M{R(a_^5fonZn(UoPt|e@j%@8;G zZth#Ufl}h=?m|ac#jGcrg;62!j*?TJ!W@6GZO7A$Ph_b8NVNKi;!F1Xa`k%#v9Y>@bInP4gN+ zwC1U`h-ftn{*_#xvju3jsHxB}ut)`v7h7(!P_#?fRu1F%pECK0xUI1g8flB$evkbK zg)X%thhPrZ`0<>lkjtz9G0^=&P@M=Q$u#ItLq!8q%-0eYX*vj--XnrhaF{*s)5_5! zEM@gNv8c=Bi<=s%5PWQ0Lgpwspuh6We&zNgggG}7R~Qzu=3MptXuz9=3GH2D9^ub> zZBr|8MW0>Bn>`~n)dwHIG+MZ|!Ms6&To?h^yM{A7zphnDa+9F8&=M?tkuBa!d_P@p zhHHTA_on{X_7?Ej`!8XhBefKu0R%)^xE+gcIXqaq9d69r2q{9aQegLe*n(O@*f_V~ zh~{6sZo*_vm4PMPkB|&-&ugOx_7@4a5nFPinjIFkS+D&FT~j~pHGsS8)~ghv zjpHeeR+8<}B^8-Rrt;9l>{2nz5YGH2Z)Y;9+ld7h|1)-#vN`;JT>Q6W=zm*={tp-bg$32XLec-?;y-yR9MT<<49xx+Ra=_& z^R^JVVw0FJXrzvH-aQCBhceDNM?w_=dsnme(Ij^{r{;B;w7`B)dZB(vCJyL4CbNYJ z#jmACSXxQZ9OPyydqCI7YZ#5xmF~{>2UIC{pdUe~IzQ2V>%)gNA`ErY*YQkr-;c`T z$F)+#-HK5PiQUUNRA6$%Bg~-g#^FBx^SL-@xG|FndcbHph#vF@=U^0Zcs7^20LIGN zjDF}27wIFo=2T|D`+YXOEujTFAl{dj`WL6ccKK8k4tpi5T=}J#s(%)}(h1%MyE5?? zH&i@y`UozfT6jcS_6>nFd!FgR9Y2~cTs-u=s^Y zbdxh;v>Rj2{z{xyTIOo8Fvn^6fX3VC`f=~@^5TnSS_^P2Yv8%u@2Mb^?;p9BSf_u^ zq9}~*335+w03VWA^6HF|;>cLMe8ai7Ty;2X0xA2JD{0Kr-P>LS)fQvSqxUOAO9L;t zxmke8PIfs$5kU`eKHN0w+T&;o&u9~c+=JO;?iHsu!kI}tDOR9pI2TTN^Yarqc=c#c zxWeDrNK{dEW9J^X)|#gtZ)P|8*ZCx6#dDU_=#caQrmKH|@pi8;RW#L}iIEB>essd# z%?&*43=f&7Kdev|4E?iylj{8(qdo`_{B#r5B-$fNxf3YG*6>ByD0&9Q%t1{cQ!e+; zFKkK>zdhg|04p4o6>Cc_%eIm`Nc~kPeO2p`9Q(zAl?|N=$W0JEK~%`V64}2F(7|0| zCQ`H%?#>s-^#3M{03p%mNv1}z#j;j3mFGU^0v@vZ6x;oU?S?a&Od^h-q$j zg&Is|F{)O+oGIn*bQ!8O*w_6tRL_5f&&|E)53G*c_#(!m^5>H$D~11KNib`WWjUp3 zg4mvd|M5UJeuqyy8him{v8f*SoC+`EJ=u4RU3@(J(wjt^Z%tRWKo-Uw<@Czk+$K@| z8z@EcWdw31EmS+n9L#3t_Am=Zsk=+^H17DugeBTxiRhA)8@gY67cWxM07g%x{H(?+}Hsu^$ zrnvTm$PJ--4=F>Iz)MHEu^i;}+0Ii?p#VkCC_6E-vAYip6W_hsN=`0y}D>|nr- z?*uiuFN_(-HWJ{?tU@8!5A4O-H$&f!oawv%r+Y8U{X7)@RL-HH{ajEZrlNuaV4 z*F=Kxqhk*PcS~RXK)Zj}*zy{X7pC7MXz&tS>QHxf)-%`Z(C=Bu$5J>u%AXtNDVLr%ZpW_&q4_{*n=KU7&L*@8KF3TITXXvXjSv|S(QB)rm9aT3fYDYno&df2)uz>=dn6q#<*G<`rGFQKl z`~@<6+3*#z!d%#yYkK;VETf*m-7j|8uto^5aefeJe~&0+tNkW9FK(MUwkFXHb`ihM&B0KJnd^blM68k?K|f00`aHvQ}Y?U%IR3$V_0MNN-z=7`&GXYeEV zn*d0BRGfkN@eO(dMrPF>FyDJA1804@aa=TMWBoZAYTOa@U^KrBK)THN7wFkop0+V3 zkT7)zkbdNaEznG2mRfQ17?L;mc+E`+==|6Ve#^c*tjNO8SQu8ND!?$BBnMiMvHja{ zG+-FLWf-%K0C=riD^z}wfn*AlH<9Aqm0;9zmjkHMjaOM4|9#_MiF<2$`C@eg?!Aql zQ-~T7(kpm!=F3yGO?k2aE`|_TMsCOWyXt(+5+z4$;llHNoQN_JbB6l?Uke|K?{%2u zTbf0H5W%$&fF5oFi-G>f_q9N>B_nI?c^WQB^H-n9iv^STeNauO`Wa2kyrMi>SxaI` zT>0&9*lf0s?!=dnb4#6ZcbZWf6uOf(W(O+PS}`=S8bbn2=TNcBD9PxRjIb6II==Fu zFJZnW$0#hVUB|5Z?z24VbrK9lB_DhCn+-!wtq{gvY~a@0t}~^`C}N-wD>TVhTUA{; z03l}%QniV0bYA72eP#T5ZR^a@{BMGZiZ{JnX8A+W7CmjGcEEMr;ARVu46b}Mq12AOA6geKEc`7%WN?!RJJIa=ltUQ84mNY?!m{<0; z1FW8krY%-1o{2ScqtSE%{cYA&;I|0 zpOePifN&XtMD?NBkRT8EVTDZ=xS^?b=$3;S9$u>j98Fp={534gZru9Y+?Z@u!lN`1`u0my#Cqu!$9VK zopyROz8UX#MY07#YzV)Y@%UgEOtBc=7Ouh>_+lOCVh2#PM0gy2yXFl@Ak*ttWp-Tt>5dW%Q zh_Tu?s6= zmt}*u<8{3+KH8g|%{GXCjG zkC*m#Y3TNweG22@QHH%mWV}`9Z#v6kJxiAR_rYIK*5R5%d=e`Ij}WA<@GEi>lhL`< zH8=99ta;z%xy<9)Zg}H3jcUlAcu}c?cTCtuk(J>Y9pG7|^y@%sz95&@wCf5pB(8;$ zQBmHOjNzGCHue)p?n29s+N!)J4cAluediL@W3F8v(y9=uVqZ;f{gDlhxyLM5AnbJ9 z)=NW72ufv53BlsSzpl|j@y>k+;}4QQB>*2t`ahjBrZ>403j-lEK|l?K@`EfgKcFlVSZkU zwbd7>bDu}+FzAOfWhr^h!=ph<5uRbbR*o-+q9M$A(rA$3a}*G1nN1Ae=iJ$ zfG+~(PtD@};t)UK71Yh}VuuR<55RWNB=8JVl08AQTIQWa)`SH-DTdRKpSV!fTBIPyFX-oHd#YBcN zHvvVZk3%42u1l?$1t_G;1$LFHt^2AH^ z6+WyXuhK36ggGju&Q3^vv6rzRtp{reecRCxO!d{2aZ=t3mgDD_^X~=Faha;E2+^(i z)d3#{u#-rQ(HdFoP*N$}{A$i8!#sSaK*mgV)y(9HIMlVAf z8zo=I3f#-S^6>=1H?4YM;7EO)>)tNp56X*>U*!|$pN0)^C3Ma~hLp6@x4-SNj2fG@Vmub$dQ!9$p7bm!MF<(dV zoDVU4V5|!iIBBS^9KDcHYAyNdjXrt0$oa>qDjk8-Tl*~6Fuv!*m{Kc`u9Wp7lHNL! znYn%nF!Fq3VecAs@gk|p*KT42JPQ@b@l71ngjBOb`xpqR=MiKf1(r)$Xw0sMi525) za%E&L8B@ueUxt&keGkhBLEK(-N1$U`ScaESlKxgCUfgfW0!Q*;kc-I3<@=I(67r~4 zRI-ZsRG>UEI!=&%B=Ft1khLNfWIWF4ad@N^e(r-P#C` z-~<#tc=B%buOp4u)68r0S4Ha+)JaX*oqD#X#Mnp?^=AxG7F$L#Db-JKlVXexkM)7P zF>|}^qeRROptu&sLwN$@;Ag=vo&z2=2n4x5*qB^al(+p)aKgwDVjbBegd4j8_b1BA zjVQ5^F+oe-JJNJ!4`B6GxERr2MuUw+QhcLnhxdcd=#n>Jr}aKVInFT2Oh>g?K@AAx z&FJW9+l)JKaI{<-doV2Te(*G;zeuL}2}V7W{@2UK$ZNw*VufJ>DibrBNB(T%B)m5u zO?Ys3ywWHaAqr@DX2hTNXX#=>)eV1_%PaQX^(zVDytdIel&c87>X6#1B6Kg*6<4mn zk?${wEeoeNDj*OP?oE5yjjzp+3sJIvwp^zCHLi>P%I%~O`2kAMOyT8Q3?Ip}s~ClV zPb%b>I4i~hD$Q|r;Y1x>Vv1}b!}ak@(s%8c#W{C}N5#F4e?$P2$G~cWl_Cf{ct3#z&_99Vce-b%E zVeg{NljOVdCXpr-ADCfiD%YGC9hsfYKbTP3&{!NfXp2I{9_@%buB@Yz?@20E2bRa3 zRU^Q?SYa4Kw}w|ZtkF-_xs3ZrPDzxDf^M?IrG3A{hASGa{P~;^pfoG%LNa1Jl?1o# zI>JPFlgg8Lg>I2PeyS~L3fJH3trOKioJ?3U)hAIy*qLIs>@p_`BOLxMNk%VZ%)Q!8 z)q%Wy`{~S(#uq$dV?Px9>D*9zL>BDz-p~OJHAPsjNV&UR{w`hv3|AlKcNKLV6HbGA zSNOgOTXh0>Q)3&;$O!LYNVZ5?e8?1c(%!vjlcu5aasqFN=5)dbj9gTy-$9fw4?&XK zgBEXwVF_k-(NAsREBTdDz%iAW)*h>Sdl(rHjXGXJOvUju17^!OaD1{Mx0EN8NW^6bW z?0%^W4q~JF!fJ(_zFd!I#QosT*UM1#@UL|}w!M)pmf&E_YIzOAOUUCC;@oDK1+4@~ zh+J{gpM2@7XKbT@d8^KIASgA4;U~xB$=?3-tZ<8z`Z6Ke)&97oiyIm8<%nap_b31A z@~m38G@U2^krG7F2oI__GEUG%Hh%{ksVCOmKnA%)q$?{Z-{cJr? zpiK47;sa{uvX{g~tRFXPb9i*SrH6pvj;al@@^_U=|NUMrs7?SNXEcGN&Q2!zooOm_ zbTqHvz`Hx{Y#Jo76Py*!AY?t{s6pbjX>TiNxxo%X?1t@DrikvUPHFY$Oijc>?2i73Xigtm`=RDFJJ_ouU#rA z5Jm{Zg%K}fnHsgGu3K7&b-;CJwD#ON2MmTl5s#1S8XYK+T&z!;{HHh%bD5 zK{4Hv(WXvfWf$dZuIeKK>S>GXaL-uCKR~<^n^cz=W`U&o_fdxyg`tVGof~YQI)^~Hzy{2 zkYz++)3YtWF#Fh0QY4j~0rjaLDvn89?>t|bAWWEzu1kWX<;1X9@~Kl#>-7Rp#|MmG4eN)K`8s-k2r6J z`Y#{IrTP+8qAh;ewZFOTy-xNU8GE#T4qxFnCZ*P0P&t-wXhDjky?KpNY1;Q zrH>A4?l!yfjaI&zm66_3O!L8%2iM0@TzYF{6#iLW$epdPAf%HGZUpGD{_Te^6~z7( zH=3}Yd}{sSE|eUcu>u!1yjt@vM*&%dK0g;0Iv99dh%n2 zXzcw&Od4lgAf#TK1Vi~5+R=tEb(y4C8pmVnBRs$Yhv{B0ioOOUV`$uYFu&EMYNA7*aqq~86fcP&XAZVKbGYb+4!`)C~s*Xd}glQRL z9Rp?@EO0e~#VjFdj71IC70Qk&pJKoiz4XaBGc|fO9LsMP(RvFtz?N$P+V5@V<%eMTXv9-~p=#B?K4G^NHtD za^Qgr2@LniFoh)gS)v9pl+B@ncn!!H2IR&FJf{b9Vco^ijpVANvTKq~Rs0>01uA76 z=!8*W&+U#XW~U6ym9594pfi=+<@9W-qglao1t|+41h<_w$yC%PV2_sPiYch49b0Fvvu1?H6`& z$c(nm5UfAvSW{%Qc>Ong+6Jk2np!6L^>C$b&%DeDe;iyw*Ra%2?hdb~`dyu0M#jELQ;CShi2f1}p?Cf%)ULm0>k$!t@= zcjhna+V4jrDHQKJ?*`>mjGOg2oWRH;MC5J*eGg#d&VfB*@ak7K*t{L{gmu(HHezm1 zK9Oc?D<63#N%vu?6vm4jw~Ti=G`9uaE(2}q7mI}X!Cw4&b301PN+%FUpfvc9Ft+P{ zoFtJGWUpa0NyWaSzoz->g*Wav&Z~_{uN{p{D}=tKq)B_1Acfm|k^^Bs2tqFl=r;^$ zUujNr(D()g>Yhj~JqPp9NWBZ?37YOH6tN?#L@xgGo+~e0Ml+op>A1U4C%I&ff=u`) z+Fov?nobc%o@subz7N91#yyPAmagACuswbaam=~*9P47ACQ3p~?VuCR5b=8KRmj~+ zt#fRg`r_lrhX&(v;L7^iBM->RRSse8N<5Pa8g)Woi17-V_~md31w>aR%4cKD{sXMM z`-5A+<_@TRXeU>s?ngqxPs-hHdH6Vn|J7xM$;!CrfUo7+Gx~F=|3o{8a|IFbMw8>2 z$oByFe%G-85ctEaypHBMF*~6Q=`_YM-YEv9(4A-HtEWe>6J{UB+$SOQ9%B3ZJoocs zBoEo=rWZ#TQ&-~EO`(?3YbFrSY?tXWbRYo7#e}kcR7vv!*IcF-EDU?Uu0>&giCR)y z_$Z%gF^izkJdXamyVKAliK$MKX?yR4UCZo~BDPRqdFPc*v~?jRnM=EUAn% z-B5BqWK+6JUpJcjITKh$e_#Wu7sz-71M{5X?}kCJO_N4@e6qV)+f zAdUZV32KbXm}iq|_SXujS+#(;ErfipUwNKVxCI{napr=qqlLotX;_ndD%9z~Ggbud zl`G8Y>J-r>|AU*#r1ixwtRVZZgK5tQ=$;8vp2FO1yPPFOT$0X(!f5rE@mFu|?K9U% zL>%>ub`qu%n_@g)iLX&y&NOv?V*ab9hpL}l|2idVC zCn1>V$tn)Vju(IH+!0z#&`|d_fsfo>iCK8ZIO$BQ!$3QhDZP8DW&}R`4E4w z>x9vHHGKxknPe!*39GsCvBY%|Oj0q?8!|BFA{5Jg-ov9l7VGx^Z97D|0>80h?nSSM z5`04##BInFQA;S_qvn$WLGwvu&=6@r+V?boTK5cSx*1A@LyHJw zK_$=-8Rmt)_n(7ts?`Q#Gt3O56kRSNHhC(8bBo~@0cJr}WN1@fP7}Dop{%AN7Z2GI7+Te>IG)cm3y7L zqgB^>49E_?K)`Q0$6Lvg9`-+1r-konpeqh?Au*Ugho^}}F|`>~|+&2m_b;K{sPlaQP zf=4U6t`%h&mB6=MT;6>blH-WTkag0vrMy2{{#6 z+22^09>Za>tJ*GR2kC6+H=@VaQ3LLuMQ$&9J-xX^@iY3K3_}so`AB_*jgFbAjcf6R zI!}zbiGX48xRTP$zI$DzL!Rgf%`Y&?q-9Gwc@i3#QRPH7H-tt;O@R;nPbkWA_7h8qqoO@v-ix^G@KDqt`wYM&Ex({EpyUOJSt^%_=#9u$SIA%m zvEPph%&3HHzjRD;HPYKF6EQ@6hES3TT0-pxN8HPfR4RP0j zEm%naU_fT{h2(U2=hdlurwSCJ29c`k07fY!%4^U3&M}^K7^z4(jijkw3l+uAhY zEj}b6m${qkw(@ai#B+P(4^I}sH~N|HKPA<`<9KRu{bKELpz$Hvt8f*<4<`J{h%j}+ zA6dFqRMv8G{kcxxkLmPb2Tm!d3hui)*f_{}fA;&ha{w{m3*6O@uZscaQJQ#p*U$v3 z;N0f6qemVZT#*4Mv85GvCdecUqRTN0yP^wHpVSA1kf>uE0UUEr(LI|=z<0Oh8T%=R zlM0TQfxiGgWTnxPuyZ6yc0Yq-rb1Z>pRq!OQnFr0eIz=21w|QF=~5(1*bds~Z_qRR zy0z>3rFokn^}{w=cWW z5h%BgC*H9F-x*cEdzrCj50m-+{Aqs57S5y47s4oRy9pAfo;xm>b#zCMMLcxbHNsKC z7eOU1B3oxK>@RzIJ``ncJMg&+O$Ts{fEGjoW_t;}(@*hMuw_~u>etj$9t1?mDseDG z)-ybX#60*M6URZQePI;4;+UBRLC-K5)Daj&N&Ea5r$Y-xJ#FFMkgrC%o|2RiQFkcIunzkU9<21XUMhHMh_`C>^gUhx~1jl22Muam|Th&t?71=a?pY{*jUDDp1{80=dU|@`hAwRHS=1vk&>RO8XFtW8_J&e?I$w{%(UN8@@Z0gSbaB3B zdw5uJWk#myybk8@G(Kr3|3vIP*UV^rH+p5*{reuiJ>kRq1~M^JlmVZ@&oo#^?lf>r ztfIC8=R>I5dvc+@I%&Q++X7m)gN;q1-9EbsokLBBIBI7B8=f~4Up8}dUX|Tny0>Vg zYojb5kB_6TZcFz8&fV?(H!ngs;*oUvZUssAMNG=?1ZUqy{w^)Cs`MX*-awan@@Q1V zgpAh{m{FNqCtJ|Hwn6@Ss*vzV=_ zbFkpt)8LQ~AUcuBDiFW->~pTjF!Gm@!=E#W@TS3Lt?#)Pe+le({R6=Jq(p@^HUfIH zj-Ro1qLG4zHlF_M{BBYi<2wIxOm=^U!z`ouL8W4sr56zsi>*+Qb9f94#=n+=y$u$^I*?Ed|lKSVm#i|6=ddT?%0}0R0qP*pHuX@&NonO#8qqnYNwns9bo8 zq<}$=Y?S;4z0fUO-M>f%1IZ7iErlX_&l4+1@^D`AtKYuptobnJ2Yy3i%whs-qfqw^ z8TqM|p#RJ?>|%77dOjzjS>i}ccow2U#1HNJWJru!4LH_J?fYG&>H7U)<#d?C2mG1vsmC z0jpXn@Xb@<%=ed#z3MxhVt36*IH4JG)f5h95Bo-6acRna-i=udgH~(c@87;X!A9H9 zVSTs-;`h&oU9(kRVHU8HS&T98=>^&K-CZc0G-sfAO|X|<9_oqWPyys~9@fTUxxP=1 z;6)dfPx$u{8o}N81^nz7BY%`#80u21Eny`2%~QV!(pOo?%?fp{1X9h(TNM?Qu7Z`> zeJkUd#q(Aid5%oe0H3GM@1%J6gl~p zjfyu)yYaFIaHMzpD{%)RYzT_lJvUS{arFy4S!p;-bt@ja9#uN%BI}V@VMo6VaD5@S z>6bjWUmmOdgev}FiEB~Q*%cywes>5aP+auYwC|KKuWnc-sPv{xf+fg@$?EKFzL&7T z9ifkS4)4tVp8)3p82);ELmZ*+T~Ik|SNNpDC~TI{(>1*hU2(`WK#;D<;huN<_g#Pr z3C*1zSvBL1fH-IbP4GA8yYHNEof`wq&@DdIoy?X9K+^N?TJrPXC3~qpiOlc$%RVS< z5*$3?dNT7;jtZR`USD3YTL3zZd7gPP!3$6*x6fHbNRX&c7*RMXRsR0~#sQ#gHtC!P zvAJYyg^?L6v+<9E=JE~`q;md=Q|uD)%Yi>-i_TQ)UUJ4=JH_lMGGyJmFLNZwVW|bd z;TjDnw5N?<&QX*UHoxl(SP`wyesY2#(W~&+#v@EX(^5(62x&nR$-cKg7?e|y-o^PJ z;}KgWUY?8>X~lM&_;Nz&Z4vJEjM!1IX2kpX;L9uVh{&kNe zCmL&@*WO4>SQiZs`Np=OgR{2Ie)6ISQK{j4%wm~&PnTXCWYa3qEd!qyG^`M}a(*>} ktTv|_-}%63bp+P`06O=JHKKsp{%6Pg%K#}GJ|7wX+3qO1!2kdN diff --git a/website/images/photos/ivan-blinkov.jpg b/website/images/photos/ivan-blinkov.jpg deleted file mode 100644 index b11d4504bde10be512998ae9097c3f22c09abd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92933 zcmeFabyQW&+c&|}G>DWUT>>IVN{BRAw6q{7DJ>xJ z+o0aJ_jf(d`@HwF*89hAEw=7GpEGmqnQN|zJ$v?tnX~b;IRIBtPC*WUfQKo>6adcF zS>LMLx_G*H*t)pFxNmR*0#XVpNbA7)fqO0iKtSnV?s*t8@hf`!i7dAAuJsB4eG$%1U>U9+>2L zvH*Z|@gMX@14sbK+4$Kc5C)(kBO{|AqoSaoVxWTO6?9Zo^eb4Hm{%|{v2d|2j$h8t zAOCrUprN5*V_}ov;*t>J;NTFRUvLQVF2wMDI#|D)|Gf3*@3UzD8GxN_063DCE*4gR zW!t&BXKer;I%q3UX*>Xe2Z7>2&N?8uU=Z55wa>G2aUi20K~W(98af8%6$k)@BK;W( zdLSXApk4&x0#FDN5)>JViiQMUpdJt?5*{*uLW9pGsewvhPU{vz2v5vGBdU_3d!_l| z6F0HNCcS&;7K1d8R#NVBghxBC3{!GmHQzODOV8JX^XQCsF)&F;!%}4B@@sU5bePGu z7j`;^Kkr(3-Fr~*qH|<%&)PdYwXn8pbZMXerk;&Y#KWSx?y)c51?2T@eIwI~>wCt( z9`_m9pfZS0#^Il=?;18Rwa0_Fi39SO+^%nu$C^0~$+ z8lc9gZXt>AoT^R2S06snNfBvUxNmXO^GJshCuuR{BA&OSc?|N-%iO)DoeWxsgp`p_ zmW;{LQ-^;$ulhAJ%h1A3SW14)KUJ2~{afk(R_FhN%4ZV*7Ff+7cu+h*9N1;&(zB@O zG>LwptTSGw4T8qBGX1h=WzSd#-E{74mcC|WEnxVJchW$l79xByUaE8Fi#VG;nd!=C zZC6eH^HS$P8*B!{?txRYR-Wg>>&^;aX*2l5Lte=%hvg9&Qxj=pap|

AWWLW}RVO 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%0DGXIEE$!BSsIV`KVJ>|B6BMH=}tPwazh=avtYA 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 yJ$|2-XCyJHTx&PT1K*B=+ diff --git a/website/images/photos/jason-chan.jpg b/website/images/photos/jason-chan.jpg deleted file mode 100644 index 2f69200a0b4cee2a28baac1cde364f569c5b3bcc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16772 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 diff --git a/website/images/photos/kevin-wang.jpg b/website/images/photos/kevin-wang.jpg deleted file mode 100644 index 90eff0a3402fcfe5bf1b9423b0a6642a197ce27a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22029 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 ha{mAyLE+^%e&;=Fou8+A|Et|q-My;1 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∈YgH0Ctz_m;%SnTTRzZ%pw#}!8T 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 RW#n-yXvkBt2VJOV|JlA?#A*Nl diff --git a/website/images/photos/kseniia-sumarokova.jpg b/website/images/photos/kseniia-sumarokova.jpg deleted file mode 100644 index 8125d39869abbfac3362996c888589e809f8a3e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108389 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 diff --git a/website/images/photos/maksim-kita.jpg b/website/images/photos/maksim-kita.jpg deleted file mode 100644 index d740059b71a6484f952d984ffb18aa629768a2d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102345 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 |Nz)PoYhqy&n)HQ(Vrk&*bO zMn*@ZIT;>@sh8E6WAK;8B&ixDf;1?Bt4t^bLjcVK?YV*^QZUuXafF6cSWdOvR)835 z@wU+b&lK*lxD&4>DgvyQX}l7vg?PH16N5?>qT?8b?4Ni#VNgC~F&>@diefDgNF0ol z$V@@dtW2e1z(#Awz&V3dFzYUoU}%Hp=jgl&a1*9Fn8FViXGjJEZ5`YQO5t3Ap&=xj zD5lO(fT@ziiqg=E-pHO9IL0)WI2%k3;xp1F@;wdz0NjsA^o);4$n;E)NXW>@^ll3b z5|YBiqE0Xzcmeb@;ZOr2dda~+NXsbbPh|3dSU@<^&S{gQ7{OZ3kJ=mE>k+ku_kEf^-h7g`M@$3WC;}{ z$XtqXKNpF#L;+?)L&eCz3V9jed9YLe0FtsjBhoz)k?9!;qdB|=(omF*EJD5NMvGC1 zP$#Yta6FirXe0q*5yT}qB1u9888$cha626giLtqMnW%`H)5<1*AjP{xZ@;kO&Ep-xKhNKSLZV#91MEK>)7Q9qAP3h0vP;jPcZ0lhbdNq{8j zLJD%5#plcOqBjBgM+57Z2*i&v0fWw!!MI~jcq}_Lu$A3# z{jOmRS2dK`AliK=sniR2)UyiG0fI`YKc@0O7|-v=tYd`8$jHdV$i~?sTinOF@;pWw zvm%84`6er4a$%pt2GoGS1u+Cr^HDeh2@8^&m}nQ2!lo^&!G)`c#L!9-Lr!o3(-Tv4 zcn(UK`3x-usq)xqiyth2XtKc8I*p|FPOt6}k(+iirPcDHZib!rp z$IertUdgXXDA`$5<~If_B(;dKwkE7AW(1kpRF+(Wc|w~bx=hytlFVe#wWeaaK$$vb zW0&iKA)pjk^*v+KGBLz@21ZAWj9?9FZ)k{NC~FOXbbM_N7;-Tn72K*qN2PWu=L*kM zO|i%{Tt{rJ;V0Z0gq*=EiHQ_+FcXj+{NybmrebO(KMYi4#i$}yOdy7fJ0~G{(xIt_ z5W<{>4o|-gkAMR7kogBnWSLPJArXe7NL{17!Pq z!3xQe^4?AWv3(F>-b5v$K~dAf2qUgo#=|7nfuwWC7`6?@hnLxv`_0t!FFO~lGxe8> zlP9-i8X)%bh6F8Zt6Mc&!Aa#s33zN{9@t4;3Fq8>u)aX-kMTyZ!6rt5$U)qmm{$Up zQ&ZWbTdBjt3n?Vf$LoSF!K|CQ2 zaHvNvLx0`-)A*?Mk4VVK^l~$?fl(3-5TiU{45wiI5t|B_crX+LThLrmfP0wYw7x=p z_N?L|JbNl($zZ|oMh91dWA@6CEk}6&S%|OO3ERBUck`!!uZwoAfTLY&AxJ z0|LDpr_#1UVb6dYqXeRm;a>=5k$q}W!kIpBP&`DYI`oV`5X8jD$jHRV?*Noww$s65 zt-N%N$%g%LU>N~{V8#RD>d;hZmy+d8gkdNarUcSxCleY>@uzlTVqVwxi7TisNbG?+ zoURF|c`%F|=3p-S!azV8j>euFpKO*WnFo(M;H*s+mRh?Kf{6Y�iK9()wIS>v^HX z1IZ*tY)emX2!$%p7--2OC@y-DWElTbpS?uTA03-mM zExu07Z(&E+Kd+odNJ$TXgYm2*BxlI_!$BNE6#@nsPCyJNe2~N3uO^{L!;h1OF9gzn zOC;?HgDWzKFPUXPSVDa&8c?7h8wYdfei7=PucBaJWEA(;*?{dq(oH7;LJ@Lyg=6Q^ zfE$@c3^cF0BAwVzo5|Z#8PwLeV?deSIiQGT);zNoqz&Qhmk!n>o1GZ5wlJmMP8Q#{ zzOYyU_papkE>8mX zCc9YY0uU;=Q1KepC=yu>RPt3Ue)71Ti*!oX(^%45h?nBud@Fk?;0ES?C*LDA2V(=h zg4yEabdr!i1jG`}8aEIgl-(4tY=Of}lXf&4YK$7qCYzTm>jeTP-`dL>*eT#4^_a-R z@NzOeBOFYOjMEb_9RNYBp-7-qje>+5zLHuY_l$9lu?OhM1j?L>)y46=^Fm%M1rE$K zmkXr%PKsu<%;JwOqJb^C<>0BLLoFMD+)wKxA}%0

0+T!l@zOhM&AH1e-x^-(3p7 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�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 PEt(<4#2~jUYux|YU#@^V diff --git a/website/images/photos/marcel-birkner.jpg b/website/images/photos/marcel-birkner.jpg deleted file mode 100644 index 6ec821cfb6614aabc9a41828e84a1eed202af39d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22130 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 NFwZ(ZaQ?p^{|^eSS^fY3 diff --git a/website/images/photos/mark-zitnik.jpg b/website/images/photos/mark-zitnik.jpg deleted file mode 100644 index 7ffc5105258d3b5819acb561b331cb5add24dac9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16076 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 T&4t8ECILieu@-Nmf7ky%`~|YK diff --git a/website/images/photos/martin-choluj.jpg b/website/images/photos/martin-choluj.jpg deleted file mode 100644 index 200685ab2feccb3d7e6e1eee5f1efee90920a84d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20690 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 diff --git a/website/images/photos/miel-donkers.jpg b/website/images/photos/miel-donkers.jpg deleted file mode 100644 index 8d67c2440b1ce3cc165d1c304b023ea5385559a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14642 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 ?aMLA1vXlt zVo?#{UmxO?2lcEuaf_CmLD?sly3vp#eb76o#zSZz9cFO z4wjru#z+I`l?aH;ivfdMca$}XPg%dK?|s>2RjBxsL%%#biI4M$5?NH3y)ZRff~rr- zg)XTrW(4g0ta)1PLdhKkdFq>*oQX2r7&{IdZG%f(^TT^7ZMYbXL*9iVO4qEtNBAe-kn6(~x?0 z)cV5Q@@L7s+ThhWpT{@$Mn(S?OxQ8NRO!~4u4v5ebfm_0uyEJ0R^H$vB;O>A8aCMA z2Nh5id(=7_S@$ka(mf`;A<=0bDKwkaLshE}IYy0ZdzFAE~?S$St2Rc3m( z6`90jwz?*C!ZN38r+8^uqscOAO`{3q%68iFP}jwqUC#Opmlap1cho%ScQ52U@P)v2 zAarQaXoi?jat&w3QJOVllpN*F3JRxGfcn!11xUkBQri4cnoSI%D~Jdm4VPPM9t{@> zba4|FrK-;MkI98!d+-meBLTt$=T)@@<|3u{9Xi7gCs*l5=&Lg36EZlhnRggj`miB= zIaQ-k3J0xcu^Up3-|k`kd)-MVsN=Z(T;uMXeLeN7I|qPti|1Hat64UGY6`|(bz0M- z9~@xPT@9ieI+GvFhaUHS)@M7`@r{Z@wn~ssqA-9COg;bz4w-Qu0NgD W-+LJeh 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 diff --git a/website/images/photos/mike-volpi.jpg b/website/images/photos/mike-volpi.jpg deleted file mode 100644 index 777563497590c3d45e9c7660d6f4bbe635fe0c7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25414 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

    J}D|vP&%vir5un*uA`a+!|g20igwVeD; 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 diff --git a/website/images/photos/niek-lok.jpg b/website/images/photos/niek-lok.jpg deleted file mode 100644 index bfa2d8895653445ab5efed7c0745221efe0024f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19205 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 diff --git a/website/images/photos/nihat-hosgur.jpg b/website/images/photos/nihat-hosgur.jpg deleted file mode 100644 index fb3dd4517131ecd3cf3ce0c0d77cdee12ac31fb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25938 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 diff --git a/website/images/photos/nikita-mikhailov.jpg b/website/images/photos/nikita-mikhailov.jpg deleted file mode 100644 index d956d334798af80eb8348f57c376fafed50ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88588 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 CYRUru diff --git a/website/images/photos/nikolay-degterinsky.jpg b/website/images/photos/nikolay-degterinsky.jpg deleted file mode 100644 index 620c2d83f5118d8d6f03fd5fea857ad64ab7b47b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31782 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 diff --git a/website/images/photos/nir-peled.jpg b/website/images/photos/nir-peled.jpg deleted file mode 100644 index a8952465164082c6de9b79e095fa814d56627491..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16177 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 z

    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 z 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 diff --git a/website/images/photos/peter-fenton.jpg b/website/images/photos/peter-fenton.jpg deleted file mode 100644 index b84f25e703e8d8b40c15fd6a95d6966212285e2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12028 zcma)hXIN8B({K`cPw1hB&^v^n(gTFvJ3%^$bdVC7Afa~%RZxo32^|3eZ$;@vQ9u+_ zRGL&35xfOK-sscbAMf*B-<~Tc=j`mv&dg5PopZi;z5?LZi}Jk!02mob0cZjLY3F+Y z2paF@9}NHkD9PRG0KoYQg}hIA_*GST`Jga44{Wd}PR=VhP(I4zs=T6{f;>Q7E9$C; zmp?8X=85z64bp&rxjzVp`C>KT4$5WFVfSMC$4(E1?m3mlRd> zmHvf`4)($a;)25eg^T?UF7m(P{x*letK^x{xKQ5+99BOxI1u(nYE|F=&WqxIrT0%< z?0@G4@n3P}$;`2z0745y0Z|k{2mk~DQb2&`BY*@le^fvq z@IMiN5(uK8q6W~=0s$0c!2f~($Ph{@YA^su`RAenfGB>0fdB{@Af(7jrELXgqxJ|% zK(N!~3qQ8z(1HA+KpsUw9{XnwAOs}DN};IDMrj52V5d5N4PYdLK@bWEKnrmEP6$S% z1a$#1)E`m8B776fSP%k8ixP&Z5NRhUu{0s#Llns(CV(xvF{Zc9=$(@07h8B01hcO2 zV$A$AvnXck?^2lORP2kYcCGjgm>Hrm2_w5$eqVCflh&2Lm>Nq!GXs%V-*Fm_Hu@AX=aL zRdV903`Bcwj?ttjTal7W(PO6!s`fFwJ)AN&l()Dbeh3t61Fe)L>BDCR`dLDq6kW>Z zUNt4ysp@T*T^A+hGd5efhdGa}Adra!9Wc{xrXl}O4T7TJH^)Gzf)3auiX=jS@lB>7 zfEd_E?S3L3S$PRi;K+q7+6I44o;tr;&Aae-?0y~BHD>eDuo89{rF~*g+S4SX)a=McHW>jGnS2Q@&APWFyeOeywtqkK)eOG8gNLN2* z5jX$`n`_)WJ@^eN_h92;5M z>vKPaJxY+bZB>_vdDGb}e5s7Gm_CubX0$p$DDcT|8-uC*k@T`I&d89B=C|2f zGh?ApKutI$hLaY(OVYMcp>y026{JB#<*=p(Ib0$L*BqO|2_0KTG()JbykqOcBnub! z1h}#xZ97}lfVEE^?QF6ZV-QF*-GaK+x6%T#pX)sSL{H z0DM!PadVV)9$RlU$4cgxe^`0eWyK&qdTJ^N|1k0#91yMRfSuGrmNH!4eQ=HuHh#o4 z=Tp@y$ANY%H{Q1W+F`IyN^g5;(-rIesq`Fh*g!onGw>|0=%YAm<)WfRSpqj#LcC>n z1!9+oNZ=)D*TNuVX?4KAEfE5xMuT9o?yZuZgeY1nveiRs@l~q|7JP0XJZA z4>SEbsluU?r!0~-7Cg1wtdC`p{q_zL=_5Ys^&l6v+DA*2R)xWwFa(o~T+3*$gofro zY0{I+ zuTM)PO{%=H#~R5!=vv-P&GA)y!QigZlT_# zR-!yUxl9Sv=$7uE3X|7~I#RjTa0%YXEa& ziV@a-IU0z?Ui#_@A(f|=PKy`uog~)S?Qov@S}c#&ZD`2b>FbN;-@0M3bg`(fL>~dz zDsz9EN!1gU5y~nJh>w`6_A7T4H?uFCR&9CMuRkocR{MZ0gVfwSl~UVjTevYZb@n(j zwBdHGqWzP91TDmGU;iC+;p{v!^m8{QXb6Nb7)Jp)3YaFaTKZjXPz${@?qa*3O(ZF! zDNc+0P0cKOhdu<9R0>U`oxQHnVabF$rG<0yN*J93=#q9D@|lswv+!rEmhIYt@&t z8VBLdXj7wu>cjqT82mW^Q^a!iZsTIBuZp9|opBz0iPwPT=cockNAW#ZQp;fL=h>?{ zW*b#|nuCGp8AW#v^ZaNa9mce;>WBR^FgEkagOvn}o4L1jmGr)TvpRB8dWVQySvO>DFpAZD)%xsB1G1=6&MnR#u*BYJy z!HpAK3bSSllU&^I-Le7Sg*kqxR@Tz_Qto$K?E*5RDil5dlzD2{Gv9Jg1T-*;F8i!C znoyIzsrH%QWn=gCqI-{L8!vC*zGgR5hM(5L3nGy`DW;@*VqHAqS9?Bo?y!vJ-{Z|A zHc14G+yF6e4_QWVf9&7kEa=(+VdwO<|B?3p>;Z(dTilWMMYFVYD6*qap~6If5wO!3 zc01IMGBNg>Lo?l3u7xZgAIh)q+}-K^h&;tR@;}IK!Rc6K O+G}3Ndb)m572wo9rP!$>??3XYpeMo;H(u$a1Ru#3-#enoP zeWwzwD2DX26J+VBsRjjPtRZHv@f=tim~NjjgTS@L!`@Y_A?;5WUuouk?{A%hp9)8v zTV#})%RPKaUb>x5Q!|gF@d>9^bd{gsya(Oc1J)2yJywWX2aNu`EdujdG6_+4CU(~o zP{tr~N?E6Y0iO{+*e)EjjfBbG!z@g^jf-Trv+Rmt>l!9)f0UJ}5*9uO(Bo^@k(~{a zsc-B28l-ttxAl30inh+E>=FuXzm1J-%%)Y&_Rh($~w>9dG9j6?qs~@ehr01*) zyVN&p^uak{?`pwu!s_~UYfktCtKxFLF(s>*dk{p@k*#k>YNBo=?j31OO(6QsQR4kN zD6>ekoiuZGwWAelyfnsfF~VHUcFnq#Y48bg@P>M_t zgQE9EO->E_w<8tT6^;SH(sIo~UT!&gEXg2F&VhA~ysR0s74z zs4%k7%g}Ux0;x-#T81mpI(*}0gtsXzy{+godGn_0s!q5IPKR9p1l(vqPRemkQJ;F|6G{rur!dahz zG~_usWiM7!xZ4$iG^yalPR=J3`envTRZ0-Gu2GHA7htv(?7z-DR>|niMKXr_~ACtgJ&cXEYS7Ujzb|LaESjUYIB?&21GT zb8G+TnRNq?dUVDdWh~XxSG&#m%Q%WQ#M z4-K<~&{|EzNs80Vm_>hSHkNv#ry|=m!fhN{blh^1$$K!8p~*Sdv8}rVkyeq#c|GSe zBcHmm3BGiRU?Q@f){<&?s2W0E)=|JA=r%vgI%@;hStP{hw9R$U#rc=qK$Ag6=FRyu zSDKm2IF3QdEn(EI)cc!#!^EgMr_l|)YKomYE3YgCz`~`d?(tq=k6mM@uU`Jz4#{z5 z+W^K+#)y88YI62c#L#7f$pM+!XO04nWD5*$ z8NKFP*t9m0WBcKGA=Q#mjiMTi7w44)&XzT35gHMr_Y>s3Uo;)gG=J6IitUB>HPbi| z=5g$-7v0-6qBxkOxTD0DeA{uaH(QmW%9OCN;y=y2`rtAG0j!0AZ}V6(Nsz_2RhJS1 zF9DI3EVRrhiMQ~S3KeHzFqeGD!l=(cnsS(?t^Hqh6PlY=mk$crFu9jyb{%_3IyULK z*>~EN$vj!DrG41O6 zw1LpRl2FibpE|EZ|snM>JbHTYos39wg2Tc=%=@1lSk! zUb*E-avfTEVM6M5w-wVj21FdXzJZwpae6*Gixm|LUE}R<+E1m^yG@h(wxHIEjK`W8 zcDg?fvWhsuDWcJ-aHVt}k{+yA(>O#TXo@6^0c653S6)!`i4U2euF*cZyOUpr``~9g z&m{9YT4|>wi|N;hii&CtgXMeQqVj-ZOX>8u9z!3QYK)Yh=9<8TH4}@_zVUmN8xJtl zGz-3bFXc2_=~KgTuq+ohy5inFH5DB=$K$bSX@Ts|f6agS;r?}EZ4A9LpsBoe4j8Ob z9q!tS9uWe}{m}<)S_)c>7bcboTv4#45dq=M8aQqQMm7H`_$DXx-7$RUxVrV zNHKx9+3Fkf+SHS=o6LFE=@rIiChy#E?7A$*3*+nU%$#Z;mM~Jbargh1F105URB4Uzd8eCL*vCs*Kt+EvubN<=fn&36Kz$? z?Y-j1tag6N;%?ss=6<4*MZAt}{2EF!%gdX9lJ?rDrn;cmv3$5@M)W23>__Xm?2a+G zh#7lDo|roNS;feC+kIa%PxGJ2?wO};GA=qtHkI$`4lPRJ^%?FBbv>!HOAGA@n^=g` z+WETxA)5gSWW}Ez`^rB~n;g%boY>viJO^y|y%!KMe6BiJbntEP{f}=f40krj&ZGmZ zBP*@42Mxbl4T{<$M455`CxA+R`M!X=pBkjiGqK!l*J~O!oo?TeRe>1v)sojFAx7y2qHpc-iyakTFXRYL&Ap7>Kg@=%8E|sE1k4X9FN_O?N-EyX=WUqw zzIhI4zIRx2(tP}@FaEvO&Vk+Y1KNN3BOBQt?MuzrDU-0|i0l|mux=199({mkdn|HM z%()^GzZ3SgGIOS3DLP@_rY$JeKJx;1+M(rp>-n2T0>(E(TSD?YV#kLmPHJo~d}&zs zW{!@eAHN~-UEnrCvuV}?bxTPYt|jrk>4?Y}qTr$F*qd>2L%`he?&#+kHT}>C8_!KM zd8_Z4VxB6UHnGUfrpo~g-ubkpTzJ*iEbWCU%TT};!ZH~>;)tM|{N3s`w+p3zO~2aJGCd87`O@T(n+qS#WU z=dap4e!ot6z5hm@zpW`@DKt){ZBp3aCF4}JaJUi0)Qn$CG2XOJ=r->9#F1{k0N`P5 z)50q0j1I3#K6mPiTUeKKinrHxuj&5?PPRmsRI0&5$fqJ16GM20{r-h1 z0z6#0uxVpAHs~O++-M;faZ#7~$5d}JQ*no7;|&(UwGO(yZM)Fk$)B<=fo}Ya0XAZ7 zZyAcNoqis`6!uy*KVlxxj8}0UbZgC2+c!+PU~Jtp@sVF;SH48x46_nqVU-r-id1+T z7UZE;;ReEc@9MrgGcK39<6bI9G3x#58Gn_0-L}aYg#|B3rZe!02G@4kcjMBGiX!il zgMc8RqNDq*qGs#NcGcdtJ{|nl^%sAR{j%OEI%RtMDhnI8GM;J@5^HBJVCHzBNHY6j zQMu;Y_o8IY^P@%aYp>ckE!nUTev!Y?ScWWn9Xc(<7;(5i^9&dlT%1j=dc$G5FPV6pu$m=o`C z86U#N10KNbKfATzlHvI4Z^+%2bHEMPAH2&m$hESEkL|gXzm31WFI@5VvUt=;$Jq@B zHsO(TfW`IY1v>jpA;aHUow|r(cgcz_(L>#mg)XUp8Rd#sVr$JyNt@Gn5r1lN+orY_6OGB$d8~e9FHd z5V(R{cx77p^t&>%vosT+>n_c%?h(&3eszhv(+yRS{(8k98uW8$y!Pu4H;f;jVEvBO zMN_UouQL_!Ub17QkCRi+lgLFJWPK|R+x0X)F-%=%`{=NvT6vSGOxawnA2}fPu(veO zDH_t7l|n5a|yH(q%}FeG!C@7JA2k3X2rs}{@U#jj}pJ@ef2$iZl_6X>AncYQhLtN-To-USzx z3eybfHgyI(t9v@7B!w|K;P;S(z>*0N2|5sPf*A#Uxp8_C5FvGK^K$I9qgEFXm(@wi z}n+ATf7?OIQ*`D zMNk0v*=i0oS@WN#>KAVaF;=h&e^eH*57e++q?3D6EI-0I0iLpezI&_IxFcu5Se&aQ ze9|zj`6gE2p0w0JRyTmbYTQ~@ZbaFB+?~T?dSfkqH?EKG>aJQ<=={cX)!Cp0_d)UV zgQqAV)IThfxuw}KJxaSFcS7X$?J0sS4QO0&+Wf-2kY4Jv#NFf|yaIUsiWV`eaE&&t|HZ zls>xOI`YwHLa8h7l(ehx^Nw0!{nuS>9(d53OHM@$-}4qy<8_xP6CdI+cSFY4;CS!9K^3N# zxCpAOe2gc3F)edaNw;J|32Fa!R)oDBIGr5`Rwai|8cG^+P2R#Sn)KtwK|}va`}kk2 z6&SvqgoD0;n3sDlD<;4C9`3@OJ+wcvEf_wmwj|K^*yMLR8l8&QMXSNlqxpXQ8%B$$ z(#aP{tdcBf4%7t2+sE-Izu{b#+BzQF<&3!b2(1QJvw-dn023GQAEUi-h6ogMi@DyK zH)eFCcR$-Kare{A{Bz0gkIzDLni0*=;+ewh5uzUt+&MS}VV63WrIOk51gDIPG+G}% z)=O+wl3Cp=u9j4KeN}#7hNP4@@u~u9@*~5qitf{vqf4*Qa7Ol6LUjwXbE}j?DjNg9W^BiYQK9`U$VX}t-`Tf#O>6z4- z)9G|*?@-&)!1yt~iJ76t0jl%UJpCtf7PT>hn)gcbI435wZ)hTOs41NjS^c?ey$(EW z>~rgHBkA*4rxdb{3a~Sg{cz>!%t9|YF>4W$ch>tBd%Acj|M-d+uFGkX_FsNVZ&p~u ztume(>6_~~6os_bFW&Pbz5A+>*$Es=GT!8;JV+^kvNgl3dU;9o)SqV(-|4 zlyBJgm8}ytd{qr%^PrHbqQ#}ur2q`k8Z^}bY%*@Lx^YpJqxe$mUzH;4NwV)Ajb(}R z(~`@>(H4=z&n%YJVohY~FI2)h79!1spPd6<@7~J1K0YurlYi0CP?KW@>vi}O|963_%yPZ zTt4;@p>ggR)N0MMhGy_K2J`S#GwMn~E-4aT={YsBDZxTytQWi#-_k9CGHfvAS$Amj z;Ui+>r2doCKi%JDK#gM_TXp}{mKSH0*A+)Yhuq;uO{*A;$u*O=nV#o> zV&yhC=il~ELh@H_H>wRE?)oX~zHcROrwgf))~apqns;ReW|iPMxjsnyRUkG5S8Mh+ z_1Z77zAbI2OVsLyF3-Q9n4DhEGN&ZxOg(b~Ca+l}U^5KLcMW(b`RoP$$Jdz|^{@cJ zf(HUx0vF@&(EGyOQz&(a%H&USIs^}Gp%#*mf&_A3Rm?~S^?_gu1)FPUK7LxMeyGa9 z|E=|*mYdV7LyYGzbYS(f*Yma3gZKE08T!)VXdlH^-|HNn^DZ#m0T$x77UM2sodxP> zzKQ*mi6;>i?tLc1%_*-Gty0V_E*Hs=40M{gF?rg%zuq`^dWxMc!RIiqmkYwLi!zB~ z@I3}ui~PRNWClLBZhg40e;`IY)`&?P$EEF9M+kV(F)Bntr^*=f7JSH zsHE4{N!Q--XmTds6Kg)PUs6C@gl7ns<%Mr^73BoPiak4~3sK5A6>_@$*~H87SBhr5 zN&9$C#OqDRc+nWX>+10hTX1q65{rTZ8{zEGIyiEVkl59jtPNDZU@De7NkQaG3MJnN zgIkDfLzb@fp9Yb~1N(3B8b!@SxLemo+h>1F-lD!;sAzsOG7@C7$}keGUs*e`cjv3G ztev5#u$%wlRrdi+pS z1tiTVWlW?yj$`U(X9bMK^NwVGJ+;WRPX13`6?^ID{d6`rwBIatxGOVgAZ&_^uE%#F z#d4inssMrqT{~qjESD&Ica_QCQxKEstJI_(;JTQfSG891IRGaAeT0yrA9i2fbd@C~ zwxJADViQNMBt%MH^TwYmaHT7%&4&2 zQ~40;b>NxVUxB#1*HPi|Rqe7@_zM`dog>sX{5o1MdJvhLFBII*Zc2mG#Y>L7W{|%$ zrpoCw^qtBaQ~s9Ujyc9hF`$S9_+02x<6E1!BFlEmp(5OWtqD*aKKCue-^GsbS#ZJr z?x9ewNZdS;W(s)@Xr)?ixC|V@G#d1%e5HzC9C*Eox>a;d@JLGjrf&9Cx@+|z;sg+BZdr>gr0=YUtw&War| zLnx4gzJFne7cX2Vi%W763PzJ_AHNGiAl)IXSQnv^7dDTsHvkc(fMEKhP>|_0O||NH zshC?Fqf?9cyKKpZsvN_w((0Zr4(=j0OtV?Os>z(>M7MI%w##Lp&H)Oyw*qq8%II_^ zc6UhlG!?9xDc@geIQ2Nxe(%Svzo1#)O@HLFWr3^iOx+#-$>|Yj9RKpQQ%8H>;BgpH zqP*v3dE6~2;6qH&neO1vvU?@qqN*8EE!29!02llC1KRv^RbzQ}m5w+>l2vrWp+)3H zTqO5|diEf_Y{pn&>rUf1@VE46-SCIfOU}&_(haMRzF5g zPogpj`;K8mYRG2u9dCA|Z|mxT1TUw87IOBigqjY3lB83?9GcteogL~ zb)yv&Nt%mjmRW)4y)RZday&uua}Xv^+%0_Z7wdu+N84z@C7ocWj`15kQ(%+8b&M3q zEtngO#Jm715fhj{=Ow82Gh3c}!ct+-wAC9C(6RxA@rM`vJZXa&hW2z9)b> z!@)vcl6;ok&N;jV=gV>N|Jo+_J*1thi#7QPuFoY;#?37f`c>+-0 z)enK>znWV8lHIPmd03(M=zdOO$S+Y{69<=gPpu3&_Qmds2r-g>TqkXQ*%^%7?73F$p28|aPq}{*evlAe)#VU2PQ_^I) z3u)CMoO?^ODW7RHK}g!nt`KqMS#`OA;f5k@ZA{~*yJ&ZmR4s1VdX^!yjUsB`zKvK< z6uUkt8Dr8=_QCe{r3O2wRv-TBAgg=Y^P0SuZoZu#;&Izgp=?FEe)lhJub*9-s%|Kf zWxzPL0XHqBeSb{-xBAT+LhTSeF=X5AP zBCPgza;HRms!MY(H-D3&5!UJacXVr{?srqyp*s{}9YI`zv0nE#4~yd%o??wfue_A) zRPu6X8E|}6Sz>1|CLo1xX=V)YKmeB+k_?LyRso@vMdERCvAHXTNz~2(nIa$!R^qrAR+7B*86t4 zG@i>rSZm=NVDl<-6}ey*IO5|omd>+Ro`bxnBU{Y@{dG_o^=*F0N@F}Pt^p(#HX9!1 z8b7CiiK^n81O`40J(`N4MYdJO^DPTsUqio)_Fh;JhN2&4$}E`V>7RUA zx>JVs2r6Ns-}sUp5&P@r+!_{{#Lw>K>y+EJnZ7WUxwYE1Ei=!1Y~vNxH>O@Kaw0C) zOs&J0n>i=NF{U*gu4@Q0Z)+?JA@YdbVpJ;fbPG=-l(>3+)GMkcN1~gvBK#1{>zVZk z2(z25mln?X(><}Lf|)wYm+_?pFh)a#M?DHaLD@+@Dy=fIS(UteTwvdH-%$fWYhfYr zQ%`rjYNvN&@-R>9lhmA^@6)hIz?z^3!`&M!S0E&y4sA7>-LizsZ`3TaSX`lc)W?uA zN1cssr3+gk3i#U;@>>sdhAPjQ$`@5ZcjbPWvxx@YYO2o>c8Lr91=W$8mah%|`p`8& zZ$8^gd^T)q_b!k5t&TA8HlAzTEQ0?F|KC<#4VACBZDfC-{r7t6-2tzq%&NsNDTwSO zr49Y)qpuU*>s+hP>~4M3yKW2?)0^}5%j=kli}u|r_V!m>{NDbFihJg;IXP&uBSzHN z%ng4Q>jj3s*fn)o^`y-yQ_A_=>N|a7>)WsA zzM}0~ZP(8M9O0=M(s%R5X?$k3;Hp4NJ_dHED1xERT~o-b)p`_R##BhBZJN17DXO3= zlE4orCsJT9*NEA^BwY2szH8d`LNV=k8CA?q|HuNiUdM;)uwn*)BW|+ z(wB6Pw#r*hHhCUabsS4)gb(DXe5xIOT5g!3D%l#n9hO&s9jR9Sp13znNZU~0`iZ@k zpgql&{Y~nf9un`_-ubCHS%mQ%fL=U$iGWr-DD1LKh&*UY*)<&<>Rk%0D!!0@e{>|7 z8$piSyPSOlZecD?4qdbqU1OKjx&>QJU4zw(f$f-tT3Ju1j{o$pX(h?0{(Q?F{N0a4 zVp^jro!^_RTt4-4DgSjz^^BRJHYl71L~xS?VY+}{9eYh!%WP*Q3s5#x;Sf3$vIrEa zT_(9zW*KQV<*TE^ILw}?GAgV_`}Hrusmkj9vlw{;Z&i5T{j->O>6NRxDNMeSGxu17 z`MtKfD;@EOc7b`d z20vjq)%|&*EEc$SXs0h;csx-e`5FGnX*K+mdqTyg0lq0n3*h!CSQj*xiBs0u$8i86 zbZ$>PCi?oN4%D`SFLg07tphAg$FF&J84b0||Ku1F!;-%z4}7Z}ItLWOdS?cra#qpy1m(lfc*NYegl2adt$cZT7^oT i{<_h@K<(AqjtAPlr-)yck~TzI;w3T3E^UMJrT+n~XV0(z diff --git a/website/images/photos/placeholder.png b/website/images/photos/placeholder.png deleted file mode 100644 index 820802d67a25d7366635cb7dc2b4d29cc35859cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2037 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%0AKR(2VfeIHO?>2btl%Yc2Ha?%MVI>CEePZ9Y}qPO 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 IJ3gup{y;WHOh>9z`D{HqOhc-~CUv|e-5}yz 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

    k%Dm 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 diff --git a/website/images/photos/ryadh-dahimene.jpg b/website/images/photos/ryadh-dahimene.jpg deleted file mode 100644 index 622ad8a7beed6f3ed43259f2f8992852609320f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27217 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

    3k|*hom_})fu*z#bvD{qYhbExd9j?F&%X)_h{F# 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

  1. bo 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 HTOa?~NR~Zl diff --git a/website/images/photos/tanya-bragin.jpg b/website/images/photos/tanya-bragin.jpg deleted file mode 100644 index 0b5a6972b018b27fb2c0309f03faec940285a933..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17216 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 diff --git a/website/images/photos/thom-oconnor.jpg b/website/images/photos/thom-oconnor.jpg deleted file mode 100644 index e44ece64548748bf76cea3d03fe1741522c9c6de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28116 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%0DDsADS}ZqD~AbYa*znl8~}mTVJC0nP3S>RMMH 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 diff --git a/website/images/photos/tyler-hannan.jpg b/website/images/photos/tyler-hannan.jpg deleted file mode 100644 index e14d9544bfe4f9d960c26428871091a2190ae012..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27020 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 he{~BVh_DaqrR1i?X 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

    %P?E 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

    5NAW{6YH8$rZRYyn>+G#F+Z0~R{t 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

    UutD9JkOpjP)M<=%U264Ey(- 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 diff --git a/website/images/photos/yossi-kahlon.jpg b/website/images/photos/yossi-kahlon.jpg deleted file mode 100644 index 4a9672f99bcc830acc9343c259365e7ba6a89746..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13330 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 diff --git a/website/images/photos/yuko-takagi.jpg b/website/images/photos/yuko-takagi.jpg deleted file mode 100644 index eb44e4142564d4fe72b123abd69fff8f206b266c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21425 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 OQ#IRv$AbF*p86jdn5Ad{ diff --git a/website/images/photos/yury-izrailevsky.jpg b/website/images/photos/yury-izrailevsky.jpg deleted file mode 100644 index 0115a248e1e9da9ae9de8b779a629c3bbf5b25fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47756 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 Holp7`XyWe} diff --git a/website/images/placeholder-newsletter-form.png b/website/images/placeholder-newsletter-form.png deleted file mode 100644 index dc487941782c4be1a2f30647879f60cf20f70cca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5410 zcmX9?2{cp<8@~3KB+A&ylrJ(@NsEx|+sGO*L)j)XvhTYI!!TtxBKy9S#I+5Nm0RStS5w{*= zVVomsl|TSs;x#e6qkVLAL?93%2;?9FDLJ{)KO)XID8?@|$~Pp+%P-P9;E}gC!7G3f zMEV3q1_Tq`f{30$k^cS!U*9NqzbKD@2v`3wSHCc~{~!M0uD)T6jF*3ut6#XAU%0D( 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≧X0)xh1aCpizOP9FQIBKj~>gO(yZDTDJ&Ip2Y#kw%J%u5*D zfDY!8O+Go$)Ma1QlbA}Zbxk4`VGEIA=##jOI^x~JPD(&s$t+dXprRn;Ob6X5NTPOK zX-x`nh7J#y@ecS>Ii2G^6?VlrM`6yph}%H^D|cc!zW zLaso;){|kX^_GTmBMa@TyXANT%82$NH zgl*qCE%x&b^G#^jT(X@bn>D)X2qL4UlBDwIZ(&FSHuoQJ7L9~u2_-1n@90vV!aYzD z0!t|mN=CjL_;z>l^=hDl8}_$cCFLxck@_9detB{nAqeC>3i>`p6?_Dalx#3trI@+= zHgXQ~uHJIu#BsW9G+A-pO8S><)=7E4K~T10wiaa|yNI_#L(jxiNpV(iMUa^aHdwx`T zveTHtV0Ir`XS$EonR;MD4e)%up|v7?b~X zu?4P}waljSHmZvVY%*_MZZ96Y7oC%HsuK2yNl7q3Mh>nDX1xrOyxj39MlarMW*9MJ z=>*`;bl-K52S1&?q$VOG5=TnE&7CSVt!2IPk7R#CD)5Q345<M%Qo$wA$Z(aRegPnM^!H V@Ilp5RK|ZjKwsNPt6IY@@_(ORE+PN` diff --git a/website/images/welcome-home.png b/website/images/welcome-home.png deleted file mode 100644 index 7bbcc2c612fe00ab941e1a2b12b6fbaf85cdb2b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31219 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 F{{eTj Date: Sun, 7 Aug 2022 14:17:42 +0200 Subject: [PATCH 372/672] Update src/Storages/StorageFile.cpp --- src/Storages/StorageFile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Storages/StorageFile.cpp b/src/Storages/StorageFile.cpp index 4f9296ddbb3..8dcd9c6e914 100644 --- a/src/Storages/StorageFile.cpp +++ b/src/Storages/StorageFile.cpp @@ -694,13 +694,13 @@ Pipe StorageFile::read( if (fetch_columns.empty()) fetch_columns.push_back(ExpressionActions::getSmallestColumn(storage_snapshot->metadata->getColumns().getAllPhysical())); columns_description = storage_snapshot->getDescriptionForColumns(fetch_columns); - block_for_format = storage_snapshot->getSampleBlockForColumns(columns_description.getNamesOfPhysical()); } else { columns_description = storage_snapshot->metadata->getColumns(); - block_for_format = storage_snapshot->getSampleBlockForColumns(columns_description.getNamesOfPhysical()); } + + block_for_format = storage_snapshot->getSampleBlockForColumns(columns_description.getNamesOfPhysical()); /// In case of reading from fd we have to check whether we have already created /// the read buffer from it in Storage constructor (for schema inference) or not. From 254d14b1d360eb873ffb055bba28aeef463e54d8 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Sun, 7 Aug 2022 13:12:11 +0000 Subject: [PATCH 373/672] Remove debug trace from DistinctStep + this information can be seen later via EXPLAIN PLAN --- src/Processors/QueryPlan/DistinctStep.cpp | 28 +---------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/Processors/QueryPlan/DistinctStep.cpp b/src/Processors/QueryPlan/DistinctStep.cpp index c268cb44267..aa0e66b4b84 100644 --- a/src/Processors/QueryPlan/DistinctStep.cpp +++ b/src/Processors/QueryPlan/DistinctStep.cpp @@ -52,28 +52,6 @@ static SortDescription getSortDescription(const SortDescription & input_sort_des return distinct_sort_desc; } -static Poco::Logger * getLogger() -{ - static Poco::Logger & logger = Poco::Logger::get("DistinctStep"); - return &logger; -} - -static String dumpColumnNames(const Names & columns) -{ - WriteBufferFromOwnString wb; - bool first = true; - - for (const auto & name : columns) - { - if (!first) - wb << ", "; - first = false; - - wb << name; - } - return wb.str(); -} - DistinctStep::DistinctStep( const DataStream & input_stream_, const SizeLimits & set_size_limits_, @@ -112,11 +90,7 @@ void DistinctStep::transformPipeline(QueryPipelineBuilder & pipeline, const Buil if (optimize_distinct_in_order) { - LOG_DEBUG(getLogger(), "Input sort description ({}): {}", input_stream.sort_description.size(), dumpSortDescription(input_stream.sort_description)); - LOG_DEBUG(getLogger(), "Distinct columns ({}): {}", columns.size(), dumpColumnNames(columns)); - SortDescription distinct_sort_desc = getSortDescription(input_stream.sort_description, columns); - LOG_DEBUG(getLogger(), "Distinct sort description ({}): {}", distinct_sort_desc.size(), dumpSortDescription(distinct_sort_desc)); - + const SortDescription distinct_sort_desc = getSortDescription(input_stream.sort_description, columns); if (!distinct_sort_desc.empty()) { /// pre-distinct for sorted chunks From 4ddb341cc951174e3f54991c8a812271ca9e0dd5 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Sun, 7 Aug 2022 16:29:23 +0200 Subject: [PATCH 374/672] IAST destructor intrusive list --- src/Parsers/IAST.cpp | 98 +++++++++++++++++++++++++++++++++----------- src/Parsers/IAST.h | 7 +++- 2 files changed, 80 insertions(+), 25 deletions(-) diff --git a/src/Parsers/IAST.cpp b/src/Parsers/IAST.cpp index 72fdbd924f2..600e90e25d2 100644 --- a/src/Parsers/IAST.cpp +++ b/src/Parsers/IAST.cpp @@ -28,34 +28,86 @@ const char * IAST::hilite_none = "\033[0m"; IAST::~IAST() { - /// If deleter was set, move our children to it. - /// Will avoid recursive destruction. - if (deleter) - { - deleter->push_back(std::move(children)); - return; - } + /** Create intrusive linked list of children to delete. + * Each ASTPtr child contains pointer to next child to delete. + */ + ASTPtr delete_list_head_holder = nullptr; + bool delete_directly = next_to_delete_list_head == nullptr; + ASTPtr & delete_list_head_reference = next_to_delete_list_head ? *next_to_delete_list_head : delete_list_head_holder; - std::list queue; - queue.push_back(std::move(children)); - while (!queue.empty()) + /// Move children into intrusive list + for (auto & child : children) { - for (auto & node : queue.front()) + /** If two threads remove ASTPtr concurrently, + * it is possible that neither thead will see use_count == 1. + * It is ok. Will need one more extra stack frame in this case. + */ + if (child.use_count() != 1) + continue; + + ASTPtr child_to_delete; + child_to_delete.swap(child); + + if (!delete_list_head_reference) { - /// If two threads remove ASTPtr concurrently, - /// it is possible that neither thead will see use_count == 1. - /// It is ok. Will need one more extra stack frame in this case. - if (node.use_count() == 1) - { - /// Deleter is only set when current thread is the single owner. - /// No extra synchronisation is needed. - ASTPtr to_delete; - node.swap(to_delete); - to_delete->deleter = &queue; - } + /// Initialize list first time + delete_list_head_reference = std::move(child_to_delete); + continue; } - queue.pop_front(); + ASTPtr previous_head = std::move(delete_list_head_reference); + delete_list_head_reference = std::move(child_to_delete); + delete_list_head_reference->next_to_delete = std::move(previous_head); + } + + if (!delete_directly) + return; + + while (delete_list_head_reference) + { + auto next_node_to_delete = std::move(delete_list_head_reference->next_to_delete); + + /** Pass list head into child before destruction. + * It is important to properly handle cases where subclass has member same as one of its children. + * + * class ASTSubclass : IAST + * { + * ASTPtr first_child; /// Same as first child + * } + * + * In such case we must move children into list only in IAST destructor. + * If we try to move child to delete children into list before subclasses desruction, + * first child use count will be 2. + */ + { + ASTPtr child_to_delete; + child_to_delete.swap(delete_list_head_reference); + child_to_delete->next_to_delete_list_head = &delete_list_head_reference; + } + + /** Here we have 2 cases: + * 1. Child during deletion does not move any node into list, then just replace current head with next node to delete. + * 2. Child during deletion moved some nodes into hist, then iterate list and add next to delete node in list tail. + */ + if (delete_list_head_reference == nullptr) + { + delete_list_head_reference = std::move(next_node_to_delete); + } + else + { + ASTPtr delete_list_tail = delete_list_head_reference; + + while (delete_list_tail) + { + if (delete_list_tail->next_to_delete == nullptr) + { + delete_list_tail->next_to_delete = std::move(next_node_to_delete); + break; + } + + delete_list_tail = delete_list_tail->next_to_delete; + } + } } } diff --git a/src/Parsers/IAST.h b/src/Parsers/IAST.h index 5714a829693..e91d419acd8 100644 --- a/src/Parsers/IAST.h +++ b/src/Parsers/IAST.h @@ -275,8 +275,11 @@ public: private: size_t checkDepthImpl(size_t max_depth, size_t level) const; - /// This deleter is used in ~IAST to avoid possible stack overflow in destructor. - std::list * deleter = nullptr; + /** Forward linked list of ASTPtr to delete. + * Used in IAST destructor to avoid possible stack overflow. + */ + ASTPtr next_to_delete = nullptr; + ASTPtr * next_to_delete_list_head = nullptr; }; template From 59db59fe83869c9ae854ea1a39c6165a68fbbf25 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Sun, 7 Aug 2022 18:00:07 +0200 Subject: [PATCH 375/672] Fix style check --- src/Parsers/IAST.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parsers/IAST.cpp b/src/Parsers/IAST.cpp index 600e90e25d2..9469cf9f16d 100644 --- a/src/Parsers/IAST.cpp +++ b/src/Parsers/IAST.cpp @@ -87,7 +87,7 @@ IAST::~IAST() /** Here we have 2 cases: * 1. Child during deletion does not move any node into list, then just replace current head with next node to delete. - * 2. Child during deletion moved some nodes into hist, then iterate list and add next to delete node in list tail. + * 2. Child during deletion moved some nodes into list, then iterate list and add next to delete node in list tail. */ if (delete_list_head_reference == nullptr) { From d7bdf9e601babfa49c99f0939a864d517854dcba Mon Sep 17 00:00:00 2001 From: AlPerevyshin Date: Wed, 16 Feb 2022 16:28:12 +0300 Subject: [PATCH 376/672] Add SLRU cache --- src/Common/LRUCache.h | 22 +++-- src/Common/SLRUCache.h | 219 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+), 8 deletions(-) create mode 100644 src/Common/SLRUCache.h diff --git a/src/Common/LRUCache.h b/src/Common/LRUCache.h index c149ee184ab..fe2e56bd66c 100644 --- a/src/Common/LRUCache.h +++ b/src/Common/LRUCache.h @@ -167,12 +167,7 @@ public: void reset() { std::lock_guard lock(mutex); - queue.clear(); - cells.clear(); - insert_tokens.clear(); - current_size = 0; - hits = 0; - misses = 0; + resetImpl(); } virtual ~LRUCache() = default; @@ -193,6 +188,17 @@ protected: Cells cells TSA_GUARDED_BY(mutex); mutable std::mutex mutex; + + void resetImpl() + { + queue.clear(); + cells.clear(); + insert_tokens.clear(); + current_size = 0; + hits = 0; + misses = 0; + } + private: /// Represents pending insertion attempt. @@ -275,7 +281,7 @@ private: const WeightFunction weight_function; - MappedPtr getImpl(const Key & key) TSA_REQUIRES(mutex) + virtual MappedPtr getImpl(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) { auto it = cells.find(key); if (it == cells.end()) @@ -291,7 +297,7 @@ private: return cell.value; } - void setImpl(const Key & key, const MappedPtr & mapped) TSA_REQUIRES(mutex) + virtual void setImpl(const Key & key, const MappedPtr & mapped, [[maybe_unused]] std::lock_guard & cache_lock) { auto [it, inserted] = cells.emplace(std::piecewise_construct, std::forward_as_tuple(key), diff --git a/src/Common/SLRUCache.h b/src/Common/SLRUCache.h new file mode 100644 index 00000000000..aea62124d25 --- /dev/null +++ b/src/Common/SLRUCache.h @@ -0,0 +1,219 @@ +#include + +namespace DB +{ + +template +struct TrivialSLRUCacheWeightFunction +{ + size_t operator()(const T &) const + { + return 1; + } +}; + + +template , typename WeightFunction = TrivialSLRUCacheWeightFunction> +class SLRUCache: public LRUCache +{ +public: + using Key = TKey; + using Mapped = TMapped; + using MappedPtr = std::shared_ptr; + using Base = LRUCache; + + SLRUCache(size_t max_protected_size_, size_t max_size_) + : max_protected_size(std::max(static_cast(1), max_protected_size_)) + , max_size(std::max(max_protected_size + 1, max_size_)) + , Base(max_size) + {} + + void remove(const Key & key) + { + std::lock_guard lock(mutex); + auto it = cells.find(key); + if (it == cells.end()) + return; + auto & cell = it->second; + current_size -= cell.size; + auto & queue = cell.is_protected ? protected_queue : probationary_queue; + queue.erase(it); + cells.erase(it); + } + + size_t weight() const + { + std::lock_guard lock(mutex); + return current_size; + } + + size_t count() const + { + std::lock_guard lock(mutex); + return cells.size(); + } + + size_t maxProtectedSize() const + { + return max_protected_size; + } + + void reset() + { + std::lock_guard lock(mutex); + resetImpl(); + } + +protected: + using SLRUQueue = std::list; + using SLRUQueueIterator = typename SLRUQueue::iterator; + + struct Cell + { + bool is_protected; + MappedPtr value; + size_t size; + SLRUQueueIterator queue_iterator; + }; + + using Cells = std::unordered_map; + + Cells cells; + + void resetImpl() + { + cells.clear(); + probationary_queue.clear(); + protected_queue.clear(); + current_size = 0; + current_protected_size = 0; + Base::resetImpl(); + } + +private: + SLRUQueue probationary_queue; + SLRUQueue protected_queue; + + size_t current_size = 0; + size_t current_protected_size = 0; + const size_t max_size; + const size_t max_protected_size; + + MappedPtr getImpl(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override + { + auto it = cells.find(key); + if (it == cells.end()) + { + return MappedPtr(); + } + + Cell & cell = it->second; + + if (cell.is_protected) + { + protected_queue.splice(protected_queue.end(), protected_queue, cell.queue_iterator); + } + else + { + cell.is_protected = true; + current_protected_size += cell.size; + protected_queue.splice(protected_queue.end(), probationary_queue, cell.queue_iterator); + } + + removeOverflow(protected_queue, max_protected_size, current_protected_size); + + return cell.value; + } + + void setImpl(const Key & key, const MappedPtr & mapped, [[maybe_unused]] std::lock_guard & cache_lock) override + { + auto [it, inserted] = cells.emplace(std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple()); + + Cell & cell = it->second; + + if (inserted) + { + try + { + cell.queue_iterator = probationary_queue.insert(probationary_queue.end(), key); + } + catch (...) + { + cells.erase(it); + throw; + } + } + else + { + current_size -= cell.size; + if (cell.is_protected) + { + current_protected_size -= cell.size; + protected_queue.splice(protected_queue.end(), protected_queue, cell.queue_iterator); + } + else + { + cell.is_protected = true; + protected_queue.splice(protected_queue.end(), probationary_queue, cell.queue_iterator); + } + } + + cell.value = mapped; + cell.size = cell.value ? weight_function(*cell.value) : 0; + current_size += cell.size; + current_protected_size += cell.is_protected ? cell.size : 0; + + removeOverflow(protected_queue, max_protected_size, current_protected_size); + removeOverflow(probationary_queue, max_size, current_size); + } + + void removeOverflow(SLRUQueue & queue, const size_t max_size, size_t & current_size) + { + size_t current_weight_lost = 0; + size_t queue_size = queue.size(); + + while (current_size > max_size && queue_size > 1) + { + const Key & key = queue.front(); + + auto it = cells.find(key); + if (it == cells.end()) + { + LOG_ERROR(&Poco::Logger::get("LRUCache"), "LRUCache became inconsistent. There must be a bug in it."); + abort(); + } + + const auto & cell = it->second; + + current_size -= cell.size; + + if (cell.is_protected) + { + cell.is_protected = false; + probationary_queue.splice(probationary_queue.end(), queue, cell.queue_iterator); + } + else + { + current_weight_lost += cell.size; + cells.erase(it); + queue.pop_front(); + } + + --queue_size; + } + + onRemoveOverflowWeightLoss(current_weight_lost); + + if (current_size > (1ull << 63)) + { + LOG_ERROR(&Poco::Logger::get("LRUCache"), "LRUCache became inconsistent. There must be a bug in it."); + abort(); + } + } + +}; + + +} From a0d2da726145db6330205f97f849bdbd0e1407ff Mon Sep 17 00:00:00 2001 From: alexX512 Date: Thu, 10 Mar 2022 20:03:05 +0300 Subject: [PATCH 377/672] Add tests for SLRU cache --- src/Common/SLRUCache.h | 29 +++++-- src/Common/tests/gtest_slru_cahce.cpp | 119 ++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 src/Common/tests/gtest_slru_cahce.cpp diff --git a/src/Common/SLRUCache.h b/src/Common/SLRUCache.h index aea62124d25..7b002275998 100644 --- a/src/Common/SLRUCache.h +++ b/src/Common/SLRUCache.h @@ -1,3 +1,5 @@ +#pragma once + #include namespace DB @@ -21,11 +23,12 @@ public: using Mapped = TMapped; using MappedPtr = std::shared_ptr; using Base = LRUCache; + using Base::mutex; SLRUCache(size_t max_protected_size_, size_t max_size_) - : max_protected_size(std::max(static_cast(1), max_protected_size_)) + : Base(0) + , max_protected_size(std::max(static_cast(1), max_protected_size_)) , max_size(std::max(max_protected_size + 1, max_size_)) - , Base(max_size) {} void remove(const Key & key) @@ -53,6 +56,11 @@ public: return cells.size(); } + size_t maxSize() const + { + return max_size; + } + size_t maxProtectedSize() const { return max_protected_size; @@ -94,10 +102,12 @@ private: SLRUQueue probationary_queue; SLRUQueue protected_queue; - size_t current_size = 0; size_t current_protected_size = 0; - const size_t max_size; + size_t current_size = 0; const size_t max_protected_size; + const size_t max_size; + + WeightFunction weight_function; MappedPtr getImpl(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override { @@ -169,12 +179,12 @@ private: removeOverflow(probationary_queue, max_size, current_size); } - void removeOverflow(SLRUQueue & queue, const size_t max_size, size_t & current_size) + void removeOverflow(SLRUQueue & queue, const size_t max_weight_size, size_t & current_weight_size) { size_t current_weight_lost = 0; size_t queue_size = queue.size(); - while (current_size > max_size && queue_size > 1) + while (current_weight_size > max_weight_size && queue_size > 1) { const Key & key = queue.front(); @@ -185,9 +195,9 @@ private: abort(); } - const auto & cell = it->second; + auto & cell = it->second; - current_size -= cell.size; + current_weight_size -= cell.size; if (cell.is_protected) { @@ -213,6 +223,9 @@ private: } } + + /// Override this method if you want to track how much weight was lost in removeOverflow method. + virtual void onRemoveOverflowWeightLoss(size_t /*weight_loss*/) override {} }; diff --git a/src/Common/tests/gtest_slru_cahce.cpp b/src/Common/tests/gtest_slru_cahce.cpp new file mode 100644 index 00000000000..fa34bb77995 --- /dev/null +++ b/src/Common/tests/gtest_slru_cahce.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include + +TEST(SLRUCache, set) +{ + using SimpleSLRUCache = DB::SLRUCache; + auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + slru_cache.set(1, std::make_shared(2)); + slru_cache.set(2, std::make_shared(3)); + + auto w = slru_cache.weight(); + auto n = slru_cache.count(); + ASSERT_EQ(w, 2); + ASSERT_EQ(n, 2); +} + +TEST(SLRUCache, update) +{ + using SimpleSLRUCache = DB::SLRUCache; + auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + slru_cache.set(1, std::make_shared(2)); + slru_cache.set(1, std::make_shared(3)); + auto val = slru_cache.get(1); + ASSERT_TRUE(val != nullptr); + ASSERT_TRUE(*val == 3); +} + +TEST(SLRUCache, get) +{ + using SimpleSLRUCache = DB::SLRUCache; + auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + slru_cache.set(1, std::make_shared(2)); + slru_cache.set(2, std::make_shared(3)); + SimpleSLRUCache::MappedPtr value = slru_cache.get(1); + ASSERT_TRUE(value != nullptr); + ASSERT_EQ(*value, 2); + + value = slru_cache.get(2); + ASSERT_TRUE(value != nullptr); + ASSERT_EQ(*value, 3); +} + +struct ValueWeight +{ + size_t operator()(const size_t & x) const { return x; } +}; + +TEST(SLRUCache, evictOnWeight) +{ + using SimpleSLRUCache = DB::SLRUCache, ValueWeight>; + auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + slru_cache.set(1, std::make_shared(2)); + slru_cache.set(2, std::make_shared(3)); + slru_cache.set(3, std::make_shared(4)); + slru_cache.set(3, std::make_shared(5)); + + auto n = slru_cache.count(); + ASSERT_EQ(n, 2); + + auto w = slru_cache.weight(); + ASSERT_EQ(w, 9); + + auto value = slru_cache.get(1); + ASSERT_TRUE(value == nullptr); + value = slru_cache.get(2); + ASSERT_TRUE(value == nullptr); +} + +TEST(SLRUCache, evictFromProtectedPart) +{ + using SimpleSLRUCache = DB::SLRUCache, ValueWeight>; + auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + slru_cache.set(1, std::make_shared(2)); + slru_cache.set(1, std::make_shared(2)); + + slru_cache.set(2, std::make_shared(5)); + slru_cache.set(2, std::make_shared(5)); + + slru_cache.set(3, std::make_shared(5)); + + auto value = slru_cache.get(1); + ASSERT_TRUE(value == nullptr); +} + +TEST(SLRUCache, evictStreamProtected) +{ + using SimpleSLRUCache = DB::SLRUCache, ValueWeight>; + auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + slru_cache.set(1, std::make_shared(2)); + slru_cache.set(1, std::make_shared(2)); + + slru_cache.set(2, std::make_shared(3)); + slru_cache.set(2, std::make_shared(3)); + + for (int key = 3; key < 10; ++key) { + slru_cache.set(key, std::make_shared(1 + key % 5)); + } + + auto value = slru_cache.get(1); + ASSERT_TRUE(value != nullptr); + ASSERT_EQ(*value, 2); + + value = slru_cache.get(2); + ASSERT_TRUE(value != nullptr); + ASSERT_EQ(*value, 3); +} + +TEST(SLRUCache, getOrSet) +{ + using SimpleSLRUCache = DB::SLRUCache, ValueWeight>; + auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + size_t x = 5; + auto load_func = [&] { return std::make_shared(x); }; + auto [value, loaded] = slru_cache.getOrSet(1, load_func); + ASSERT_TRUE(value != nullptr); + ASSERT_TRUE(*value == 5); +} From 1133e42367e239717cc913a5096acf16cacd7c2a Mon Sep 17 00:00:00 2001 From: alexX512 Date: Thu, 28 Apr 2022 05:56:26 +0000 Subject: [PATCH 378/672] Add CacheBase clas instead of LRUCache and SLRUCache fo simpler configuration oache policiesf --- programs/local/LocalServer.cpp | 3 +- programs/server/Server.cpp | 3 +- src/Common/CacheBase.h | 272 ++++++++++++++++++++++++++ src/Common/ICachePolicy.h | 41 ++++ src/Common/LRUCache.h | 6 +- src/Common/LRUCachePolicy.h | 186 ++++++++++++++++++ src/Common/SLRUCache.h | 6 +- src/Common/SLRUCachePolicy.h | 225 +++++++++++++++++++++ src/Common/tests/gtest_lru_cache.cpp | 28 +-- src/Common/tests/gtest_slru_cahce.cpp | 66 +++++-- src/IO/UncompressedCache.h | 9 +- src/Interpreters/Context.cpp | 4 +- src/Interpreters/Context.h | 2 +- 13 files changed, 808 insertions(+), 43 deletions(-) create mode 100644 src/Common/CacheBase.h create mode 100644 src/Common/ICachePolicy.h create mode 100644 src/Common/LRUCachePolicy.h create mode 100644 src/Common/SLRUCachePolicy.h diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index aa96551d37c..404b7d08e12 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -558,9 +558,10 @@ void LocalServer::processConfig() global_context->getProcessList().setMaxSize(0); /// Size of cache for uncompressed blocks. Zero means disabled. + String uncompressed_cache_policy = config().getString("uncompressed_cache_policy", ""); size_t uncompressed_cache_size = config().getUInt64("uncompressed_cache_size", 0); if (uncompressed_cache_size) - global_context->setUncompressedCache(uncompressed_cache_size); + global_context->setUncompressedCache(uncompressed_cache_size, uncompressed_cache_policy); /// Size of cache for marks (index of MergeTree family of tables). size_t mark_cache_size = config().getUInt64("mark_cache_size", 5368709120); diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index b86ce4a841c..e513becbffe 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -1362,6 +1362,7 @@ int Server::main(const std::vector & /*args*/) size_t max_cache_size = memory_amount * cache_size_to_ram_max_ratio; /// Size of cache for uncompressed blocks. Zero means disabled. + String uncompressed_cache_policy = config().getString("uncompressed_cache_policy", ""); size_t uncompressed_cache_size = config().getUInt64("uncompressed_cache_size", 0); if (uncompressed_cache_size > max_cache_size) { @@ -1369,7 +1370,7 @@ int Server::main(const std::vector & /*args*/) LOG_INFO(log, "Uncompressed cache size was lowered to {} because the system has low amount of memory", formatReadableSizeWithBinarySuffix(uncompressed_cache_size)); } - global_context->setUncompressedCache(uncompressed_cache_size); + global_context->setUncompressedCache(uncompressed_cache_size, uncompressed_cache_policy); /// Load global settings from default_profile and system_profile. global_context->setDefaultProfiles(config()); diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h new file mode 100644 index 00000000000..a9330cf495e --- /dev/null +++ b/src/Common/CacheBase.h @@ -0,0 +1,272 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + + +namespace DB +{ + +/// Thread-safe cache that evicts entries using special cache policy +/// (default policy evicts entries which are not used for a long time). +/// WeightFunction is a functor that takes Mapped as a parameter and returns "weight" (approximate size) +/// of that value. +/// Cache starts to evict entries when their total weight exceeds max_size. +/// Value weight should not change after insertion. +template , typename WeightFunction = TrivialWeightFunction> +class CacheBase +{ +public: + using Key = TKey; + using Mapped = TMapped; + using MappedPtr = std::shared_ptr; + + CacheBase(size_t max_size) : CacheBase(default_cache_policy_name, max_size) {} + + template + CacheBase(std::string cache_policy_name, Args... args) { + auto onWeightLossFunction = [&](size_t weight_loss) { + onRemoveOverflowWeightLoss(weight_loss); + }; + + if (cache_policy_name == "") { + cache_policy_name = default_cache_policy_name; + } + + policy_name = cache_policy_name; + + LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Cache policy name \"{}\"", cache_policy_name); + if (cache_policy_name == "LRU") { + using LRUPolicy = LRUCachePolicy; + cache_policy = std::make_unique(onWeightLossFunction, args...); + } + else if (cache_policy_name == "SLRU") { + using SLRUPolicy = SLRUCachePolicy; + cache_policy = std::make_unique(onWeightLossFunction, args...); + } + else { + LOG_ERROR(&Poco::Logger::get("CacheBase"), "Undeclared cache policy name \"{}\"", cache_policy_name); + abort(); + } + } + + MappedPtr get(const Key & key) + { + std::lock_guard lock(mutex); + + auto res = cache_policy->get(key); + if (res) + ++hits; + else + ++misses; + + return res; + } + + void set(const Key & key, const MappedPtr & mapped) + { + std::lock_guard lock(mutex); + + cache_policy->set(key, mapped); + } + + /// If the value for the key is in the cache, returns it. If it is not, calls load_func() to + /// produce it, saves the result in the cache and returns it. + /// Only one of several concurrent threads calling getOrSet() will call load_func(), + /// others will wait for that call to complete and will use its result (this helps prevent cache stampede). + /// Exceptions occurring in load_func will be propagated to the caller. Another thread from the + /// set of concurrent threads will then try to call its load_func etc. + /// + /// Returns std::pair of the cached value and a bool indicating whether the value was produced during this call. + template + std::pair getOrSet(const Key & key, LoadFunc && load_func) + { + LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Cache getOrSet. Cache policy name \"{}\"", policy_name); + InsertTokenHolder token_holder; + { + std::lock_guard cache_lock(mutex); + + auto val = cache_policy->get(key); + if (val) + { + ++hits; + return std::make_pair(val, false); + } + + auto & token = insert_tokens[key]; + if (!token) + token = std::make_shared(*this); + + token_holder.acquire(&key, token, cache_lock); + } + + InsertToken * token = token_holder.token.get(); + + std::lock_guard token_lock(token->mutex); + + token_holder.cleaned_up = token->cleaned_up; + + if (token->value) + { + /// Another thread already produced the value while we waited for token->mutex. + ++hits; + return std::make_pair(token->value, false); + } + + ++misses; + token->value = load_func(); + + std::lock_guard cache_lock(mutex); + + /// Insert the new value only if the token is still in present in insert_tokens. + /// (The token may be absent because of a concurrent reset() call). + bool result = false; + auto token_it = insert_tokens.find(key); + if (token_it != insert_tokens.end() && token_it->second.get() == token) + { + cache_policy->set(key, token->value); + result = true; + } + + if (!token->cleaned_up) + token_holder.cleanup(token_lock, cache_lock); + + return std::make_pair(token->value, result); + } + + void getStats(size_t & out_hits, size_t & out_misses) const + { + std::lock_guard lock(mutex); + out_hits = hits; + out_misses = misses; + } + + void reset() + { + std::lock_guard lock(mutex); + insert_tokens.clear(); + hits = 0; + misses = 0; + cache_policy->reset(); + } + + void remove(const Key & key) + { + std::lock_guard lock(mutex); + cache_policy->remove(key); + } + + size_t weight() const + { + std::lock_guard lock(mutex); + return cache_policy->weight(); + } + + size_t count() const + { + std::lock_guard lock(mutex); + return cache_policy->count(); + } + + size_t maxSize() const + { + std::lock_guard lock(mutex); + return cache_policy->maxSize(); + } + + virtual ~CacheBase() = default; + +protected: + mutable std::mutex mutex; + +private: + using CachePolicy = ICachePolicy; + + std::unique_ptr cache_policy; + String policy_name; // DELETE THIS FIELD (only for debug) + + inline static const String default_cache_policy_name = "LRU"; + + std::atomic hits {0}; + std::atomic misses {0}; + + /// Represents pending insertion attempt. + struct InsertToken + { + explicit InsertToken(CacheBase & cache_) : cache(cache_) {} + + std::mutex mutex; + bool cleaned_up = false; /// Protected by the token mutex + MappedPtr value; /// Protected by the token mutex + + CacheBase & cache; + size_t refcount = 0; /// Protected by the cache mutex + }; + + using InsertTokenById = std::unordered_map, HashFunction>; + + /// This class is responsible for removing used insert tokens from the insert_tokens map. + /// Among several concurrent threads the first successful one is responsible for removal. But if they all + /// fail, then the last one is responsible. + struct InsertTokenHolder + { + const Key * key = nullptr; + std::shared_ptr token; + bool cleaned_up = false; + + InsertTokenHolder() = default; + + void acquire(const Key * key_, const std::shared_ptr & token_, [[maybe_unused]] std::lock_guard & cache_lock) + { + key = key_; + token = token_; + ++token->refcount; + } + + void cleanup([[maybe_unused]] std::lock_guard & token_lock, [[maybe_unused]] std::lock_guard & cache_lock) + { + token->cache.insert_tokens.erase(*key); + token->cleaned_up = true; + cleaned_up = true; + } + + ~InsertTokenHolder() + { + if (!token) + return; + + if (cleaned_up) + return; + + std::lock_guard token_lock(token->mutex); + + if (token->cleaned_up) + return; + + std::lock_guard cache_lock(token->cache.mutex); + + --token->refcount; + if (token->refcount == 0) + cleanup(token_lock, cache_lock); + } + }; + + friend struct InsertTokenHolder; + + InsertTokenById insert_tokens; + + /// Override this method if you want to track how much weight was lost in removeOverflow method. + virtual void onRemoveOverflowWeightLoss(size_t /*weight_loss*/) {} +}; + + +} diff --git a/src/Common/ICachePolicy.h b/src/Common/ICachePolicy.h new file mode 100644 index 00000000000..8ef322c310e --- /dev/null +++ b/src/Common/ICachePolicy.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +namespace DB +{ + +template +struct TrivialWeightFunction +{ + size_t operator()(const T &) const + { + return 1; + } +}; + +template , typename WeightFunction = TrivialWeightFunction> +class ICachePolicy +{ +public: + using Key = TKey; + using Mapped = TMapped; + using MappedPtr = std::shared_ptr; + using OnWeightLossFunction = std::function; + + virtual size_t weight() const = 0; + virtual size_t count() const = 0; + virtual size_t maxSize() const = 0; + + virtual void reset() = 0; + virtual void remove(const Key & key) = 0; + virtual MappedPtr get(const Key & key) = 0; + virtual void set(const Key & key, const MappedPtr & mapped) = 0; + + virtual ~ICachePolicy() = default; + +protected: + OnWeightLossFunction on_weight_loss_function = [](size_t) {}; +}; + +} diff --git a/src/Common/LRUCache.h b/src/Common/LRUCache.h index fe2e56bd66c..ce767fa0b1f 100644 --- a/src/Common/LRUCache.h +++ b/src/Common/LRUCache.h @@ -14,8 +14,10 @@ namespace DB { +// TODO: rewrite all using of class LRUCache to class CacheBase with cache_policy = "LRU" + template -struct TrivialWeightFunction +struct TrivialLRUWeightFunction { size_t operator()(const T &) const { @@ -29,7 +31,7 @@ struct TrivialWeightFunction /// of that value. /// Cache starts to evict entries when their total weight exceeds max_size. /// Value weight should not change after insertion. -template , typename WeightFunction = TrivialWeightFunction> +template , typename WeightFunction = TrivialLRUWeightFunction> class LRUCache { public: diff --git a/src/Common/LRUCachePolicy.h b/src/Common/LRUCachePolicy.h new file mode 100644 index 00000000000..41cdcbe3e33 --- /dev/null +++ b/src/Common/LRUCachePolicy.h @@ -0,0 +1,186 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace DB +{ + +/// Cache policy LRU evicts entries which are not used for a long time. +/// WeightFunction is a functor that takes Mapped as a parameter and returns "weight" (approximate size) +/// of that value. +/// Cache starts to evict entries when their total weight exceeds max_size. +/// Value weight should not change after insertion. +/// To work with the thread-safe implementation of this class use a class "CacheBase" with first parameter "LRU" +/// and next parameters in the same order as in the constructor of the current class. +template , typename WeightFunction = TrivialWeightFunction> +class LRUCachePolicy: public ICachePolicy +{ +public: + using Key = TKey; + using Mapped = TMapped; + using MappedPtr = std::shared_ptr; + + using Base = ICachePolicy; + using typename Base::OnWeightLossFunction; + + /** Initialize LRUCachePolicy with max_size and max_elements_size. + * max_elements_size == 0 means no elements size restrictions. + */ + explicit LRUCachePolicy(size_t max_size_, size_t max_elements_size_ = 0) + : max_size(std::max(static_cast(1), max_size_)) + , max_elements_size(max_elements_size_) + {} + + template + LRUCachePolicy(OnWeightLossFunction on_weight_loss_function_, Args... args) : LRUCachePolicy(args...) { + Base::on_weight_loss_function = on_weight_loss_function_; + } + + size_t weight() const override + { + return current_size; + } + + size_t count() const override + { + return cells.size(); + } + + size_t maxSize() const override + { + return max_size; + } + + void reset() override + { + queue.clear(); + cells.clear(); + current_size = 0; + } + + void remove(const Key & key) override + { + auto it = cells.find(key); + if (it == cells.end()) + return; + auto & cell = it->second; + current_size -= cell.size; + queue.erase(cell.queue_iterator); + cells.erase(it); + } + + MappedPtr get(const Key & key) override + { + auto it = cells.find(key); + if (it == cells.end()) + { + return MappedPtr(); + } + + Cell & cell = it->second; + + /// Move the key to the end of the queue. The iterator remains valid. + queue.splice(queue.end(), queue, cell.queue_iterator); + + return cell.value; + } + + void set(const Key & key, const MappedPtr & mapped) override + { + auto [it, inserted] = cells.emplace(std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple()); + + Cell & cell = it->second; + + if (inserted) + { + try + { + cell.queue_iterator = queue.insert(queue.end(), key); + } + catch (...) + { + cells.erase(it); + throw; + } + } + else + { + current_size -= cell.size; + queue.splice(queue.end(), queue, cell.queue_iterator); + } + + cell.value = mapped; + cell.size = cell.value ? weight_function(*cell.value) : 0; + current_size += cell.size; + + removeOverflow(); + } + +protected: + using LRUQueue = std::list; + using LRUQueueIterator = typename LRUQueue::iterator; + + LRUQueue queue; + + struct Cell + { + MappedPtr value; + size_t size; + LRUQueueIterator queue_iterator; + }; + + using Cells = std::unordered_map; + + Cells cells; + + /// Total weight of values. + size_t current_size = 0; + const size_t max_size; + const size_t max_elements_size; + + WeightFunction weight_function; + + void removeOverflow() + { + size_t current_weight_lost = 0; + size_t queue_size = cells.size(); + + while ((current_size > max_size || (max_elements_size != 0 && queue_size > max_elements_size)) && (queue_size > 1)) + { + const Key & key = queue.front(); + + auto it = cells.find(key); + if (it == cells.end()) + { + LOG_ERROR(&Poco::Logger::get("LRUCache"), "LRUCache became inconsistent. There must be a bug in it."); + abort(); + } + + const auto & cell = it->second; + + current_size -= cell.size; + current_weight_lost += cell.size; + + cells.erase(it); + queue.pop_front(); + --queue_size; + } + + Base::on_weight_loss_function(current_weight_lost); + + if (current_size > (1ull << 63)) + { + LOG_ERROR(&Poco::Logger::get("LRUCache"), "LRUCache became inconsistent. There must be a bug in it."); + abort(); + } + } +}; + +} diff --git a/src/Common/SLRUCache.h b/src/Common/SLRUCache.h index 7b002275998..c1c8daf5975 100644 --- a/src/Common/SLRUCache.h +++ b/src/Common/SLRUCache.h @@ -14,6 +14,8 @@ struct TrivialSLRUCacheWeightFunction } }; +// TODO: rewrite all using of class LRUCache to class CacheBase with cache_policy = "SLRU" + template , typename WeightFunction = TrivialSLRUCacheWeightFunction> class SLRUCache: public LRUCache @@ -25,9 +27,9 @@ public: using Base = LRUCache; using Base::mutex; - SLRUCache(size_t max_protected_size_, size_t max_size_) + SLRUCache(size_t max_size_, size_t max_protected_size_ = 0) : Base(0) - , max_protected_size(std::max(static_cast(1), max_protected_size_)) + , max_protected_size(max_protected_size_ != 0 ? max_protected_size_ : max_size_ / 2 + 1) , max_size(std::max(max_protected_size + 1, max_size_)) {} diff --git a/src/Common/SLRUCachePolicy.h b/src/Common/SLRUCachePolicy.h new file mode 100644 index 00000000000..56f2ae698a4 --- /dev/null +++ b/src/Common/SLRUCachePolicy.h @@ -0,0 +1,225 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace DB +{ + +/// Cache policy SLRU evicts entries which were used only once and are not used for a long time, +/// this policy protects entries which were used more then once from a sequential scan. +/// WeightFunction is a functor that takes Mapped as a parameter and returns "weight" (approximate size) +/// of that value. +/// Cache starts to evict entries when their total weight exceeds max_size. +/// Value weight should not change after insertion. +/// To work with the thread-safe implementation of this class use a class "CacheBase" with first parameter "SLRU" +/// and next parameters in the same order as in the constructor of the current class. +template , typename WeightFunction = TrivialWeightFunction> +class SLRUCachePolicy: public ICachePolicy +{ +public: + using Key = TKey; + using Mapped = TMapped; + using MappedPtr = std::shared_ptr; + + using Base = ICachePolicy; + using typename Base::OnWeightLossFunction; + + /** Initialize SLRUCachePolicy with max_size and max_protected_size. + * max_protected_size shows how many of the most frequently used entries will not be evicted after a sequential scan. + * max_protected_size == 0 means that the default protected size is equal to half of the total max size. + */ + SLRUCachePolicy(size_t max_size_, size_t max_protected_size_ = 0) + : max_protected_size(max_protected_size_ != 0 ? max_protected_size_ : max_size_ / 2 + 1) + , max_size(std::max(max_protected_size + 1, max_size_)) + {} + + template + SLRUCachePolicy(OnWeightLossFunction on_weight_loss_function_, Args... args) : SLRUCachePolicy(args...) { + Base::on_weight_loss_function = on_weight_loss_function_; + } + + size_t weight() const override + { + return current_size; + } + + size_t count() const override + { + return cells.size(); + } + + size_t maxSize() const override + { + return max_size; + } + + void reset() override + { + cells.clear(); + probationary_queue.clear(); + protected_queue.clear(); + current_size = 0; + current_protected_size = 0; + } + + void remove(const Key & key) override + { + auto it = cells.find(key); + if (it == cells.end()) + return; + auto & cell = it->second; + current_size -= cell.size; + auto & queue = cell.is_protected ? protected_queue : probationary_queue; + queue.erase(cell.queue_iterator); + cells.erase(it); + } + + MappedPtr get(const Key & key) override + { + auto it = cells.find(key); + if (it == cells.end()) + { + return MappedPtr(); + } + + Cell & cell = it->second; + + if (cell.is_protected) + { + protected_queue.splice(protected_queue.end(), protected_queue, cell.queue_iterator); + } + else + { + cell.is_protected = true; + current_protected_size += cell.size; + protected_queue.splice(protected_queue.end(), probationary_queue, cell.queue_iterator); + } + + removeOverflow(protected_queue, max_protected_size, current_protected_size); + + return cell.value; + } + + void set(const Key & key, const MappedPtr & mapped) override + { + auto [it, inserted] = cells.emplace(std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple()); + + Cell & cell = it->second; + + if (inserted) + { + try + { + cell.queue_iterator = probationary_queue.insert(probationary_queue.end(), key); + } + catch (...) + { + cells.erase(it); + throw; + } + } + else + { + current_size -= cell.size; + if (cell.is_protected) + { + current_protected_size -= cell.size; + protected_queue.splice(protected_queue.end(), protected_queue, cell.queue_iterator); + } + else + { + cell.is_protected = true; + protected_queue.splice(protected_queue.end(), probationary_queue, cell.queue_iterator); + } + } + + cell.value = mapped; + cell.size = cell.value ? weight_function(*cell.value) : 0; + current_size += cell.size; + current_protected_size += cell.is_protected ? cell.size : 0; + + removeOverflow(protected_queue, max_protected_size, current_protected_size); + removeOverflow(probationary_queue, max_size, current_size); + } + +protected: + using SLRUQueue = std::list; + using SLRUQueueIterator = typename SLRUQueue::iterator; + + SLRUQueue probationary_queue; + SLRUQueue protected_queue; + + struct Cell + { + bool is_protected; + MappedPtr value; + size_t size; + SLRUQueueIterator queue_iterator; + }; + + using Cells = std::unordered_map; + + Cells cells; + + size_t current_protected_size = 0; + size_t current_size = 0; + const size_t max_protected_size; + const size_t max_size; + + WeightFunction weight_function; + + void removeOverflow(SLRUQueue & queue, const size_t max_weight_size, size_t & current_weight_size) + { + size_t current_weight_lost = 0; + size_t queue_size = queue.size(); + + while (current_weight_size > max_weight_size && queue_size > 1) + { + const Key & key = queue.front(); + + auto it = cells.find(key); + if (it == cells.end()) + { + LOG_ERROR(&Poco::Logger::get("SLRUCache"), "SLRUCache became inconsistent. There must be a bug in it."); + abort(); + } + + auto & cell = it->second; + + current_weight_size -= cell.size; + + if (cell.is_protected) + { + cell.is_protected = false; + probationary_queue.splice(probationary_queue.end(), queue, cell.queue_iterator); + } + else + { + current_weight_lost += cell.size; + cells.erase(it); + queue.pop_front(); + } + + --queue_size; + } + + if (current_weight_lost > 0) { + Base::on_weight_loss_function(current_weight_lost); + } + + if (current_size > (1ull << 63)) + { + LOG_ERROR(&Poco::Logger::get("SLRUCache"), "SLRUCache became inconsistent. There must be a bug in it."); + abort(); + } + } +}; + +} diff --git a/src/Common/tests/gtest_lru_cache.cpp b/src/Common/tests/gtest_lru_cache.cpp index 7694a76ea72..9a2cb354bd5 100644 --- a/src/Common/tests/gtest_lru_cache.cpp +++ b/src/Common/tests/gtest_lru_cache.cpp @@ -1,12 +1,12 @@ #include #include #include -#include +#include TEST(LRUCache, set) { - using SimpleLRUCache = DB::LRUCache; - auto lru_cache = SimpleLRUCache(10, 10); + using SimpleCacheBase = DB::CacheBase; + auto lru_cache = SimpleCacheBase("LRU", /*max_size*/ 10, /*max_elements_size*/ 10); lru_cache.set(1, std::make_shared(2)); lru_cache.set(2, std::make_shared(3)); @@ -18,8 +18,8 @@ TEST(LRUCache, set) TEST(LRUCache, update) { - using SimpleLRUCache = DB::LRUCache; - auto lru_cache = SimpleLRUCache(10, 10); + using SimpleCacheBase = DB::CacheBase; + auto lru_cache = SimpleCacheBase("LRU", /*max_size*/ 10, /*max_elements_size*/ 10); lru_cache.set(1, std::make_shared(2)); lru_cache.set(1, std::make_shared(3)); auto val = lru_cache.get(1); @@ -29,11 +29,11 @@ TEST(LRUCache, update) TEST(LRUCache, get) { - using SimpleLRUCache = DB::LRUCache; - auto lru_cache = SimpleLRUCache(10, 10); + using SimpleCacheBase = DB::CacheBase; + auto lru_cache = SimpleCacheBase("LRU", /*max_size*/ 10, /*max_elements_size*/ 10); lru_cache.set(1, std::make_shared(2)); lru_cache.set(2, std::make_shared(3)); - SimpleLRUCache::MappedPtr value = lru_cache.get(1); + SimpleCacheBase::MappedPtr value = lru_cache.get(1); ASSERT_TRUE(value != nullptr); ASSERT_EQ(*value, 2); @@ -49,8 +49,8 @@ struct ValueWeight TEST(LRUCache, evictOnSize) { - using SimpleLRUCache = DB::LRUCache; - auto lru_cache = SimpleLRUCache(20, 3); + using SimpleCacheBase = DB::CacheBase; + auto lru_cache = SimpleCacheBase("LRU", /*max_size*/ 20, /*max_elements_size*/ 3); lru_cache.set(1, std::make_shared(2)); lru_cache.set(2, std::make_shared(3)); lru_cache.set(3, std::make_shared(4)); @@ -65,8 +65,8 @@ TEST(LRUCache, evictOnSize) TEST(LRUCache, evictOnWeight) { - using SimpleLRUCache = DB::LRUCache, ValueWeight>; - auto lru_cache = SimpleLRUCache(10, 10); + using SimpleCacheBase = DB::CacheBase, ValueWeight>; + auto lru_cache = SimpleCacheBase("LRU", /*max_size*/ 10, /*max_elements_size*/ 10); lru_cache.set(1, std::make_shared(2)); lru_cache.set(2, std::make_shared(3)); lru_cache.set(3, std::make_shared(4)); @@ -86,8 +86,8 @@ TEST(LRUCache, evictOnWeight) TEST(LRUCache, getOrSet) { - using SimpleLRUCache = DB::LRUCache, ValueWeight>; - auto lru_cache = SimpleLRUCache(10, 10); + using SimpleCacheBase = DB::CacheBase, ValueWeight>; + auto lru_cache = SimpleCacheBase("LRU", /*max_size*/ 10, /*max_elements_size*/ 10); size_t x = 10; auto load_func = [&] { return std::make_shared(x); }; auto [value, loaded] = lru_cache.getOrSet(1, load_func); diff --git a/src/Common/tests/gtest_slru_cahce.cpp b/src/Common/tests/gtest_slru_cahce.cpp index fa34bb77995..b4bebe73084 100644 --- a/src/Common/tests/gtest_slru_cahce.cpp +++ b/src/Common/tests/gtest_slru_cahce.cpp @@ -2,11 +2,12 @@ #include #include #include +#include TEST(SLRUCache, set) { - using SimpleSLRUCache = DB::SLRUCache; - auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + using SimpleCacheBase = DB::CacheBase; + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); @@ -18,8 +19,8 @@ TEST(SLRUCache, set) TEST(SLRUCache, update) { - using SimpleSLRUCache = DB::SLRUCache; - auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + using SimpleCacheBase = DB::CacheBase; + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(1, std::make_shared(3)); auto val = slru_cache.get(1); @@ -29,11 +30,11 @@ TEST(SLRUCache, update) TEST(SLRUCache, get) { - using SimpleSLRUCache = DB::SLRUCache; - auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + using SimpleCacheBase = DB::CacheBase; + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); - SimpleSLRUCache::MappedPtr value = slru_cache.get(1); + SimpleCacheBase::MappedPtr value = slru_cache.get(1); ASSERT_TRUE(value != nullptr); ASSERT_EQ(*value, 2); @@ -42,6 +43,39 @@ TEST(SLRUCache, get) ASSERT_EQ(*value, 3); } +TEST(SLRUCache, remove) +{ + using SimpleCacheBase = DB::CacheBase; + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); + slru_cache.set(1, std::make_shared(2)); + slru_cache.set(2, std::make_shared(3)); + SimpleCacheBase::MappedPtr value = slru_cache.get(1); + ASSERT_TRUE(value != nullptr); + ASSERT_EQ(*value, 2); + + slru_cache.remove(2); + value = slru_cache.get(2); + ASSERT_TRUE(value == nullptr); +} + +TEST(SLRUCache, reset) +{ + using SimpleCacheBase = DB::CacheBase; + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); + slru_cache.set(1, std::make_shared(2)); + slru_cache.set(2, std::make_shared(3)); + + slru_cache.set(2, std::make_shared(4)); // add to protected_queue + + slru_cache.reset(); + + SimpleCacheBase::MappedPtr value = slru_cache.get(1); + ASSERT_TRUE(value == nullptr); + + value = slru_cache.get(2); + ASSERT_TRUE(value == nullptr); +} + struct ValueWeight { size_t operator()(const size_t & x) const { return x; } @@ -49,12 +83,12 @@ struct ValueWeight TEST(SLRUCache, evictOnWeight) { - using SimpleSLRUCache = DB::SLRUCache, ValueWeight>; - auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + using SimpleCacheBase = DB::CacheBase, ValueWeight>; + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); slru_cache.set(3, std::make_shared(4)); - slru_cache.set(3, std::make_shared(5)); + slru_cache.set(4, std::make_shared(5)); auto n = slru_cache.count(); ASSERT_EQ(n, 2); @@ -70,8 +104,8 @@ TEST(SLRUCache, evictOnWeight) TEST(SLRUCache, evictFromProtectedPart) { - using SimpleSLRUCache = DB::SLRUCache, ValueWeight>; - auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + using SimpleCacheBase = DB::CacheBase, ValueWeight>; + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(1, std::make_shared(2)); @@ -86,8 +120,8 @@ TEST(SLRUCache, evictFromProtectedPart) TEST(SLRUCache, evictStreamProtected) { - using SimpleSLRUCache = DB::SLRUCache, ValueWeight>; - auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + using SimpleCacheBase = DB::CacheBase, ValueWeight>; + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(1, std::make_shared(2)); @@ -109,8 +143,8 @@ TEST(SLRUCache, evictStreamProtected) TEST(SLRUCache, getOrSet) { - using SimpleSLRUCache = DB::SLRUCache, ValueWeight>; - auto slru_cache = SimpleSLRUCache(/*max_protected_size=*/5, /*max_total_size=*/10); + using SimpleCacheBase = DB::CacheBase, ValueWeight>; + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); size_t x = 5; auto load_func = [&] { return std::make_shared(x); }; auto [value, loaded] = slru_cache.getOrSet(1, load_func); diff --git a/src/IO/UncompressedCache.h b/src/IO/UncompressedCache.h index 93ca1235a42..d1d1f49cf9f 100644 --- a/src/IO/UncompressedCache.h +++ b/src/IO/UncompressedCache.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace ProfileEvents @@ -36,14 +37,14 @@ struct UncompressedSizeWeightFunction /** Cache of decompressed blocks for implementation of CachedCompressedReadBuffer. thread-safe. */ -class UncompressedCache : public LRUCache +class UncompressedCache : public CacheBase { private: - using Base = LRUCache; + using Base = CacheBase; public: - explicit UncompressedCache(size_t max_size_in_bytes) - : Base(max_size_in_bytes) {} + explicit UncompressedCache(size_t max_size_in_bytes, String uncompressed_cache_policy = "") + : Base(uncompressed_cache_policy, max_size_in_bytes) {} /// Calculate key from path to file and offset. static UInt128 hash(const String & path_to_file, size_t offset) diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index a5629b33d22..bbdb653b4a6 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -1657,14 +1657,14 @@ ProcessList::Element * Context::getProcessListElement() const } -void Context::setUncompressedCache(size_t max_size_in_bytes) +void Context::setUncompressedCache(size_t max_size_in_bytes, String uncompressed_cache_policy) { auto lock = getLock(); if (shared->uncompressed_cache) throw Exception("Uncompressed cache has been already created.", ErrorCodes::LOGICAL_ERROR); - shared->uncompressed_cache = std::make_shared(max_size_in_bytes); + shared->uncompressed_cache = std::make_shared(max_size_in_bytes, uncompressed_cache_policy); } diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index cf508c7bfdb..5f310d67b86 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -780,7 +780,7 @@ public: void setSystemZooKeeperLogAfterInitializationIfNeeded(); /// Create a cache of uncompressed blocks of specified size. This can be done only once. - void setUncompressedCache(size_t max_size_in_bytes); + void setUncompressedCache(size_t max_size_in_bytes, String uncompressed_cache_policy); std::shared_ptr getUncompressedCache() const; void dropUncompressedCache() const; From c606ccc75d20535090a1c496b87c18a43d666866 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Thu, 28 Apr 2022 07:48:15 +0000 Subject: [PATCH 379/672] Fix formatting problems --- src/Common/CacheBase.h | 49 ++++++++++++++------------- src/Common/ICachePolicy.h | 3 +- src/Common/LRUCachePolicy.h | 22 ++++++------ src/Common/SLRUCache.h | 17 +++------- src/Common/SLRUCachePolicy.h | 18 +++++----- src/Common/tests/gtest_slru_cahce.cpp | 9 ++--- src/Core/Settings.h | 1 + 7 files changed, 59 insertions(+), 60 deletions(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index a9330cf495e..58c093d536f 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -4,11 +4,11 @@ #include #include -#include -#include -#include -#include #include +#include +#include +#include +#include #include @@ -16,7 +16,7 @@ namespace DB { -/// Thread-safe cache that evicts entries using special cache policy +/// Thread-safe cache that evicts entries using special cache policy /// (default policy evicts entries which are not used for a long time). /// WeightFunction is a functor that takes Mapped as a parameter and returns "weight" (approximate size) /// of that value. @@ -32,28 +32,31 @@ public: CacheBase(size_t max_size) : CacheBase(default_cache_policy_name, max_size) {} - template - CacheBase(std::string cache_policy_name, Args... args) { - auto onWeightLossFunction = [&](size_t weight_loss) { - onRemoveOverflowWeightLoss(weight_loss); - }; + template + CacheBase(std::string cache_policy_name, Args... args) + { + auto onWeightLossFunction = [&](size_t weight_loss) { onRemoveOverflowWeightLoss(weight_loss); }; - if (cache_policy_name == "") { + if (cache_policy_name == "") + { cache_policy_name = default_cache_policy_name; } - + policy_name = cache_policy_name; LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Cache policy name \"{}\"", cache_policy_name); - if (cache_policy_name == "LRU") { + if (cache_policy_name == "LRU") + { using LRUPolicy = LRUCachePolicy; cache_policy = std::make_unique(onWeightLossFunction, args...); } - else if (cache_policy_name == "SLRU") { + else if (cache_policy_name == "SLRU") + { using SLRUPolicy = SLRUCachePolicy; cache_policy = std::make_unique(onWeightLossFunction, args...); } - else { + else + { LOG_ERROR(&Poco::Logger::get("CacheBase"), "Undeclared cache policy name \"{}\"", cache_policy_name); abort(); } @@ -142,7 +145,7 @@ public: return std::make_pair(token->value, result); } - + void getStats(size_t & out_hits, size_t & out_misses) const { std::lock_guard lock(mutex); @@ -150,7 +153,7 @@ public: out_misses = misses; } - void reset() + void reset() { std::lock_guard lock(mutex); insert_tokens.clear(); @@ -159,7 +162,7 @@ public: cache_policy->reset(); } - void remove(const Key & key) + void remove(const Key & key) { std::lock_guard lock(mutex); cache_policy->remove(key); @@ -171,7 +174,7 @@ public: return cache_policy->weight(); } - size_t count() const + size_t count() const { std::lock_guard lock(mutex); return cache_policy->count(); @@ -193,11 +196,11 @@ private: std::unique_ptr cache_policy; String policy_name; // DELETE THIS FIELD (only for debug) - + inline static const String default_cache_policy_name = "LRU"; - std::atomic hits {0}; - std::atomic misses {0}; + std::atomic hits{0}; + std::atomic misses{0}; /// Represents pending insertion attempt. struct InsertToken @@ -263,7 +266,7 @@ private: friend struct InsertTokenHolder; InsertTokenById insert_tokens; - + /// Override this method if you want to track how much weight was lost in removeOverflow method. virtual void onRemoveOverflowWeightLoss(size_t /*weight_loss*/) {} }; diff --git a/src/Common/ICachePolicy.h b/src/Common/ICachePolicy.h index 8ef322c310e..3fb34631e87 100644 --- a/src/Common/ICachePolicy.h +++ b/src/Common/ICachePolicy.h @@ -4,7 +4,6 @@ namespace DB { - template struct TrivialWeightFunction { @@ -17,7 +16,7 @@ struct TrivialWeightFunction template , typename WeightFunction = TrivialWeightFunction> class ICachePolicy { -public: +public: using Key = TKey; using Mapped = TMapped; using MappedPtr = std::shared_ptr; diff --git a/src/Common/LRUCachePolicy.h b/src/Common/LRUCachePolicy.h index 41cdcbe3e33..63a71946a1c 100644 --- a/src/Common/LRUCachePolicy.h +++ b/src/Common/LRUCachePolicy.h @@ -2,23 +2,22 @@ #include -#include #include +#include #include -namespace DB +namespace DB { - /// Cache policy LRU evicts entries which are not used for a long time. /// WeightFunction is a functor that takes Mapped as a parameter and returns "weight" (approximate size) /// of that value. /// Cache starts to evict entries when their total weight exceeds max_size. /// Value weight should not change after insertion. -/// To work with the thread-safe implementation of this class use a class "CacheBase" with first parameter "LRU" +/// To work with the thread-safe implementation of this class use a class "CacheBase" with first parameter "LRU" /// and next parameters in the same order as in the constructor of the current class. template , typename WeightFunction = TrivialWeightFunction> -class LRUCachePolicy: public ICachePolicy +class LRUCachePolicy : public ICachePolicy { public: using Key = TKey; @@ -32,12 +31,13 @@ public: * max_elements_size == 0 means no elements size restrictions. */ explicit LRUCachePolicy(size_t max_size_, size_t max_elements_size_ = 0) - : max_size(std::max(static_cast(1), max_size_)) - , max_elements_size(max_elements_size_) - {} + : max_size(std::max(static_cast(1), max_size_)), max_elements_size(max_elements_size_) + { + } - template - LRUCachePolicy(OnWeightLossFunction on_weight_loss_function_, Args... args) : LRUCachePolicy(args...) { + template + LRUCachePolicy(OnWeightLossFunction on_weight_loss_function_, Args... args) : LRUCachePolicy(args...) + { Base::on_weight_loss_function = on_weight_loss_function_; } @@ -144,7 +144,7 @@ protected: size_t current_size = 0; const size_t max_size; const size_t max_elements_size; - + WeightFunction weight_function; void removeOverflow() diff --git a/src/Common/SLRUCache.h b/src/Common/SLRUCache.h index c1c8daf5975..02ab17e873d 100644 --- a/src/Common/SLRUCache.h +++ b/src/Common/SLRUCache.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include @@ -18,7 +18,7 @@ struct TrivialSLRUCacheWeightFunction template , typename WeightFunction = TrivialSLRUCacheWeightFunction> -class SLRUCache: public LRUCache +class SLRUCache : public LRUCache { public: using Key = TKey; @@ -58,15 +58,9 @@ public: return cells.size(); } - size_t maxSize() const - { - return max_size; - } + size_t maxSize() const { return max_size; } - size_t maxProtectedSize() const - { - return max_protected_size; - } + size_t maxProtectedSize() const { return max_protected_size; } void reset() { @@ -108,7 +102,7 @@ private: size_t current_size = 0; const size_t max_protected_size; const size_t max_size; - + WeightFunction weight_function; MappedPtr getImpl(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override @@ -225,7 +219,6 @@ private: } } - /// Override this method if you want to track how much weight was lost in removeOverflow method. virtual void onRemoveOverflowWeightLoss(size_t /*weight_loss*/) override {} }; diff --git a/src/Common/SLRUCachePolicy.h b/src/Common/SLRUCachePolicy.h index 56f2ae698a4..ece0ebdeb71 100644 --- a/src/Common/SLRUCachePolicy.h +++ b/src/Common/SLRUCachePolicy.h @@ -2,12 +2,12 @@ #include -#include #include +#include #include -namespace DB +namespace DB { /// Cache policy SLRU evicts entries which were used only once and are not used for a long time, @@ -16,10 +16,10 @@ namespace DB /// of that value. /// Cache starts to evict entries when their total weight exceeds max_size. /// Value weight should not change after insertion. -/// To work with the thread-safe implementation of this class use a class "CacheBase" with first parameter "SLRU" +/// To work with the thread-safe implementation of this class use a class "CacheBase" with first parameter "SLRU" /// and next parameters in the same order as in the constructor of the current class. template , typename WeightFunction = TrivialWeightFunction> -class SLRUCachePolicy: public ICachePolicy +class SLRUCachePolicy : public ICachePolicy { public: using Key = TKey; @@ -38,8 +38,9 @@ public: , max_size(std::max(max_protected_size + 1, max_size_)) {} - template - SLRUCachePolicy(OnWeightLossFunction on_weight_loss_function_, Args... args) : SLRUCachePolicy(args...) { + template + SLRUCachePolicy(OnWeightLossFunction on_weight_loss_function_, Args... args) : SLRUCachePolicy(args...) + { Base::on_weight_loss_function = on_weight_loss_function_; } @@ -172,7 +173,7 @@ protected: size_t current_size = 0; const size_t max_protected_size; const size_t max_size; - + WeightFunction weight_function; void removeOverflow(SLRUQueue & queue, const size_t max_weight_size, size_t & current_weight_size) @@ -210,7 +211,8 @@ protected: --queue_size; } - if (current_weight_lost > 0) { + if (current_weight_lost > 0) + { Base::on_weight_loss_function(current_weight_lost); } diff --git a/src/Common/tests/gtest_slru_cahce.cpp b/src/Common/tests/gtest_slru_cahce.cpp index b4bebe73084..43bc6ee40a6 100644 --- a/src/Common/tests/gtest_slru_cahce.cpp +++ b/src/Common/tests/gtest_slru_cahce.cpp @@ -1,8 +1,8 @@ #include #include #include -#include #include +#include TEST(SLRUCache, set) { @@ -111,7 +111,7 @@ TEST(SLRUCache, evictFromProtectedPart) slru_cache.set(2, std::make_shared(5)); slru_cache.set(2, std::make_shared(5)); - + slru_cache.set(3, std::make_shared(5)); auto value = slru_cache.get(1); @@ -127,8 +127,9 @@ TEST(SLRUCache, evictStreamProtected) slru_cache.set(2, std::make_shared(3)); slru_cache.set(2, std::make_shared(3)); - - for (int key = 3; key < 10; ++key) { + + for (int key = 3; key < 10; ++key) + { slru_cache.set(key, std::make_shared(1 + key % 5)); } diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 92af0576129..651b1efd8c8 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -97,6 +97,7 @@ static constexpr UInt64 operator""_GiB(unsigned long long value) M(UInt64, hsts_max_age, 0, "Expired time for hsts. 0 means disable HSTS.", 0) \ M(Bool, extremes, false, "Calculate minimums and maximums of the result columns. They can be output in JSON-formats.", IMPORTANT) \ M(Bool, use_uncompressed_cache, false, "Whether to use the cache of uncompressed blocks.", 0) \ + M(String, uncompressed_cache_policy, "", "Which cache policy use for the cache of uncompressed blocks.", 0) \ M(Bool, replace_running_query, false, "Whether the running request should be canceled with the same id as the new one.", 0) \ M(UInt64, max_replicated_fetches_network_bandwidth_for_server, 0, "The maximum speed of data exchange over the network in bytes per second for replicated fetches. Zero means unlimited. Only has meaning at server startup.", 0) \ M(UInt64, max_replicated_sends_network_bandwidth_for_server, 0, "The maximum speed of data exchange over the network in bytes per second for replicated sends. Zero means unlimited. Only has meaning at server startup.", 0) \ From 6bf29cb6103ff3a33d6e2fc6cbc5360b7e3f0be0 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sat, 30 Apr 2022 11:53:59 +0000 Subject: [PATCH 380/672] Change class LRUCache to class CachBase. Check running CacheBase with default pcahce policy SLRU --- src/Access/MultipleAccessStorage.h | 4 +- src/Common/CacheBase.h | 7 +- src/Common/ColumnsHashing.h | 4 +- src/Common/LRUCache.h | 375 ------------------ src/Common/LRUResourceCache.h | 2 +- src/Common/SLRUCache.h | 227 ----------- src/Common/SLRUCachePolicy.h | 29 +- src/Common/tests/gtest_slru_cahce.cpp | 56 ++- src/Functions/IFunction.cpp | 1 - src/IO/MMappedFileCache.h | 6 +- src/IO/UncompressedCache.h | 1 - src/Interpreters/Aggregator.cpp | 4 +- .../JIT/CompiledExpressionCache.h | 6 +- src/Interpreters/MergeJoin.h | 4 +- .../Formats/Impl/AvroRowInputFormat.cpp | 6 +- src/Storages/Hive/HiveCommon.h | 4 +- src/Storages/Hive/HiveFile.h | 2 +- src/Storages/MarkCache.h | 6 +- 18 files changed, 89 insertions(+), 655 deletions(-) delete mode 100644 src/Common/LRUCache.h delete mode 100644 src/Common/SLRUCache.h diff --git a/src/Access/MultipleAccessStorage.h b/src/Access/MultipleAccessStorage.h index 58cf09fd0ff..bc9a4705785 100644 --- a/src/Access/MultipleAccessStorage.h +++ b/src/Access/MultipleAccessStorage.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include @@ -63,7 +63,7 @@ private: std::shared_ptr getStoragesInternal() const; std::shared_ptr nested_storages TSA_GUARDED_BY(mutex); - mutable LRUCache ids_cache TSA_GUARDED_BY(mutex); + mutable CacheBase ids_cache TSA_GUARDED_BY(mutex); mutable std::mutex mutex; }; diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index 58c093d536f..3d23be687cc 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -30,10 +30,11 @@ public: using Mapped = TMapped; using MappedPtr = std::shared_ptr; - CacheBase(size_t max_size) : CacheBase(default_cache_policy_name, max_size) {} + CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase(default_cache_policy_name, max_size, max_elements_size) {} + /// TODO: Rewrite "Args... args" to custom struct with fields for all cache policies. template - CacheBase(std::string cache_policy_name, Args... args) + CacheBase(String cache_policy_name, Args... args) { auto onWeightLossFunction = [&](size_t weight_loss) { onRemoveOverflowWeightLoss(weight_loss); }; @@ -197,7 +198,7 @@ private: std::unique_ptr cache_policy; String policy_name; // DELETE THIS FIELD (only for debug) - inline static const String default_cache_policy_name = "LRU"; + inline static const String default_cache_policy_name = "SLRU"; std::atomic hits{0}; std::atomic misses{0}; diff --git a/src/Common/ColumnsHashing.h b/src/Common/ColumnsHashing.h index e921f4fbf9a..950dd6dc8be 100644 --- a/src/Common/ColumnsHashing.h +++ b/src/Common/ColumnsHashing.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include "Columns/IColumn.h" #include @@ -193,7 +193,7 @@ public: void set(const DictionaryKey & key, const CachedValuesPtr & mapped) { cache.set(key, mapped); } private: - using Cache = LRUCache; + using Cache = CacheBase; Cache cache; }; diff --git a/src/Common/LRUCache.h b/src/Common/LRUCache.h deleted file mode 100644 index ce767fa0b1f..00000000000 --- a/src/Common/LRUCache.h +++ /dev/null @@ -1,375 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include - - -namespace DB -{ - -// TODO: rewrite all using of class LRUCache to class CacheBase with cache_policy = "LRU" - -template -struct TrivialLRUWeightFunction -{ - size_t operator()(const T &) const - { - return 1; - } -}; - - -/// Thread-safe cache that evicts entries which are not used for a long time. -/// WeightFunction is a functor that takes Mapped as a parameter and returns "weight" (approximate size) -/// of that value. -/// Cache starts to evict entries when their total weight exceeds max_size. -/// Value weight should not change after insertion. -template , typename WeightFunction = TrivialLRUWeightFunction> -class LRUCache -{ -public: - using Key = TKey; - using Mapped = TMapped; - using MappedPtr = std::shared_ptr; - - /** Initialize LRUCache with max_size and max_elements_size. - * max_elements_size == 0 means no elements size restrictions. - */ - explicit LRUCache(size_t max_size_, size_t max_elements_size_ = 0) - : max_size(std::max(static_cast(1), max_size_)) - , max_elements_size(max_elements_size_) - {} - - MappedPtr get(const Key & key) - { - std::lock_guard lock(mutex); - - auto res = getImpl(key); - if (res) - ++hits; - else - ++misses; - - return res; - } - - void set(const Key & key, const MappedPtr & mapped) - { - std::lock_guard lock(mutex); - - setImpl(key, mapped); - } - - void remove(const Key & key) - { - std::lock_guard lock(mutex); - auto it = cells.find(key); - if (it == cells.end()) - return; - auto & cell = it->second; - current_size -= cell.size; - queue.erase(cell.queue_iterator); - cells.erase(it); - } - - /// If the value for the key is in the cache, returns it. If it is not, calls load_func() to - /// produce it, saves the result in the cache and returns it. - /// Only one of several concurrent threads calling getOrSet() will call load_func(), - /// others will wait for that call to complete and will use its result (this helps prevent cache stampede). - /// Exceptions occurring in load_func will be propagated to the caller. Another thread from the - /// set of concurrent threads will then try to call its load_func etc. - /// - /// Returns std::pair of the cached value and a bool indicating whether the value was produced during this call. - template - std::pair getOrSet(const Key & key, LoadFunc && load_func) - { - InsertTokenHolder token_holder; - { - std::lock_guard cache_lock(mutex); - - auto val = getImpl(key); - if (val) - { - ++hits; - return std::make_pair(val, false); - } - - auto & token = insert_tokens[key]; - if (!token) - token = std::make_shared(*this); - - token_holder.acquire(&key, token, cache_lock); - } - - InsertToken * token = token_holder.token.get(); - - std::lock_guard token_lock(token->mutex); - - token_holder.cleaned_up = token->cleaned_up; - - if (token->value) - { - /// Another thread already produced the value while we waited for token->mutex. - ++hits; - return std::make_pair(token->value, false); - } - - ++misses; - token->value = load_func(); - - std::lock_guard cache_lock(mutex); - - /// Insert the new value only if the token is still in present in insert_tokens. - /// (The token may be absent because of a concurrent reset() call). - bool result = false; - auto token_it = insert_tokens.find(key); - if (token_it != insert_tokens.end() && token_it->second.get() == token) - { - setImpl(key, token->value); - result = true; - } - - if (!token->cleaned_up) - token_holder.cleanup(token_lock, cache_lock); - - return std::make_pair(token->value, result); - } - - void getStats(size_t & out_hits, size_t & out_misses) const - { - std::lock_guard lock(mutex); - out_hits = hits; - out_misses = misses; - } - - size_t weight() const - { - std::lock_guard lock(mutex); - return current_size; - } - - size_t count() const - { - std::lock_guard lock(mutex); - return cells.size(); - } - - size_t maxSize() const - { - return max_size; - } - - void reset() - { - std::lock_guard lock(mutex); - resetImpl(); - } - - virtual ~LRUCache() = default; - -protected: - using LRUQueue = std::list; - using LRUQueueIterator = typename LRUQueue::iterator; - - struct Cell - { - MappedPtr value; - size_t size; - LRUQueueIterator queue_iterator; - }; - - using Cells = std::unordered_map; - - Cells cells TSA_GUARDED_BY(mutex); - - mutable std::mutex mutex; - - void resetImpl() - { - queue.clear(); - cells.clear(); - insert_tokens.clear(); - current_size = 0; - hits = 0; - misses = 0; - } - -private: - - /// Represents pending insertion attempt. - struct InsertToken - { - explicit InsertToken(LRUCache & cache_) : cache(cache_) {} - - std::mutex mutex; - bool cleaned_up TSA_GUARDED_BY(mutex) = false; - MappedPtr value TSA_GUARDED_BY(mutex); - - LRUCache & cache; - size_t refcount = 0; /// Protected by the cache mutex - }; - - using InsertTokenById = std::unordered_map, HashFunction>; - - /// This class is responsible for removing used insert tokens from the insert_tokens map. - /// Among several concurrent threads the first successful one is responsible for removal. But if they all - /// fail, then the last one is responsible. - struct InsertTokenHolder - { - const Key * key = nullptr; - std::shared_ptr token; - bool cleaned_up = false; - - InsertTokenHolder() = default; - - void acquire(const Key * key_, const std::shared_ptr & token_, [[maybe_unused]] std::lock_guard & cache_lock) - TSA_NO_THREAD_SAFETY_ANALYSIS // disabled only because we can't reference the parent-level cache mutex from here - { - key = key_; - token = token_; - ++token->refcount; - } - - void cleanup([[maybe_unused]] std::lock_guard & token_lock, [[maybe_unused]] std::lock_guard & cache_lock) - TSA_NO_THREAD_SAFETY_ANALYSIS // disabled only because we can't reference the parent-level cache mutex from here - { - token->cache.insert_tokens.erase(*key); - token->cleaned_up = true; - cleaned_up = true; - } - - ~InsertTokenHolder() - { - if (!token) - return; - - if (cleaned_up) - return; - - std::lock_guard token_lock(token->mutex); - - if (token->cleaned_up) - return; - - std::lock_guard cache_lock(token->cache.mutex); - - --token->refcount; - if (token->refcount == 0) - cleanup(token_lock, cache_lock); - } - }; - - friend struct InsertTokenHolder; - - - InsertTokenById insert_tokens TSA_GUARDED_BY(mutex); - - LRUQueue queue TSA_GUARDED_BY(mutex); - - /// Total weight of values. - size_t current_size TSA_GUARDED_BY(mutex) = 0; - const size_t max_size; - const size_t max_elements_size; - - std::atomic hits {0}; - std::atomic misses {0}; - - const WeightFunction weight_function; - - virtual MappedPtr getImpl(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) - { - auto it = cells.find(key); - if (it == cells.end()) - { - return MappedPtr(); - } - - Cell & cell = it->second; - - /// Move the key to the end of the queue. The iterator remains valid. - queue.splice(queue.end(), queue, cell.queue_iterator); - - return cell.value; - } - - virtual void setImpl(const Key & key, const MappedPtr & mapped, [[maybe_unused]] std::lock_guard & cache_lock) - { - auto [it, inserted] = cells.emplace(std::piecewise_construct, - std::forward_as_tuple(key), - std::forward_as_tuple()); - - Cell & cell = it->second; - - if (inserted) - { - try - { - cell.queue_iterator = queue.insert(queue.end(), key); - } - catch (...) - { - cells.erase(it); - throw; - } - } - else - { - current_size -= cell.size; - queue.splice(queue.end(), queue, cell.queue_iterator); - } - - cell.value = mapped; - cell.size = cell.value ? weight_function(*cell.value) : 0; - current_size += cell.size; - - removeOverflow(); - } - - void removeOverflow() TSA_REQUIRES(mutex) - { - size_t current_weight_lost = 0; - size_t queue_size = cells.size(); - - while ((current_size > max_size || (max_elements_size != 0 && queue_size > max_elements_size)) && (queue_size > 1)) - { - const Key & key = queue.front(); - - auto it = cells.find(key); - if (it == cells.end()) - { - LOG_ERROR(&Poco::Logger::get("LRUCache"), "LRUCache became inconsistent. There must be a bug in it."); - abort(); - } - - const auto & cell = it->second; - - current_size -= cell.size; - current_weight_lost += cell.size; - - cells.erase(it); - queue.pop_front(); - --queue_size; - } - - onRemoveOverflowWeightLoss(current_weight_lost); - - if (current_size > (1ull << 63)) - { - LOG_ERROR(&Poco::Logger::get("LRUCache"), "LRUCache became inconsistent. There must be a bug in it."); - abort(); - } - } - - /// Override this method if you want to track how much weight was lost in removeOverflow method. - virtual void onRemoveOverflowWeightLoss(size_t /*weight_loss*/) {} -}; - - -} diff --git a/src/Common/LRUResourceCache.h b/src/Common/LRUResourceCache.h index 904c69b25ae..1fe3075a2a3 100644 --- a/src/Common/LRUResourceCache.h +++ b/src/Common/LRUResourceCache.h @@ -24,7 +24,7 @@ struct TrivialLRUResourceCacheReleaseFunction }; /** - * Similar to implementation in LRUCache.h, but with the difference that keys can + * Similar to implementation in LRUCachePolicy.h, but with the difference that keys can * only be evicted when they are releasable. Release state is controlled by this implementation. * get() and getOrSet() methods return a Holder to actual value, which does release() in destructor. * diff --git a/src/Common/SLRUCache.h b/src/Common/SLRUCache.h deleted file mode 100644 index 02ab17e873d..00000000000 --- a/src/Common/SLRUCache.h +++ /dev/null @@ -1,227 +0,0 @@ -#pragma once - -#include - -namespace DB -{ - -template -struct TrivialSLRUCacheWeightFunction -{ - size_t operator()(const T &) const - { - return 1; - } -}; - -// TODO: rewrite all using of class LRUCache to class CacheBase with cache_policy = "SLRU" - - -template , typename WeightFunction = TrivialSLRUCacheWeightFunction> -class SLRUCache : public LRUCache -{ -public: - using Key = TKey; - using Mapped = TMapped; - using MappedPtr = std::shared_ptr; - using Base = LRUCache; - using Base::mutex; - - SLRUCache(size_t max_size_, size_t max_protected_size_ = 0) - : Base(0) - , max_protected_size(max_protected_size_ != 0 ? max_protected_size_ : max_size_ / 2 + 1) - , max_size(std::max(max_protected_size + 1, max_size_)) - {} - - void remove(const Key & key) - { - std::lock_guard lock(mutex); - auto it = cells.find(key); - if (it == cells.end()) - return; - auto & cell = it->second; - current_size -= cell.size; - auto & queue = cell.is_protected ? protected_queue : probationary_queue; - queue.erase(it); - cells.erase(it); - } - - size_t weight() const - { - std::lock_guard lock(mutex); - return current_size; - } - - size_t count() const - { - std::lock_guard lock(mutex); - return cells.size(); - } - - size_t maxSize() const { return max_size; } - - size_t maxProtectedSize() const { return max_protected_size; } - - void reset() - { - std::lock_guard lock(mutex); - resetImpl(); - } - -protected: - using SLRUQueue = std::list; - using SLRUQueueIterator = typename SLRUQueue::iterator; - - struct Cell - { - bool is_protected; - MappedPtr value; - size_t size; - SLRUQueueIterator queue_iterator; - }; - - using Cells = std::unordered_map; - - Cells cells; - - void resetImpl() - { - cells.clear(); - probationary_queue.clear(); - protected_queue.clear(); - current_size = 0; - current_protected_size = 0; - Base::resetImpl(); - } - -private: - SLRUQueue probationary_queue; - SLRUQueue protected_queue; - - size_t current_protected_size = 0; - size_t current_size = 0; - const size_t max_protected_size; - const size_t max_size; - - WeightFunction weight_function; - - MappedPtr getImpl(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override - { - auto it = cells.find(key); - if (it == cells.end()) - { - return MappedPtr(); - } - - Cell & cell = it->second; - - if (cell.is_protected) - { - protected_queue.splice(protected_queue.end(), protected_queue, cell.queue_iterator); - } - else - { - cell.is_protected = true; - current_protected_size += cell.size; - protected_queue.splice(protected_queue.end(), probationary_queue, cell.queue_iterator); - } - - removeOverflow(protected_queue, max_protected_size, current_protected_size); - - return cell.value; - } - - void setImpl(const Key & key, const MappedPtr & mapped, [[maybe_unused]] std::lock_guard & cache_lock) override - { - auto [it, inserted] = cells.emplace(std::piecewise_construct, - std::forward_as_tuple(key), - std::forward_as_tuple()); - - Cell & cell = it->second; - - if (inserted) - { - try - { - cell.queue_iterator = probationary_queue.insert(probationary_queue.end(), key); - } - catch (...) - { - cells.erase(it); - throw; - } - } - else - { - current_size -= cell.size; - if (cell.is_protected) - { - current_protected_size -= cell.size; - protected_queue.splice(protected_queue.end(), protected_queue, cell.queue_iterator); - } - else - { - cell.is_protected = true; - protected_queue.splice(protected_queue.end(), probationary_queue, cell.queue_iterator); - } - } - - cell.value = mapped; - cell.size = cell.value ? weight_function(*cell.value) : 0; - current_size += cell.size; - current_protected_size += cell.is_protected ? cell.size : 0; - - removeOverflow(protected_queue, max_protected_size, current_protected_size); - removeOverflow(probationary_queue, max_size, current_size); - } - - void removeOverflow(SLRUQueue & queue, const size_t max_weight_size, size_t & current_weight_size) - { - size_t current_weight_lost = 0; - size_t queue_size = queue.size(); - - while (current_weight_size > max_weight_size && queue_size > 1) - { - const Key & key = queue.front(); - - auto it = cells.find(key); - if (it == cells.end()) - { - LOG_ERROR(&Poco::Logger::get("LRUCache"), "LRUCache became inconsistent. There must be a bug in it."); - abort(); - } - - auto & cell = it->second; - - current_weight_size -= cell.size; - - if (cell.is_protected) - { - cell.is_protected = false; - probationary_queue.splice(probationary_queue.end(), queue, cell.queue_iterator); - } - else - { - current_weight_lost += cell.size; - cells.erase(it); - queue.pop_front(); - } - - --queue_size; - } - - onRemoveOverflowWeightLoss(current_weight_lost); - - if (current_size > (1ull << 63)) - { - LOG_ERROR(&Poco::Logger::get("LRUCache"), "LRUCache became inconsistent. There must be a bug in it."); - abort(); - } - } - - /// Override this method if you want to track how much weight was lost in removeOverflow method. - virtual void onRemoveOverflowWeightLoss(size_t /*weight_loss*/) override {} -}; - - -} diff --git a/src/Common/SLRUCachePolicy.h b/src/Common/SLRUCachePolicy.h index ece0ebdeb71..834d8e66953 100644 --- a/src/Common/SLRUCachePolicy.h +++ b/src/Common/SLRUCachePolicy.h @@ -33,9 +33,11 @@ public: * max_protected_size shows how many of the most frequently used entries will not be evicted after a sequential scan. * max_protected_size == 0 means that the default protected size is equal to half of the total max size. */ - SLRUCachePolicy(size_t max_size_, size_t max_protected_size_ = 0) - : max_protected_size(max_protected_size_ != 0 ? max_protected_size_ : max_size_ / 2 + 1) + /// TODO: construct from special struct with cache policy parametrs (also with max_protected_size). + SLRUCachePolicy(size_t max_size_, size_t max_elements_size_ = 0) + : max_protected_size(max_size_ / 2) , max_size(std::max(max_protected_size + 1, max_size_)) + , max_elements_size(max_elements_size_) {} template @@ -101,7 +103,7 @@ public: protected_queue.splice(protected_queue.end(), probationary_queue, cell.queue_iterator); } - removeOverflow(protected_queue, max_protected_size, current_protected_size); + removeOverflow(protected_queue, max_protected_size, current_protected_size, /*is_protected=*/true); return cell.value; } @@ -146,8 +148,8 @@ public: current_size += cell.size; current_protected_size += cell.is_protected ? cell.size : 0; - removeOverflow(protected_queue, max_protected_size, current_protected_size); - removeOverflow(probationary_queue, max_size, current_size); + removeOverflow(protected_queue, max_protected_size, current_protected_size, /*is_protected=*/true); + removeOverflow(probationary_queue, max_size, current_size, /*is_protected=*/false); } protected: @@ -173,15 +175,26 @@ protected: size_t current_size = 0; const size_t max_protected_size; const size_t max_size; + const size_t max_elements_size; WeightFunction weight_function; - void removeOverflow(SLRUQueue & queue, const size_t max_weight_size, size_t & current_weight_size) + void removeOverflow(SLRUQueue & queue, const size_t max_weight_size, size_t & current_weight_size, bool is_protected) { size_t current_weight_lost = 0; size_t queue_size = queue.size(); - while (current_weight_size > max_weight_size && queue_size > 1) + auto need_remove = [&]() -> bool + { + if (is_protected) + { + return current_weight_size > max_weight_size && queue_size > 1; + } + return ((max_elements_size != 0 && queue_size > max_elements_size) + || (current_weight_size > max_weight_size)) && (queue_size > 1); + }; + + while (need_remove()) { const Key & key = queue.front(); @@ -211,7 +224,7 @@ protected: --queue_size; } - if (current_weight_lost > 0) + if (!is_protected) { Base::on_weight_loss_function(current_weight_lost); } diff --git a/src/Common/tests/gtest_slru_cahce.cpp b/src/Common/tests/gtest_slru_cahce.cpp index 43bc6ee40a6..10a392224fb 100644 --- a/src/Common/tests/gtest_slru_cahce.cpp +++ b/src/Common/tests/gtest_slru_cahce.cpp @@ -7,7 +7,7 @@ TEST(SLRUCache, set) { using SimpleCacheBase = DB::CacheBase; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); @@ -20,21 +20,23 @@ TEST(SLRUCache, set) TEST(SLRUCache, update) { using SimpleCacheBase = DB::CacheBase; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); slru_cache.set(1, std::make_shared(2)); slru_cache.set(1, std::make_shared(3)); - auto val = slru_cache.get(1); - ASSERT_TRUE(val != nullptr); - ASSERT_TRUE(*val == 3); + + auto value = slru_cache.get(1); + ASSERT_TRUE(value != nullptr); + ASSERT_TRUE(*value == 3); } TEST(SLRUCache, get) { using SimpleCacheBase = DB::CacheBase; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); - SimpleCacheBase::MappedPtr value = slru_cache.get(1); + + auto value = slru_cache.get(1); ASSERT_TRUE(value != nullptr); ASSERT_EQ(*value, 2); @@ -46,10 +48,11 @@ TEST(SLRUCache, get) TEST(SLRUCache, remove) { using SimpleCacheBase = DB::CacheBase; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); - SimpleCacheBase::MappedPtr value = slru_cache.get(1); + + auto value = slru_cache.get(1); ASSERT_TRUE(value != nullptr); ASSERT_EQ(*value, 2); @@ -61,15 +64,15 @@ TEST(SLRUCache, remove) TEST(SLRUCache, reset) { using SimpleCacheBase = DB::CacheBase; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); - slru_cache.set(2, std::make_shared(4)); // add to protected_queue + slru_cache.set(2, std::make_shared(4)); /// add to protected_queue slru_cache.reset(); - SimpleCacheBase::MappedPtr value = slru_cache.get(1); + auto value = slru_cache.get(1); ASSERT_TRUE(value == nullptr); value = slru_cache.get(2); @@ -81,10 +84,31 @@ struct ValueWeight size_t operator()(const size_t & x) const { return x; } }; +TEST(SLRUCache, evictOnElements) +{ + using SimpleCacheBase = DB::CacheBase, ValueWeight>; + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/1); + slru_cache.set(1, std::make_shared(2)); + slru_cache.set(2, std::make_shared(3)); + + auto n = slru_cache.count(); + ASSERT_EQ(n, 1); + + auto w = slru_cache.weight(); + ASSERT_EQ(w, 3); + + auto value = slru_cache.get(1); + ASSERT_TRUE(value == nullptr); + value = slru_cache.get(2); + ASSERT_TRUE(value != nullptr); + ASSERT_TRUE(*value == 3); +} + + TEST(SLRUCache, evictOnWeight) { using SimpleCacheBase = DB::CacheBase, ValueWeight>; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); slru_cache.set(3, std::make_shared(4)); @@ -105,7 +129,7 @@ TEST(SLRUCache, evictOnWeight) TEST(SLRUCache, evictFromProtectedPart) { using SimpleCacheBase = DB::CacheBase, ValueWeight>; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); slru_cache.set(1, std::make_shared(2)); slru_cache.set(1, std::make_shared(2)); @@ -121,7 +145,7 @@ TEST(SLRUCache, evictFromProtectedPart) TEST(SLRUCache, evictStreamProtected) { using SimpleCacheBase = DB::CacheBase, ValueWeight>; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); slru_cache.set(1, std::make_shared(2)); slru_cache.set(1, std::make_shared(2)); @@ -145,7 +169,7 @@ TEST(SLRUCache, evictStreamProtected) TEST(SLRUCache, getOrSet) { using SimpleCacheBase = DB::CacheBase, ValueWeight>; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_protected_size=*/5); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); size_t x = 5; auto load_func = [&] { return std::make_shared(x); }; auto [value, loaded] = slru_cache.getOrSet(1, load_func); diff --git a/src/Functions/IFunction.cpp b/src/Functions/IFunction.cpp index 5be2ea3c5e3..c1c6606e40f 100644 --- a/src/Functions/IFunction.cpp +++ b/src/Functions/IFunction.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include diff --git a/src/IO/MMappedFileCache.h b/src/IO/MMappedFileCache.h index fe5e7e8e1f7..0a8a80d15d0 100644 --- a/src/IO/MMappedFileCache.h +++ b/src/IO/MMappedFileCache.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -21,10 +21,10 @@ namespace DB /** Cache of opened and mmapped files for reading. * mmap/munmap is heavy operation and better to keep mapped file to subsequent use than to map/unmap every time. */ -class MMappedFileCache : public LRUCache +class MMappedFileCache : public CacheBase { private: - using Base = LRUCache; + using Base = CacheBase; public: explicit MMappedFileCache(size_t max_size_in_bytes) diff --git a/src/IO/UncompressedCache.h b/src/IO/UncompressedCache.h index d1d1f49cf9f..2aa29e102da 100644 --- a/src/IO/UncompressedCache.h +++ b/src/IO/UncompressedCache.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include diff --git a/src/Interpreters/Aggregator.cpp b/src/Interpreters/Aggregator.cpp index fc5774735a0..07360a70595 100644 --- a/src/Interpreters/Aggregator.cpp +++ b/src/Interpreters/Aggregator.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include @@ -54,7 +54,7 @@ public: size_t median_size; // roughly the size we're going to preallocate on each thread }; - using Cache = DB::LRUCache; + using Cache = DB::CacheBase; using CachePtr = std::shared_ptr; using Params = DB::Aggregator::Params::StatsCollectingParams; diff --git a/src/Interpreters/JIT/CompiledExpressionCache.h b/src/Interpreters/JIT/CompiledExpressionCache.h index 936b5798280..a2a8141759c 100644 --- a/src/Interpreters/JIT/CompiledExpressionCache.h +++ b/src/Interpreters/JIT/CompiledExpressionCache.h @@ -3,7 +3,7 @@ #include "config_core.h" #if USE_EMBEDDED_COMPILER -# include +# include # include # include @@ -35,10 +35,10 @@ struct CompiledFunctionWeightFunction } }; -class CompiledExpressionCache : public LRUCache +class CompiledExpressionCache : public CacheBase { public: - using Base = LRUCache; + using Base = CacheBase; using Base::Base; }; diff --git a/src/Interpreters/MergeJoin.h b/src/Interpreters/MergeJoin.h index 3b8ad6063e3..99bd7deffe2 100644 --- a/src/Interpreters/MergeJoin.h +++ b/src/Interpreters/MergeJoin.h @@ -2,7 +2,7 @@ #include -#include +#include #include #include #include @@ -69,7 +69,7 @@ private: size_t operator()(const Block & block) const { return block.bytes(); } }; - using Cache = LRUCache, BlockByteWeight>; + using Cache = CacheBase, BlockByteWeight>; mutable std::shared_mutex rwlock; std::shared_ptr table_join; diff --git a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp index 6b056e64d41..75a318ce372 100644 --- a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp @@ -6,7 +6,7 @@ #include -#include +#include #include #include @@ -777,13 +777,13 @@ private: } Poco::URI base_url; - LRUCache schema_cache; + CacheBase schema_cache; }; using ConfluentSchemaRegistry = AvroConfluentRowInputFormat::SchemaRegistry; #define SCHEMA_REGISTRY_CACHE_MAX_SIZE 1000 /// Cache of Schema Registry URL -> SchemaRegistry -static LRUCache schema_registry_cache(SCHEMA_REGISTRY_CACHE_MAX_SIZE); +static CacheBase schema_registry_cache(SCHEMA_REGISTRY_CACHE_MAX_SIZE); static std::shared_ptr getConfluentSchemaRegistry(const FormatSettings & format_settings) { diff --git a/src/Storages/Hive/HiveCommon.h b/src/Storages/Hive/HiveCommon.h index 55f79bbaaeb..297b79da935 100644 --- a/src/Storages/Hive/HiveCommon.h +++ b/src/Storages/Hive/HiveCommon.h @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include @@ -135,7 +135,7 @@ private: void tryCallHiveClient(std::function func); - LRUCache table_metadata_cache; + CacheBase table_metadata_cache; ThriftHiveMetastoreClientPool client_pool; Poco::Logger * log = &Poco::Logger::get("HiveMetastoreClient"); diff --git a/src/Storages/Hive/HiveFile.h b/src/Storages/Hive/HiveFile.h index a4bd345aa48..26e1b844b9d 100644 --- a/src/Storages/Hive/HiveFile.h +++ b/src/Storages/Hive/HiveFile.h @@ -170,7 +170,7 @@ protected: using HiveFilePtr = std::shared_ptr; using HiveFiles = std::vector; -using HiveFilesCache = LRUCache; +using HiveFilesCache = CacheBase; using HiveFilesCachePtr = std::shared_ptr; class HiveTextFile : public IHiveFile diff --git a/src/Storages/MarkCache.h b/src/Storages/MarkCache.h index a3f92650426..834472c2d41 100644 --- a/src/Storages/MarkCache.h +++ b/src/Storages/MarkCache.h @@ -2,7 +2,7 @@ #include -#include +#include #include #include #include @@ -34,10 +34,10 @@ struct MarksWeightFunction /** Cache of 'marks' for StorageMergeTree. * Marks is an index structure that addresses ranges in column file, corresponding to ranges of primary key. */ -class MarkCache : public LRUCache +class MarkCache : public CacheBase { private: - using Base = LRUCache; + using Base = CacheBase; public: explicit MarkCache(size_t max_size_in_bytes) From 4f52efd4bb93618ac8ec14331a5d522b7b0a9194 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sat, 30 Apr 2022 13:53:13 +0000 Subject: [PATCH 381/672] Build fix --- src/Common/LRUCachePolicy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/LRUCachePolicy.h b/src/Common/LRUCachePolicy.h index 63a71946a1c..aba9e3ae537 100644 --- a/src/Common/LRUCachePolicy.h +++ b/src/Common/LRUCachePolicy.h @@ -5,7 +5,7 @@ #include #include -#include +#include namespace DB { From 910645a52a85eb81d91098fd91fc212f8f211267 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sat, 30 Apr 2022 16:55:26 +0000 Subject: [PATCH 382/672] Include fix in SLRUCachePolicy.h --- src/Common/SLRUCachePolicy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/SLRUCachePolicy.h b/src/Common/SLRUCachePolicy.h index 834d8e66953..72bc2f57dde 100644 --- a/src/Common/SLRUCachePolicy.h +++ b/src/Common/SLRUCachePolicy.h @@ -5,7 +5,7 @@ #include #include -#include +#include namespace DB { From 9940b69bd7102b1e0fbfe8fb4ee5ecbef8474ce4 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sat, 30 Apr 2022 18:00:13 +0000 Subject: [PATCH 383/672] Include fix in ICachePolicy.h --- src/Common/ICachePolicy.h | 1 + src/Common/tests/gtest_slru_cahce.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/ICachePolicy.h b/src/Common/ICachePolicy.h index 3fb34631e87..8ca84a5c5cb 100644 --- a/src/Common/ICachePolicy.h +++ b/src/Common/ICachePolicy.h @@ -1,5 +1,6 @@ #pragma once +#include #include namespace DB diff --git a/src/Common/tests/gtest_slru_cahce.cpp b/src/Common/tests/gtest_slru_cahce.cpp index 10a392224fb..3f66ebdc7e3 100644 --- a/src/Common/tests/gtest_slru_cahce.cpp +++ b/src/Common/tests/gtest_slru_cahce.cpp @@ -2,7 +2,6 @@ #include #include #include -#include TEST(SLRUCache, set) { From a94950d3acfec9ec2a5b0f9f76cbb0273644b049 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sat, 30 Apr 2022 22:01:16 +0000 Subject: [PATCH 384/672] Test only with LRU --- src/Common/CacheBase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index 3d23be687cc..3cafb259f04 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -30,7 +30,7 @@ public: using Mapped = TMapped; using MappedPtr = std::shared_ptr; - CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase(default_cache_policy_name, max_size, max_elements_size) {} + CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase("LRU", max_size, max_elements_size) {} /// TODO: Rewrite "Args... args" to custom struct with fields for all cache policies. template From 69cc89e36e4a2bcb68c15d36944c2aa91cfb4bb5 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Tue, 10 May 2022 10:05:55 +0000 Subject: [PATCH 385/672] Check with default cache policy = LRU --- src/Common/CacheBase.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index 3cafb259f04..e992f94de04 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -94,7 +94,6 @@ public: template std::pair getOrSet(const Key & key, LoadFunc && load_func) { - LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Cache getOrSet. Cache policy name \"{}\"", policy_name); InsertTokenHolder token_holder; { std::lock_guard cache_lock(mutex); @@ -198,7 +197,7 @@ private: std::unique_ptr cache_policy; String policy_name; // DELETE THIS FIELD (only for debug) - inline static const String default_cache_policy_name = "SLRU"; + inline static const String default_cache_policy_name = "LRU"; std::atomic hits{0}; std::atomic misses{0}; From 50a8887ab9088bad6b9a01ca94ab84aef01b9383 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sat, 14 May 2022 04:15:57 +0000 Subject: [PATCH 386/672] Add [[maybe_unused]] locks and delete all objects from cache queue --- src/Common/CacheBase.h | 21 +++++++++------------ src/Common/ICachePolicy.h | 14 +++++++------- src/Common/LRUCachePolicy.h | 16 ++++++++-------- src/Common/SLRUCachePolicy.h | 18 +++++++++--------- 4 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index e992f94de04..0795394fc42 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -43,8 +43,6 @@ public: cache_policy_name = default_cache_policy_name; } - policy_name = cache_policy_name; - LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Cache policy name \"{}\"", cache_policy_name); if (cache_policy_name == "LRU") { @@ -67,7 +65,7 @@ public: { std::lock_guard lock(mutex); - auto res = cache_policy->get(key); + auto res = cache_policy->get(key, lock); if (res) ++hits; else @@ -80,7 +78,7 @@ public: { std::lock_guard lock(mutex); - cache_policy->set(key, mapped); + cache_policy->set(key, mapped, lock); } /// If the value for the key is in the cache, returns it. If it is not, calls load_func() to @@ -98,7 +96,7 @@ public: { std::lock_guard cache_lock(mutex); - auto val = cache_policy->get(key); + auto val = cache_policy->get(key, cache_lock); if (val) { ++hits; @@ -136,7 +134,7 @@ public: auto token_it = insert_tokens.find(key); if (token_it != insert_tokens.end() && token_it->second.get() == token) { - cache_policy->set(key, token->value); + cache_policy->set(key, token->value, cache_lock); result = true; } @@ -159,31 +157,31 @@ public: insert_tokens.clear(); hits = 0; misses = 0; - cache_policy->reset(); + cache_policy->reset(lock); } void remove(const Key & key) { std::lock_guard lock(mutex); - cache_policy->remove(key); + cache_policy->remove(key, lock); } size_t weight() const { std::lock_guard lock(mutex); - return cache_policy->weight(); + return cache_policy->weight(lock); } size_t count() const { std::lock_guard lock(mutex); - return cache_policy->count(); + return cache_policy->count(lock); } size_t maxSize() const { std::lock_guard lock(mutex); - return cache_policy->maxSize(); + return cache_policy->maxSize(lock); } virtual ~CacheBase() = default; @@ -195,7 +193,6 @@ private: using CachePolicy = ICachePolicy; std::unique_ptr cache_policy; - String policy_name; // DELETE THIS FIELD (only for debug) inline static const String default_cache_policy_name = "LRU"; diff --git a/src/Common/ICachePolicy.h b/src/Common/ICachePolicy.h index 8ca84a5c5cb..a0cfa3c4977 100644 --- a/src/Common/ICachePolicy.h +++ b/src/Common/ICachePolicy.h @@ -23,14 +23,14 @@ public: using MappedPtr = std::shared_ptr; using OnWeightLossFunction = std::function; - virtual size_t weight() const = 0; - virtual size_t count() const = 0; - virtual size_t maxSize() const = 0; + virtual size_t weight([[maybe_unused]] std::lock_guard & cache_lock) const = 0; + virtual size_t count([[maybe_unused]] std::lock_guard & cache_lock) const = 0; + virtual size_t maxSize([[maybe_unused]] std::lock_guard & cache_lock) const = 0; - virtual void reset() = 0; - virtual void remove(const Key & key) = 0; - virtual MappedPtr get(const Key & key) = 0; - virtual void set(const Key & key, const MappedPtr & mapped) = 0; + virtual void reset([[maybe_unused]] std::lock_guard & cache_lock) = 0; + virtual void remove(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) = 0; + virtual MappedPtr get(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) = 0; + virtual void set(const Key & key, const MappedPtr & mapped, [[maybe_unused]] std::lock_guard & cache_lock) = 0; virtual ~ICachePolicy() = default; diff --git a/src/Common/LRUCachePolicy.h b/src/Common/LRUCachePolicy.h index aba9e3ae537..56bd9144e8f 100644 --- a/src/Common/LRUCachePolicy.h +++ b/src/Common/LRUCachePolicy.h @@ -41,29 +41,29 @@ public: Base::on_weight_loss_function = on_weight_loss_function_; } - size_t weight() const override + size_t weight([[maybe_unused]] std::lock_guard & cache_lock) const override { return current_size; } - size_t count() const override + size_t count([[maybe_unused]] std::lock_guard & cache_lock) const override { return cells.size(); } - size_t maxSize() const override + size_t maxSize([[maybe_unused]] std::lock_guard & cache_lock) const override { return max_size; } - void reset() override + void reset([[maybe_unused]] std::lock_guard & cache_lock) override { queue.clear(); cells.clear(); current_size = 0; } - void remove(const Key & key) override + void remove(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override { auto it = cells.find(key); if (it == cells.end()) @@ -74,7 +74,7 @@ public: cells.erase(it); } - MappedPtr get(const Key & key) override + MappedPtr get(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override { auto it = cells.find(key); if (it == cells.end()) @@ -90,7 +90,7 @@ public: return cell.value; } - void set(const Key & key, const MappedPtr & mapped) override + void set(const Key & key, const MappedPtr & mapped, [[maybe_unused]] std::lock_guard & cache_lock) override { auto [it, inserted] = cells.emplace(std::piecewise_construct, std::forward_as_tuple(key), @@ -152,7 +152,7 @@ protected: size_t current_weight_lost = 0; size_t queue_size = cells.size(); - while ((current_size > max_size || (max_elements_size != 0 && queue_size > max_elements_size)) && (queue_size > 1)) + while ((current_size > max_size || (max_elements_size != 0 && queue_size > max_elements_size)) && (queue_size > 0)) { const Key & key = queue.front(); diff --git a/src/Common/SLRUCachePolicy.h b/src/Common/SLRUCachePolicy.h index 72bc2f57dde..19c0e131d7a 100644 --- a/src/Common/SLRUCachePolicy.h +++ b/src/Common/SLRUCachePolicy.h @@ -46,22 +46,22 @@ public: Base::on_weight_loss_function = on_weight_loss_function_; } - size_t weight() const override + size_t weight([[maybe_unused]] std::lock_guard & cache_lock) const override { return current_size; } - size_t count() const override + size_t count([[maybe_unused]] std::lock_guard & cache_lock) const override { return cells.size(); } - size_t maxSize() const override + size_t maxSize([[maybe_unused]] std::lock_guard & cache_lock) const override { return max_size; } - void reset() override + void reset([[maybe_unused]] std::lock_guard & cache_lock) override { cells.clear(); probationary_queue.clear(); @@ -70,7 +70,7 @@ public: current_protected_size = 0; } - void remove(const Key & key) override + void remove(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override { auto it = cells.find(key); if (it == cells.end()) @@ -82,7 +82,7 @@ public: cells.erase(it); } - MappedPtr get(const Key & key) override + MappedPtr get(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override { auto it = cells.find(key); if (it == cells.end()) @@ -108,7 +108,7 @@ public: return cell.value; } - void set(const Key & key, const MappedPtr & mapped) override + void set(const Key & key, const MappedPtr & mapped, [[maybe_unused]] std::lock_guard & cache_lock) override { auto [it, inserted] = cells.emplace(std::piecewise_construct, std::forward_as_tuple(key), @@ -188,10 +188,10 @@ protected: { if (is_protected) { - return current_weight_size > max_weight_size && queue_size > 1; + return current_weight_size > max_weight_size && queue_size > 0; } return ((max_elements_size != 0 && queue_size > max_elements_size) - || (current_weight_size > max_weight_size)) && (queue_size > 1); + || (current_weight_size > max_weight_size)) && (queue_size > 0); }; while (need_remove()) From 994a9fc005d0f2950c3936accc7c967cb5236cfc Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sat, 14 May 2022 04:44:32 +0000 Subject: [PATCH 387/672] Minor fixes --- src/Common/ICachePolicy.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Common/ICachePolicy.h b/src/Common/ICachePolicy.h index a0cfa3c4977..16ff9113f84 100644 --- a/src/Common/ICachePolicy.h +++ b/src/Common/ICachePolicy.h @@ -2,6 +2,7 @@ #include #include +#include namespace DB { From daab74925c3df435b3467059b677ad682e55e1e5 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sat, 14 May 2022 11:46:50 +0000 Subject: [PATCH 388/672] Test with default cache policy = SLRU --- src/Common/CacheBase.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index 0795394fc42..2978be5f23d 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -30,7 +30,7 @@ public: using Mapped = TMapped; using MappedPtr = std::shared_ptr; - CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase("LRU", max_size, max_elements_size) {} + CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase("SLRU", max_size, max_elements_size) {} /// TODO: Rewrite "Args... args" to custom struct with fields for all cache policies. template @@ -194,7 +194,7 @@ private: std::unique_ptr cache_policy; - inline static const String default_cache_policy_name = "LRU"; + inline static const String default_cache_policy_name = "SLRU"; std::atomic hits{0}; std::atomic misses{0}; From a238e349cd08fddabb633714be8f0a5d746c2b95 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sun, 15 May 2022 03:38:44 +0000 Subject: [PATCH 389/672] Unblocking get max_size --- src/Common/CacheBase.h | 3 +-- src/Common/ICachePolicy.h | 2 +- src/Common/LRUCachePolicy.h | 2 +- src/Common/SLRUCachePolicy.h | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index 2978be5f23d..b6bf4ee08e5 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -180,8 +180,7 @@ public: size_t maxSize() const { - std::lock_guard lock(mutex); - return cache_policy->maxSize(lock); + return cache_policy->maxSize(); } virtual ~CacheBase() = default; diff --git a/src/Common/ICachePolicy.h b/src/Common/ICachePolicy.h index 16ff9113f84..a278a67c34d 100644 --- a/src/Common/ICachePolicy.h +++ b/src/Common/ICachePolicy.h @@ -26,7 +26,7 @@ public: virtual size_t weight([[maybe_unused]] std::lock_guard & cache_lock) const = 0; virtual size_t count([[maybe_unused]] std::lock_guard & cache_lock) const = 0; - virtual size_t maxSize([[maybe_unused]] std::lock_guard & cache_lock) const = 0; + virtual size_t maxSize() const = 0; virtual void reset([[maybe_unused]] std::lock_guard & cache_lock) = 0; virtual void remove(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) = 0; diff --git a/src/Common/LRUCachePolicy.h b/src/Common/LRUCachePolicy.h index 56bd9144e8f..d3523e3ad16 100644 --- a/src/Common/LRUCachePolicy.h +++ b/src/Common/LRUCachePolicy.h @@ -51,7 +51,7 @@ public: return cells.size(); } - size_t maxSize([[maybe_unused]] std::lock_guard & cache_lock) const override + size_t maxSize() const override { return max_size; } diff --git a/src/Common/SLRUCachePolicy.h b/src/Common/SLRUCachePolicy.h index 19c0e131d7a..1be8781e22f 100644 --- a/src/Common/SLRUCachePolicy.h +++ b/src/Common/SLRUCachePolicy.h @@ -56,7 +56,7 @@ public: return cells.size(); } - size_t maxSize([[maybe_unused]] std::lock_guard & cache_lock) const override + size_t maxSize() const override { return max_size; } From c044fee7a0c05e0e9e634ed89afa3936b116a838 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sun, 15 May 2022 05:47:19 +0000 Subject: [PATCH 390/672] Test with LRU --- src/Common/CacheBase.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index b6bf4ee08e5..28acafb5e3d 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -30,7 +30,7 @@ public: using Mapped = TMapped; using MappedPtr = std::shared_ptr; - CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase("SLRU", max_size, max_elements_size) {} + CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase("LRU", max_size, max_elements_size) {} /// TODO: Rewrite "Args... args" to custom struct with fields for all cache policies. template @@ -193,7 +193,7 @@ private: std::unique_ptr cache_policy; - inline static const String default_cache_policy_name = "SLRU"; + inline static const String default_cache_policy_name = "LRU"; std::atomic hits{0}; std::atomic misses{0}; From 8be62770aab1ad5d2499833131c7c5ff01570681 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sun, 15 May 2022 10:11:17 +0000 Subject: [PATCH 391/672] Test with SLRU and debug logs --- src/Common/CacheBase.h | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index 28acafb5e3d..19bb8a57adf 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -30,7 +31,7 @@ public: using Mapped = TMapped; using MappedPtr = std::shared_ptr; - CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase("LRU", max_size, max_elements_size) {} + CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase("SLRU", max_size, max_elements_size) {} /// TODO: Rewrite "Args... args" to custom struct with fields for all cache policies. template @@ -65,6 +66,11 @@ public: { std::lock_guard lock(mutex); + auto weight = cache_policy->weight(lock); + auto max_weight = cache_policy->maxSize(); + LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Info before get. Weight: {}. Max weight: {}", weight, max_weight); + assert(weight <= max_weight); + auto res = cache_policy->get(key, lock); if (res) ++hits; @@ -78,6 +84,11 @@ public: { std::lock_guard lock(mutex); + auto weight = cache_policy->weight(lock); + auto max_weight = cache_policy->maxSize(); + LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Info before set. Weight: {}. Max weight: {}", weight, max_weight); + assert(weight <= max_weight); + cache_policy->set(key, mapped, lock); } @@ -96,6 +107,11 @@ public: { std::lock_guard cache_lock(mutex); + auto weight = cache_policy->weight(cache_lock); + auto max_weight = cache_policy->maxSize(); + LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Info before getOrSet. Weight: {}. Max weight: {}", weight, max_weight); + assert(weight <= max_weight); + auto val = cache_policy->get(key, cache_lock); if (val) { @@ -154,6 +170,12 @@ public: void reset() { std::lock_guard lock(mutex); + + auto weight = cache_policy->weight(lock); + auto max_weight = cache_policy->maxSize(); + LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Info before reset. Weight: {}. Max weight: {}", weight, max_weight); + assert(weight <= max_weight); + insert_tokens.clear(); hits = 0; misses = 0; @@ -163,6 +185,12 @@ public: void remove(const Key & key) { std::lock_guard lock(mutex); + + auto weight = cache_policy->weight(lock); + auto max_weight = cache_policy->maxSize(); + LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Info before remove. Weight: {}. Max weight: {}", weight, max_weight); + assert(weight <= max_weight); + cache_policy->remove(key, lock); } @@ -193,7 +221,7 @@ private: std::unique_ptr cache_policy; - inline static const String default_cache_policy_name = "LRU"; + inline static const String default_cache_policy_name = "SLRU"; std::atomic hits{0}; std::atomic misses{0}; From a057e1607a857dd8606bf1ef1e26884816bd25ad Mon Sep 17 00:00:00 2001 From: alexX512 Date: Tue, 17 May 2022 08:11:02 +0000 Subject: [PATCH 392/672] Fix remove operation, add evict ion i from cachef number of elements greater than max_elements_size --- src/Common/SLRUCachePolicy.h | 12 +++++++--- src/Common/tests/gtest_slru_cahce.cpp | 33 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/Common/SLRUCachePolicy.h b/src/Common/SLRUCachePolicy.h index 1be8781e22f..b56a5646e02 100644 --- a/src/Common/SLRUCachePolicy.h +++ b/src/Common/SLRUCachePolicy.h @@ -77,6 +77,10 @@ public: return; auto & cell = it->second; current_size -= cell.size; + if (cell.is_protected) + { + current_protected_size -= cell.size; + } auto & queue = cell.is_protected ? protected_queue : probationary_queue; queue.erase(cell.queue_iterator); cells.erase(it); @@ -188,10 +192,12 @@ protected: { if (is_protected) { - return current_weight_size > max_weight_size && queue_size > 0; + return ((max_elements_size != 0 && cells.size() - probationary_queue.size() > max_elements_size - probationary_queue.size()) + || (current_weight_size > max_weight_size)) + && (queue_size > 0); } - return ((max_elements_size != 0 && queue_size > max_elements_size) - || (current_weight_size > max_weight_size)) && (queue_size > 0); + return ((max_elements_size != 0 && cells.size() > max_elements_size) || (current_weight_size > max_weight_size)) + && (queue_size > 0); }; while (need_remove()) diff --git a/src/Common/tests/gtest_slru_cahce.cpp b/src/Common/tests/gtest_slru_cahce.cpp index 3f66ebdc7e3..1c4fcaddce0 100644 --- a/src/Common/tests/gtest_slru_cahce.cpp +++ b/src/Common/tests/gtest_slru_cahce.cpp @@ -60,6 +60,39 @@ TEST(SLRUCache, remove) ASSERT_TRUE(value == nullptr); } +TEST(SLRUCache, removeFromProtected) +{ + using SimpleCacheBase = DB::CacheBase; + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/2, /*max_elements_size=*/0); + slru_cache.set(1, std::make_shared(2)); + slru_cache.set(1, std::make_shared(3)); + + auto value = slru_cache.get(1); + ASSERT_TRUE(value != nullptr); + ASSERT_EQ(*value, 3); + + slru_cache.remove(1); + value = slru_cache.get(1); + ASSERT_TRUE(value == nullptr); + + slru_cache.set(1, std::make_shared(4)); + slru_cache.set(1, std::make_shared(5)); + + slru_cache.set(2, std::make_shared(6)); + slru_cache.set(3, std::make_shared(7)); + + value = slru_cache.get(1); + ASSERT_TRUE(value != nullptr); + ASSERT_EQ(*value, 5); + + value = slru_cache.get(3); + ASSERT_TRUE(value != nullptr); + ASSERT_EQ(*value, 7); + + value = slru_cache.get(2); + ASSERT_TRUE(value == nullptr); +} + TEST(SLRUCache, reset) { using SimpleCacheBase = DB::CacheBase; From 5bed94a1706b5bb47f19ed5814e282c92eb668d2 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Tue, 17 May 2022 19:52:17 +0000 Subject: [PATCH 393/672] Restart GH From 2be72560f47f47fb5b4125de3240380be3797848 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Tue, 14 Jun 2022 07:08:40 +0000 Subject: [PATCH 394/672] Add loading of mark cache policy --- programs/local/LocalServer.cpp | 3 ++- programs/server/Server.cpp | 4 +++- src/Common/CacheBase.h | 33 +-------------------------------- src/Core/Settings.h | 1 - src/Interpreters/Context.cpp | 4 ++-- src/Interpreters/Context.h | 2 +- src/Storages/MarkCache.h | 4 ++-- 7 files changed, 11 insertions(+), 40 deletions(-) diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index 404b7d08e12..ce31600642a 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -564,9 +564,10 @@ void LocalServer::processConfig() global_context->setUncompressedCache(uncompressed_cache_size, uncompressed_cache_policy); /// Size of cache for marks (index of MergeTree family of tables). + String mark_cache_policy = config().getString("mark_cache_policy", ""); size_t mark_cache_size = config().getUInt64("mark_cache_size", 5368709120); if (mark_cache_size) - global_context->setMarkCache(mark_cache_size); + global_context->setMarkCache(mark_cache_size, mark_cache_policy); /// Size of cache for uncompressed blocks of MergeTree indices. Zero means disabled. size_t index_uncompressed_cache_size = config().getUInt64("index_uncompressed_cache_size", 0); diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index e513becbffe..21bf7ee4355 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -1363,6 +1363,7 @@ int Server::main(const std::vector & /*args*/) /// Size of cache for uncompressed blocks. Zero means disabled. String uncompressed_cache_policy = config().getString("uncompressed_cache_policy", ""); + LOG_INFO(log, "Uncompressed cache policy name {}", uncompressed_cache_policy); size_t uncompressed_cache_size = config().getUInt64("uncompressed_cache_size", 0); if (uncompressed_cache_size > max_cache_size) { @@ -1389,6 +1390,7 @@ int Server::main(const std::vector & /*args*/) /// Size of cache for marks (index of MergeTree family of tables). size_t mark_cache_size = config().getUInt64("mark_cache_size", 5368709120); + String mark_cache_policy = config().getString("mark_cache_policy", ""); if (!mark_cache_size) LOG_ERROR(log, "Too low mark cache size will lead to severe performance degradation."); if (mark_cache_size > max_cache_size) @@ -1397,7 +1399,7 @@ int Server::main(const std::vector & /*args*/) LOG_INFO(log, "Mark cache size was lowered to {} because the system has low amount of memory", formatReadableSizeWithBinarySuffix(mark_cache_size)); } - global_context->setMarkCache(mark_cache_size); + global_context->setMarkCache(mark_cache_size, mark_cache_policy); /// Size of cache for uncompressed blocks of MergeTree indices. Zero means disabled. size_t index_uncompressed_cache_size = config().getUInt64("index_uncompressed_cache_size", 0); diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index 19bb8a57adf..e08f8201845 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -31,7 +31,7 @@ public: using Mapped = TMapped; using MappedPtr = std::shared_ptr; - CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase("SLRU", max_size, max_elements_size) {} + CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase(default_cache_policy_name, max_size, max_elements_size) {} /// TODO: Rewrite "Args... args" to custom struct with fields for all cache policies. template @@ -44,7 +44,6 @@ public: cache_policy_name = default_cache_policy_name; } - LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Cache policy name \"{}\"", cache_policy_name); if (cache_policy_name == "LRU") { using LRUPolicy = LRUCachePolicy; @@ -65,12 +64,6 @@ public: MappedPtr get(const Key & key) { std::lock_guard lock(mutex); - - auto weight = cache_policy->weight(lock); - auto max_weight = cache_policy->maxSize(); - LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Info before get. Weight: {}. Max weight: {}", weight, max_weight); - assert(weight <= max_weight); - auto res = cache_policy->get(key, lock); if (res) ++hits; @@ -83,12 +76,6 @@ public: void set(const Key & key, const MappedPtr & mapped) { std::lock_guard lock(mutex); - - auto weight = cache_policy->weight(lock); - auto max_weight = cache_policy->maxSize(); - LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Info before set. Weight: {}. Max weight: {}", weight, max_weight); - assert(weight <= max_weight); - cache_policy->set(key, mapped, lock); } @@ -106,12 +93,6 @@ public: InsertTokenHolder token_holder; { std::lock_guard cache_lock(mutex); - - auto weight = cache_policy->weight(cache_lock); - auto max_weight = cache_policy->maxSize(); - LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Info before getOrSet. Weight: {}. Max weight: {}", weight, max_weight); - assert(weight <= max_weight); - auto val = cache_policy->get(key, cache_lock); if (val) { @@ -170,12 +151,6 @@ public: void reset() { std::lock_guard lock(mutex); - - auto weight = cache_policy->weight(lock); - auto max_weight = cache_policy->maxSize(); - LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Info before reset. Weight: {}. Max weight: {}", weight, max_weight); - assert(weight <= max_weight); - insert_tokens.clear(); hits = 0; misses = 0; @@ -185,12 +160,6 @@ public: void remove(const Key & key) { std::lock_guard lock(mutex); - - auto weight = cache_policy->weight(lock); - auto max_weight = cache_policy->maxSize(); - LOG_DEBUG(&Poco::Logger::get("CacheBase"), "Info before remove. Weight: {}. Max weight: {}", weight, max_weight); - assert(weight <= max_weight); - cache_policy->remove(key, lock); } diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 651b1efd8c8..92af0576129 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -97,7 +97,6 @@ static constexpr UInt64 operator""_GiB(unsigned long long value) M(UInt64, hsts_max_age, 0, "Expired time for hsts. 0 means disable HSTS.", 0) \ M(Bool, extremes, false, "Calculate minimums and maximums of the result columns. They can be output in JSON-formats.", IMPORTANT) \ M(Bool, use_uncompressed_cache, false, "Whether to use the cache of uncompressed blocks.", 0) \ - M(String, uncompressed_cache_policy, "", "Which cache policy use for the cache of uncompressed blocks.", 0) \ M(Bool, replace_running_query, false, "Whether the running request should be canceled with the same id as the new one.", 0) \ M(UInt64, max_replicated_fetches_network_bandwidth_for_server, 0, "The maximum speed of data exchange over the network in bytes per second for replicated fetches. Zero means unlimited. Only has meaning at server startup.", 0) \ M(UInt64, max_replicated_sends_network_bandwidth_for_server, 0, "The maximum speed of data exchange over the network in bytes per second for replicated sends. Zero means unlimited. Only has meaning at server startup.", 0) \ diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index bbdb653b4a6..1b9fffb6bce 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -1683,14 +1683,14 @@ void Context::dropUncompressedCache() const } -void Context::setMarkCache(size_t cache_size_in_bytes) +void Context::setMarkCache(size_t cache_size_in_bytes, String mark_cache_policy) { auto lock = getLock(); if (shared->mark_cache) throw Exception("Mark cache has been already created.", ErrorCodes::LOGICAL_ERROR); - shared->mark_cache = std::make_shared(cache_size_in_bytes); + shared->mark_cache = std::make_shared(cache_size_in_bytes, mark_cache_policy); } MarkCachePtr Context::getMarkCache() const diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index 5f310d67b86..3ae50b6268d 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -785,7 +785,7 @@ public: void dropUncompressedCache() const; /// Create a cache of marks of specified size. This can be done only once. - void setMarkCache(size_t cache_size_in_bytes); + void setMarkCache(size_t cache_size_in_bytes, String mark_cache_policy); std::shared_ptr getMarkCache() const; void dropMarkCache() const; diff --git a/src/Storages/MarkCache.h b/src/Storages/MarkCache.h index 834472c2d41..56c89338b0d 100644 --- a/src/Storages/MarkCache.h +++ b/src/Storages/MarkCache.h @@ -40,8 +40,8 @@ private: using Base = CacheBase; public: - explicit MarkCache(size_t max_size_in_bytes) - : Base(max_size_in_bytes) {} + explicit MarkCache(size_t max_size_in_bytes, String mark_cache_policy = "") + : Base(mark_cache_policy, max_size_in_bytes) {} /// Calculate key from path to file and offset. static UInt128 hash(const String & path_to_file) From 77def2039490db16c6ce7d1da1335a10c6a2a711 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Tue, 14 Jun 2022 07:51:46 +0000 Subject: [PATCH 395/672] Fix typos in some js code and ignore some typos --- utils/check-style/codespell-ignore-words.list | 6 ++++++ utils/trace-visualizer/js/d3-gantt.js | 2 +- utils/trace-visualizer/js/d3-tip-0.8.0-alpha.1.js | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/utils/check-style/codespell-ignore-words.list b/utils/check-style/codespell-ignore-words.list index 7aabaff17c5..f9560802eb1 100644 --- a/utils/check-style/codespell-ignore-words.list +++ b/utils/check-style/codespell-ignore-words.list @@ -11,3 +11,9 @@ offsett numer ue alse +nodel +curveLinear +ot +te +fo +ba diff --git a/utils/trace-visualizer/js/d3-gantt.js b/utils/trace-visualizer/js/d3-gantt.js index e3a51a33c21..45f7707aa61 100644 --- a/utils/trace-visualizer/js/d3-gantt.js +++ b/utils/trace-visualizer/js/d3-gantt.js @@ -307,7 +307,7 @@ x = d3.scaleLinear() .domain([timeDomainStart, timeDomainEnd]) .range([0, width]) - //.clamp(true); // dosn't work with zoom/pan + //.clamp(true); // doesn't work with zoom/pan xZoomed = x; y = d3.scaleBand() .domain([...data.bands]) diff --git a/utils/trace-visualizer/js/d3-tip-0.8.0-alpha.1.js b/utils/trace-visualizer/js/d3-tip-0.8.0-alpha.1.js index ad3a6c0d199..19e509309f2 100644 --- a/utils/trace-visualizer/js/d3-tip-0.8.0-alpha.1.js +++ b/utils/trace-visualizer/js/d3-tip-0.8.0-alpha.1.js @@ -26,7 +26,7 @@ root.d3.tip = factory(d3, d3) } }(this, function(d3Collection, d3Selection) { - // Public - contructs a new tooltip + // Public - constructs a new tooltip // // Returns a tip return function() { From 8adf9f24484faf0a369d6bb8f51708977538b5ef Mon Sep 17 00:00:00 2001 From: alexX512 Date: Thu, 30 Jun 2022 15:17:30 +0000 Subject: [PATCH 396/672] fix compile error --- src/Common/CacheBase.h | 12 +++++++----- utils/check-style/codespell-ignore-words.list | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index e08f8201845..dde4237ab62 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -11,7 +11,8 @@ #include #include -#include +#include +#include namespace DB @@ -176,6 +177,7 @@ public: } size_t maxSize() const + TSA_NO_THREAD_SAFETY_ANALYSIS // disabled because max_size of cache_policy is a constant parameter { return cache_policy->maxSize(); } @@ -188,7 +190,7 @@ protected: private: using CachePolicy = ICachePolicy; - std::unique_ptr cache_policy; + std::unique_ptr cache_policy TSA_GUARDED_BY(mutex); inline static const String default_cache_policy_name = "SLRU"; @@ -201,8 +203,8 @@ private: explicit InsertToken(CacheBase & cache_) : cache(cache_) {} std::mutex mutex; - bool cleaned_up = false; /// Protected by the token mutex - MappedPtr value; /// Protected by the token mutex + bool cleaned_up TSA_GUARDED_BY(mutex) = false; + MappedPtr value TSA_GUARDED_BY(mutex); CacheBase & cache; size_t refcount = 0; /// Protected by the cache mutex @@ -258,7 +260,7 @@ private: friend struct InsertTokenHolder; - InsertTokenById insert_tokens; + InsertTokenById insert_tokens TSA_GUARDED_BY(mutex); /// Override this method if you want to track how much weight was lost in removeOverflow method. virtual void onRemoveOverflowWeightLoss(size_t /*weight_loss*/) {} diff --git a/utils/check-style/codespell-ignore-words.list b/utils/check-style/codespell-ignore-words.list index f9560802eb1..062e8a1622b 100644 --- a/utils/check-style/codespell-ignore-words.list +++ b/utils/check-style/codespell-ignore-words.list @@ -12,7 +12,6 @@ numer ue alse nodel -curveLinear ot te fo From 62f84b88ebef9464fcbc9c0bf98c519fdbaee4b5 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sun, 7 Aug 2022 17:16:07 +0000 Subject: [PATCH 397/672] Review fixes --- src/Common/CacheBase.h | 17 ++++++------ src/Common/ICachePolicy.h | 12 ++++----- src/Common/LRUCachePolicy.h | 19 +++++-------- src/Common/SLRUCachePolicy.h | 39 +++++++++++++-------------- src/Common/tests/gtest_slru_cahce.cpp | 20 +++++++------- src/IO/UncompressedCache.h | 2 +- src/Interpreters/Context.cpp | 4 +-- src/Interpreters/Context.h | 4 +-- src/Storages/MarkCache.h | 2 +- 9 files changed, 57 insertions(+), 62 deletions(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index dde4237ab62..78e44c85aca 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -36,9 +36,9 @@ public: /// TODO: Rewrite "Args... args" to custom struct with fields for all cache policies. template - CacheBase(String cache_policy_name, Args... args) + CacheBase(String cache_policy_name, size_t max_size, size_t max_elements_size = 0, double size_ratio = 0.5) { - auto onWeightLossFunction = [&](size_t weight_loss) { onRemoveOverflowWeightLoss(weight_loss); }; + auto on_weight_loss_function = [&](size_t weight_loss) { onRemoveOverflowWeightLoss(weight_loss); }; if (cache_policy_name == "") { @@ -48,17 +48,16 @@ public: if (cache_policy_name == "LRU") { using LRUPolicy = LRUCachePolicy; - cache_policy = std::make_unique(onWeightLossFunction, args...); + cache_policy = std::make_unique(max_size, max_elements_size, on_weight_loss_function); } else if (cache_policy_name == "SLRU") { using SLRUPolicy = SLRUCachePolicy; - cache_policy = std::make_unique(onWeightLossFunction, args...); + cache_policy = std::make_unique(max_size, max_elements_size, size_ratio, on_weight_loss_function); } else { - LOG_ERROR(&Poco::Logger::get("CacheBase"), "Undeclared cache policy name \"{}\"", cache_policy_name); - abort(); + throw Exception("Undeclared cache policy name: " + cache_policy_name, ErrorCodes::BAD_ARGUMENTS); } } @@ -223,14 +222,16 @@ private: InsertTokenHolder() = default; - void acquire(const Key * key_, const std::shared_ptr & token_, [[maybe_unused]] std::lock_guard & cache_lock) + void acquire(const Key * key_, const std::shared_ptr & token_, std::lock_guard & /* cache_lock */) + TSA_NO_THREAD_SAFETY_ANALYSIS // disabled only because we can't reference the parent-level cache mutex from here { key = key_; token = token_; ++token->refcount; } - void cleanup([[maybe_unused]] std::lock_guard & token_lock, [[maybe_unused]] std::lock_guard & cache_lock) + void cleanup(std::lock_guard & token_lock, std::lock_guard & /* cache_lock */) + TSA_NO_THREAD_SAFETY_ANALYSIS // disabled only because we can't reference the parent-level cache mutex from here { token->cache.insert_tokens.erase(*key); token->cleaned_up = true; diff --git a/src/Common/ICachePolicy.h b/src/Common/ICachePolicy.h index a278a67c34d..4e5916f125e 100644 --- a/src/Common/ICachePolicy.h +++ b/src/Common/ICachePolicy.h @@ -24,14 +24,14 @@ public: using MappedPtr = std::shared_ptr; using OnWeightLossFunction = std::function; - virtual size_t weight([[maybe_unused]] std::lock_guard & cache_lock) const = 0; - virtual size_t count([[maybe_unused]] std::lock_guard & cache_lock) const = 0; + virtual size_t weight(std::lock_guard & /* cache_lock */) const = 0; + virtual size_t count(std::lock_guard & /* cache_lock */) const = 0; virtual size_t maxSize() const = 0; - virtual void reset([[maybe_unused]] std::lock_guard & cache_lock) = 0; - virtual void remove(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) = 0; - virtual MappedPtr get(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) = 0; - virtual void set(const Key & key, const MappedPtr & mapped, [[maybe_unused]] std::lock_guard & cache_lock) = 0; + virtual void reset(std::lock_guard & /* cache_lock */) = 0; + virtual void remove(const Key & key, std::lock_guard & /* cache_lock */) = 0; + virtual MappedPtr get(const Key & key, std::lock_guard & /* cache_lock */) = 0; + virtual void set(const Key & key, const MappedPtr & mapped, std::lock_guard & /* cache_lock */) = 0; virtual ~ICachePolicy() = default; diff --git a/src/Common/LRUCachePolicy.h b/src/Common/LRUCachePolicy.h index d3523e3ad16..3c069eb276b 100644 --- a/src/Common/LRUCachePolicy.h +++ b/src/Common/LRUCachePolicy.h @@ -30,23 +30,18 @@ public: /** Initialize LRUCachePolicy with max_size and max_elements_size. * max_elements_size == 0 means no elements size restrictions. */ - explicit LRUCachePolicy(size_t max_size_, size_t max_elements_size_ = 0) + explicit LRUCachePolicy(size_t max_size_, size_t max_elements_size_ = 0, OnWeightLossFunction on_weight_loss_function_ = {}) : max_size(std::max(static_cast(1), max_size_)), max_elements_size(max_elements_size_) - { - } - - template - LRUCachePolicy(OnWeightLossFunction on_weight_loss_function_, Args... args) : LRUCachePolicy(args...) { Base::on_weight_loss_function = on_weight_loss_function_; } - size_t weight([[maybe_unused]] std::lock_guard & cache_lock) const override + size_t weight(std::lock_guard & /* cache_lock */) const override { return current_size; } - size_t count([[maybe_unused]] std::lock_guard & cache_lock) const override + size_t count(std::lock_guard & /* cache_lock */) const override { return cells.size(); } @@ -56,14 +51,14 @@ public: return max_size; } - void reset([[maybe_unused]] std::lock_guard & cache_lock) override + void reset(std::lock_guard & /* cache_lock */) override { queue.clear(); cells.clear(); current_size = 0; } - void remove(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override + void remove(const Key & key, std::lock_guard & /* cache_lock */) override { auto it = cells.find(key); if (it == cells.end()) @@ -74,7 +69,7 @@ public: cells.erase(it); } - MappedPtr get(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override + MappedPtr get(const Key & key, std::lock_guard & /* cache_lock */) override { auto it = cells.find(key); if (it == cells.end()) @@ -90,7 +85,7 @@ public: return cell.value; } - void set(const Key & key, const MappedPtr & mapped, [[maybe_unused]] std::lock_guard & cache_lock) override + void set(const Key & key, const MappedPtr & mapped, std::lock_guard & /* cache_lock */) override { auto [it, inserted] = cells.emplace(std::piecewise_construct, std::forward_as_tuple(key), diff --git a/src/Common/SLRUCachePolicy.h b/src/Common/SLRUCachePolicy.h index b56a5646e02..76640c47aa7 100644 --- a/src/Common/SLRUCachePolicy.h +++ b/src/Common/SLRUCachePolicy.h @@ -34,24 +34,20 @@ public: * max_protected_size == 0 means that the default protected size is equal to half of the total max size. */ /// TODO: construct from special struct with cache policy parametrs (also with max_protected_size). - SLRUCachePolicy(size_t max_size_, size_t max_elements_size_ = 0) - : max_protected_size(max_size_ / 2) - , max_size(std::max(max_protected_size + 1, max_size_)) + SLRUCachePolicy(size_t max_size_, size_t max_elements_size_ = 0, double size_ratio = 0.5, OnWeightLossFunction on_weight_loss_function_ = {}) + : max_protected_size(max_size_ * std::min(1.0, size_ratio)) + , max_size(max_size_) , max_elements_size(max_elements_size_) - {} + { + Base::on_weight_loss_function = on_weight_loss_function_; + } - template - SLRUCachePolicy(OnWeightLossFunction on_weight_loss_function_, Args... args) : SLRUCachePolicy(args...) - { - Base::on_weight_loss_function = on_weight_loss_function_; - } - - size_t weight([[maybe_unused]] std::lock_guard & cache_lock) const override + size_t weight(std::lock_guard & /* cache_lock */) const override { return current_size; } - size_t count([[maybe_unused]] std::lock_guard & cache_lock) const override + size_t count(std::lock_guard & /* cache_lock */) const override { return cells.size(); } @@ -61,7 +57,7 @@ public: return max_size; } - void reset([[maybe_unused]] std::lock_guard & cache_lock) override + void reset(std::lock_guard & /* cache_lock */) override { cells.clear(); probationary_queue.clear(); @@ -70,7 +66,7 @@ public: current_protected_size = 0; } - void remove(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override + void remove(const Key & key, std::lock_guard & /* cache_lock */) override { auto it = cells.find(key); if (it == cells.end()) @@ -86,7 +82,7 @@ public: cells.erase(it); } - MappedPtr get(const Key & key, [[maybe_unused]] std::lock_guard & cache_lock) override + MappedPtr get(const Key & key, std::lock_guard & /* cache_lock */) override { auto it = cells.find(key); if (it == cells.end()) @@ -105,14 +101,13 @@ public: cell.is_protected = true; current_protected_size += cell.size; protected_queue.splice(protected_queue.end(), probationary_queue, cell.queue_iterator); + removeOverflow(protected_queue, max_protected_size, current_protected_size, /*is_protected=*/true); } - removeOverflow(protected_queue, max_protected_size, current_protected_size, /*is_protected=*/true); - return cell.value; } - void set(const Key & key, const MappedPtr & mapped, [[maybe_unused]] std::lock_guard & cache_lock) override + void set(const Key & key, const MappedPtr & mapped, std::lock_guard & /* cache_lock */) override { auto [it, inserted] = cells.emplace(std::piecewise_construct, std::forward_as_tuple(key), @@ -165,7 +160,7 @@ protected: struct Cell { - bool is_protected; + bool is_protected = false; MappedPtr value; size_t size; SLRUQueueIterator queue_iterator; @@ -192,7 +187,11 @@ protected: { if (is_protected) { - return ((max_elements_size != 0 && cells.size() - probationary_queue.size() > max_elements_size - probationary_queue.size()) + /// Check if after remove all elements from probationary part there will be no more then max elements + /// in protected queue and weight of all protected elements will be less then max protected weight. + /// It's not possible to check only cells.size() > max_elements_size + /// because protected elements move to probationary part and still remain in cache. + return ((max_elements_size != 0 && cells.size() - probationary_queue.size() > max_elements_size) || (current_weight_size > max_weight_size)) && (queue_size > 0); } diff --git a/src/Common/tests/gtest_slru_cahce.cpp b/src/Common/tests/gtest_slru_cahce.cpp index 1c4fcaddce0..a25d28c35dc 100644 --- a/src/Common/tests/gtest_slru_cahce.cpp +++ b/src/Common/tests/gtest_slru_cahce.cpp @@ -6,7 +6,7 @@ TEST(SLRUCache, set) { using SimpleCacheBase = DB::CacheBase; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0, /*size_ratio*/0.5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); @@ -19,7 +19,7 @@ TEST(SLRUCache, set) TEST(SLRUCache, update) { using SimpleCacheBase = DB::CacheBase; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0, /*size_ratio*/0.5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(1, std::make_shared(3)); @@ -31,7 +31,7 @@ TEST(SLRUCache, update) TEST(SLRUCache, get) { using SimpleCacheBase = DB::CacheBase; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0, /*size_ratio*/0.5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); @@ -47,7 +47,7 @@ TEST(SLRUCache, get) TEST(SLRUCache, remove) { using SimpleCacheBase = DB::CacheBase; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0, /*size_ratio*/0.5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); @@ -63,7 +63,7 @@ TEST(SLRUCache, remove) TEST(SLRUCache, removeFromProtected) { using SimpleCacheBase = DB::CacheBase; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/2, /*max_elements_size=*/0); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/2, /*max_elements_size=*/0, /*size_ratio*/0.5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(1, std::make_shared(3)); @@ -96,7 +96,7 @@ TEST(SLRUCache, removeFromProtected) TEST(SLRUCache, reset) { using SimpleCacheBase = DB::CacheBase; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0, /*size_ratio*/0.5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); @@ -140,7 +140,7 @@ TEST(SLRUCache, evictOnElements) TEST(SLRUCache, evictOnWeight) { using SimpleCacheBase = DB::CacheBase, ValueWeight>; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0, /*size_ratio*/0.5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(2, std::make_shared(3)); slru_cache.set(3, std::make_shared(4)); @@ -161,7 +161,7 @@ TEST(SLRUCache, evictOnWeight) TEST(SLRUCache, evictFromProtectedPart) { using SimpleCacheBase = DB::CacheBase, ValueWeight>; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0, /*size_ratio*/0.5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(1, std::make_shared(2)); @@ -177,7 +177,7 @@ TEST(SLRUCache, evictFromProtectedPart) TEST(SLRUCache, evictStreamProtected) { using SimpleCacheBase = DB::CacheBase, ValueWeight>; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0, /*size_ratio*/0.5); slru_cache.set(1, std::make_shared(2)); slru_cache.set(1, std::make_shared(2)); @@ -201,7 +201,7 @@ TEST(SLRUCache, evictStreamProtected) TEST(SLRUCache, getOrSet) { using SimpleCacheBase = DB::CacheBase, ValueWeight>; - auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0); + auto slru_cache = SimpleCacheBase("SLRU", /*max_total_size=*/10, /*max_elements_size=*/0, /*size_ratio*/0.5); size_t x = 5; auto load_func = [&] { return std::make_shared(x); }; auto [value, loaded] = slru_cache.getOrSet(1, load_func); diff --git a/src/IO/UncompressedCache.h b/src/IO/UncompressedCache.h index 2aa29e102da..438f768c735 100644 --- a/src/IO/UncompressedCache.h +++ b/src/IO/UncompressedCache.h @@ -42,7 +42,7 @@ private: using Base = CacheBase; public: - explicit UncompressedCache(size_t max_size_in_bytes, String uncompressed_cache_policy = "") + explicit UncompressedCache(size_t max_size_in_bytes, const String & uncompressed_cache_policy = "") : Base(uncompressed_cache_policy, max_size_in_bytes) {} /// Calculate key from path to file and offset. diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 1b9fffb6bce..b29a421e7ee 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -1657,7 +1657,7 @@ ProcessList::Element * Context::getProcessListElement() const } -void Context::setUncompressedCache(size_t max_size_in_bytes, String uncompressed_cache_policy) +void Context::setUncompressedCache(size_t max_size_in_bytes, const String & uncompressed_cache_policy) { auto lock = getLock(); @@ -1683,7 +1683,7 @@ void Context::dropUncompressedCache() const } -void Context::setMarkCache(size_t cache_size_in_bytes, String mark_cache_policy) +void Context::setMarkCache(size_t cache_size_in_bytes, const String & mark_cache_policy) { auto lock = getLock(); diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index 3ae50b6268d..6c5ffa5243b 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -780,12 +780,12 @@ public: void setSystemZooKeeperLogAfterInitializationIfNeeded(); /// Create a cache of uncompressed blocks of specified size. This can be done only once. - void setUncompressedCache(size_t max_size_in_bytes, String uncompressed_cache_policy); + void setUncompressedCache(size_t max_size_in_bytes, const String & uncompressed_cache_policy); std::shared_ptr getUncompressedCache() const; void dropUncompressedCache() const; /// Create a cache of marks of specified size. This can be done only once. - void setMarkCache(size_t cache_size_in_bytes, String mark_cache_policy); + void setMarkCache(size_t cache_size_in_bytes, const String & mark_cache_policy); std::shared_ptr getMarkCache() const; void dropMarkCache() const; diff --git a/src/Storages/MarkCache.h b/src/Storages/MarkCache.h index 56c89338b0d..b8924d43384 100644 --- a/src/Storages/MarkCache.h +++ b/src/Storages/MarkCache.h @@ -40,7 +40,7 @@ private: using Base = CacheBase; public: - explicit MarkCache(size_t max_size_in_bytes, String mark_cache_policy = "") + explicit MarkCache(size_t max_size_in_bytes, const String & mark_cache_policy = "") : Base(mark_cache_policy, max_size_in_bytes) {} /// Calculate key from path to file and offset. From 269631cfbeb859b22988ea8910b6394e8b4ce5fa Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sun, 7 Aug 2022 17:35:31 +0000 Subject: [PATCH 398/672] Fix typo --- src/Common/SLRUCachePolicy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/SLRUCachePolicy.h b/src/Common/SLRUCachePolicy.h index 76640c47aa7..02b9133626e 100644 --- a/src/Common/SLRUCachePolicy.h +++ b/src/Common/SLRUCachePolicy.h @@ -187,7 +187,7 @@ protected: { if (is_protected) { - /// Check if after remove all elements from probationary part there will be no more then max elements + /// Check if after remove all elements from probationary part there will be no more than max elements /// in protected queue and weight of all protected elements will be less then max protected weight. /// It's not possible to check only cells.size() > max_elements_size /// because protected elements move to probationary part and still remain in cache. From 1c9d40e4a83a210f20955efb071f7e0463994386 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sun, 7 Aug 2022 20:37:11 +0000 Subject: [PATCH 399/672] Fix build error --- src/Common/CacheBase.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index 78e44c85aca..526f1473121 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -17,6 +18,10 @@ namespace DB { +namespace ErrorCodes +{ + extern const int BAD_ARGUMENTS; +} /// Thread-safe cache that evicts entries using special cache policy /// (default policy evicts entries which are not used for a long time). @@ -230,7 +235,7 @@ private: ++token->refcount; } - void cleanup(std::lock_guard & token_lock, std::lock_guard & /* cache_lock */) + void cleanup(std::lock_guard & /* token_lock */, std::lock_guard & /* cache_lock */) TSA_NO_THREAD_SAFETY_ANALYSIS // disabled only because we can't reference the parent-level cache mutex from here { token->cache.insert_tokens.erase(*key); From f16c668a0cb271a0c4f0e39ab8018177129b48aa Mon Sep 17 00:00:00 2001 From: alexX512 Date: Sun, 7 Aug 2022 20:44:26 +0000 Subject: [PATCH 400/672] Delete Args from CacheBase --- src/Common/CacheBase.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index 526f1473121..b8aa45a3443 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -39,8 +39,7 @@ public: CacheBase(size_t max_size, size_t max_elements_size = 0) : CacheBase(default_cache_policy_name, max_size, max_elements_size) {} - /// TODO: Rewrite "Args... args" to custom struct with fields for all cache policies. - template + /// TODO: Rewrite to custom struct with fields for all cache policies. CacheBase(String cache_policy_name, size_t max_size, size_t max_elements_size = 0, double size_ratio = 0.5) { auto on_weight_loss_function = [&](size_t weight_loss) { onRemoveOverflowWeightLoss(weight_loss); }; From d413c40c4152772fcbf4f9efd140f3b9e00006f5 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 8 Aug 2022 00:37:36 +0200 Subject: [PATCH 401/672] Remove old code from the website (part 2) --- docs/tools/build.py | 1 - docs/tools/website.py | 27 - website/benchmark/benchmark.js | 464 ------------------ website/benchmark/versions/index.html | 52 -- website/benchmark/versions/queries.js | 291 ----------- .../benchmark/versions/results/1.1.54378.json | 85 ---- .../benchmark/versions/results/1.1.54394.json | 85 ---- .../benchmark/versions/results/18.01.0.json | 85 ---- .../benchmark/versions/results/18.04.0.json | 85 ---- .../benchmark/versions/results/18.05.1.json | 85 ---- .../benchmark/versions/results/18.06.0.json | 85 ---- .../benchmark/versions/results/18.10.3.json | 85 ---- .../benchmark/versions/results/18.12.17.json | 85 ---- .../benchmark/versions/results/18.14.19.json | 85 ---- .../benchmark/versions/results/18.16.1.json | 85 ---- .../versions/results/19.01.16.79.json | 85 ---- .../versions/results/19.03.9.12_adaptive.json | 85 ---- .../versions/results/19.04.5.35.json | 85 ---- .../versions/results/19.05.4.22.json | 85 ---- .../versions/results/19.06.3.18_adaptive.json | 85 ---- .../versions/results/19.07.5.29_adaptive.json | 85 ---- .../versions/results/19.08.3.8_adaptive.json | 85 ---- .../versions/results/19.09.5.36_adaptive.json | 85 ---- .../versions/results/19.10.1.5_adaptive.json | 85 ---- .../versions/results/19.11.14.1_adaptive.json | 85 ---- .../versions/results/19.13.7.57_adaptive.json | 85 ---- .../versions/results/19.14.13.4_adaptive.json | 85 ---- .../versions/results/19.15.7.30_adaptive.json | 85 ---- .../results/19.16.19.85_adaptive.json | 85 ---- .../versions/results/19.17.10.1_adaptive.json | 85 ---- .../results/20.01.16.120_adaptive.json | 85 ---- .../versions/results/20.03.21.2_adaptive.json | 85 ---- .../results/20.04.9.110_adaptive.json | 85 ---- .../versions/results/20.05.5.74_adaptive.json | 85 ---- .../versions/results/20.06.11.1_adaptive.json | 85 ---- .../versions/results/20.07.4.11_adaptive.json | 85 ---- .../versions/results/20.08.19.4.json | 85 ---- .../versions/results/20.08.19.4_adaptive.json | 86 ---- .../versions/results/20.09.7.11_adaptive.json | 85 ---- .../versions/results/20.10.7.4_adaptive.json | 85 ---- .../versions/results/20.11.7.16_adaptive.json | 85 ---- .../versions/results/20.12.8.5_adaptive.json | 86 ---- .../versions/results/21.01.9.41_adaptive.json | 85 ---- .../results/21.02.10.48_adaptive.json | 85 ---- .../versions/results/21.03.20.1_adaptive.json | 85 ---- .../versions/results/21.04.7.3_adaptive.json | 85 ---- .../versions/results/21.05.9.4_adaptive.json | 85 ---- .../versions/results/21.06.9.7_adaptive.json | 85 ---- .../versions/results/21.07.11.3_adaptive.json | 85 ---- .../versions/results/21.08.15.7_adaptive.json | 85 ---- .../versions/results/21.09.6.24_adaptive.json | 85 ---- .../versions/results/21.10.6.2_adaptive.json | 85 ---- .../versions/results/21.11.11.1_adaptive.json | 85 ---- .../versions/results/21.12.4.1_adaptive.json | 85 ---- .../versions/results/22.1.4.30_adaptive.json | 85 ---- .../versions/results/22.2.3.5_adaptive.json | 85 ---- .../versions/results/22.3.3.44_adaptive.json | 85 ---- .../benchmark/versions/scripts/benchmarks.sh | 66 --- .../versions/scripts/brown_queries.sql | 16 - .../benchmark/versions/scripts/ch_queries.sql | 43 -- website/benchmark/versions/scripts/runner.sh | 3 - .../versions/scripts/ssb_queries.sql | 13 - 62 files changed, 5398 deletions(-) delete mode 100644 website/benchmark/benchmark.js delete mode 100644 website/benchmark/versions/index.html delete mode 100644 website/benchmark/versions/queries.js delete mode 100644 website/benchmark/versions/results/1.1.54378.json delete mode 100644 website/benchmark/versions/results/1.1.54394.json delete mode 100644 website/benchmark/versions/results/18.01.0.json delete mode 100644 website/benchmark/versions/results/18.04.0.json delete mode 100644 website/benchmark/versions/results/18.05.1.json delete mode 100644 website/benchmark/versions/results/18.06.0.json delete mode 100644 website/benchmark/versions/results/18.10.3.json delete mode 100644 website/benchmark/versions/results/18.12.17.json delete mode 100644 website/benchmark/versions/results/18.14.19.json delete mode 100644 website/benchmark/versions/results/18.16.1.json delete mode 100644 website/benchmark/versions/results/19.01.16.79.json delete mode 100644 website/benchmark/versions/results/19.03.9.12_adaptive.json delete mode 100644 website/benchmark/versions/results/19.04.5.35.json delete mode 100644 website/benchmark/versions/results/19.05.4.22.json delete mode 100644 website/benchmark/versions/results/19.06.3.18_adaptive.json delete mode 100644 website/benchmark/versions/results/19.07.5.29_adaptive.json delete mode 100644 website/benchmark/versions/results/19.08.3.8_adaptive.json delete mode 100644 website/benchmark/versions/results/19.09.5.36_adaptive.json delete mode 100644 website/benchmark/versions/results/19.10.1.5_adaptive.json delete mode 100644 website/benchmark/versions/results/19.11.14.1_adaptive.json delete mode 100644 website/benchmark/versions/results/19.13.7.57_adaptive.json delete mode 100644 website/benchmark/versions/results/19.14.13.4_adaptive.json delete mode 100644 website/benchmark/versions/results/19.15.7.30_adaptive.json delete mode 100644 website/benchmark/versions/results/19.16.19.85_adaptive.json delete mode 100644 website/benchmark/versions/results/19.17.10.1_adaptive.json delete mode 100644 website/benchmark/versions/results/20.01.16.120_adaptive.json delete mode 100644 website/benchmark/versions/results/20.03.21.2_adaptive.json delete mode 100644 website/benchmark/versions/results/20.04.9.110_adaptive.json delete mode 100644 website/benchmark/versions/results/20.05.5.74_adaptive.json delete mode 100644 website/benchmark/versions/results/20.06.11.1_adaptive.json delete mode 100644 website/benchmark/versions/results/20.07.4.11_adaptive.json delete mode 100644 website/benchmark/versions/results/20.08.19.4.json delete mode 100644 website/benchmark/versions/results/20.08.19.4_adaptive.json delete mode 100644 website/benchmark/versions/results/20.09.7.11_adaptive.json delete mode 100644 website/benchmark/versions/results/20.10.7.4_adaptive.json delete mode 100644 website/benchmark/versions/results/20.11.7.16_adaptive.json delete mode 100644 website/benchmark/versions/results/20.12.8.5_adaptive.json delete mode 100644 website/benchmark/versions/results/21.01.9.41_adaptive.json delete mode 100644 website/benchmark/versions/results/21.02.10.48_adaptive.json delete mode 100644 website/benchmark/versions/results/21.03.20.1_adaptive.json delete mode 100644 website/benchmark/versions/results/21.04.7.3_adaptive.json delete mode 100644 website/benchmark/versions/results/21.05.9.4_adaptive.json delete mode 100644 website/benchmark/versions/results/21.06.9.7_adaptive.json delete mode 100644 website/benchmark/versions/results/21.07.11.3_adaptive.json delete mode 100644 website/benchmark/versions/results/21.08.15.7_adaptive.json delete mode 100644 website/benchmark/versions/results/21.09.6.24_adaptive.json delete mode 100644 website/benchmark/versions/results/21.10.6.2_adaptive.json delete mode 100644 website/benchmark/versions/results/21.11.11.1_adaptive.json delete mode 100644 website/benchmark/versions/results/21.12.4.1_adaptive.json delete mode 100644 website/benchmark/versions/results/22.1.4.30_adaptive.json delete mode 100644 website/benchmark/versions/results/22.2.3.5_adaptive.json delete mode 100644 website/benchmark/versions/results/22.3.3.44_adaptive.json delete mode 100644 website/benchmark/versions/scripts/benchmarks.sh delete mode 100644 website/benchmark/versions/scripts/brown_queries.sql delete mode 100644 website/benchmark/versions/scripts/ch_queries.sql delete mode 100644 website/benchmark/versions/scripts/runner.sh delete mode 100644 website/benchmark/versions/scripts/ssb_queries.sql diff --git a/docs/tools/build.py b/docs/tools/build.py index 3756cf66794..7c872435092 100755 --- a/docs/tools/build.py +++ b/docs/tools/build.py @@ -21,7 +21,6 @@ def build(args): website.build_website(args) if not args.skip_website: - website.process_benchmark_results(args) website.minify_website(args) redirects.build_static_redirects(args) diff --git a/docs/tools/website.py b/docs/tools/website.py index 21d91ef1068..d6954fafa55 100644 --- a/docs/tools/website.py +++ b/docs/tools/website.py @@ -232,30 +232,3 @@ def minify_website(args): with open(js_out, "rb") as f: js_digest = hashlib.sha3_224(f.read()).hexdigest()[0:8] logging.info(js_digest) - - -def process_benchmark_results(args): - benchmark_root = os.path.join(args.website_dir, "benchmark") - required_keys = { - "versions": ["version", "system"], - } - for benchmark_kind in ["versions"]: - results = [] - results_root = os.path.join(benchmark_root, benchmark_kind, "results") - for result in sorted(os.listdir(results_root)): - result_file = os.path.join(results_root, result) - logging.info(f"Reading benchmark result from {result_file}") - with open(result_file, "r") as f: - result = json.loads(f.read()) - for item in result: - for required_key in required_keys[benchmark_kind]: - assert ( - required_key in item - ), f'No "{required_key}" in {result_file}' - results += result - results_js = os.path.join( - args.output_dir, "benchmark", benchmark_kind, "results.js" - ) - with open(results_js, "w") as f: - data = json.dumps(results) - f.write(f"var results = {data};") diff --git a/website/benchmark/benchmark.js b/website/benchmark/benchmark.js deleted file mode 100644 index 09607b27263..00000000000 --- a/website/benchmark/benchmark.js +++ /dev/null @@ -1,464 +0,0 @@ -var data_sizes = - [ - {id: "10000000", name: "10 mln."}, - {id: "100000000", name: "100 mln."}, - {id: "1000000000", name: "1 bn."} - ]; - - -var systems = []; -var systems_full = []; -var system_kinds = []; -var systems_uniq = {}; -for (r in results) { - if (systems_uniq[results[r].system]) - continue; - systems_uniq[results[r].system] = 1; - systems.push(results[r].system); - systems_full.push(results[r].system_full); - system_kinds.push(results[r].kind); -} - -var runs = ["first (cold cache)", "second", "third"]; -var current_runs = ['1', '2']; - -try { - var state = JSON.parse(decodeURIComponent(window.location.hash.substring(1))); - current_data_size = state[0]; - current_systems = state[1]; - current_runs = state[2]; -} catch (e) { -} - -if (!current_systems.length) { - current_systems = systems; -} - -function update_hash() { - window.location.hash = JSON.stringify([current_data_size, current_systems, current_runs]); -} - - -function generate_selectors(elem) { - var html = '

    Compare

    '; - - var available_results = results; - - if (current_data_size) { - available_results = results.filter(function (run) { - return run.data_size == current_data_size; - }); - } - var available_systems_for_current_data_size = available_results.map(function (run) { - return run.system; - }); - - for (var i = 0; i < systems.length; i++) { - var selected = current_systems.indexOf(systems[i]) != -1; - var available = available_systems_for_current_data_size.indexOf(systems[i]) != -1; - var button_class = 'btn-outline-dark'; - if (system_kinds[i] == 'cloud' || system_kinds[i] == 'vps') { - button_class = 'btn-light'; - } else if (system_kinds[i] == 'desktop' || system_kinds[i] == 'laptop') { - button_class = 'btn-outline-secondary'; - }; - - html += '

    Dataset size

    '; - - for (var i = 0; i < data_sizes.length; i++) { - html += ''; - } - } - - html += '

    Run

    '; - - for (var i = 0; i < runs.length; i++) { - html += ''; - } - - html += '
    '; - - elem.html(html); - - $('button').focus(function() { - $('[data-toggle="tooltip"]').tooltip('hide'); - }); - - $('#systems_selector button:not(.disabled)').click(function (event) { - event.preventDefault(); - var target = $(event.target || event.srcElement); - - if (target.hasClass("active") && current_systems.length == 1) { - return; - } - - target.toggleClass("active"); - - current_systems = $.map($('#systems_selector button'), function (elem) { - return $(elem).hasClass("active") ? $(elem).html() : null - }).filter(function (x) { - return x; - }); - - update_hash(); - generate_selectors(elem); - generate_comparison_table(); - generate_diagram(); - }); - - if (current_data_size) { - $('#data_size_selector button').click(function (event) { - event.preventDefault(); - var target = $(event.target || event.srcElement); - - current_data_size = target.attr("data-size-id"); - - update_hash(); - generate_selectors(elem); - generate_comparison_table(); - generate_diagram(); - }); - } - - $('#runs_selector button').click(function (event) { - event.preventDefault(); - var target = $(event.target || event.srcElement); - - if (target.hasClass("active") && current_runs.length == 1) { - return; - } - - target.toggleClass("active"); - - current_runs = $.map($('#runs_selector button'), function (elem) { - return $(elem).hasClass("active") ? $(elem).attr("data-run-id") : null - }).filter(function (x) { - return x; - }); - - update_hash(); - generate_selectors(elem); - generate_comparison_table(); - generate_diagram(); - }); -} - -function format_number_cell(value, ratio) { - var html = ""; - - var redness = (ratio - 1) / ratio; - var blackness = ratio < 10 ? 0 : ((ratio - 10) / ratio / 2); - - var color = !value ? "#FFF" : - ratio == 1 ? - ("rgba(0, 255, 0, 1)") : - ("rgba(" + ~~(255 * (1 - blackness)) + ", 0, 0, " + redness + ")"); - - html += ""; - html += value ? - (ratio == 1 ? "" : ("×" + ratio.toFixed(2))) + " (" + value.toFixed(3) + " s.)" : - "—"; - html += ""; - - return html; -} - -/* Ratio of execution time to best execution time: - * system index -> run index -> query index -> ratio. - */ -var ratios = []; - - -function generate_comparison_table() { - ratios = []; - - var filtered_results = results; - if (current_data_size) { - filtered_results = filtered_results.filter(function (x) { - return x.data_size == current_data_size; - }); - } - filtered_results = filtered_results.filter(function (x) { - return current_systems.indexOf(x.system) != -1; - }); - - var html = ""; - - html += ""; - html += ""; - html += ""; - html += ""; - for (var j = 0; j < filtered_results.length; j++) { - html += ""; - - for (var i = 0; i < queries.length; i++) { - html += ""; - html += ""; - - html += ""; - - // Max and min execution time per system, for each of three runs - var minimums = [0, 0, 0], maximums = [0, 0, 0]; - - for (var j = 0; j < filtered_results.length; j++) { - for (var current_run_idx = 0; current_run_idx < current_runs.length; current_run_idx++) { - var k = current_runs[current_run_idx]; - var value = filtered_results[j].result[i][k]; - - if (value && (!minimums[k] || value < minimums[k])) { - minimums[k] = value; - - // Ignore below 10ms - if (minimums[k] < 0.01) { - minimums[k] = 0.01; - } - } - - if (value > maximums[k]) { - maximums[k] = value; - } - } - } - - for (var j = 0; j < filtered_results.length; j++) { - if (!ratios[j]) { - ratios[j] = []; - } - - for (var current_run_idx = 0; current_run_idx < current_runs.length; current_run_idx++) { - var k = current_runs[current_run_idx]; - var value = filtered_results[j].result[i][k]; - - var ratio = value / minimums[k]; - - ratios[j][k] = ratios[j][k] || []; - - if (ratio && ratio <= 1) { - ratio = 1; - } - - ratios[j][k].push(ratio); - - html += format_number_cell(value, ratio); - } - } - html += ""; - } - - if (current_systems.length) { - html += ""; - html += ""; - html += ""; - - for (var j = 0; j < filtered_results.length; j++) { - for (var k = 0; k < current_runs.length; k++) { - html += ""; - } - } - - html += ""; - html += ""; - - for (var j = 0; j < filtered_results.length; j++) { - html += ""; - } - - html += ""; - } - - html += "
    Query"; - } - html += "
    " + queries[i].query + "
    Geometric mean of ratios
    "; - - $('#comparison_table').html(html); - - for (var i = 0; i < queries.length; i++) { - $('#query_checkbox' + i).click(function () { - calculate_totals(); - generate_diagram(); - }); - } - $('#query_checkbox_toggler').click(function () { - for (var i = 0; i < queries.length; i++) { - var item = $('#query_checkbox' + i); - item.prop("checked", !item.prop("checked")); - } - }); - - calculate_totals(); -} - - -function calculate_totals() { - if (!current_systems.length) return; - var filtered_results = results; - if (current_data_size) { - filtered_results = filtered_results.filter(function (x) { - return x.data_size == current_data_size; - }); - } - - filtered_results = filtered_results.filter(function (x) { - return current_systems.indexOf(x.system) != -1; - }); - - var total_ratios = []; - - for (var j = 0; j < filtered_results.length; j++) { - for (var current_run_idx = 0; current_run_idx < current_runs.length; current_run_idx++) { - var k = current_runs[current_run_idx]; - - var current_ratios = ratios[j][k].filter( - function (x, i) { - return x && $("#query_checkbox" + i).is(':checked'); - } - ); - - var ratio = Math.pow( - current_ratios.reduce( - function (acc, cur) { - return acc * cur; - }, - 1), - 1 / current_ratios.length); - - total_ratios[j] = total_ratios[j] || 1; - total_ratios[j] *= ratio; - - $("#totals" + j + "_" + k).attr("data-ratio", ratio).html("x" + ratio.toFixed(2)); - } - } - - for (var j = 0; j < filtered_results.length; j++) { - var total_ratio = Math.pow(total_ratios[j], 1 / current_runs.length); - $("#absolute_totals" + j).attr("data-ratio", total_ratio).html("x" + total_ratio.toFixed(2)); - } -} - - -function generate_diagram() { - var html = ""; - var filtered_results = results; - if (current_data_size) { - filtered_results = filtered_results.filter(function (x) { - return x.data_size == current_data_size && current_systems.indexOf(x.system) != -1; - }); - } - filtered_results = filtered_results.filter(function (x) { - return current_systems.indexOf(x.system) != -1; - }); - - var max_ratio = 1; - var min_ratio = 0; - - var max_total_ratio = 1; - var min_total_ratio = 0; - - for (var j = 0; j < filtered_results.length; j++) { - for (var current_run_idx = 0; current_run_idx < current_runs.length; current_run_idx++) { - var k = current_runs[current_run_idx]; - var ratio = +$("#totals" + j + "_" + k).attr("data-ratio"); - - if (ratio > max_ratio) { - max_ratio = ratio; - } - - if (!min_ratio || ratio < min_ratio) { - min_ratio = ratio; - } - } - - var total_ratio = +$("#absolute_totals" + j).attr("data-ratio"); - - if (total_ratio > max_total_ratio) { - max_total_ratio = total_ratio; - } - - if (!min_total_ratio || total_ratio < min_total_ratio) { - min_total_ratio = total_ratio; - } - } - - html += ""; - var table_rows = []; - - for (var j = 0; j < filtered_results.length; j++) { - var total_ratio = +$("#absolute_totals" + j).attr("data-ratio"); - var table_row = ""; - - table_row += ""; - table_row += ""; - - table_row += ""; - - table_row += ""; - table_row += ""; - table_rows.push({ data: table_row, value: total_ratio / min_total_ratio }); - } - table_rows.sort(function(a, b) { return a.value - b.value; }); - - for (var j = 0; j < table_rows.length; j++) { - html += table_rows[j].data; - } - - html += "
    " : "") + ""; - - for (var current_run_idx = 0; current_run_idx < current_runs.length; current_run_idx++) { - var k = current_runs[current_run_idx]; - - var ratio = +$("#totals" + j + "_" + k).attr("data-ratio"); - var percents = (ratio * 100 / max_ratio).toFixed(2); - - if (!ratio) { - ratio = +$("#absolute_totals" + j).attr("data-ratio"); - percents = (ratio * 100 / max_total_ratio).toFixed(2); - } - - - table_row += '
     
    '; - - } - - - table_row += "
    " + (total_ratio / min_total_ratio).toFixed(2) + "
    "; - - $('#diagram').html(html); - $('[data-toggle="tooltip"]').tooltip({ - trigger: 'hover', - delay: { "show": 300, "hide": 100 } - }); -} - -generate_selectors($('#selectors')); -generate_comparison_table(); -generate_diagram(); diff --git a/website/benchmark/versions/index.html b/website/benchmark/versions/index.html deleted file mode 100644 index cce85934a9b..00000000000 --- a/website/benchmark/versions/index.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends 'templates/base.html' %} - -{% set title = 'Performance comparison of different ClickHouse versions' %} -{% set extra_js = [ - 'queries.js?' + rev_short, - 'results.js?' + rev_short, - '../benchmark.js?' + rev_short] -%} -{% set url = 'https://clickhouse.com/benchmark/versions/' %} -{% set no_footer = True %} - -{% block content %} -
    -
    - -
    -
    - - ClickHouse - -

    {{ title }}

    -
    -
    - -
    -
    -
    - -
    -
    -

    Relative query processing time (lower is better)

    -
    -
    -
    - -
    -
    -

    Full results

    -
    -
    -
    - -
    -
    -

    Comments

    -

    Hardware used to run tests: x86_64 AWS m5.8xlarge Ubuntu 20.04.

    -

    Old versions had no support of this features/syntax: toISOWeek, SUBSTRING, EXTRACT, WITH. Queries were changed. All versions runs use same queries.

    -

    Star Schema Benchmark Patrick O'Neil, Elizabeth (Betty) O'Neil and Xuedong Chen. "The Star Schema Benchmark," Online Publication of Database Generation program., January 2007. http://www.cs.umb.edu/~poneil/StarSchemaB.pdf

    -
    -

    -
    -
    -{% endblock %} diff --git a/website/benchmark/versions/queries.js b/website/benchmark/versions/queries.js deleted file mode 100644 index f2de5a3c95a..00000000000 --- a/website/benchmark/versions/queries.js +++ /dev/null @@ -1,291 +0,0 @@ -var current_data_size = 0; - -var current_systems = []; - -var queries = - [ - { - "query": "SELECT machine_name, MIN(cpu) AS cpu_min, MAX(cpu) AS cpu_max, AVG(cpu) AS cpu_avg, MIN(net_in) AS net_in_min, MAX(net_in) AS net_in_max, AVG(net_in) AS net_in_avg, MIN(net_out) AS net_out_min, MAX(net_out) AS net_out_max, AVG(net_out) AS net_out_avg FROM ( SELECT machine_name, ifNull(cpu_user, 0.0) AS cpu, ifNull(bytes_in, 0.0) AS net_in, ifNull(bytes_out, 0.0) AS net_out FROM mgbench.logs1 WHERE machine_name IN ('anansi','aragog','urd') AND log_time >= toDateTime('2017-01-11 00:00:00')) AS r GROUP BY machine_name;", - "comment": "Q1.1: What is the CPU/network utilization for each web server since midnight?", - }, - { - "query": "SELECT machine_name, log_time FROM mgbench.logs1 WHERE (machine_name LIKE 'cslab%' OR machine_name LIKE 'mslab%') AND load_one IS NULL AND log_time >= toDateTime('2017-01-10 00:00:00') ORDER BY machine_name, log_time;", - "comment": "Q1.2: Which computer lab machines have been offline in the past day?", - }, - { - "query": "SELECT dt, hr, AVG(load_fifteen) AS load_fifteen_avg, AVG(load_five) AS load_five_avg, AVG(load_one) AS load_one_avg, AVG(mem_free) AS mem_free_avg, AVG(swap_free) AS swap_free_avg FROM ( SELECT CAST(log_time AS DATE) AS dt, toHour(log_time) AS hr, load_fifteen, load_five, load_one, mem_free, swap_free FROM mgbench.logs1 WHERE machine_name = 'babbage' AND load_fifteen IS NOT NULL AND load_five IS NOT NULL AND load_one IS NOT NULL AND mem_free IS NOT NULL AND swap_free IS NOT NULL AND log_time >= toDateTime('2017-01-01 00:00:00')) AS r GROUP BY dt, hr ORDER BY dt, hr;", - "comment": "Q1.3: What are the hourly average metrics during the past 10 days for a specific workstation?", - }, - { - "query": "SELECT machine_name, COUNT(*) AS spikes FROM mgbench.logs1 WHERE machine_group = 'Servers' AND cpu_wio > 0.99 AND log_time >= toDateTime('2016-12-01 00:00:00') AND log_time < toDateTime('2017-01-01 00:00:00') GROUP BY machine_name ORDER BY spikes DESC LIMIT 10;", - "comment": "Q1.4: Over 1 month, how often was each server blocked on disk I/O?", - }, - { - "query": "SELECT machine_name, dt, MIN(mem_free) AS mem_free_min FROM ( SELECT machine_name, CAST(log_time AS DATE) AS dt, mem_free FROM mgbench.logs1 WHERE machine_group = 'DMZ' AND mem_free IS NOT NULL ) AS r GROUP BY machine_name, dt HAVING MIN(mem_free) < 10000 ORDER BY machine_name, dt;", - "comment": "Q1.5: Which externally reachable VMs have run low on memory?", - }, - { - "query": "SELECT dt, hr, SUM(net_in) AS net_in_sum, SUM(net_out) AS net_out_sum, SUM(net_in) + SUM(net_out) AS both_sum FROM ( SELECT CAST(log_time AS DATE) AS dt, toHour(log_time) AS hr, ifNull(bytes_in, 0.0) / 1000000000.0 AS net_in, ifNull(bytes_out, 0.0) / 1000000000.0 AS net_out FROM mgbench.logs1 WHERE machine_name IN ('allsorts','andes','bigred','blackjack','bonbon','cadbury','chiclets','cotton','crows','dove','fireball','hearts','huey','lindt','milkduds','milkyway','mnm','necco','nerds','orbit','peeps','poprocks','razzles','runts','smarties','smuggler','spree','stride','tootsie','trident','wrigley','york') ) AS r GROUP BY dt, hr ORDER BY both_sum DESC LIMIT 10;", - "comment": "Q1.6: What is the total hourly network traffic across all file servers?", - }, - { - "query": "SELECT * FROM mgbench.logs2 WHERE status_code >= 500 AND log_time >= toDateTime('2012-12-18 00:00:00') ORDER BY log_time;", - "comment": "Q2.1: Which requests have caused server errors within the past 2 weeks?", - }, - { - "query": "SELECT * FROM mgbench.logs2 WHERE status_code >= 200 AND status_code < 300 AND request LIKE '%/etc/passwd%' AND log_time >= toDateTime('2012-05-06 00:00:00') AND log_time < toDateTime('2012-05-20 00:00:00');", - "comment": "Q2.3: What was the average path depth for top-level requests in the past month?", - }, - { - "query": "SELECT top_level, AVG(length(request) - length(replaceOne(request, '/',''))) AS depth_avg FROM ( SELECT substring(request, 1, len) AS top_level, request FROM ( SELECT position('/', substring(request, 2)) AS len, request FROM mgbench.logs2 WHERE status_code >= 200 AND status_code < 300 AND log_time >= toDateTime('2012-12-01 00:00:00')) AS r WHERE len > 0 ) AS s WHERE top_level IN ('/about','/courses','/degrees','/events','/grad','/industry','/news','/people','/publications','/research','/teaching','/ugrad') GROUP BY top_level ORDER BY top_level;", - "comment": "Q2.2: During a specific 2-week period, was the user password file leaked?", - }, - { - "query": "SELECT client_ip, COUNT(*) AS num_requests FROM mgbench.logs2 WHERE log_time >= toDateTime('2012-10-01 00:00:00') GROUP BY client_ip HAVING COUNT(*) >= 100000 ORDER BY num_requests DESC;", - "comment": "Q2.4: During the last 3 months, which clients have made an excessive number of requests?", - }, - { - "query": "SELECT dt, COUNT(DISTINCT client_ip) FROM ( SELECT CAST(log_time AS DATE) AS dt, client_ip FROM mgbench.logs2) AS r GROUP BY dt ORDER BY dt;", - "comment": "Q2.5: What are the daily unique visitors?", - }, - { - "query": "SELECT AVG(transfer) / 125000000.0 AS transfer_avg, MAX(transfer) / 125000000.0 AS transfer_max FROM ( SELECT log_time, SUM(object_size) AS transfer FROM mgbench.logs2 GROUP BY log_time) AS r;", - "comment": "Q2.6: What are the average and maximum data transfer rates (Gbps)?", - }, - { - "query": "SELECT * FROM mgbench.logs3 WHERE event_type = 'temperature' AND event_value <= 32.0 AND log_time >= '2019-11-29 17:00:00';", - "comment": "Q3.1: Did the indoor temperature reach freezing over the weekend?", - }, - { - "query": "SELECT device_name, device_floor, COUNT(*) AS ct FROM mgbench.logs3 WHERE event_type = 'door_open' AND log_time >= '2019-06-01 00:00:00' GROUP BY device_name, device_floor ORDER BY ct DESC;", - "comment": "Q3.4: Over the past 6 months, how frequently were each door opened?", - }, - { - "query": "SELECT yr, mo, SUM(coffee_hourly_avg) AS coffee_monthly_sum, AVG(coffee_hourly_avg) AS coffee_monthly_avg, SUM(printer_hourly_avg) AS printer_monthly_sum, AVG(printer_hourly_avg) AS printer_monthly_avg, SUM(projector_hourly_avg) AS projector_monthly_sum, AVG(projector_hourly_avg) AS projector_monthly_avg, SUM(vending_hourly_avg) AS vending_monthly_sum, AVG(vending_hourly_avg) AS vending_monthly_avg FROM ( SELECT dt, yr, mo, hr, AVG(coffee) AS coffee_hourly_avg, AVG(printer) AS printer_hourly_avg, AVG(projector) AS projector_hourly_avg, AVG(vending) AS vending_hourly_avg FROM ( SELECT CAST(log_time AS DATE) AS dt, toYear(log_time) AS yr, EXTRACT(MONTH FROM log_time) AS mo, toHour(log_time) AS hr, CASE WHEN device_name LIKE 'coffee%' THEN event_value END AS coffee, CASE WHEN device_name LIKE 'printer%' THEN event_value END AS printer, CASE WHEN device_name LIKE 'projector%' THEN event_value END AS projector, CASE WHEN device_name LIKE 'vending%' THEN event_value END AS vending FROM mgbench.logs3 WHERE device_type = 'meter' ) AS r GROUP BY dt, yr, mo, hr ) AS s GROUP BY yr, mo ORDER BY yr, mo;", - "comment": " -- Q3.6: For each device category, what are the monthly power consumption metrics?", - }, - { - "query": "SELECT sum(LO_EXTENDEDPRICE * LO_DISCOUNT) AS revenue FROM lineorder_flat WHERE F_YEAR = 1993 AND LO_DISCOUNT BETWEEN 1 AND 3 AND LO_QUANTITY < 25;", - "comment": " -- Q1.1", - }, - { - "query": "SELECT sum(LO_EXTENDEDPRICE * LO_DISCOUNT) AS revenue FROM lineorder_flat WHERE toYYYYMM(LO_ORDERDATE) = 199401 AND LO_DISCOUNT BETWEEN 4 AND 6 AND LO_QUANTITY BETWEEN 26 AND 35;", - "comment": " -- Q1.2", - }, - { - "query": "SELECT sum(LO_EXTENDEDPRICE * LO_DISCOUNT) AS revenue FROM lineorder_flat WHERE toRelativeWeekNum(LO_ORDERDATE) - toRelativeWeekNum(toDate('1994-01-01')) = 6 AND F_YEAR = 1994 AND LO_DISCOUNT BETWEEN 5 AND 7 AND LO_QUANTITY BETWEEN 26 AND 35;", - "comment": " -- Q1.3", - }, - { - "query": "SELECT sum(LO_REVENUE), F_YEAR AS year, P_BRAND FROM lineorder_flat WHERE P_CATEGORY = 'MFGR#12' AND S_REGION = 'AMERICA' GROUP BY year, P_BRAND ORDER BY year, P_BRAND;", - "comment": " -- Q2.1", - }, - { - "query": "SELECT sum(LO_REVENUE), F_YEAR AS year, P_BRAND FROM lineorder_flat WHERE P_BRAND >= 'MFGR#2221' AND P_BRAND <= 'MFGR#2228' AND S_REGION = 'ASIA' GROUP BY year, P_BRAND ORDER BY year, P_BRAND;", - "comment": " -- Q2.2", - }, - { - "query": "SELECT sum(LO_REVENUE), F_YEAR AS year, P_BRAND FROM lineorder_flat WHERE P_BRAND = 'MFGR#2239' AND S_REGION = 'EUROPE' GROUP BY year, P_BRAND ORDER BY year, P_BRAND;", - "comment": " -- Q2.3", - }, - { - "query": "SELECT C_NATION, S_NATION, F_YEAR AS year, sum(LO_REVENUE) AS revenue FROM lineorder_flat WHERE C_REGION = 'ASIA' AND S_REGION = 'ASIA' AND year >= 1992 AND year <= 1997 GROUP BY C_NATION, S_NATION, year ORDER BY year ASC, revenue DESC;", - "comment": " -- Q3.1", - }, - { - "query": "SELECT C_CITY, S_CITY, F_YEAR AS year, sum(LO_REVENUE) AS revenue FROM lineorder_flat WHERE C_NATION = 'UNITED STATES' AND S_NATION = 'UNITED STATES' AND year >= 1992 AND year <= 1997 GROUP BY C_CITY, S_CITY, year ORDER BY year ASC, revenue DESC;", - "comment": " -- Q3.2", - }, - { - "query": "SELECT C_CITY, S_CITY, F_YEAR AS year, sum(LO_REVENUE) AS revenue FROM lineorder_flat WHERE (C_CITY = 'UNITED KI1' OR C_CITY = 'UNITED KI5') AND (S_CITY = 'UNITED KI1' OR S_CITY = 'UNITED KI5') AND year >= 1992 AND year <= 1997 GROUP BY C_CITY, S_CITY, year ORDER BY year ASC, revenue DESC;", - "comment": " -- Q3.3", - }, - { - "query": "SELECT C_CITY, S_CITY, F_YEAR AS year, sum(LO_REVENUE) AS revenue FROM lineorder_flat WHERE (C_CITY = 'UNITED KI1' OR C_CITY = 'UNITED KI5') AND (S_CITY = 'UNITED KI1' OR S_CITY = 'UNITED KI5') AND toYYYYMM(LO_ORDERDATE) = 199712 GROUP BY C_CITY, S_CITY, year ORDER BY year ASC, revenue DESC;", - "comment": " -- Q3.4", - }, - { - "query": "SELECT F_YEAR AS year, C_NATION, sum(LO_REVENUE - LO_SUPPLYCOST) AS profit FROM lineorder_flat WHERE C_REGION = 'AMERICA' AND S_REGION = 'AMERICA' AND (P_MFGR = 'MFGR#1' OR P_MFGR = 'MFGR#2') GROUP BY year, C_NATION ORDER BY year ASC, C_NATION ASC;", - "comment": " -- Q4.1", - }, - { - "query": "SELECT F_YEAR AS year, S_NATION, P_CATEGORY, sum(LO_REVENUE - LO_SUPPLYCOST) AS profit FROM lineorder_flat WHERE C_REGION = 'AMERICA' AND S_REGION = 'AMERICA' AND (year = 1997 OR year = 1998) AND (P_MFGR = 'MFGR#1' OR P_MFGR = 'MFGR#2') GROUP BY year, S_NATION, P_CATEGORY ORDER BY year ASC, S_NATION ASC, P_CATEGORY ASC;", - "comment": " -- Q4.2", - }, - { - "query": "SELECT F_YEAR AS year, S_CITY, P_BRAND, sum(LO_REVENUE - LO_SUPPLYCOST) AS profit FROM lineorder_flat WHERE S_NATION = 'UNITED STATES' AND (year = 1997 OR year = 1998) AND P_CATEGORY = 'MFGR#14' GROUP BY year, S_CITY, P_BRAND ORDER BY year ASC, S_CITY ASC, P_BRAND ASC;", - "comment": " -- Q4.", - }, - { - "query": "SELECT count() FROM hits", - "comment": "", - }, - { - "query": "SELECT count() FROM hits WHERE AdvEngineID != 0", - "comment": "", - }, - { - "query": "SELECT sum(AdvEngineID), count(), avg(ResolutionWidth) FROM hits", - "comment": "", - }, - { - "query": "SELECT sum(UserID) FROM hits", - "comment": "", - }, - { - "query": "SELECT uniq(UserID) FROM hits", - "comment": "", - }, - { - "query": "SELECT uniq(SearchPhrase) FROM hits", - "comment": "", - }, - { - "query": "SELECT min(EventDate), max(EventDate) FROM hits", - "comment": "", - }, - { - "query": "SELECT AdvEngineID, count() FROM hits WHERE AdvEngineID != 0 GROUP BY AdvEngineID ORDER BY count() DESC", - "comment": "", - }, - { - "query": "SELECT RegionID, uniq(UserID) AS u FROM hits GROUP BY RegionID ORDER BY u DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT RegionID, sum(AdvEngineID), count() AS c, avg(ResolutionWidth), uniq(UserID) FROM hits GROUP BY RegionID ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT MobilePhoneModel, uniq(UserID) AS u FROM hits WHERE MobilePhoneModel != '' GROUP BY MobilePhoneModel ORDER BY u DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT MobilePhone, MobilePhoneModel, uniq(UserID) AS u FROM hits WHERE MobilePhoneModel != '' GROUP BY MobilePhone, MobilePhoneModel ORDER BY u DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase, count() AS c FROM hits WHERE SearchPhrase != '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase, uniq(UserID) AS u FROM hits WHERE SearchPhrase != '' GROUP BY SearchPhrase ORDER BY u DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchEngineID, SearchPhrase, count() AS c FROM hits WHERE SearchPhrase != '' GROUP BY SearchEngineID, SearchPhrase ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT UserID, count() FROM hits GROUP BY UserID ORDER BY count() DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT UserID, SearchPhrase, count() FROM hits GROUP BY UserID, SearchPhrase ORDER BY count() DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT UserID, SearchPhrase, count() FROM hits GROUP BY UserID, SearchPhrase LIMIT 10", - "comment": "", - }, - { - "query": "SELECT UserID, toMinute(EventTime) AS m, SearchPhrase, count() FROM hits GROUP BY UserID, m, SearchPhrase ORDER BY count() DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT UserID FROM hits WHERE UserID = 12345678901234567890", - "comment": "", - }, - { - "query": "SELECT count() FROM hits WHERE URL LIKE '%metrika%'", - "comment": "", - }, - { - "query": "SELECT SearchPhrase, any(URL), count() AS c FROM hits WHERE URL LIKE '%metrika%' AND SearchPhrase != '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase, any(URL), any(Title), count() AS c, uniq(UserID) FROM hits WHERE Title LIKE '%Яндекс%' AND URL NOT LIKE '%.yandex.%' AND SearchPhrase != '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT * FROM hits WHERE URL LIKE '%metrika%' ORDER BY EventTime LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase FROM hits WHERE SearchPhrase != '' ORDER BY EventTime LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase FROM hits WHERE SearchPhrase != '' ORDER BY SearchPhrase LIMIT 10", - "comment": "", - }, - { - "query": "SELECT SearchPhrase FROM hits WHERE SearchPhrase != '' ORDER BY EventTime, SearchPhrase LIMIT 10", - "comment": "", - }, - { - "query": "SELECT CounterID, avg(length(URL)) AS l, count() AS c FROM hits WHERE URL != '' GROUP BY CounterID HAVING c > 100000 ORDER BY l DESC LIMIT 25", - "comment": "", - }, - { - "query": "SELECT domainWithoutWWW(Referer) AS key, avg(length(Referer)) AS l, count() AS c, any(Referer) FROM hits WHERE Referer != '' GROUP BY key HAVING c > 100000 ORDER BY l DESC LIMIT 25", - "comment": "", - }, - { - "query": "SELECT sum(ResolutionWidth), sum(ResolutionWidth + 1), sum(ResolutionWidth + 2), sum(ResolutionWidth + 3), sum(ResolutionWidth + 4), sum(ResolutionWidth + 5), sum(ResolutionWidth + 6), sum(ResolutionWidth + 7), sum(ResolutionWidth + 8), sum(ResolutionWidth + 9), sum(ResolutionWidth + 10), sum(ResolutionWidth + 11), sum(ResolutionWidth + 12), sum(ResolutionWidth + 13), sum(ResolutionWidth + 14), sum(ResolutionWidth + 15), sum(ResolutionWidth + 16), sum(ResolutionWidth + 17), sum(ResolutionWidth + 18), sum(ResolutionWidth + 19), sum(ResolutionWidth + 20), sum(ResolutionWidth + 21), sum(ResolutionWidth + 22), sum(ResolutionWidth + 23), sum(ResolutionWidth + 24), sum(ResolutionWidth + 25), sum(ResolutionWidth + 26), sum(ResolutionWidth + 27), sum(ResolutionWidth + 28), sum(ResolutionWidth + 29), sum(ResolutionWidth + 30), sum(ResolutionWidth + 31), sum(ResolutionWidth + 32), sum(ResolutionWidth + 33), sum(ResolutionWidth + 34), sum(ResolutionWidth + 35), sum(ResolutionWidth + 36), sum(ResolutionWidth + 37), sum(ResolutionWidth + 38), sum(ResolutionWidth + 39), sum(ResolutionWidth + 40), sum(ResolutionWidth + 41), sum(ResolutionWidth + 42), sum(ResolutionWidth + 43), sum(ResolutionWidth + 44), sum(ResolutionWidth + 45), sum(ResolutionWidth + 46), sum(ResolutionWidth + 47), sum(ResolutionWidth + 48), sum(ResolutionWidth + 49), sum(ResolutionWidth + 50), sum(ResolutionWidth + 51), sum(ResolutionWidth + 52), sum(ResolutionWidth + 53), sum(ResolutionWidth + 54), sum(ResolutionWidth + 55), sum(ResolutionWidth + 56), sum(ResolutionWidth + 57), sum(ResolutionWidth + 58), sum(ResolutionWidth + 59), sum(ResolutionWidth + 60), sum(ResolutionWidth + 61), sum(ResolutionWidth + 62), sum(ResolutionWidth + 63), sum(ResolutionWidth + 64), sum(ResolutionWidth + 65), sum(ResolutionWidth + 66), sum(ResolutionWidth + 67), sum(ResolutionWidth + 68), sum(ResolutionWidth + 69), sum(ResolutionWidth + 70), sum(ResolutionWidth + 71), sum(ResolutionWidth + 72), sum(ResolutionWidth + 73), sum(ResolutionWidth + 74), sum(ResolutionWidth + 75), sum(ResolutionWidth + 76), sum(ResolutionWidth + 77), sum(ResolutionWidth + 78), sum(ResolutionWidth + 79), sum(ResolutionWidth + 80), sum(ResolutionWidth + 81), sum(ResolutionWidth + 82), sum(ResolutionWidth + 83), sum(ResolutionWidth + 84), sum(ResolutionWidth + 85), sum(ResolutionWidth + 86), sum(ResolutionWidth + 87), sum(ResolutionWidth + 88), sum(ResolutionWidth + 89) FROM hits", - "comment": "", - }, - { - "query": "SELECT SearchEngineID, ClientIP, count() AS c, sum(Refresh), avg(ResolutionWidth) FROM hits WHERE SearchPhrase != '' GROUP BY SearchEngineID, ClientIP ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT WatchID, ClientIP, count() AS c, sum(Refresh), avg(ResolutionWidth) FROM hits WHERE SearchPhrase != '' GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT WatchID, ClientIP, count() AS c, sum(Refresh), avg(ResolutionWidth) FROM hits GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT URL, count() AS c FROM hits GROUP BY URL ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT 1, URL, count() AS c FROM hits GROUP BY 1, URL ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT ClientIP AS x, x - 1, x - 2, x - 3, count() AS c FROM hits GROUP BY x, x - 1, x - 2, x - 3 ORDER BY c DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT URL, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT DontCountHits AND NOT Refresh AND notEmpty(URL) GROUP BY URL ORDER BY PageViews DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT Title, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT DontCountHits AND NOT Refresh AND notEmpty(Title) GROUP BY Title ORDER BY PageViews DESC LIMIT 10", - "comment": "", - }, - { - "query": "SELECT URL, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT Refresh AND IsLink AND NOT IsDownload GROUP BY URL ORDER BY PageViews DESC LIMIT 1000", - "comment": "", - }, - { - "query": "SELECT TraficSourceID, SearchEngineID, AdvEngineID, ((SearchEngineID = 0 AND AdvEngineID = 0) ? Referer : '') AS Src, URL AS Dst, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT Refresh GROUP BY TraficSourceID, SearchEngineID, AdvEngineID, Src, Dst ORDER BY PageViews DESC LIMIT 1000", - "comment": "", - }, - { - "query": "SELECT URLHash, EventDate, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT Refresh AND TraficSourceID IN (-1, 6) AND RefererHash = halfMD5('http://yandex.ru/') GROUP BY URLHash, EventDate ORDER BY PageViews DESC LIMIT 100", - "comment": "", - }, - { - "query": "SELECT WindowClientWidth, WindowClientHeight, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-31') AND NOT Refresh AND NOT DontCountHits AND URLHash = halfMD5('http://yandex.ru/') GROUP BY WindowClientWidth, WindowClientHeight ORDER BY PageViews DESC LIMIT 10000;", - "comment": "", - }, - { - "query": "SELECT toStartOfMinute(EventTime) AS Minute, count() AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= toDate('2013-07-01') AND EventDate <= toDate('2013-07-02') AND NOT Refresh AND NOT DontCountHits GROUP BY Minute ORDER BY Minute;", - "comment": "", - } - ]; diff --git a/website/benchmark/versions/results/1.1.54378.json b/website/benchmark/versions/results/1.1.54378.json deleted file mode 100644 index 987d4d50231..00000000000 --- a/website/benchmark/versions/results/1.1.54378.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2018-04-16 1.1.54378", - "system_full": "ClickHouse 1.1.54378 2018-04-16", - "version": "1.1.54378", - "kind": "", - "comments": "", - "result": - [ - -[0.099, 0.017, 0.014], -[0.176, 0.005, 0.005], -[0.818, 0.018, 0.016], -[0.163, 0.011, 0.011], -[0.264, 0.039, 0.031], -[1.025, 0.026, 0.024], -[0.076, 0.004, 0.003], -[0.335, 0.018, 0.018], -[0.511, 0.034, 0.034], -[0.541, 0.090, 0.088], -[1.881, 0.506, 0.497], -[1.515, 0.546, 0.495], -[0.033, 0.003, 0.003], -[0.034, 0.016, 0.016], -[0.123, 0.105, 0.103], -[3.318, 0.090, 0.088], -[2.609, 0.146, 0.145], -[3.887, 0.067, 0.066], -[1.417, 0.067, 0.065], -[7.034, 0.332, 0.328], -[4.289, 0.177, 0.176], -[1.773, 0.204, 0.214], -[63.407, 2.564, 2.490], -[42.496, 2.011, 1.993], -[4.502, 0.218, 0.216], -[1.952, 0.120, 0.121], -[0.474, 0.059, 0.062], -[6.655, 0.106, 0.101], -[0.044, 0.010, 0.010], -[0.042, 0.007, 0.007], -[0.480, 0.060, 0.056], -[1.684, 0.042, 0.039], -[1.721, 0.091, 0.091], -[2.587, 0.207, 0.206], -[0.067, 0.044, 0.044], -[0.027, 0.008, 0.007], -[2.282, 0.428, 0.425], -[2.273, 0.501, 0.500], -[1.072, 0.133, 0.133], -[1.717, 0.163, 0.163], -[2.604, 0.431, 0.418], -[3.794, 0.546, 0.545], -[2.398, 0.538, 0.585], -[1.272, 0.564, 0.534], -[4.313, 1.445, 1.481], -[3.711, 0.860, 0.831], -[8.169, 3.717, 3.386], -[0.962, 0.029, 0.028], -[18.979, 0.538, 0.533], -[20.836, 0.642, 0.667], -[38.657, 1.461, 1.388], -[29.873, 0.735, 0.675], -[4.756, 0.189, 0.188], -[2.304, 0.161, 0.161], -[5.272, 0.196, 0.195], -[18.616, 0.557, 0.552], -[15.437, 1.136, 1.125], -[2.560, 2.523, 2.497], -[3.912, 0.561, 0.557], -[9.879, 0.774, 0.793], -[9.665, 4.632, 4.677], -[18.892, 2.283, 2.142], -[18.894, 2.264, 2.160], -[0.953, 0.921, 0.915], -[0.099, 0.075, 0.076], -[0.055, 0.038, 0.037], -[0.055, 0.033, 0.031], -[0.202, 0.159, 0.159], -[0.038, 0.011, 0.011], -[0.023, 0.009, 0.009], -[0.012, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/1.1.54394.json b/website/benchmark/versions/results/1.1.54394.json deleted file mode 100644 index b88336c2fcd..00000000000 --- a/website/benchmark/versions/results/1.1.54394.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2018-07-12 1.1.54394", - "system_full": "ClickHouse 1.1.54394 2018-07-12", - "version": "1.1.54394", - "kind": "", - "comments": "", - "result": - [ - -[0.141, 0.016, 0.014], -[0.152, 0.005, 0.005], -[0.820, 0.018, 0.016], -[0.161, 0.011, 0.011], -[0.266, 0.033, 0.032], -[1.024, 0.026, 0.024], -[0.076, 0.004, 0.003], -[0.335, 0.017, 0.017], -[0.508, 0.033, 0.032], -[0.553, 0.090, 0.090], -[1.839, 0.501, 0.497], -[1.519, 0.490, 0.513], -[0.035, 0.003, 0.003], -[0.033, 0.015, 0.015], -[0.116, 0.135, 0.105], -[3.324, 0.088, 0.085], -[2.614, 0.145, 0.144], -[3.882, 0.068, 0.066], -[1.421, 0.067, 0.066], -[7.042, 0.315, 0.312], -[4.326, 0.170, 0.168], -[1.810, 0.205, 0.198], -[63.409, 2.479, 2.463], -[42.496, 1.974, 1.963], -[4.501, 0.223, 0.211], -[1.947, 0.115, 0.114], -[0.488, 0.058, 0.060], -[6.636, 0.103, 0.099], -[0.050, 0.010, 0.010], -[0.043, 0.007, 0.006], -[0.482, 0.055, 0.055], -[1.669, 0.040, 0.041], -[1.713, 0.093, 0.091], -[2.563, 0.201, 0.202], -[0.064, 0.041, 0.041], -[0.028, 0.007, 0.007], -[2.305, 0.425, 0.424], -[2.270, 0.502, 0.506], -[1.070, 0.139, 0.138], -[1.689, 0.166, 0.165], -[2.616, 0.447, 0.459], -[3.835, 0.562, 0.540], -[2.452, 0.581, 0.560], -[1.282, 0.540, 0.544], -[4.321, 1.565, 1.467], -[3.678, 0.811, 0.812], -[8.241, 3.565, 3.693], -[0.961, 0.028, 0.027], -[18.976, 0.509, 0.482], -[20.838, 0.601, 0.737], -[38.809, 1.514, 1.353], -[29.889, 0.690, 0.628], -[4.546, 0.190, 0.188], -[2.314, 0.163, 0.165], -[5.264, 0.197, 0.193], -[18.615, 0.527, 0.516], -[15.439, 1.156, 1.138], -[2.552, 2.521, 2.509], -[3.918, 0.566, 0.557], -[9.883, 0.790, 0.794], -[9.662, 4.592, 4.830], -[18.976, 2.262, 2.254], -[18.926, 2.132, 2.173], -[0.945, 0.918, 0.921], -[0.099, 0.074, 0.074], -[0.053, 0.038, 0.038], -[0.052, 0.030, 0.030], -[0.199, 0.156, 0.155], -[0.038, 0.011, 0.011], -[0.023, 0.009, 0.009], -[0.011, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/18.01.0.json b/website/benchmark/versions/results/18.01.0.json deleted file mode 100644 index 15ed8360b47..00000000000 --- a/website/benchmark/versions/results/18.01.0.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2018-07-20 18.1", - "system_full": "ClickHouse 18.1.0 2018-07-20", - "version": "18.1.0", - "kind": "", - "comments": "", - "result": - [ - -[0.130, 0.015, 0.013], -[0.139, 0.005, 0.005], -[0.822, 0.017, 0.016], -[0.160, 0.011, 0.010], -[0.263, 0.051, 0.059], -[0.994, 0.025, 0.024], -[0.076, 0.004, 0.004], -[0.334, 0.017, 0.017], -[0.507, 0.031, 0.030], -[0.553, 0.087, 0.084], -[1.867, 0.486, 0.484], -[1.528, 0.493, 0.493], -[0.034, 0.003, 0.003], -[0.033, 0.015, 0.015], -[0.117, 0.099, 0.099], -[3.353, 0.087, 0.086], -[2.632, 0.145, 0.144], -[3.913, 0.067, 0.065], -[1.433, 0.063, 0.062], -[7.058, 0.303, 0.301], -[4.355, 0.163, 0.161], -[1.826, 0.187, 0.212], -[63.427, 2.395, 2.319], -[42.481, 1.974, 1.956], -[4.485, 0.213, 0.218], -[1.945, 0.123, 0.116], -[0.445, 0.056, 0.055], -[6.633, 0.102, 0.099], -[0.047, 0.010, 0.010], -[0.044, 0.007, 0.007], -[0.479, 0.055, 0.055], -[1.695, 0.040, 0.039], -[1.731, 0.092, 0.095], -[2.547, 0.197, 0.192], -[0.061, 0.041, 0.041], -[0.025, 0.007, 0.007], -[2.289, 0.429, 0.426], -[2.240, 0.506, 0.502], -[1.062, 0.128, 0.126], -[1.688, 0.155, 0.155], -[2.571, 0.456, 0.423], -[3.814, 0.538, 0.546], -[2.467, 0.563, 0.531], -[1.263, 0.545, 0.553], -[4.303, 1.526, 1.539], -[3.667, 0.911, 0.922], -[8.280, 3.588, 3.559], -[0.938, 0.028, 0.027], -[18.975, 0.519, 0.513], -[20.851, 0.588, 0.724], -[38.765, 1.508, 1.345], -[29.904, 0.684, 0.648], -[4.591, 0.180, 0.175], -[2.350, 0.151, 0.150], -[5.295, 0.185, 0.185], -[18.635, 0.520, 0.512], -[15.431, 1.169, 1.144], -[2.543, 2.542, 2.504], -[3.918, 0.545, 0.540], -[9.879, 0.781, 0.765], -[9.687, 4.567, 4.636], -[18.949, 2.314, 2.136], -[18.946, 2.168, 2.227], -[0.950, 0.902, 0.934], -[0.098, 0.073, 0.075], -[0.056, 0.038, 0.037], -[0.055, 0.030, 0.030], -[0.205, 0.157, 0.155], -[0.037, 0.011, 0.011], -[0.022, 0.009, 0.009], -[0.011, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/18.04.0.json b/website/benchmark/versions/results/18.04.0.json deleted file mode 100644 index 65f41e6b9a4..00000000000 --- a/website/benchmark/versions/results/18.04.0.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2018-07-28 18.4", - "system_full": "ClickHouse 18.4.0 2018-07-28", - "version": "18.4.0", - "kind": "", - "comments": "", - "result": - [ - -[0.145, 0.015, 0.013], -[0.042, 0.006, 0.005], -[0.662, 0.016, 0.015], -[0.161, 0.011, 0.010], -[0.268, 0.030, 0.029], -[1.026, 0.025, 0.023], -[0.076, 0.004, 0.003], -[0.334, 0.018, 0.018], -[0.501, 0.031, 0.031], -[0.551, 0.089, 0.086], -[1.893, 0.495, 0.489], -[1.521, 0.506, 0.479], -[0.033, 0.003, 0.003], -[0.032, 0.015, 0.015], -[0.163, 0.131, 0.103], -[3.347, 0.090, 0.087], -[2.623, 0.145, 0.146], -[3.895, 0.068, 0.067], -[1.421, 0.063, 0.064], -[7.053, 0.307, 0.304], -[4.320, 0.159, 0.159], -[1.811, 0.194, 0.200], -[63.418, 2.390, 2.315], -[42.482, 1.944, 1.934], -[4.507, 0.213, 0.210], -[1.947, 0.116, 0.119], -[0.481, 0.057, 0.056], -[6.653, 0.100, 0.098], -[0.045, 0.010, 0.010], -[0.043, 0.007, 0.007], -[0.481, 0.054, 0.055], -[1.683, 0.043, 0.044], -[1.732, 0.092, 0.091], -[2.570, 0.192, 0.193], -[0.056, 0.039, 0.039], -[0.025, 0.007, 0.007], -[2.313, 0.428, 0.426], -[2.253, 0.497, 0.498], -[1.076, 0.121, 0.121], -[1.728, 0.148, 0.149], -[2.579, 0.444, 0.414], -[3.796, 0.532, 0.531], -[2.427, 0.556, 0.563], -[1.267, 0.544, 0.542], -[4.314, 1.538, 1.516], -[3.662, 0.804, 0.869], -[8.244, 3.696, 3.698], -[0.956, 0.028, 0.027], -[18.975, 0.514, 0.507], -[20.853, 0.583, 0.726], -[38.834, 1.380, 1.363], -[29.884, 0.675, 0.640], -[4.554, 0.175, 0.173], -[2.360, 0.147, 0.145], -[5.300, 0.179, 0.179], -[18.661, 0.514, 0.505], -[15.432, 1.161, 1.153], -[2.528, 2.542, 2.512], -[3.929, 0.543, 0.533], -[9.838, 0.765, 0.761], -[9.589, 4.506, 4.642], -[18.961, 2.245, 2.185], -[18.935, 2.127, 2.154], -[0.950, 0.913, 0.889], -[0.098, 0.072, 0.072], -[0.054, 0.037, 0.037], -[0.054, 0.030, 0.030], -[0.203, 0.153, 0.158], -[0.037, 0.011, 0.011], -[0.023, 0.009, 0.009], -[0.012, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/18.05.1.json b/website/benchmark/versions/results/18.05.1.json deleted file mode 100644 index 3d58829d607..00000000000 --- a/website/benchmark/versions/results/18.05.1.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2018-07-31 18.5", - "system_full": "ClickHouse 18.5.1 2018-07-31", - "version": "18.5.1", - "kind": "", - "comments": "", - "result": - [ - -[0.130, 0.015, 0.013], -[0.147, 0.005, 0.005], -[0.819, 0.017, 0.015], -[0.162, 0.011, 0.011], -[0.263, 0.040, 0.054], -[1.003, 0.025, 0.023], -[0.076, 0.004, 0.003], -[0.335, 0.017, 0.017], -[0.509, 0.032, 0.031], -[0.551, 0.095, 0.087], -[1.829, 0.489, 0.496], -[1.510, 0.518, 0.492], -[0.035, 0.003, 0.003], -[0.032, 0.015, 0.015], -[0.122, 0.127, 0.101], -[3.329, 0.090, 0.087], -[2.609, 0.143, 0.141], -[3.895, 0.067, 0.066], -[1.433, 0.064, 0.064], -[7.038, 0.307, 0.305], -[4.335, 0.160, 0.160], -[1.817, 0.216, 0.214], -[63.378, 2.378, 2.313], -[42.494, 1.940, 1.929], -[4.510, 0.212, 0.209], -[1.955, 0.119, 0.117], -[0.496, 0.058, 0.056], -[6.639, 0.100, 0.097], -[0.046, 0.010, 0.010], -[0.044, 0.007, 0.006], -[0.525, 0.055, 0.056], -[1.739, 0.043, 0.041], -[1.749, 0.091, 0.091], -[2.566, 0.193, 0.189], -[0.061, 0.041, 0.041], -[0.026, 0.007, 0.007], -[2.331, 0.427, 0.426], -[2.279, 0.504, 0.502], -[1.054, 0.122, 0.121], -[1.735, 0.149, 0.150], -[2.649, 0.426, 0.415], -[3.799, 0.552, 0.564], -[2.437, 0.573, 0.522], -[1.255, 0.532, 0.556], -[4.340, 1.534, 1.446], -[3.647, 0.811, 0.846], -[8.212, 3.519, 3.542], -[0.951, 0.028, 0.027], -[18.978, 0.661, 0.508], -[20.848, 0.583, 0.575], -[38.808, 1.432, 1.348], -[29.875, 0.679, 0.651], -[4.778, 0.176, 0.174], -[2.370, 0.148, 0.146], -[5.302, 0.186, 0.178], -[18.666, 0.522, 0.514], -[15.419, 1.157, 1.141], -[2.527, 2.526, 2.513], -[3.948, 0.539, 0.544], -[9.857, 0.772, 0.750], -[9.827, 4.565, 4.514], -[18.957, 2.301, 2.151], -[18.952, 2.147, 2.239], -[0.940, 0.897, 0.907], -[0.099, 0.072, 0.073], -[0.055, 0.037, 0.037], -[0.054, 0.030, 0.029], -[0.193, 0.155, 0.152], -[0.035, 0.011, 0.010], -[0.023, 0.009, 0.009], -[0.012, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/18.06.0.json b/website/benchmark/versions/results/18.06.0.json deleted file mode 100644 index d033aabc4f2..00000000000 --- a/website/benchmark/versions/results/18.06.0.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2018-08-01 18.6", - "system_full": "ClickHouse 18.6.0 2018-08-01", - "version": "18.6.0", - "kind": "", - "comments": "", - "result": - [ - -[0.125, 0.014, 0.013], -[0.156, 0.005, 0.005], -[0.818, 0.016, 0.015], -[0.162, 0.011, 0.011], -[0.265, 0.044, 0.031], -[1.023, 0.025, 0.023], -[0.076, 0.004, 0.004], -[0.335, 0.019, 0.017], -[0.508, 0.032, 0.031], -[0.551, 0.088, 0.086], -[1.844, 0.493, 0.491], -[1.520, 0.485, 0.492], -[0.035, 0.003, 0.003], -[0.033, 0.015, 0.015], -[0.155, 0.109, 0.129], -[3.314, 0.090, 0.088], -[2.611, 0.144, 0.142], -[3.902, 0.066, 0.065], -[1.423, 0.064, 0.062], -[7.049, 0.304, 0.330], -[4.330, 0.159, 0.158], -[1.834, 0.193, 0.176], -[63.516, 2.328, 2.310], -[42.645, 1.945, 1.913], -[4.521, 0.212, 0.217], -[1.923, 0.112, 0.114], -[0.479, 0.056, 0.055], -[6.627, 0.101, 0.097], -[0.047, 0.010, 0.009], -[0.043, 0.007, 0.006], -[0.482, 0.058, 0.055], -[1.693, 0.043, 0.043], -[1.744, 0.098, 0.093], -[2.565, 0.192, 0.192], -[0.059, 0.040, 0.040], -[0.026, 0.007, 0.007], -[2.325, 0.425, 0.426], -[2.265, 0.501, 0.499], -[1.043, 0.122, 0.122], -[1.718, 0.151, 0.150], -[2.627, 0.425, 0.441], -[3.801, 0.530, 0.528], -[2.398, 0.525, 0.520], -[1.238, 0.523, 0.543], -[4.345, 1.505, 1.513], -[3.667, 0.851, 0.852], -[8.282, 3.515, 3.493], -[0.962, 0.028, 0.028], -[18.978, 0.518, 0.514], -[20.849, 0.814, 0.578], -[38.796, 1.382, 1.331], -[29.874, 0.665, 0.650], -[4.545, 0.181, 0.174], -[2.356, 0.147, 0.145], -[5.302, 0.180, 0.179], -[18.680, 0.516, 0.509], -[15.430, 1.162, 1.158], -[2.515, 2.502, 2.538], -[3.927, 0.533, 0.525], -[9.878, 0.769, 0.767], -[9.608, 4.694, 4.443], -[19.021, 2.138, 2.202], -[18.958, 2.174, 2.204], -[0.956, 0.899, 0.929], -[0.099, 0.074, 0.073], -[0.055, 0.037, 0.037], -[0.051, 0.031, 0.030], -[0.203, 0.157, 0.156], -[0.040, 0.011, 0.011], -[0.024, 0.009, 0.009], -[0.012, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/18.10.3.json b/website/benchmark/versions/results/18.10.3.json deleted file mode 100644 index a29bdff3f7a..00000000000 --- a/website/benchmark/versions/results/18.10.3.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2018-08-13 18.10", - "system_full": "ClickHouse 18.10.3 2018-08-13", - "version": "18.10.3", - "kind": "", - "comments": "", - "result": - [ - -[0.138, 0.015, 0.013], -[0.119, 0.006, 0.006], -[0.820, 0.017, 0.016], -[0.161, 0.011, 0.011], -[0.270, 0.044, 0.046], -[1.009, 0.025, 0.024], -[0.076, 0.004, 0.004], -[0.335, 0.017, 0.017], -[0.505, 0.032, 0.030], -[0.554, 0.087, 0.090], -[1.887, 0.524, 0.504], -[1.530, 0.485, 0.490], -[0.034, 0.003, 0.003], -[0.032, 0.022, 0.020], -[0.174, 0.132, 0.134], -[3.331, 0.091, 0.087], -[2.632, 0.154, 0.152], -[3.881, 0.074, 0.072], -[1.421, 0.067, 0.067], -[7.055, 0.298, 0.291], -[4.370, 0.161, 0.160], -[1.828, 0.417, 0.423], -[63.020, 2.017, 1.993], -[42.473, 1.863, 1.855], -[4.489, 0.220, 0.215], -[1.947, 0.158, 0.208], -[0.357, 0.078, 0.092], -[6.607, 0.115, 0.103], -[0.043, 0.011, 0.010], -[0.043, 0.008, 0.008], -[0.483, 0.060, 0.061], -[1.687, 0.042, 0.042], -[1.732, 0.093, 0.093], -[2.572, 0.194, 0.192], -[0.066, 0.048, 0.048], -[0.028, 0.008, 0.008], -[2.290, 0.435, 0.438], -[2.276, 0.503, 0.506], -[1.054, 0.127, 0.126], -[1.702, 0.160, 0.165], -[2.545, 0.429, 0.464], -[3.846, 0.551, 0.535], -[2.413, 0.575, 0.554], -[1.244, 0.539, 0.582], -[4.310, 1.570, 1.539], -[3.635, 0.910, 0.868], -[8.212, 4.811, 4.268], -[0.947, 0.028, 0.027], -[18.972, 0.518, 0.506], -[20.843, 0.588, 0.572], -[38.776, 1.377, 1.363], -[29.917, 0.670, 0.630], -[4.779, 0.186, 0.182], -[2.330, 0.163, 0.153], -[5.283, 0.193, 0.187], -[18.637, 0.544, 0.518], -[15.417, 1.178, 1.161], -[2.396, 2.348, 2.330], -[3.916, 0.537, 0.539], -[9.855, 0.752, 0.735], -[9.330, 4.220, 4.258], -[18.911, 2.108, 2.111], -[18.849, 2.087, 2.145], -[0.942, 0.885, 0.891], -[0.102, 0.077, 0.073], -[0.062, 0.038, 0.038], -[0.052, 0.030, 0.030], -[0.251, 0.173, 0.193], -[0.035, 0.011, 0.011], -[0.023, 0.009, 0.009], -[0.011, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/18.12.17.json b/website/benchmark/versions/results/18.12.17.json deleted file mode 100644 index 8a4ad751168..00000000000 --- a/website/benchmark/versions/results/18.12.17.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2018-09-16 18.12", - "system_full": "ClickHouse 18.12.17 2018-09-16", - "version": "18.12.17", - "kind": "", - "comments": "", - "result": - [ - -[0.132, 0.018, 0.014], -[0.093, 0.006, 0.006], -[0.818, 0.020, 0.017], -[0.164, 0.012, 0.012], -[0.262, 0.042, 0.044], -[0.988, 0.028, 0.026], -[0.076, 0.004, 0.004], -[0.337, 0.020, 0.021], -[0.506, 0.038, 0.036], -[0.544, 0.098, 0.100], -[1.844, 0.533, 0.538], -[1.497, 0.510, 0.509], -[0.034, 0.004, 0.004], -[0.036, 0.016, 0.016], -[0.142, 0.148, 0.131], -[3.307, 0.098, 0.096], -[2.591, 0.160, 0.157], -[3.871, 0.082, 0.079], -[1.398, 0.074, 0.074], -[7.032, 0.349, 0.336], -[4.265, 0.170, 0.165], -[1.831, 0.221, 0.214], -[63.399, 2.641, 2.602], -[42.509, 2.107, 2.060], -[4.499, 0.233, 0.229], -[1.916, 0.146, 0.140], -[0.418, 0.064, 0.071], -[6.635, 0.123, 0.119], -[0.046, 0.011, 0.011], -[0.037, 0.008, 0.008], -[0.485, 0.062, 0.067], -[1.641, 0.044, 0.043], -[1.696, 0.097, 0.092], -[2.573, 0.200, 0.196], -[0.067, 0.046, 0.046], -[0.032, 0.010, 0.009], -[2.249, 0.429, 0.431], -[2.248, 0.513, 0.508], -[1.058, 0.132, 0.132], -[1.720, 0.162, 0.159], -[2.538, 0.437, 0.431], -[3.844, 0.542, 0.544], -[2.392, 0.533, 0.540], -[1.258, 0.541, 0.530], -[4.264, 1.392, 1.386], -[3.673, 0.799, 0.787], -[8.001, 2.947, 2.931], -[0.935, 0.060, 0.028], -[18.966, 0.610, 0.583], -[20.808, 0.629, 0.617], -[38.800, 1.481, 1.506], -[29.883, 0.663, 0.637], -[4.797, 0.190, 0.188], -[2.316, 0.167, 0.162], -[5.250, 0.199, 0.195], -[18.608, 0.545, 0.518], -[15.452, 1.180, 1.163], -[2.484, 2.458, 2.456], -[3.906, 0.493, 0.500], -[9.845, 0.714, 0.712], -[9.286, 4.143, 4.528], -[18.894, 2.139, 2.143], -[18.917, 2.145, 2.108], -[0.943, 0.872, 0.896], -[0.104, 0.081, 0.079], -[0.064, 0.045, 0.041], -[0.059, 0.036, 0.034], -[0.244, 0.183, 0.183], -[0.040, 0.012, 0.012], -[0.026, 0.011, 0.010], -[0.013, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/18.14.19.json b/website/benchmark/versions/results/18.14.19.json deleted file mode 100644 index b988dd2387c..00000000000 --- a/website/benchmark/versions/results/18.14.19.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2018-12-19 18.14", - "system_full": "ClickHouse 18.14.19 2018-12-19", - "version": "18.14.19", - "kind": "", - "comments": "", - "result": - [ - -[0.133, 0.016, 0.015], -[0.157, 0.006, 0.006], -[0.816, 0.018, 0.017], -[0.164, 0.012, 0.012], -[0.263, 0.036, 0.040], -[1.029, 0.027, 0.025], -[0.076, 0.004, 0.004], -[0.337, 0.018, 0.018], -[0.503, 0.034, 0.032], -[0.557, 0.088, 0.091], -[1.912, 0.517, 0.510], -[1.523, 0.506, 0.485], -[0.038, 0.004, 0.004], -[0.035, 0.015, 0.015], -[0.141, 0.109, 0.141], -[3.336, 0.088, 0.086], -[2.626, 0.147, 0.144], -[3.906, 0.068, 0.065], -[1.436, 0.067, 0.065], -[7.020, 0.316, 0.300], -[4.302, 0.169, 0.166], -[1.817, 0.197, 0.192], -[63.459, 2.171, 2.150], -[42.546, 1.915, 1.894], -[4.504, 0.219, 0.214], -[1.864, 0.155, 0.146], -[0.428, 0.074, 0.069], -[6.621, 0.111, 0.106], -[0.043, 0.010, 0.009], -[0.044, 0.008, 0.007], -[0.480, 0.059, 0.060], -[1.686, 0.041, 0.040], -[1.725, 0.090, 0.091], -[2.558, 0.195, 0.191], -[0.063, 0.046, 0.043], -[0.033, 0.008, 0.008], -[2.275, 0.434, 0.422], -[2.260, 0.507, 0.511], -[1.074, 0.123, 0.122], -[1.718, 0.151, 0.150], -[2.602, 0.429, 0.418], -[3.819, 0.552, 0.535], -[2.433, 0.508, 0.515], -[1.265, 0.543, 0.536], -[4.278, 1.386, 1.376], -[3.658, 0.865, 0.774], -[7.959, 2.910, 2.931], -[0.948, 0.033, 0.060], -[18.942, 0.572, 0.552], -[20.834, 0.579, 0.570], -[38.782, 1.378, 1.348], -[29.262, 0.607, 0.636], -[4.575, 0.185, 0.178], -[2.330, 0.158, 0.153], -[5.290, 0.193, 0.187], -[18.670, 0.513, 0.505], -[15.443, 1.150, 1.134], -[2.452, 2.435, 2.429], -[3.926, 0.474, 0.477], -[9.856, 0.684, 0.678], -[9.269, 4.033, 4.061], -[18.931, 2.123, 2.073], -[18.914, 2.101, 2.123], -[0.910, 0.860, 0.860], -[0.104, 0.076, 0.078], -[0.060, 0.040, 0.038], -[0.057, 0.033, 0.035], -[0.232, 0.173, 0.162], -[0.035, 0.025, 0.012], -[0.024, 0.013, 0.010], -[0.012, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/18.16.1.json b/website/benchmark/versions/results/18.16.1.json deleted file mode 100644 index 1e570b5b946..00000000000 --- a/website/benchmark/versions/results/18.16.1.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2018-12-21 18.16", - "system_full": "ClickHouse 18.16.1 2018-12-21", - "version": "18.16.1", - "kind": "", - "comments": "", - "result": - [ - -[0.133, 0.194, 0.016], -[0.215, 0.082, 0.007], -[0.818, 0.050, 0.021], -[0.156, 0.066, 0.016], -[0.269, 0.038, 0.045], -[1.159, 0.032, 0.029], -[0.086, 0.008, 0.008], -[0.335, 0.065, 0.024], -[0.488, 0.040, 0.040], -[0.550, 0.097, 0.097], -[1.906, 0.593, 0.584], -[1.503, 0.533, 0.522], -[0.038, 0.005, 0.005], -[0.036, 0.023, 0.017], -[0.185, 0.198, 0.162], -[3.335, 0.096, 0.057], -[2.631, 0.172, 0.142], -[3.884, 0.084, 0.057], -[1.463, 0.072, 0.072], -[7.189, 0.312, 0.306], -[4.632, 0.174, 0.171], -[1.865, 0.245, 0.194], -[63.399, 2.148, 2.072], -[42.517, 1.863, 1.819], -[4.507, 0.249, 0.221], -[2.078, 0.138, 0.141], -[0.455, 0.102, 0.070], -[11.873, 0.138, 0.124], -[0.046, 0.012, 0.012], -[0.032, 0.009, 0.009], -[0.482, 0.060, 0.060], -[1.682, 0.045, 0.045], -[1.719, 0.097, 0.096], -[2.567, 0.203, 0.202], -[0.063, 0.045, 0.044], -[0.027, 0.010, 0.010], -[2.283, 0.434, 0.437], -[2.256, 0.520, 0.521], -[1.078, 0.136, 0.135], -[1.693, 0.162, 0.161], -[2.589, 0.464, 0.460], -[3.809, 0.623, 0.589], -[2.391, 0.562, 0.579], -[1.265, 0.575, 0.579], -[4.293, 1.441, 1.485], -[3.656, 0.792, 0.796], -[7.960, 3.260, 3.240], -[0.923, 0.030, 0.029], -[18.973, 0.584, 0.581], -[20.841, 0.600, 0.593], -[38.786, 1.403, 1.398], -[39.036, 0.702, 0.684], -[4.554, 0.194, 0.195], -[2.325, 0.169, 0.165], -[5.239, 0.200, 0.197], -[18.609, 0.522, 0.517], -[15.427, 1.150, 1.153], -[2.475, 2.443, 2.442], -[3.870, 0.524, 0.523], -[9.837, 0.757, 0.748], -[9.334, 4.308, 4.309], -[18.947, 2.232, 2.243], -[18.972, 2.260, 2.283], -[0.991, 0.930, 0.932], -[0.116, 0.116, 0.093], -[0.061, 0.075, 0.050], -[0.056, 0.062, 0.036], -[0.272, 0.303, 0.222], -[0.041, 0.043, 0.014], -[0.025, 0.041, 0.012], -[0.014, 0.030, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/19.01.16.79.json b/website/benchmark/versions/results/19.01.16.79.json deleted file mode 100644 index d20ac409a7a..00000000000 --- a/website/benchmark/versions/results/19.01.16.79.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-04-02 19.1", - "system_full": "ClickHouse 19.1.16.79 2019-04-02", - "version": "19.1.16.79", - "kind": "", - "comments": "", - "result": - [ - -[0.139, 0.015, 0.012], -[0.041, 0.005, 0.005], -[0.817, 0.017, 0.016], -[0.160, 0.011, 0.010], -[0.264, 0.060, 0.034], -[0.987, 0.025, 0.023], -[0.075, 0.004, 0.004], -[0.335, 0.017, 0.017], -[0.507, 0.031, 0.031], -[0.555, 0.087, 0.087], -[1.861, 0.494, 0.490], -[1.510, 0.477, 0.488], -[0.033, 0.003, 0.003], -[0.031, 0.015, 0.015], -[0.156, 0.100, 0.129], -[3.293, 0.092, 0.088], -[2.613, 0.146, 0.141], -[3.902, 0.067, 0.065], -[1.432, 0.063, 0.062], -[7.049, 0.304, 0.304], -[4.342, 0.160, 0.159], -[1.827, 0.198, 0.197], -[63.412, 2.385, 2.319], -[42.497, 1.936, 1.924], -[4.499, 0.213, 0.209], -[1.975, 0.120, 0.112], -[0.460, 0.060, 0.057], -[6.642, 0.101, 0.095], -[0.045, 0.010, 0.010], -[0.042, 0.007, 0.007], -[0.482, 0.055, 0.058], -[1.667, 0.043, 0.042], -[1.720, 0.093, 0.094], -[2.556, 0.192, 0.188], -[0.059, 0.039, 0.039], -[0.026, 0.007, 0.007], -[2.299, 0.431, 0.425], -[2.258, 0.505, 0.502], -[1.050, 0.122, 0.121], -[1.715, 0.149, 0.153], -[2.605, 0.441, 0.423], -[3.786, 0.563, 0.520], -[2.393, 0.532, 0.532], -[1.268, 0.556, 0.533], -[4.330, 1.530, 1.482], -[3.647, 0.792, 0.833], -[8.224, 3.651, 3.551], -[0.941, 0.028, 0.027], -[18.967, 0.511, 0.507], -[20.837, 0.583, 0.583], -[38.807, 1.472, 1.356], -[29.895, 0.646, 0.669], -[4.550, 0.175, 0.173], -[2.371, 0.147, 0.145], -[5.296, 0.179, 0.177], -[18.656, 0.513, 0.501], -[15.434, 1.153, 1.145], -[2.549, 2.527, 2.507], -[3.922, 0.537, 0.554], -[9.872, 0.770, 0.784], -[9.630, 4.556, 4.342], -[18.955, 2.259, 2.280], -[18.975, 2.125, 2.130], -[0.930, 0.908, 0.909], -[0.099, 0.074, 0.073], -[0.053, 0.037, 0.036], -[0.055, 0.030, 0.029], -[0.201, 0.156, 0.158], -[0.034, 0.011, 0.011], -[0.023, 0.009, 0.009], -[0.011, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/19.03.9.12_adaptive.json b/website/benchmark/versions/results/19.03.9.12_adaptive.json deleted file mode 100644 index a61f28e7caa..00000000000 --- a/website/benchmark/versions/results/19.03.9.12_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-04-02 19.3", - "system_full": "ClickHouse 19.3.9.12 2019-04-02(adaptive)", - "version": "19.3.9.12", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.045, 0.003, 0.003], -[0.022, 0.002, 0.002], -[0.018, 0.003, 0.004], -[0.016, 0.002, 0.002], -[0.018, 0.002, 0.002], -[0.017, 0.003, 0.003], -[0.018, 0.002, 0.002], -[0.073, 0.029, 0.029], -[0.023, 0.011, 0.011], -[0.010, 0.002, 0.002], -[1.082, 0.342, 0.362], -[0.613, 0.344, 0.320], -[0.035, 0.003, 0.003], -[0.031, 0.014, 0.019], -[0.132, 0.102, 0.106], -[3.420, 0.095, 0.090], -[3.012, 0.748, 0.750], -[2.259, 0.060, 0.057], -[1.270, 0.047, 0.048], -[4.462, 0.226, 0.204], -[4.212, 0.184, 0.177], -[1.818, 0.219, 0.206], -[63.399, 2.050, 2.016], -[42.454, 1.432, 1.429], -[3.563, 0.822, 0.814], -[0.785, 0.133, 0.141], -[0.118, 0.079, 0.082], -[3.214, 0.089, 0.077], -[0.010, 0.001, 0.001], -[0.029, 0.008, 0.009], -[0.382, 0.025, 0.026], -[1.707, 0.031, 0.031], -[1.691, 0.079, 0.091], -[2.601, 0.200, 0.212], -[0.025, 0.012, 0.011], -[0.018, 0.009, 0.009], -[2.124, 0.417, 0.421], -[1.997, 0.462, 0.461], -[1.043, 0.130, 0.129], -[1.665, 0.145, 0.147], -[2.617, 0.485, 0.478], -[3.811, 0.606, 0.569], -[2.376, 0.559, 0.562], -[1.199, 0.566, 0.570], -[4.246, 1.434, 1.415], -[3.672, 0.828, 0.813], -[7.948, 3.037, 3.016], -[0.911, 0.048, 0.039], -[19.548, 0.495, 0.449], -[21.486, 0.568, 0.557], -[40.178, 1.318, 1.298], -[39.525, 0.609, 0.554], -[4.645, 0.196, 0.204], -[2.291, 0.171, 0.175], -[5.290, 0.234, 0.213], -[19.139, 0.450, 0.450], -[15.793, 0.791, 0.776], -[1.155, 1.141, 1.128], -[3.837, 0.491, 0.484], -[9.820, 0.730, 0.704], -[9.079, 4.077, 4.048], -[19.457, 2.042, 2.017], -[19.483, 2.137, 2.010], -[0.899, 0.829, 0.833], -[0.178, 0.135, 0.123], -[0.073, 0.051, 0.048], -[0.072, 0.037, 0.042], -[0.314, 0.274, 0.253], -[0.059, 0.017, 0.028], -[0.041, 0.012, 0.022], -[0.013, 0.002, 0.002] - ] - } -] diff --git a/website/benchmark/versions/results/19.04.5.35.json b/website/benchmark/versions/results/19.04.5.35.json deleted file mode 100644 index 884754b3370..00000000000 --- a/website/benchmark/versions/results/19.04.5.35.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-05-13 19.4", - "system_full": "ClickHouse 19.4.5.35 2019-05-13", - "version": "19.4.5.35", - "kind": "", - "comments": "", - "result": - [ - -[0.072, 0.009, 0.009], -[0.075, 0.032, 0.032], -[0.066, 0.008, 0.008], -[0.055, 0.012, 0.012], -[0.072, 0.036, 0.036], -[0.124, 0.019, 0.018], -[0.101, 0.005, 0.004], -[0.335, 0.033, 0.032], -[0.503, 0.047, 0.047], -[0.528, 0.094, 0.093], -[1.898, 0.567, 0.540], -[1.502, 0.515, 0.508], -[0.035, 0.004, 0.004], -[0.032, 0.013, 0.015], -[0.134, 0.110, 0.104], -[3.333, 0.095, 0.095], -[2.608, 0.159, 0.152], -[2.203, 0.063, 0.062], -[1.316, 0.057, 0.058], -[5.163, 0.198, 0.186], -[4.884, 0.160, 0.153], -[1.903, 0.188, 0.184], -[63.457, 2.105, 2.057], -[42.487, 1.567, 1.545], -[4.497, 0.232, 0.226], -[1.435, 0.145, 0.166], -[0.262, 0.053, 0.059], -[2.915, 0.098, 0.081], -[0.102, 0.011, 0.011], -[0.045, 0.008, 0.007], -[0.481, 0.066, 0.066], -[1.682, 0.046, 0.046], -[1.710, 0.099, 0.101], -[2.556, 0.213, 0.206], -[0.075, 0.050, 0.046], -[0.031, 0.008, 0.008], -[2.245, 0.429, 0.420], -[2.246, 0.490, 0.483], -[1.073, 0.117, 0.119], -[1.735, 0.152, 0.157], -[2.644, 0.415, 0.401], -[3.812, 0.553, 0.556], -[2.404, 0.521, 0.515], -[1.262, 0.556, 0.561], -[4.287, 1.389, 1.380], -[3.628, 0.785, 0.777], -[8.027, 2.905, 2.912], -[0.945, 0.081, 0.028], -[18.915, 0.529, 0.503], -[20.803, 0.595, 0.580], -[38.783, 1.390, 1.363], -[38.820, 0.679, 0.639], -[4.565, 0.180, 0.183], -[2.309, 0.161, 0.150], -[5.264, 0.191, 0.183], -[18.646, 0.540, 0.521], -[15.425, 1.115, 1.092], -[2.306, 2.275, 2.265], -[3.941, 0.478, 0.488], -[9.883, 0.714, 0.704], -[9.498, 4.322, 4.348], -[18.919, 2.039, 2.010], -[18.883, 2.028, 1.980], -[0.916, 0.851, 0.867], -[0.114, 0.083, 0.077], -[0.058, 0.039, 0.041], -[0.053, 0.035, 0.028], -[0.248, 0.186, 0.182], -[0.041, 0.016, 0.014], -[0.025, 0.011, 0.010], -[0.013, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/19.05.4.22.json b/website/benchmark/versions/results/19.05.4.22.json deleted file mode 100644 index dc1505ce9d4..00000000000 --- a/website/benchmark/versions/results/19.05.4.22.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-05-13 19.5", - "system_full": "ClickHouse 19.5.4.22 2019-05-13", - "kind": "", - "version": "19.5.4.22", - "comments": "", - "result": - [ - -[0.067, 0.010, 0.009], -[0.076, 0.032, 0.032], -[0.045, 0.008, 0.008], -[0.049, 0.011, 0.011], -[0.071, 0.035, 0.035], -[0.232, 0.020, 0.021], -[0.101, 0.005, 0.005], -[0.334, 0.035, 0.033], -[0.499, 0.047, 0.048], -[0.532, 0.096, 0.091], -[1.878, 0.562, 0.555], -[1.484, 0.513, 0.504], -[0.034, 0.003, 0.003], -[0.031, 0.015, 0.015], -[0.130, 0.114, 0.108], -[3.323, 0.095, 0.095], -[2.616, 0.160, 0.147], -[2.218, 0.063, 0.061], -[1.333, 0.059, 0.057], -[5.176, 0.211, 0.196], -[4.883, 0.167, 0.172], -[1.905, 0.194, 0.185], -[63.470, 2.105, 2.067], -[42.523, 1.606, 1.560], -[4.501, 0.231, 0.231], -[1.412, 0.159, 0.162], -[0.247, 0.060, 0.057], -[2.935, 0.075, 0.084], -[0.073, 0.010, 0.010], -[0.046, 0.008, 0.007], -[0.481, 0.063, 0.061], -[1.669, 0.042, 0.043], -[1.693, 0.097, 0.096], -[2.565, 0.211, 0.207], -[0.062, 0.044, 0.044], -[0.029, 0.008, 0.008], -[2.243, 0.438, 0.429], -[2.231, 0.493, 0.485], -[1.071, 0.119, 0.121], -[1.717, 0.143, 0.145], -[2.616, 0.422, 0.416], -[3.792, 0.523, 0.542], -[2.383, 0.535, 0.503], -[1.249, 0.568, 0.560], -[4.310, 1.423, 1.386], -[3.654, 0.776, 0.764], -[8.060, 2.973, 2.907], -[0.962, 0.058, 0.029], -[18.952, 0.492, 0.487], -[20.838, 0.593, 0.563], -[38.796, 1.366, 1.345], -[38.903, 0.672, 0.637], -[4.568, 0.181, 0.177], -[2.335, 0.148, 0.151], -[5.300, 0.194, 0.181], -[18.630, 0.520, 0.498], -[15.435, 1.086, 1.064], -[2.320, 2.304, 2.300], -[3.932, 0.477, 0.485], -[9.834, 0.713, 0.687], -[9.485, 4.400, 4.381], -[18.930, 2.046, 1.980], -[18.933, 2.012, 1.974], -[0.949, 0.882, 0.875], -[0.112, 0.097, 0.082], -[0.054, 0.043, 0.046], -[0.052, 0.029, 0.036], -[0.243, 0.207, 0.188], -[0.044, 0.014, 0.019], -[0.026, 0.010, 0.010], -[0.015, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/19.06.3.18_adaptive.json b/website/benchmark/versions/results/19.06.3.18_adaptive.json deleted file mode 100644 index 6a3814b0cdb..00000000000 --- a/website/benchmark/versions/results/19.06.3.18_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-06-15 19.6", - "system_full": "ClickHouse 19.6.3.18 2019-06-15(adaptive)", - "version": "19.6.3.18", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.033, 0.003, 0.003], -[0.015, 0.002, 0.002], -[0.021, 0.003, 0.003], -[0.015, 0.002, 0.002], -[0.016, 0.002, 0.002], -[0.019, 0.003, 0.003], -[0.018, 0.001, 0.001], -[0.063, 0.024, 0.024], -[0.022, 0.008, 0.008], -[0.015, 0.002, 0.002], -[1.237, 0.349, 0.337], -[0.600, 0.297, 0.316], -[0.040, 0.003, 0.003], -[0.034, 0.014, 0.013], -[0.135, 0.109, 0.099], -[3.433, 0.097, 0.093], -[3.003, 0.670, 0.666], -[2.257, 0.063, 0.064], -[1.274, 0.051, 0.052], -[4.465, 0.202, 0.199], -[4.254, 0.161, 0.164], -[1.818, 0.177, 0.188], -[63.476, 2.082, 2.042], -[42.480, 1.577, 1.542], -[3.488, 0.723, 0.708], -[0.786, 0.147, 0.156], -[0.115, 0.066, 0.063], -[3.706, 0.087, 0.089], -[0.060, 0.012, 0.011], -[0.055, 0.009, 0.008], -[0.433, 0.067, 0.072], -[1.620, 0.049, 0.050], -[1.647, 0.109, 0.101], -[2.557, 0.207, 0.194], -[0.078, 0.050, 0.047], -[0.037, 0.009, 0.009], -[1.997, 0.420, 0.411], -[2.002, 0.465, 0.469], -[1.027, 0.117, 0.116], -[1.681, 0.141, 0.139], -[2.648, 0.398, 0.391], -[3.791, 0.510, 0.499], -[2.353, 0.529, 0.529], -[1.226, 0.547, 0.542], -[4.267, 1.333, 1.305], -[3.680, 0.733, 0.708], -[8.051, 2.804, 2.865], -[0.917, 0.042, 0.031], -[19.546, 0.463, 0.438], -[21.449, 0.555, 0.548], -[40.133, 1.226, 1.204], -[39.599, 0.606, 0.569], -[4.663, 0.176, 0.174], -[2.341, 0.161, 0.147], -[5.326, 0.188, 0.176], -[19.228, 0.468, 0.449], -[15.821, 1.027, 1.003], -[2.306, 2.271, 2.296], -[3.861, 0.479, 0.446], -[9.817, 0.688, 0.668], -[9.318, 4.049, 4.092], -[19.435, 1.893, 1.868], -[19.280, 1.873, 1.860], -[0.921, 0.865, 0.852], -[0.095, 0.059, 0.064], -[0.055, 0.028, 0.035], -[0.048, 0.021, 0.020], -[0.191, 0.135, 0.126], -[0.039, 0.012, 0.010], -[0.028, 0.008, 0.008], -[0.016, 0.002, 0.002] - ] - } -] diff --git a/website/benchmark/versions/results/19.07.5.29_adaptive.json b/website/benchmark/versions/results/19.07.5.29_adaptive.json deleted file mode 100644 index a8b2a9d2cbb..00000000000 --- a/website/benchmark/versions/results/19.07.5.29_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-07-05 19.7", - "system_full": "ClickHouse 19.7.5.29 2019-07-05(adaptive)", - "version": "19.7.5.29", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.039, 0.003, 0.003], -[0.015, 0.002, 0.001], -[0.020, 0.003, 0.003], -[0.016, 0.002, 0.002], -[0.016, 0.002, 0.002], -[0.019, 0.003, 0.003], -[0.016, 0.001, 0.001], -[0.067, 0.025, 0.024], -[0.020, 0.008, 0.008], -[0.011, 0.002, 0.002], -[1.090, 0.342, 0.338], -[0.591, 0.306, 0.312], -[0.036, 0.003, 0.003], -[0.032, 0.014, 0.014], -[0.137, 0.107, 0.101], -[3.428, 0.100, 0.093], -[2.994, 0.681, 0.678], -[2.268, 0.066, 0.063], -[1.258, 0.052, 0.047], -[4.453, 0.211, 0.188], -[4.249, 0.163, 0.160], -[1.831, 0.176, 0.173], -[63.471, 2.099, 2.050], -[42.482, 1.609, 1.601], -[3.497, 0.735, 0.714], -[0.799, 0.147, 0.176], -[0.116, 0.056, 0.058], -[3.715, 0.088, 0.086], -[0.064, 0.011, 0.010], -[0.047, 0.008, 0.008], -[0.432, 0.065, 0.066], -[1.635, 0.049, 0.048], -[1.658, 0.105, 0.105], -[2.558, 0.201, 0.198], -[0.066, 0.040, 0.040], -[0.034, 0.008, 0.008], -[2.066, 0.432, 0.433], -[2.033, 0.491, 0.479], -[1.050, 0.122, 0.122], -[1.696, 0.147, 0.146], -[2.613, 0.427, 0.389], -[3.795, 0.508, 0.500], -[2.339, 0.509, 0.526], -[1.202, 0.541, 0.548], -[4.288, 1.364, 1.337], -[3.671, 0.734, 0.723], -[8.031, 2.884, 2.832], -[0.896, 0.038, 0.031], -[19.524, 0.461, 0.441], -[21.437, 0.578, 0.549], -[40.135, 1.244, 1.213], -[39.587, 0.612, 0.601], -[4.660, 0.181, 0.178], -[2.325, 0.161, 0.149], -[5.324, 0.203, 0.178], -[19.194, 0.483, 0.460], -[15.815, 1.027, 1.021], -[2.272, 2.248, 2.269], -[3.857, 0.466, 0.455], -[9.839, 0.694, 0.669], -[9.343, 4.105, 4.058], -[19.485, 1.906, 1.864], -[19.375, 1.905, 1.862], -[0.924, 0.848, 0.844], -[0.091, 0.066, 0.058], -[0.052, 0.029, 0.029], -[0.046, 0.024, 0.020], -[0.185, 0.132, 0.140], -[0.037, 0.010, 0.011], -[0.027, 0.008, 0.009], -[0.015, 0.002, 0.002] - ] - } -] diff --git a/website/benchmark/versions/results/19.08.3.8_adaptive.json b/website/benchmark/versions/results/19.08.3.8_adaptive.json deleted file mode 100644 index 87afb885c3d..00000000000 --- a/website/benchmark/versions/results/19.08.3.8_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-06-11 19.8", - "system_full": "ClickHouse 19.8.3.8 2019-06-11(adaptive)", - "version": "19.8.3.8", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.036, 0.003, 0.003], -[0.014, 0.002, 0.001], -[0.022, 0.003, 0.003], -[0.015, 0.002, 0.002], -[0.016, 0.002, 0.002], -[0.019, 0.003, 0.003], -[0.017, 0.001, 0.001], -[0.063, 0.026, 0.027], -[0.019, 0.008, 0.008], -[0.009, 0.002, 0.001], -[1.119, 0.338, 0.340], -[0.593, 0.300, 0.300], -[0.040, 0.003, 0.003], -[0.035, 0.014, 0.013], -[0.162, 0.107, 0.107], -[3.426, 0.100, 0.091], -[3.003, 0.671, 0.667], -[2.275, 0.062, 0.063], -[1.285, 0.054, 0.055], -[4.459, 0.210, 0.192], -[4.257, 0.179, 0.163], -[1.813, 0.178, 0.183], -[63.480, 2.122, 2.062], -[42.481, 1.598, 1.581], -[3.489, 0.717, 0.709], -[0.796, 0.124, 0.195], -[0.114, 0.054, 0.052], -[3.719, 0.088, 0.085], -[0.063, 0.011, 0.011], -[0.046, 0.009, 0.008], -[0.411, 0.060, 0.058], -[1.630, 0.044, 0.043], -[1.666, 0.098, 0.097], -[2.572, 0.200, 0.194], -[0.072, 0.041, 0.043], -[0.038, 0.009, 0.008], -[2.024, 0.439, 0.425], -[2.018, 0.486, 0.484], -[1.032, 0.122, 0.120], -[1.691, 0.153, 0.151], -[2.577, 0.398, 0.386], -[3.811, 0.511, 0.494], -[2.366, 0.527, 0.515], -[1.183, 0.535, 0.537], -[4.240, 1.323, 1.311], -[3.667, 0.733, 0.717], -[7.945, 2.772, 2.820], -[0.863, 0.046, 0.028], -[19.550, 0.424, 0.417], -[21.446, 0.539, 0.520], -[40.092, 1.202, 1.167], -[39.601, 0.617, 0.556], -[4.662, 0.175, 0.174], -[2.349, 0.156, 0.145], -[5.350, 0.181, 0.179], -[19.239, 0.455, 0.438], -[15.829, 1.015, 0.996], -[2.297, 2.269, 2.278], -[3.865, 0.468, 0.440], -[9.817, 0.674, 0.645], -[9.106, 3.916, 4.283], -[19.440, 1.872, 1.838], -[19.427, 1.858, 1.818], -[0.894, 0.835, 0.847], -[0.088, 0.059, 0.060], -[0.048, 0.027, 0.030], -[0.046, 0.022, 0.024], -[0.180, 0.137, 0.124], -[0.037, 0.011, 0.010], -[0.025, 0.008, 0.009], -[0.015, 0.002, 0.002] - ] - } -] diff --git a/website/benchmark/versions/results/19.09.5.36_adaptive.json b/website/benchmark/versions/results/19.09.5.36_adaptive.json deleted file mode 100644 index 6a4de63a0dc..00000000000 --- a/website/benchmark/versions/results/19.09.5.36_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-07-20 19.9", - "system_full": "ClickHouse 19.9.5.36 2019-07-20(adaptive)", - "version": "19.9.5.36", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.034, 0.003, 0.003], -[0.014, 0.002, 0.002], -[0.017, 0.003, 0.003], -[0.014, 0.002, 0.002], -[0.015, 0.002, 0.002], -[0.017, 0.003, 0.003], -[0.016, 0.001, 0.001], -[0.120, 0.024, 0.027], -[0.021, 0.008, 0.008], -[0.016, 0.001, 0.001], -[1.225, 0.331, 0.331], -[0.620, 0.301, 0.296], -[0.038, 0.003, 0.003], -[0.034, 0.013, 0.013], -[0.125, 0.108, 0.100], -[3.432, 0.095, 0.090], -[2.993, 0.667, 0.673], -[2.271, 0.060, 0.059], -[1.282, 0.047, 0.051], -[4.467, 0.201, 0.185], -[4.258, 0.167, 0.157], -[1.845, 0.180, 0.180], -[63.456, 2.040, 2.008], -[42.482, 1.578, 1.566], -[3.485, 0.714, 0.717], -[0.789, 0.176, 0.176], -[0.120, 0.063, 0.061], -[3.680, 0.092, 0.084], -[0.060, 0.010, 0.010], -[0.053, 0.008, 0.007], -[0.429, 0.060, 0.054], -[1.646, 0.042, 0.045], -[1.659, 0.105, 0.106], -[2.557, 0.207, 0.195], -[0.072, 0.041, 0.043], -[0.041, 0.008, 0.008], -[2.012, 0.439, 0.424], -[2.021, 0.486, 0.480], -[1.054, 0.126, 0.124], -[1.666, 0.158, 0.167], -[2.565, 0.437, 0.392], -[3.795, 0.497, 0.529], -[2.355, 0.524, 0.517], -[1.191, 0.540, 0.538], -[4.245, 1.306, 1.298], -[3.654, 0.728, 0.717], -[7.948, 2.782, 2.789], -[0.893, 0.061, 0.027], -[19.518, 0.425, 0.408], -[21.473, 0.524, 0.513], -[40.168, 1.177, 1.137], -[39.602, 0.574, 0.588], -[4.663, 0.169, 0.176], -[2.346, 0.155, 0.144], -[5.320, 0.193, 0.176], -[19.215, 0.437, 0.418], -[15.836, 0.996, 0.982], -[2.297, 2.258, 2.274], -[3.824, 0.448, 0.454], -[9.803, 0.657, 0.632], -[9.051, 3.826, 3.919], -[19.430, 1.820, 1.798], -[19.387, 1.847, 1.820], -[0.885, 0.815, 0.823], -[0.089, 0.065, 0.057], -[0.056, 0.028, 0.033], -[0.041, 0.022, 0.022], -[0.199, 0.128, 0.120], -[0.036, 0.010, 0.010], -[0.025, 0.008, 0.009], -[0.017, 0.002, 0.002] - ] - } -] diff --git a/website/benchmark/versions/results/19.10.1.5_adaptive.json b/website/benchmark/versions/results/19.10.1.5_adaptive.json deleted file mode 100644 index b66d70caab6..00000000000 --- a/website/benchmark/versions/results/19.10.1.5_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-07-12 19.10", - "system_full": "ClickHouse 19.10.1.5 2019-07-12(adaptive)", - "version": "19.10.1.5", - "type": "adaptive", - "comments": "", - "result": - [ - -[0.045, 0.003, 0.003], -[0.017, 0.001, 0.001], -[0.018, 0.003, 0.003], -[0.023, 0.002, 0.001], -[0.016, 0.002, 0.002], -[0.021, 0.003, 0.003], -[0.017, 0.001, 0.001], -[0.062, 0.024, 0.023], -[0.021, 0.008, 0.008], -[0.011, 0.001, 0.001], -[1.159, 0.338, 0.334], -[0.620, 0.323, 0.307], -[0.021, 0.002, 0.001], -[0.009, 0.001, 0.001], -[0.017, 0.004, 0.004], -[3.646, 0.094, 0.089], -[2.997, 0.679, 0.667], -[2.240, 0.060, 0.059], -[1.177, 0.060, 0.056], -[4.103, 0.224, 0.194], -[3.934, 0.167, 0.171], -[1.754, 0.188, 0.177], -[63.474, 2.051, 2.021], -[42.421, 1.550, 1.518], -[3.482, 0.725, 0.731], -[0.683, 0.161, 0.136], -[0.087, 0.058, 0.055], -[3.057, 0.061, 0.055], -[0.095, 0.010, 0.010], -[0.062, 0.008, 0.007], -[0.425, 0.062, 0.065], -[1.645, 0.047, 0.045], -[1.655, 0.117, 0.113], -[2.541, 0.220, 0.198], -[0.058, 0.044, 0.044], -[0.019, 0.008, 0.008], -[2.034, 0.434, 0.420], -[2.035, 0.485, 0.484], -[1.036, 0.116, 0.118], -[1.672, 0.150, 0.148], -[2.584, 0.397, 0.385], -[3.829, 0.512, 0.488], -[2.373, 0.518, 0.504], -[1.191, 0.546, 0.539], -[4.394, 1.359, 1.319], -[3.649, 0.754, 0.735], -[7.906, 2.846, 2.808], -[0.906, 0.044, 0.027], -[19.556, 0.441, 0.410], -[21.468, 0.529, 0.521], -[40.145, 1.194, 1.165], -[39.573, 0.559, 0.529], -[4.652, 0.180, 0.171], -[2.321, 0.154, 0.147], -[5.312, 0.188, 0.178], -[19.213, 0.444, 0.424], -[15.806, 1.006, 0.981], -[2.265, 2.257, 2.274], -[3.826, 0.470, 0.449], -[9.797, 0.666, 0.639], -[9.059, 3.902, 3.855], -[19.428, 1.865, 1.804], -[19.377, 1.843, 1.815], -[0.885, 0.828, 0.828], -[0.090, 0.061, 0.061], -[0.052, 0.029, 0.027], -[0.045, 0.021, 0.022], -[0.195, 0.129, 0.137], -[0.035, 0.011, 0.011], -[0.027, 0.009, 0.008], -[0.013, 0.002, 0.002] - ] - } -] diff --git a/website/benchmark/versions/results/19.11.14.1_adaptive.json b/website/benchmark/versions/results/19.11.14.1_adaptive.json deleted file mode 100644 index 11e9a4abffb..00000000000 --- a/website/benchmark/versions/results/19.11.14.1_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-12-04 19.11", - "system_full": "ClickHouse 19.11.14.1 2019-12-04(adaptive)", - "version": "19.11.14.1", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.037, 0.003, 0.003], -[0.018, 0.001, 0.001], -[0.019, 0.003, 0.003], -[0.017, 0.002, 0.001], -[0.015, 0.002, 0.002], -[0.020, 0.003, 0.003], -[0.018, 0.001, 0.001], -[0.066, 0.025, 0.028], -[0.020, 0.008, 0.008], -[0.011, 0.001, 0.001], -[1.138, 0.350, 0.340], -[0.580, 0.309, 0.305], -[0.034, 0.003, 0.004], -[0.031, 0.014, 0.013], -[0.137, 0.103, 0.107], -[3.421, 0.099, 0.090], -[3.002, 0.653, 0.647], -[2.253, 0.064, 0.060], -[1.258, 0.056, 0.052], -[4.436, 0.230, 0.208], -[4.214, 0.163, 0.176], -[1.854, 0.208, 0.208], -[63.371, 2.164, 2.111], -[42.444, 1.585, 1.551], -[3.448, 0.708, 0.699], -[0.793, 0.154, 0.169], -[0.104, 0.081, 0.075], -[3.178, 0.080, 0.091], -[0.039, 0.010, 0.010], -[0.060, 0.007, 0.007], -[0.427, 0.073, 0.071], -[1.616, 0.046, 0.047], -[1.654, 0.098, 0.096], -[2.561, 0.211, 0.211], -[0.064, 0.048, 0.048], -[0.019, 0.008, 0.007], -[1.989, 0.396, 0.394], -[1.991, 0.456, 0.453], -[1.020, 0.132, 0.129], -[1.640, 0.168, 0.161], -[2.523, 0.430, 0.406], -[3.826, 0.511, 0.505], -[2.356, 0.507, 0.509], -[1.177, 0.538, 0.534], -[4.299, 1.294, 1.286], -[3.662, 0.732, 0.704], -[7.885, 2.777, 2.784], -[0.911, 0.053, 0.027], -[19.526, 0.431, 0.419], -[21.478, 0.557, 0.536], -[40.140, 1.248, 1.209], -[39.497, 0.589, 0.542], -[4.644, 0.195, 0.187], -[2.302, 0.167, 0.164], -[5.303, 0.203, 0.196], -[19.169, 0.439, 0.425], -[15.754, 0.803, 0.777], -[2.251, 2.236, 2.251], -[3.831, 0.475, 0.454], -[9.802, 0.666, 0.656], -[9.119, 3.820, 3.843], -[19.443, 1.864, 1.825], -[19.451, 1.848, 1.819], -[0.854, 0.808, 0.799], -[0.153, 0.119, 0.111], -[0.071, 0.048, 0.045], -[0.065, 0.037, 0.035], -[0.319, 0.253, 0.247], -[0.051, 0.019, 0.018], -[0.033, 0.013, 0.014], -[0.013, 0.002, 0.002] - ] - } -] diff --git a/website/benchmark/versions/results/19.13.7.57_adaptive.json b/website/benchmark/versions/results/19.13.7.57_adaptive.json deleted file mode 100644 index bacbe36fbce..00000000000 --- a/website/benchmark/versions/results/19.13.7.57_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-11-06 19.13", - "system_full": "ClickHouse 19.13.7.57 2019-11-06(adaptive)", - "version": "19.13.7.57", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.032, 0.003, 0.003], -[0.014, 0.002, 0.002], -[0.019, 0.003, 0.003], -[0.017, 0.002, 0.002], -[0.017, 0.002, 0.002], -[0.018, 0.003, 0.003], -[0.018, 0.001, 0.001], -[0.065, 0.025, 0.028], -[0.022, 0.009, 0.009], -[0.013, 0.002, 0.002], -[1.218, 0.344, 0.339], -[0.597, 0.317, 0.318], -[0.033, 0.003, 0.003], -[0.031, 0.013, 0.017], -[0.141, 0.106, 0.096], -[3.401, 0.092, 0.087], -[3.037, 0.748, 0.740], -[2.263, 0.059, 0.057], -[1.277, 0.045, 0.057], -[4.439, 0.223, 0.213], -[4.197, 0.178, 0.174], -[1.817, 0.223, 0.195], -[63.350, 2.157, 2.109], -[42.422, 1.557, 1.551], -[3.545, 0.808, 0.798], -[0.791, 0.185, 0.154], -[0.110, 0.082, 0.079], -[3.174, 0.093, 0.106], -[0.047, 0.030, 0.031], -[0.023, 0.008, 0.008], -[0.306, 0.069, 0.067], -[1.635, 0.049, 0.047], -[1.669, 0.096, 0.096], -[2.579, 0.216, 0.207], -[0.060, 0.040, 0.043], -[0.017, 0.008, 0.008], -[2.057, 0.421, 0.403], -[2.015, 0.464, 0.458], -[1.039, 0.122, 0.127], -[1.676, 0.151, 0.144], -[2.624, 0.421, 0.394], -[3.777, 0.536, 0.508], -[2.352, 0.523, 0.494], -[1.205, 0.537, 0.532], -[4.218, 1.303, 1.273], -[3.674, 0.724, 0.714], -[7.911, 2.748, 2.734], -[0.910, 0.057, 0.029], -[19.529, 0.434, 0.415], -[21.471, 0.577, 0.527], -[40.121, 1.221, 1.191], -[39.482, 0.566, 0.544], -[4.644, 0.191, 0.191], -[2.312, 0.168, 0.159], -[5.286, 0.204, 0.199], -[19.174, 0.449, 0.431], -[15.773, 0.772, 0.755], -[2.270, 2.254, 2.254], -[3.855, 0.469, 0.455], -[9.782, 0.667, 0.640], -[9.127, 3.834, 3.826], -[19.407, 1.852, 1.794], -[19.405, 1.838, 1.803], -[0.850, 0.803, 0.799], -[0.146, 0.118, 0.111], -[0.070, 0.048, 0.050], -[0.067, 0.038, 0.035], -[0.318, 0.238, 0.256], -[0.058, 0.019, 0.018], -[0.034, 0.013, 0.013], -[0.013, 0.003, 0.005] - ] - } -] diff --git a/website/benchmark/versions/results/19.14.13.4_adaptive.json b/website/benchmark/versions/results/19.14.13.4_adaptive.json deleted file mode 100644 index 27e969d7046..00000000000 --- a/website/benchmark/versions/results/19.14.13.4_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2020-07-21 19.14", - "system_full": "ClickHouse 19.14.13.4 2020-07-21(adaptive)", - "version": "19.14.13.4", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.037, 0.003, 0.003], -[0.015, 0.002, 0.002], -[0.021, 0.003, 0.003], -[0.016, 0.002, 0.002], -[0.016, 0.002, 0.002], -[0.015, 0.003, 0.003], -[0.018, 0.002, 0.002], -[0.071, 0.040, 0.030], -[0.022, 0.009, 0.009], -[0.014, 0.002, 0.002], -[1.241, 0.353, 0.357], -[0.550, 0.346, 0.341], -[0.034, 0.004, 0.003], -[0.031, 0.019, 0.018], -[0.124, 0.101, 0.106], -[3.424, 0.101, 0.095], -[3.021, 0.745, 0.742], -[2.268, 0.057, 0.055], -[1.266, 0.052, 0.051], -[4.452, 0.219, 0.226], -[4.183, 0.188, 0.198], -[1.823, 0.227, 0.221], -[63.392, 2.282, 2.249], -[42.447, 1.526, 1.529], -[3.546, 0.809, 0.813], -[0.763, 0.173, 0.141], -[0.119, 0.079, 0.094], -[3.172, 0.100, 0.085], -[0.025, 0.006, 0.006], -[0.026, 0.009, 0.008], -[0.365, 0.027, 0.026], -[1.690, 0.034, 0.032], -[1.668, 0.090, 0.089], -[2.592, 0.207, 0.211], -[0.027, 0.011, 0.011], -[0.018, 0.009, 0.009], -[2.114, 0.410, 0.403], -[2.003, 0.457, 0.444], -[1.025, 0.130, 0.131], -[1.645, 0.160, 0.156], -[2.559, 0.463, 0.454], -[3.838, 0.579, 0.570], -[2.380, 0.552, 0.549], -[1.202, 0.600, 0.583], -[4.286, 1.424, 1.415], -[3.680, 0.808, 0.823], -[7.918, 3.030, 3.035], -[0.906, 0.044, 0.044], -[19.539, 0.495, 0.483], -[21.464, 0.603, 0.562], -[40.117, 1.299, 1.279], -[39.494, 0.622, 0.607], -[4.644, 0.209, 0.216], -[2.249, 0.165, 0.183], -[5.262, 0.223, 0.218], -[19.142, 0.461, 0.449], -[15.763, 0.824, 0.828], -[1.168, 1.135, 1.139], -[3.836, 0.492, 0.509], -[9.800, 0.729, 0.705], -[9.106, 4.009, 4.031], -[19.505, 1.972, 1.949], -[19.475, 2.047, 1.972], -[0.858, 0.846, 0.825], -[0.163, 0.122, 0.117], -[0.067, 0.048, 0.049], -[0.072, 0.043, 0.039], -[0.336, 0.262, 0.277], -[0.050, 0.027, 0.018], -[0.030, 0.023, 0.012], -[0.013, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/19.15.7.30_adaptive.json b/website/benchmark/versions/results/19.15.7.30_adaptive.json deleted file mode 100644 index 376058e1748..00000000000 --- a/website/benchmark/versions/results/19.15.7.30_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2019-12-29 19.15", - "system_full": "ClickHouse 19.15.7.30 2019-12-29(adaptive)", - "version": "19.15.7.30", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.036, 0.003, 0.003], -[0.014, 0.002, 0.002], -[0.016, 0.003, 0.003], -[0.016, 0.002, 0.002], -[0.015, 0.002, 0.002], -[0.017, 0.003, 0.003], -[0.016, 0.002, 0.002], -[0.067, 0.039, 0.039], -[0.021, 0.008, 0.008], -[0.011, 0.002, 0.002], -[1.241, 0.346, 0.359], -[0.572, 0.336, 0.340], -[0.034, 0.003, 0.003], -[0.032, 0.019, 0.014], -[0.130, 0.102, 0.098], -[3.424, 0.092, 0.092], -[3.032, 0.737, 0.746], -[2.283, 0.055, 0.055], -[1.275, 0.049, 0.049], -[4.531, 0.222, 0.216], -[4.224, 0.190, 0.174], -[1.808, 0.212, 0.211], -[63.378, 2.088, 2.059], -[42.433, 1.451, 1.445], -[3.535, 0.788, 0.792], -[0.783, 0.166, 0.146], -[0.117, 0.075, 0.083], -[3.194, 0.081, 0.091], -[0.025, 0.005, 0.005], -[0.025, 0.008, 0.008], -[0.400, 0.022, 0.023], -[1.716, 0.032, 0.032], -[1.684, 0.082, 0.088], -[2.604, 0.201, 0.203], -[0.029, 0.014, 0.015], -[0.019, 0.008, 0.009], -[2.128, 0.415, 0.408], -[2.008, 0.445, 0.442], -[1.031, 0.119, 0.119], -[1.673, 0.149, 0.143], -[2.623, 0.468, 0.491], -[3.772, 0.566, 0.556], -[2.345, 0.545, 0.540], -[1.198, 0.581, 0.604], -[4.245, 1.422, 1.421], -[3.641, 0.797, 0.799], -[7.931, 3.072, 3.036], -[0.899, 0.046, 0.037], -[19.526, 0.453, 0.454], -[21.447, 0.556, 0.583], -[39.978, 1.279, 1.248], -[39.505, 0.567, 0.563], -[4.637, 0.205, 0.204], -[2.262, 0.170, 0.170], -[5.273, 0.211, 0.214], -[19.142, 0.448, 0.435], -[15.800, 0.780, 0.782], -[1.154, 1.138, 1.134], -[3.820, 0.508, 0.524], -[9.790, 0.722, 0.689], -[9.068, 4.062, 4.036], -[19.494, 2.033, 1.911], -[19.512, 2.044, 1.967], -[0.860, 0.839, 0.830], -[0.151, 0.129, 0.118], -[0.068, 0.048, 0.053], -[0.068, 0.043, 0.037], -[0.312, 0.251, 0.273], -[0.051, 0.023, 0.026], -[0.034, 0.024, 0.022], -[0.014, 0.002, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/19.16.19.85_adaptive.json b/website/benchmark/versions/results/19.16.19.85_adaptive.json deleted file mode 100644 index 259b6310720..00000000000 --- a/website/benchmark/versions/results/19.16.19.85_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2020-04-27 19.16", - "system_full": "ClickHouse 19.16.19.85 2020-04-27(adaptive)", - "version": "19.16.19.85", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.035, 0.003, 0.003], -[0.015, 0.002, 0.002], -[0.017, 0.003, 0.003], -[0.015, 0.002, 0.002], -[0.015, 0.002, 0.002], -[0.014, 0.003, 0.003], -[0.015, 0.002, 0.002], -[0.107, 0.029, 0.039], -[0.021, 0.010, 0.010], -[0.015, 0.002, 0.002], -[1.238, 0.359, 0.360], -[0.573, 0.324, 0.311], -[0.032, 0.003, 0.003], -[0.032, 0.018, 0.013], -[0.133, 0.106, 0.106], -[3.420, 0.095, 0.092], -[3.048, 0.746, 0.735], -[2.274, 0.064, 0.058], -[1.277, 0.050, 0.056], -[4.456, 0.250, 0.240], -[4.146, 0.197, 0.183], -[1.821, 0.223, 0.220], -[63.395, 2.248, 2.241], -[42.449, 1.517, 1.519], -[3.568, 0.798, 0.795], -[0.793, 0.147, 0.142], -[0.128, 0.081, 0.066], -[3.217, 0.085, 0.096], -[0.026, 0.006, 0.005], -[0.027, 0.008, 0.009], -[0.363, 0.025, 0.024], -[1.716, 0.034, 0.030], -[1.683, 0.099, 0.086], -[2.517, 0.216, 0.197], -[0.030, 0.014, 0.015], -[0.018, 0.009, 0.008], -[2.142, 0.410, 0.407], -[2.009, 0.449, 0.435], -[1.020, 0.125, 0.123], -[1.677, 0.146, 0.152], -[2.577, 0.468, 0.488], -[3.794, 0.572, 0.550], -[2.362, 0.567, 0.528], -[1.204, 0.544, 0.548], -[4.506, 1.420, 1.415], -[3.668, 0.804, 0.803], -[7.920, 3.041, 3.031], -[0.917, 0.032, 0.043], -[19.548, 0.498, 0.477], -[21.481, 0.573, 0.563], -[40.165, 1.294, 1.261], -[39.503, 0.619, 0.606], -[4.642, 0.207, 0.203], -[2.278, 0.169, 0.170], -[5.301, 0.209, 0.214], -[19.193, 0.451, 0.458], -[15.769, 0.793, 0.782], -[1.171, 1.139, 1.129], -[3.804, 0.511, 0.511], -[9.797, 0.724, 0.704], -[9.091, 4.112, 4.069], -[19.481, 1.974, 1.937], -[19.489, 2.049, 1.976], -[0.849, 0.820, 0.830], -[0.144, 0.117, 0.115], -[0.068, 0.052, 0.051], -[0.075, 0.035, 0.040], -[0.321, 0.263, 0.267], -[0.052, 0.018, 0.024], -[0.032, 0.012, 0.012], -[0.012, 0.011, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/19.17.10.1_adaptive.json b/website/benchmark/versions/results/19.17.10.1_adaptive.json deleted file mode 100644 index e7471d40b3f..00000000000 --- a/website/benchmark/versions/results/19.17.10.1_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2020-07-22 19.17", - "system_full": "ClickHouse 19.17.10.1 2020-07-22(adaptive)", - "version": "19.17.10.1", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.032, 0.003, 0.003], -[0.017, 0.002, 0.002], -[0.017, 0.003, 0.003], -[0.014, 0.002, 0.002], -[0.016, 0.002, 0.002], -[0.017, 0.003, 0.003], -[0.018, 0.002, 0.002], -[0.144, 0.030, 0.038], -[0.023, 0.011, 0.011], -[0.014, 0.002, 0.002], -[1.242, 0.340, 0.347], -[0.605, 0.325, 0.327], -[0.033, 0.003, 0.003], -[0.031, 0.014, 0.017], -[0.130, 0.107, 0.108], -[3.419, 0.104, 0.095], -[3.005, 0.751, 0.754], -[2.318, 0.055, 0.049], -[1.282, 0.048, 0.040], -[4.472, 0.235, 0.219], -[4.206, 0.171, 0.171], -[1.862, 0.212, 0.205], -[63.410, 2.035, 2.023], -[42.454, 1.429, 1.427], -[3.558, 0.811, 0.802], -[0.771, 0.168, 0.149], -[0.123, 0.089, 0.070], -[3.175, 0.092, 0.102], -[0.010, 0.001, 0.001], -[0.026, 0.009, 0.009], -[0.340, 0.025, 0.024], -[1.713, 0.031, 0.031], -[1.689, 0.089, 0.079], -[2.601, 0.214, 0.197], -[0.025, 0.011, 0.011], -[0.018, 0.009, 0.009], -[2.105, 0.415, 0.411], -[2.001, 0.454, 0.458], -[1.032, 0.125, 0.124], -[1.659, 0.150, 0.161], -[2.585, 0.486, 0.474], -[3.798, 0.580, 0.578], -[2.360, 0.558, 0.542], -[1.197, 0.558, 0.563], -[4.244, 1.423, 1.425], -[3.638, 0.813, 0.807], -[7.920, 3.054, 3.030], -[0.909, 0.060, 0.040], -[19.522, 0.477, 0.454], -[21.467, 0.575, 0.563], -[40.174, 1.311, 1.269], -[39.523, 0.619, 0.584], -[4.649, 0.217, 0.201], -[2.274, 0.173, 0.167], -[5.286, 0.227, 0.216], -[19.157, 0.467, 0.464], -[15.790, 0.783, 0.784], -[1.156, 1.141, 1.129], -[3.850, 0.488, 0.518], -[9.832, 0.722, 0.685], -[9.068, 4.054, 4.058], -[19.490, 2.038, 2.035], -[19.504, 2.129, 1.977], -[0.910, 0.842, 0.848], -[0.166, 0.142, 0.122], -[0.078, 0.050, 0.048], -[0.069, 0.038, 0.042], -[0.335, 0.255, 0.256], -[0.053, 0.025, 0.019], -[0.031, 0.012, 0.022], -[0.012, 0.003, 0.002] - ] - } -] diff --git a/website/benchmark/versions/results/20.01.16.120_adaptive.json b/website/benchmark/versions/results/20.01.16.120_adaptive.json deleted file mode 100644 index 91c22297b07..00000000000 --- a/website/benchmark/versions/results/20.01.16.120_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2020-06-26 20.1", - "system_full": "ClickHouse 20.1.16.120 2020-06-26(adaptive)", - "version": "20.1.16.120", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.036, 0.003, 0.003], -[0.017, 0.002, 0.002], -[0.017, 0.004, 0.004], -[0.019, 0.002, 0.002], -[0.017, 0.002, 0.002], -[0.015, 0.003, 0.003], -[0.018, 0.002, 0.002], -[0.238, 0.032, 0.022], -[0.018, 0.009, 0.009], -[0.014, 0.002, 0.002], -[1.227, 0.336, 0.336], -[0.623, 0.346, 0.328], -[0.032, 0.003, 0.003], -[0.031, 0.014, 0.014], -[0.127, 0.095, 0.101], -[3.415, 0.109, 0.108], -[3.271, 1.135, 1.135], -[2.268, 0.057, 0.065], -[1.415, 0.049, 0.055], -[4.452, 0.210, 0.223], -[4.192, 0.195, 0.195], -[1.769, 0.238, 0.230], -[63.306, 2.245, 2.191], -[42.435, 1.591, 1.558], -[3.901, 1.178, 1.182], -[0.778, 0.194, 0.143], -[0.115, 0.076, 0.094], -[4.628, 0.097, 0.095], -[0.012, 0.001, 0.001], -[0.029, 0.009, 0.008], -[0.268, 0.027, 0.025], -[1.709, 0.034, 0.032], -[1.662, 0.093, 0.095], -[2.554, 0.222, 0.208], -[0.033, 0.015, 0.016], -[0.018, 0.009, 0.008], -[2.092, 0.425, 0.411], -[2.011, 0.461, 0.452], -[1.012, 0.130, 0.128], -[1.636, 0.160, 0.154], -[2.553, 0.501, 0.482], -[3.775, 0.595, 0.568], -[2.336, 0.548, 0.518], -[1.217, 0.554, 0.552], -[4.248, 1.415, 1.410], -[3.650, 0.815, 0.799], -[7.889, 2.996, 2.999], -[0.906, 0.035, 0.038], -[19.528, 0.487, 0.479], -[21.456, 0.586, 0.560], -[40.019, 1.302, 1.277], -[39.505, 0.621, 0.584], -[4.627, 0.186, 0.184], -[2.321, 0.152, 0.153], -[5.298, 0.193, 0.192], -[19.189, 0.460, 0.444], -[15.722, 0.766, 0.757], -[1.193, 1.173, 1.169], -[3.824, 0.529, 0.510], -[9.801, 0.735, 0.711], -[9.054, 4.012, 4.011], -[19.468, 2.084, 2.060], -[19.447, 2.108, 2.078], -[0.907, 0.857, 0.845], -[0.174, 0.146, 0.130], -[0.067, 0.045, 0.048], -[0.074, 0.047, 0.035], -[0.314, 0.237, 0.260], -[0.056, 0.013, 0.022], -[0.043, 0.010, 0.010], -[0.014, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/20.03.21.2_adaptive.json b/website/benchmark/versions/results/20.03.21.2_adaptive.json deleted file mode 100644 index 324c7f76139..00000000000 --- a/website/benchmark/versions/results/20.03.21.2_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2020-11-02 20.3", - "system_full": "ClickHouse 20.3.21.2 2020-11-02(adaptive)", - "version": "20.3.21.2", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.038, 0.003, 0.003], -[0.018, 0.002, 0.002], -[0.021, 0.004, 0.004], -[0.019, 0.002, 0.002], -[0.018, 0.002, 0.002], -[0.015, 0.003, 0.003], -[0.019, 0.002, 0.002], -[0.067, 0.028, 0.027], -[0.021, 0.009, 0.009], -[0.012, 0.002, 0.002], -[1.232, 0.332, 0.336], -[0.595, 0.316, 0.319], -[0.036, 0.004, 0.004], -[0.032, 0.014, 0.013], -[0.156, 0.098, 0.097], -[3.409, 0.100, 0.097], -[3.255, 1.130, 1.118], -[2.251, 0.063, 0.061], -[1.402, 0.052, 0.054], -[4.425, 0.245, 0.234], -[4.160, 0.189, 0.191], -[1.763, 0.228, 0.227], -[63.662, 2.106, 2.094], -[42.534, 1.413, 1.384], -[3.879, 1.163, 1.163], -[0.785, 0.185, 0.148], -[0.120, 0.081, 0.071], -[4.663, 0.110, 0.089], -[0.011, 0.001, 0.001], -[0.027, 0.010, 0.009], -[0.341, 0.025, 0.022], -[1.719, 0.033, 0.032], -[1.687, 0.085, 0.084], -[2.584, 0.196, 0.191], -[0.028, 0.015, 0.016], -[0.019, 0.009, 0.009], -[2.053, 0.315, 0.317], -[2.037, 0.355, 0.347], -[1.172, 0.122, 0.125], -[1.671, 0.145, 0.155], -[2.552, 0.483, 0.507], -[3.795, 0.557, 0.556], -[2.323, 0.516, 0.548], -[1.184, 0.526, 0.525], -[4.187, 1.384, 1.385], -[3.674, 0.827, 0.822], -[7.857, 2.964, 2.991], -[0.905, 0.062, 0.037], -[19.548, 0.516, 0.473], -[21.492, 0.549, 0.543], -[40.083, 1.304, 1.276], -[39.617, 0.624, 0.602], -[4.629, 0.186, 0.192], -[2.391, 0.157, 0.163], -[5.297, 0.195, 0.195], -[19.173, 0.464, 0.467], -[15.765, 0.794, 0.789], -[1.204, 1.180, 1.175], -[3.823, 0.503, 0.493], -[9.800, 0.708, 0.710], -[9.058, 4.009, 3.968], -[19.437, 1.976, 1.998], -[19.368, 2.031, 2.052], -[0.884, 0.838, 0.831], -[0.169, 0.117, 0.115], -[0.067, 0.046, 0.042], -[0.079, 0.042, 0.044], -[0.293, 0.245, 0.233], -[0.050, 0.014, 0.019], -[0.029, 0.018, 0.029], -[0.012, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/20.04.9.110_adaptive.json b/website/benchmark/versions/results/20.04.9.110_adaptive.json deleted file mode 100644 index 1e286c9f984..00000000000 --- a/website/benchmark/versions/results/20.04.9.110_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2020-08-20 20.4", - "system_full": "ClickHouse 20.4.9.110 2020-08-20(adaptive)", - "version": "20.4.9.110", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.034, 0.003, 0.003], -[0.061, 0.002, 0.002], -[0.018, 0.004, 0.004], -[0.019, 0.002, 0.002], -[0.016, 0.002, 0.002], -[0.017, 0.003, 0.003], -[0.017, 0.002, 0.002], -[0.058, 0.030, 0.026], -[0.021, 0.009, 0.008], -[0.011, 0.002, 0.002], -[1.115, 0.329, 0.321], -[0.626, 0.338, 0.330], -[0.032, 0.004, 0.004], -[0.030, 0.014, 0.013], -[0.118, 0.129, 0.097], -[3.428, 0.103, 0.094], -[3.001, 0.470, 0.466], -[2.266, 0.056, 0.061], -[1.414, 0.056, 0.050], -[4.436, 0.206, 0.211], -[4.218, 0.170, 0.166], -[1.825, 0.218, 0.218], -[63.353, 2.084, 2.009], -[42.428, 1.361, 1.341], -[3.240, 0.513, 0.515], -[0.769, 0.152, 0.151], -[0.107, 0.068, 0.069], -[4.636, 0.092, 0.089], -[0.012, 0.001, 0.001], -[0.035, 0.010, 0.010], -[0.262, 0.024, 0.024], -[1.695, 0.033, 0.029], -[1.671, 0.099, 0.091], -[2.564, 0.220, 0.207], -[0.027, 0.012, 0.012], -[0.020, 0.010, 0.010], -[1.986, 0.327, 0.330], -[2.008, 0.365, 0.362], -[0.997, 0.122, 0.121], -[1.657, 0.154, 0.151], -[2.537, 0.467, 0.466], -[3.767, 0.572, 0.559], -[2.315, 0.521, 0.546], -[1.126, 0.519, 0.540], -[4.176, 1.361, 1.375], -[3.667, 0.824, 0.819], -[7.799, 2.987, 2.913], -[0.890, 0.058, 0.043], -[19.493, 0.543, 0.513], -[21.321, 0.585, 0.575], -[39.970, 1.294, 1.275], -[39.519, 0.656, 0.618], -[4.625, 0.179, 0.174], -[2.327, 0.150, 0.153], -[5.305, 0.202, 0.197], -[19.199, 0.458, 0.447], -[15.612, 0.784, 0.773], -[1.152, 1.136, 1.134], -[3.821, 0.470, 0.470], -[9.794, 0.712, 0.688], -[9.036, 4.000, 3.941], -[19.410, 1.997, 1.967], -[19.237, 2.013, 2.008], -[0.884, 0.828, 0.831], -[0.172, 0.135, 0.117], -[0.067, 0.046, 0.051], -[0.072, 0.037, 0.041], -[0.293, 0.220, 0.243], -[0.053, 0.026, 0.013], -[0.032, 0.021, 0.015], -[0.015, 0.003, 0.010] - ] - } -] diff --git a/website/benchmark/versions/results/20.05.5.74_adaptive.json b/website/benchmark/versions/results/20.05.5.74_adaptive.json deleted file mode 100644 index cbed25cdf08..00000000000 --- a/website/benchmark/versions/results/20.05.5.74_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2020-08-20 20.5", - "system_full": "ClickHouse 20.5.5.74 2020-08-20(adaptive)", - "version": "20.5.5.74", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.017, 0.003, 0.003], -[0.012, 0.002, 0.002], -[0.017, 0.004, 0.004], -[0.011, 0.002, 0.002], -[0.014, 0.002, 0.002], -[0.010, 0.003, 0.003], -[0.019, 0.002, 0.002], -[0.075, 0.029, 0.026], -[0.018, 0.008, 0.008], -[0.009, 0.002, 0.002], -[0.987, 0.327, 0.319], -[0.649, 0.326, 0.291], -[0.034, 0.004, 0.004], -[0.031, 0.014, 0.014], -[0.139, 0.101, 0.097], -[3.401, 0.097, 0.090], -[2.982, 0.493, 0.470], -[2.267, 0.061, 0.054], -[1.421, 0.050, 0.046], -[4.444, 0.206, 0.206], -[4.235, 0.170, 0.171], -[1.830, 0.210, 0.213], -[63.495, 1.978, 1.955], -[42.433, 1.320, 1.303], -[3.312, 0.528, 0.515], -[0.879, 0.168, 0.163], -[0.115, 0.065, 0.071], -[4.676, 0.097, 0.093], -[0.010, 0.001, 0.001], -[0.027, 0.009, 0.009], -[0.313, 0.025, 0.023], -[1.700, 0.031, 0.029], -[1.684, 0.096, 0.096], -[2.569, 0.221, 0.207], -[0.027, 0.013, 0.012], -[0.020, 0.010, 0.009], -[1.965, 0.327, 0.331], -[2.019, 0.371, 0.363], -[1.054, 0.124, 0.121], -[1.669, 0.157, 0.144], -[2.538, 0.442, 0.427], -[3.784, 0.537, 0.544], -[2.356, 0.512, 0.524], -[1.179, 0.526, 0.516], -[4.170, 1.364, 1.314], -[3.675, 0.821, 0.795], -[7.808, 2.835, 2.806], -[0.897, 0.049, 0.040], -[19.506, 0.490, 0.478], -[21.397, 0.535, 0.574], -[39.973, 1.263, 1.231], -[39.497, 0.623, 0.580], -[4.624, 0.180, 0.177], -[2.332, 0.149, 0.149], -[5.305, 0.183, 0.185], -[19.203, 0.456, 0.443], -[15.583, 0.771, 0.783], -[1.159, 1.134, 1.123], -[3.818, 0.443, 0.460], -[9.802, 0.690, 0.662], -[9.004, 3.952, 3.935], -[19.402, 1.989, 1.922], -[19.316, 1.972, 1.892], -[0.876, 0.826, 0.797], -[0.177, 0.119, 0.112], -[0.067, 0.043, 0.049], -[0.076, 0.044, 0.039], -[0.312, 0.238, 0.245], -[0.047, 0.022, 0.017], -[0.040, 0.014, 0.015], -[0.014, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/20.06.11.1_adaptive.json b/website/benchmark/versions/results/20.06.11.1_adaptive.json deleted file mode 100644 index 816df9b93e5..00000000000 --- a/website/benchmark/versions/results/20.06.11.1_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2020-12-09 20.6", - "system_full": "ClickHouse 20.6.11.1 2020-12-09(adaptive)", - "version": "2020-12-09", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.021, 0.004, 0.004], -[0.012, 0.002, 0.002], -[0.019, 0.004, 0.004], -[0.010, 0.002, 0.002], -[0.013, 0.003, 0.003], -[0.010, 0.003, 0.004], -[0.016, 0.002, 0.002], -[0.063, 0.029, 0.026], -[0.018, 0.008, 0.009], -[0.009, 0.002, 0.002], -[0.988, 0.318, 0.320], -[0.631, 0.304, 0.315], -[0.032, 0.004, 0.004], -[0.031, 0.014, 0.015], -[0.114, 0.102, 0.133], -[3.430, 0.098, 0.093], -[3.001, 0.496, 0.488], -[2.257, 0.057, 0.059], -[1.412, 0.054, 0.054], -[4.430, 0.215, 0.214], -[4.211, 0.174, 0.172], -[1.831, 0.216, 0.217], -[63.359, 2.025, 2.017], -[42.442, 1.349, 1.339], -[3.265, 0.533, 0.540], -[0.796, 0.155, 0.178], -[0.117, 0.070, 0.065], -[4.640, 0.093, 0.101], -[0.011, 0.002, 0.001], -[0.027, 0.011, 0.010], -[0.322, 0.022, 0.025], -[1.729, 0.031, 0.031], -[1.692, 0.095, 0.092], -[2.558, 0.212, 0.196], -[0.025, 0.015, 0.013], -[0.020, 0.010, 0.010], -[2.065, 0.340, 0.335], -[2.015, 0.380, 0.378], -[1.016, 0.132, 0.131], -[1.644, 0.161, 0.156], -[2.509, 0.441, 0.447], -[3.769, 0.577, 0.530], -[2.357, 0.538, 0.530], -[1.175, 0.545, 0.532], -[4.177, 1.338, 1.320], -[3.671, 0.800, 0.804], -[7.821, 2.833, 2.787], -[0.908, 0.122, 0.075], -[19.423, 0.477, 0.463], -[21.438, 0.591, 0.545], -[39.980, 1.269, 1.253], -[39.490, 0.625, 0.589], -[4.620, 0.196, 0.182], -[2.322, 0.155, 0.158], -[5.295, 0.193, 0.198], -[19.194, 0.452, 0.449], -[15.789, 0.785, 0.765], -[1.160, 1.127, 1.128], -[3.854, 0.470, 0.451], -[9.914, 0.727, 0.703], -[9.172, 4.111, 4.067], -[19.409, 2.006, 1.927], -[19.235, 1.968, 1.900], -[0.868, 0.831, 0.798], -[0.173, 0.116, 0.116], -[0.072, 0.047, 0.048], -[0.077, 0.039, 0.045], -[0.294, 0.227, 0.228], -[0.048, 0.016, 0.025], -[0.037, 0.026, 0.018], -[0.015, 0.012, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/20.07.4.11_adaptive.json b/website/benchmark/versions/results/20.07.4.11_adaptive.json deleted file mode 100644 index e4e14d1b4ed..00000000000 --- a/website/benchmark/versions/results/20.07.4.11_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2020-10-09 20.7", - "system_full": "ClickHouse 20.7.4.11 2020-10-09(adaptive)", - "version": "20.7.4.11", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.017, 0.004, 0.004], -[0.012, 0.002, 0.002], -[0.017, 0.004, 0.004], -[0.010, 0.002, 0.002], -[0.013, 0.003, 0.003], -[0.010, 0.003, 0.003], -[0.016, 0.002, 0.003], -[0.073, 0.030, 0.027], -[0.019, 0.008, 0.009], -[0.009, 0.002, 0.003], -[1.109, 0.324, 0.320], -[0.639, 0.323, 0.287], -[0.031, 0.004, 0.004], -[0.031, 0.014, 0.014], -[0.119, 0.136, 0.094], -[3.426, 0.109, 0.100], -[2.994, 0.161, 0.161], -[2.740, 0.063, 0.060], -[1.402, 0.052, 0.054], -[4.421, 0.187, 0.190], -[4.250, 0.157, 0.138], -[1.880, 0.219, 0.201], -[63.407, 1.970, 1.960], -[42.465, 1.336, 1.321], -[2.928, 0.207, 0.218], -[1.134, 0.155, 0.142], -[0.119, 0.070, 0.072], -[4.686, 0.089, 0.089], -[0.010, 0.001, 0.002], -[0.029, 0.010, 0.010], -[0.322, 0.024, 0.026], -[1.696, 0.033, 0.030], -[1.681, 0.094, 0.092], -[2.576, 0.204, 0.197], -[0.029, 0.015, 0.015], -[0.020, 0.010, 0.011], -[2.019, 0.330, 0.328], -[2.029, 0.370, 0.378], -[1.031, 0.126, 0.134], -[1.638, 0.150, 0.150], -[2.582, 0.454, 0.413], -[3.750, 0.550, 0.518], -[2.374, 0.533, 0.504], -[1.189, 0.526, 0.509], -[4.192, 1.329, 1.299], -[3.656, 0.811, 0.788], -[7.830, 2.852, 2.762], -[0.888, 0.128, 0.056], -[19.419, 0.541, 0.491], -[21.463, 0.543, 0.536], -[40.010, 1.276, 1.247], -[39.537, 0.660, 0.607], -[4.633, 0.192, 0.189], -[2.296, 0.150, 0.154], -[5.319, 0.186, 0.181], -[19.258, 0.449, 0.449], -[15.793, 0.767, 0.766], -[1.147, 1.125, 1.121], -[3.821, 0.474, 0.466], -[9.793, 0.690, 0.666], -[9.069, 3.982, 4.006], -[19.417, 1.995, 1.882], -[19.408, 1.973, 1.894], -[0.870, 0.807, 0.799], -[0.148, 0.119, 0.111], -[0.075, 0.046, 0.052], -[0.072, 0.041, 0.039], -[0.318, 0.230, 0.226], -[0.068, 0.018, 0.025], -[0.029, 0.015, 0.014], -[0.018, 0.004, 0.004] - ] - } -] diff --git a/website/benchmark/versions/results/20.08.19.4.json b/website/benchmark/versions/results/20.08.19.4.json deleted file mode 100644 index d2b2b41ceb0..00000000000 --- a/website/benchmark/versions/results/20.08.19.4.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2021-07-11 20.8", - "system_full": "ClickHouse 20.8.19.4 2021-07-11", - "version": "20.8.19.4", - "kind": "", - "comments": "", - "result": - [ - -[0.042, 0.011, 0.012], -[0.064, 0.031, 0.032], -[0.037, 0.010, 0.010], -[0.038, 0.015, 0.015], -[0.053, 0.033, 0.029], -[0.065, 0.016, 0.017], -[0.060, 0.005, 0.006], -[0.335, 0.031, 0.029], -[0.498, 0.052, 0.053], -[0.507, 0.082, 0.084], -[1.930, 0.553, 0.551], -[1.488, 0.498, 0.476], -[0.029, 0.005, 0.005], -[0.030, 0.014, 0.014], -[0.133, 0.106, 0.102], -[3.315, 0.116, 0.103], -[2.560, 0.138, 0.145], -[2.209, 0.087, 0.063], -[1.218, 0.061, 0.056], -[5.103, 0.201, 0.192], -[4.795, 0.166, 0.161], -[1.884, 0.174, 0.196], -[63.515, 2.121, 2.103], -[42.496, 1.428, 1.386], -[4.439, 0.193, 0.187], -[1.458, 0.156, 0.155], -[0.238, 0.057, 0.063], -[5.102, 0.088, 0.085], -[0.008, 0.002, 0.002], -[0.029, 0.011, 0.011], -[0.479, 0.028, 0.026], -[1.723, 0.028, 0.031], -[1.730, 0.091, 0.093], -[2.545, 0.199, 0.195], -[0.029, 0.016, 0.016], -[0.021, 0.010, 0.010], -[2.223, 0.345, 0.329], -[2.234, 0.380, 0.382], -[1.029, 0.115, 0.113], -[1.703, 0.146, 0.138], -[2.568, 0.452, 0.430], -[3.769, 0.545, 0.548], -[2.370, 0.542, 0.518], -[1.239, 0.526, 0.528], -[4.186, 1.388, 1.335], -[3.652, 0.845, 0.824], -[7.822, 2.888, 2.870], -[0.937, 0.134, 0.052], -[18.851, 0.550, 0.538], -[20.853, 0.578, 0.608], -[38.786, 1.395, 1.372], -[38.926, 0.706, 0.656], -[4.545, 0.182, 0.184], -[2.309, 0.143, 0.142], -[5.274, 0.182, 0.182], -[18.667, 0.502, 0.524], -[15.385, 0.807, 0.816], -[1.203, 1.191, 1.180], -[3.875, 0.457, 0.468], -[9.798, 0.714, 0.673], -[9.077, 3.991, 3.987], -[18.866, 2.138, 2.090], -[18.921, 2.146, 2.070], -[0.904, 0.854, 0.846], -[0.118, 0.088, 0.114], -[0.061, 0.043, 0.039], -[0.058, 0.045, 0.037], -[0.212, 0.188, 0.161], -[0.046, 0.014, 0.012], -[0.026, 0.021, 0.009], -[0.015, 0.004, 0.011] - ] - } -] diff --git a/website/benchmark/versions/results/20.08.19.4_adaptive.json b/website/benchmark/versions/results/20.08.19.4_adaptive.json deleted file mode 100644 index 4f1ef56cdd0..00000000000 --- a/website/benchmark/versions/results/20.08.19.4_adaptive.json +++ /dev/null @@ -1,86 +0,0 @@ - -[ - { - "system": "2021-07-11 20.8", - "system_full": "ClickHouse 20.8.19.4 2021-07-11(adaptive)", - "version": "20.8.19.4", - "kind": "adaptive", - "kind": "", - "comments": "", - "result": - [ - -[0.021, 0.004, 0.004], -[0.013, 0.002, 0.002], -[0.017, 0.004, 0.004], -[0.010, 0.002, 0.002], -[0.013, 0.002, 0.003], -[0.011, 0.003, 0.003], -[0.018, 0.002, 0.002], -[0.076, 0.028, 0.021], -[0.018, 0.009, 0.008], -[0.010, 0.002, 0.002], -[1.009, 0.331, 0.329], -[0.604, 0.311, 0.319], -[0.029, 0.004, 0.004], -[0.033, 0.014, 0.014], -[0.119, 0.096, 0.098], -[3.418, 0.102, 0.098], -[3.006, 0.165, 0.157], -[2.750, 0.060, 0.066], -[1.390, 0.050, 0.051], -[4.429, 0.202, 0.214], -[4.188, 0.150, 0.159], -[1.854, 0.200, 0.206], -[63.423, 2.078, 2.029], -[42.477, 1.373, 1.340], -[2.925, 0.205, 0.209], -[1.147, 0.154, 0.146], -[0.109, 0.071, 0.085], -[4.647, 0.088, 0.091], -[0.010, 0.001, 0.001], -[0.027, 0.009, 0.010], -[0.339, 0.024, 0.025], -[1.723, 0.031, 0.031], -[1.670, 0.093, 0.091], -[2.589, 0.205, 0.212], -[0.035, 0.016, 0.017], -[0.020, 0.011, 0.010], -[1.997, 0.337, 0.323], -[2.027, 0.359, 0.365], -[1.043, 0.118, 0.116], -[1.673, 0.149, 0.146], -[2.558, 0.453, 0.425], -[3.782, 0.546, 0.525], -[2.372, 0.525, 0.502], -[1.178, 0.529, 0.515], -[4.370, 1.352, 1.305], -[3.655, 0.798, 0.791], -[7.837, 2.829, 2.802], -[0.896, 0.153, 0.086], -[19.375, 0.554, 0.506], -[21.462, 0.552, 0.559], -[40.026, 1.272, 1.253], -[39.523, 0.656, 0.638], -[4.624, 0.190, 0.191], -[2.287, 0.149, 0.148], -[5.342, 0.186, 0.191], -[19.237, 0.460, 0.446], -[15.673, 0.782, 0.759], -[1.148, 1.127, 1.125], -[3.812, 0.452, 0.447], -[9.773, 0.672, 0.652], -[8.977, 3.897, 3.828], -[19.403, 1.921, 1.925], -[19.249, 1.966, 1.929], -[0.863, 0.810, 0.787], -[0.144, 0.115, 0.114], -[0.078, 0.048, 0.045], -[0.068, 0.044, 0.045], -[0.291, 0.245, 0.221], -[0.062, 0.015, 0.014], -[0.042, 0.014, 0.011], -[0.015, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/20.09.7.11_adaptive.json b/website/benchmark/versions/results/20.09.7.11_adaptive.json deleted file mode 100644 index cd2bbae0158..00000000000 --- a/website/benchmark/versions/results/20.09.7.11_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2020-12-07 20.9", - "system_full": "ClickHouse 20.9.7.11 2020-12-07(adaptive)", - "version": "20.9.7.11", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.020, 0.004, 0.004], -[0.011, 0.002, 0.002], -[0.017, 0.004, 0.004], -[0.010, 0.002, 0.002], -[0.015, 0.003, 0.003], -[0.011, 0.003, 0.003], -[0.019, 0.002, 0.002], -[0.059, 0.031, 0.030], -[0.019, 0.009, 0.008], -[0.011, 0.002, 0.002], -[0.863, 0.338, 0.338], -[0.583, 0.306, 0.311], -[0.028, 0.004, 0.005], -[0.030, 0.014, 0.014], -[0.151, 0.093, 0.096], -[3.419, 0.110, 0.096], -[2.907, 0.154, 0.151], -[2.774, 0.054, 0.060], -[1.319, 0.044, 0.048], -[4.413, 0.185, 0.189], -[4.250, 0.147, 0.153], -[1.861, 0.204, 0.191], -[63.419, 2.030, 1.990], -[42.508, 1.353, 1.333], -[2.928, 0.208, 0.208], -[1.150, 0.179, 0.157], -[0.105, 0.079, 0.067], -[4.658, 0.097, 0.092], -[0.010, 0.002, 0.002], -[0.029, 0.010, 0.010], -[0.303, 0.025, 0.024], -[1.725, 0.031, 0.030], -[1.665, 0.085, 0.083], -[2.586, 0.197, 0.192], -[0.029, 0.016, 0.016], -[0.018, 0.010, 0.010], -[2.014, 0.309, 0.306], -[2.069, 0.340, 0.341], -[1.097, 0.121, 0.116], -[1.658, 0.142, 0.141], -[2.564, 0.470, 0.439], -[3.798, 0.538, 0.529], -[2.328, 0.537, 0.492], -[1.157, 0.515, 0.514], -[4.157, 1.346, 1.311], -[3.663, 0.796, 0.777], -[7.840, 2.878, 2.741], -[0.899, 0.126, 0.048], -[19.450, 0.536, 0.485], -[21.523, 0.539, 0.531], -[40.028, 1.275, 1.236], -[39.551, 0.647, 0.618], -[4.656, 0.178, 0.176], -[2.335, 0.151, 0.139], -[5.338, 0.176, 0.177], -[19.315, 0.452, 0.433], -[15.803, 0.770, 0.759], -[1.150, 1.122, 1.129], -[3.845, 0.449, 0.457], -[9.791, 0.689, 0.666], -[9.005, 3.932, 3.850], -[19.470, 1.954, 1.916], -[19.262, 1.966, 1.901], -[0.873, 0.798, 0.802], -[0.154, 0.114, 0.113], -[0.066, 0.041, 0.042], -[0.077, 0.056, 0.042], -[0.294, 0.243, 0.245], -[0.053, 0.016, 0.015], -[0.043, 0.011, 0.024], -[0.015, 0.005, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/20.10.7.4_adaptive.json b/website/benchmark/versions/results/20.10.7.4_adaptive.json deleted file mode 100644 index 166c3d462ac..00000000000 --- a/website/benchmark/versions/results/20.10.7.4_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2020-12-24 20.10", - "system_full": "ClickHouse 20.10.7.4 2020-12-24(adaptive)", - "version": "20.10.7.4", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.019, 0.004, 0.004], -[0.011, 0.002, 0.002], -[0.019, 0.004, 0.004], -[0.009, 0.002, 0.002], -[0.013, 0.002, 0.002], -[0.010, 0.004, 0.004], -[0.017, 0.002, 0.002], -[0.078, 0.028, 0.024], -[0.016, 0.011, 0.008], -[0.010, 0.002, 0.002], -[1.227, 0.346, 0.338], -[0.579, 0.341, 0.335], -[0.031, 0.004, 0.004], -[0.032, 0.015, 0.019], -[0.125, 0.097, 0.099], -[3.426, 0.101, 0.098], -[3.020, 0.149, 0.146], -[2.789, 0.060, 0.057], -[1.430, 0.049, 0.058], -[4.427, 0.242, 0.224], -[4.169, 0.193, 0.186], -[1.818, 0.238, 0.239], -[63.459, 2.557, 2.539], -[42.438, 1.662, 1.627], -[2.923, 0.209, 0.199], -[1.165, 0.178, 0.154], -[0.120, 0.082, 0.087], -[4.645, 0.105, 0.120], -[0.011, 0.001, 0.001], -[0.026, 0.011, 0.010], -[0.262, 0.023, 0.022], -[1.686, 0.031, 0.032], -[1.681, 0.095, 0.092], -[2.548, 0.238, 0.227], -[0.029, 0.015, 0.015], -[0.019, 0.010, 0.010], -[1.941, 0.320, 0.316], -[2.027, 0.356, 0.362], -[1.055, 0.146, 0.146], -[1.618, 0.170, 0.182], -[2.513, 0.484, 0.423], -[3.774, 0.555, 0.534], -[2.363, 0.566, 0.516], -[1.180, 0.534, 0.544], -[4.235, 1.425, 1.384], -[3.644, 0.819, 0.801], -[8.000, 3.018, 2.921], -[0.879, 0.165, 0.035], -[19.441, 0.484, 0.463], -[21.474, 0.583, 0.606], -[40.105, 1.336, 1.341], -[39.496, 0.607, 0.587], -[4.623, 0.194, 0.194], -[2.320, 0.167, 0.162], -[5.290, 0.189, 0.200], -[19.186, 0.507, 0.486], -[15.783, 0.730, 0.715], -[1.133, 1.110, 1.115], -[3.838, 0.471, 0.469], -[9.799, 0.692, 0.677], -[9.014, 3.920, 3.907], -[19.380, 1.999, 1.962], -[19.270, 1.979, 1.950], -[0.844, 0.775, 0.759], -[0.169, 0.110, 0.116], -[0.064, 0.041, 0.048], -[0.091, 0.042, 0.037], -[0.301, 0.245, 0.226], -[0.050, 0.021, 0.020], -[0.034, 0.011, 0.014], -[0.015, 0.004, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/20.11.7.16_adaptive.json b/website/benchmark/versions/results/20.11.7.16_adaptive.json deleted file mode 100644 index bf706fbdeb1..00000000000 --- a/website/benchmark/versions/results/20.11.7.16_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2021-02-03 20.11", - "system_full": "ClickHouse 20.11.7.16 2021-02-03(adaptive)", - "version": "2021-02-03", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.023, 0.004, 0.004], -[0.013, 0.002, 0.002], -[0.019, 0.005, 0.004], -[0.009, 0.002, 0.002], -[0.013, 0.003, 0.003], -[0.010, 0.003, 0.003], -[0.017, 0.002, 0.002], -[0.085, 0.030, 0.027], -[0.016, 0.009, 0.009], -[0.009, 0.002, 0.002], -[1.034, 0.337, 0.347], -[0.558, 0.330, 0.313], -[0.036, 0.004, 0.004], -[0.032, 0.016, 0.015], -[0.118, 0.099, 0.103], -[3.423, 0.102, 0.097], -[3.129, 0.146, 0.139], -[2.774, 0.064, 0.056], -[1.368, 0.058, 0.050], -[4.421, 0.259, 0.226], -[4.154, 0.204, 0.192], -[1.790, 0.239, 0.236], -[63.324, 2.536, 2.532], -[42.446, 1.660, 1.645], -[2.928, 0.211, 0.206], -[1.151, 0.137, 0.168], -[0.116, 0.079, 0.072], -[4.668, 0.125, 0.118], -[0.010, 0.001, 0.001], -[0.036, 0.010, 0.010], -[0.268, 0.020, 0.021], -[1.723, 0.032, 0.032], -[1.674, 0.104, 0.091], -[2.554, 0.237, 0.248], -[0.029, 0.016, 0.016], -[0.019, 0.009, 0.009], -[1.927, 0.319, 0.325], -[1.997, 0.363, 0.351], -[1.071, 0.147, 0.145], -[1.617, 0.176, 0.188], -[2.512, 0.455, 0.465], -[3.782, 0.551, 0.542], -[2.367, 0.530, 0.557], -[1.195, 0.560, 0.527], -[4.303, 1.411, 1.411], -[3.652, 0.830, 0.811], -[7.991, 3.038, 2.955], -[0.892, 0.143, 0.039], -[19.450, 0.493, 0.506], -[21.273, 0.604, 0.617], -[40.013, 1.335, 1.313], -[39.502, 0.628, 0.578], -[4.624, 0.202, 0.200], -[2.295, 0.158, 0.163], -[5.295, 0.193, 0.187], -[19.195, 0.524, 0.500], -[15.579, 0.730, 0.731], -[1.139, 1.114, 1.112], -[3.826, 0.492, 0.481], -[9.809, 0.707, 0.677], -[8.992, 3.956, 3.902], -[19.401, 1.989, 1.952], -[19.272, 1.963, 1.901], -[0.811, 0.772, 0.765], -[0.140, 0.110, 0.108], -[0.065, 0.047, 0.049], -[0.074, 0.040, 0.046], -[0.294, 0.231, 0.226], -[0.047, 0.021, 0.020], -[0.039, 0.011, 0.025], -[0.012, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/20.12.8.5_adaptive.json b/website/benchmark/versions/results/20.12.8.5_adaptive.json deleted file mode 100644 index 8322c449144..00000000000 --- a/website/benchmark/versions/results/20.12.8.5_adaptive.json +++ /dev/null @@ -1,86 +0,0 @@ - -[ - { - "system": "2021-03-02 20.12", - "system_full": "ClickHouse 20.12.8.5 2021-03-02(adaptive)", - "version": "2021-03-02", - "time": "2021-03-02", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.019, 0.004, 0.004], -[0.011, 0.002, 0.002], -[0.015, 0.004, 0.005], -[0.009, 0.002, 0.002], -[0.014, 0.003, 0.003], -[0.010, 0.004, 0.003], -[0.016, 0.002, 0.002], -[0.076, 0.028, 0.020], -[0.016, 0.010, 0.008], -[0.014, 0.002, 0.002], -[1.224, 0.345, 0.336], -[0.600, 0.315, 0.331], -[0.029, 0.004, 0.004], -[0.033, 0.015, 0.016], -[0.120, 0.099, 0.095], -[3.426, 0.106, 0.119], -[3.117, 0.242, 0.234], -[2.595, 0.061, 0.052], -[1.436, 0.061, 0.060], -[4.433, 0.230, 0.222], -[4.213, 0.190, 0.205], -[1.810, 0.260, 0.239], -[63.332, 2.577, 2.558], -[42.473, 1.621, 1.564], -[3.025, 0.304, 0.309], -[0.956, 0.174, 0.170], -[0.126, 0.084, 0.104], -[4.624, 0.102, 0.131], -[0.011, 0.001, 0.001], -[0.027, 0.009, 0.010], -[0.240, 0.023, 0.021], -[1.711, 0.030, 0.027], -[1.681, 0.099, 0.100], -[2.541, 0.232, 0.235], -[0.029, 0.015, 0.015], -[0.017, 0.011, 0.010], -[2.008, 0.342, 0.326], -[2.037, 0.374, 0.358], -[1.067, 0.158, 0.156], -[1.612, 0.181, 0.167], -[2.496, 0.504, 0.427], -[3.784, 0.561, 0.557], -[2.386, 0.576, 0.543], -[1.213, 0.554, 0.538], -[4.249, 1.422, 1.398], -[3.692, 0.825, 0.814], -[8.007, 2.959, 2.938], -[0.919, 0.155, 0.035], -[19.417, 0.492, 0.457], -[21.492, 0.593, 0.566], -[40.016, 1.311, 1.271], -[39.555, 0.602, 0.589], -[4.636, 0.217, 0.203], -[2.307, 0.161, 0.166], -[5.319, 0.209, 0.206], -[19.166, 0.501, 0.482], -[15.822, 0.718, 0.725], -[1.096, 1.070, 1.073], -[3.831, 0.496, 0.493], -[9.821, 0.731, 0.688], -[9.067, 3.928, 3.881], -[19.446, 1.976, 1.898], -[19.444, 2.021, 1.946], -[0.819, 0.777, 0.765], -[0.146, 0.115, 0.114], -[0.065, 0.051, 0.044], -[0.071, 0.047, 0.044], -[0.288, 0.211, 0.219], -[0.050, 0.019, 0.021], -[0.028, 0.012, 0.010], -[0.015, 0.010, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/21.01.9.41_adaptive.json b/website/benchmark/versions/results/21.01.9.41_adaptive.json deleted file mode 100644 index 541fdc92ffb..00000000000 --- a/website/benchmark/versions/results/21.01.9.41_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2021-04-13 21.1", - "system_full": "ClickHouse 21.1.9.41 2021-04-13(adaptive)", - "version": "21.1.9.41", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.020, 0.004, 0.004], -[0.011, 0.002, 0.002], -[0.015, 0.004, 0.004], -[0.009, 0.002, 0.002], -[0.014, 0.003, 0.003], -[0.010, 0.003, 0.003], -[0.015, 0.002, 0.002], -[0.081, 0.027, 0.026], -[0.017, 0.008, 0.010], -[0.016, 0.002, 0.002], -[1.227, 0.335, 0.302], -[0.656, 0.324, 0.325], -[0.033, 0.005, 0.004], -[0.034, 0.016, 0.015], -[0.128, 0.098, 0.159], -[3.429, 0.097, 0.096], -[3.031, 0.251, 0.227], -[3.755, 0.083, 0.081], -[1.369, 0.045, 0.052], -[4.412, 0.230, 0.219], -[4.211, 0.208, 0.177], -[1.788, 0.239, 0.222], -[63.467, 2.373, 2.366], -[42.459, 1.503, 1.478], -[3.071, 0.292, 0.278], -[1.660, 0.156, 0.151], -[0.360, 0.106, 0.097], -[4.666, 0.113, 0.111], -[0.011, 0.001, 0.001], -[0.027, 0.009, 0.010], -[0.277, 0.021, 0.023], -[1.700, 0.032, 0.028], -[1.670, 0.089, 0.092], -[2.554, 0.233, 0.245], -[0.028, 0.015, 0.014], -[0.020, 0.009, 0.009], -[1.918, 0.321, 0.326], -[2.024, 0.354, 0.360], -[1.097, 0.152, 0.143], -[1.621, 0.184, 0.194], -[2.496, 0.462, 0.443], -[3.811, 0.554, 0.557], -[2.351, 0.568, 0.549], -[1.207, 0.551, 0.545], -[4.253, 1.418, 1.384], -[3.671, 0.832, 0.817], -[7.993, 3.087, 2.952], -[0.922, 0.156, 0.059], -[19.390, 0.480, 0.464], -[21.473, 0.577, 0.559], -[40.078, 1.308, 1.275], -[39.505, 0.602, 0.584], -[4.637, 0.207, 0.194], -[2.299, 0.167, 0.165], -[5.295, 0.199, 0.204], -[19.197, 0.476, 0.489], -[15.831, 0.725, 0.711], -[1.103, 1.073, 1.068], -[3.845, 0.483, 0.477], -[9.822, 0.724, 0.689], -[9.006, 3.969, 3.947], -[19.443, 1.984, 1.918], -[19.262, 1.993, 1.926], -[0.823, 0.789, 0.754], -[0.161, 0.110, 0.111], -[0.064, 0.045, 0.048], -[0.070, 0.038, 0.048], -[0.290, 0.224, 0.235], -[0.050, 0.014, 0.013], -[0.040, 0.012, 0.022], -[0.013, 0.009, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/21.02.10.48_adaptive.json b/website/benchmark/versions/results/21.02.10.48_adaptive.json deleted file mode 100644 index 3c710e3e236..00000000000 --- a/website/benchmark/versions/results/21.02.10.48_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2021-04-17 21.2", - "system_full": "ClickHouse 21.2.10.48 2021-04-17(adaptive)", - "version": "21.2.10.48", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.023, 0.005, 0.005], -[0.012, 0.003, 0.003], -[0.017, 0.005, 0.005], -[0.011, 0.003, 0.003], -[0.015, 0.003, 0.003], -[0.011, 0.004, 0.004], -[0.015, 0.002, 0.002], -[0.060, 0.030, 0.021], -[0.018, 0.009, 0.008], -[0.009, 0.002, 0.002], -[1.213, 0.335, 0.324], -[0.799, 0.347, 0.310], -[0.032, 0.004, 0.004], -[0.031, 0.016, 0.017], -[0.117, 0.098, 0.101], -[3.424, 0.105, 0.096], -[3.014, 0.229, 0.234], -[3.778, 0.078, 0.077], -[1.376, 0.058, 0.057], -[4.413, 0.235, 0.198], -[4.229, 0.191, 0.201], -[1.805, 0.254, 0.228], -[63.464, 2.504, 2.457], -[42.441, 1.549, 1.610], -[3.070, 0.283, 0.279], -[1.675, 0.168, 0.168], -[0.329, 0.085, 0.085], -[4.712, 0.131, 0.104], -[0.010, 0.002, 0.002], -[0.026, 0.009, 0.009], -[0.301, 0.022, 0.022], -[1.726, 0.032, 0.032], -[1.664, 0.093, 0.101], -[2.568, 0.258, 0.247], -[0.031, 0.015, 0.016], -[0.018, 0.010, 0.010], -[1.946, 0.333, 0.317], -[2.054, 0.361, 0.371], -[1.065, 0.147, 0.150], -[1.611, 0.176, 0.164], -[2.519, 0.445, 0.442], -[3.793, 0.586, 0.572], -[2.383, 0.560, 0.555], -[1.202, 0.551, 0.557], -[4.249, 1.419, 1.410], -[3.666, 0.823, 0.793], -[7.991, 3.022, 2.988], -[0.921, 0.078, 0.040], -[19.494, 0.495, 0.472], -[21.495, 0.584, 0.589], -[40.170, 1.361, 1.331], -[39.540, 0.620, 0.591], -[4.635, 0.202, 0.201], -[2.313, 0.175, 0.176], -[5.267, 0.206, 0.191], -[19.183, 0.500, 0.493], -[15.799, 0.739, 0.723], -[1.083, 1.060, 1.060], -[3.830, 0.495, 0.470], -[9.810, 0.730, 0.724], -[9.028, 3.997, 3.907], -[19.429, 2.052, 1.930], -[19.247, 2.000, 1.937], -[0.825, 0.767, 0.785], -[0.170, 0.121, 0.113], -[0.066, 0.046, 0.044], -[0.075, 0.044, 0.038], -[0.296, 0.236, 0.241], -[0.047, 0.036, 0.026], -[0.036, 0.015, 0.021], -[0.017, 0.007, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/21.03.20.1_adaptive.json b/website/benchmark/versions/results/21.03.20.1_adaptive.json deleted file mode 100644 index 38aaf75dc91..00000000000 --- a/website/benchmark/versions/results/21.03.20.1_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2022-01-26 21.3", - "system_full": "ClickHouse 21.3.20.1 2022-01-26(adaptive)", - "version": "21.3.20.1", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.023, 0.004, 0.004], -[0.012, 0.003, 0.003], -[0.016, 0.004, 0.004], -[0.010, 0.002, 0.002], -[0.013, 0.003, 0.003], -[0.012, 0.004, 0.004], -[0.016, 0.002, 0.002], -[0.057, 0.026, 0.022], -[0.018, 0.007, 0.007], -[0.010, 0.002, 0.002], -[1.109, 0.317, 0.335], -[0.661, 0.338, 0.334], -[0.034, 0.004, 0.004], -[0.032, 0.016, 0.015], -[0.155, 0.100, 0.091], -[3.424, 0.111, 0.099], -[3.005, 0.234, 0.225], -[2.609, 0.064, 0.057], -[1.401, 0.056, 0.057], -[4.437, 0.220, 0.213], -[4.219, 0.196, 0.186], -[1.801, 0.231, 0.211], -[63.368, 2.297, 2.282], -[42.444, 1.494, 1.453], -[3.023, 0.297, 0.293], -[1.007, 0.149, 0.139], -[0.149, 0.079, 0.083], -[4.692, 0.122, 0.093], -[0.011, 0.002, 0.001], -[0.026, 0.010, 0.010], -[0.338, 0.022, 0.022], -[1.727, 0.035, 0.031], -[1.684, 0.090, 0.097], -[2.597, 0.218, 0.237], -[0.030, 0.014, 0.014], -[0.021, 0.010, 0.010], -[2.004, 0.330, 0.316], -[2.060, 0.364, 0.357], -[1.073, 0.149, 0.142], -[1.644, 0.166, 0.170], -[2.549, 0.445, 0.437], -[3.802, 0.568, 0.540], -[2.363, 0.558, 0.551], -[1.233, 0.562, 0.556], -[4.255, 1.424, 1.390], -[3.692, 0.809, 0.820], -[8.017, 3.001, 2.985], -[0.925, 0.134, 0.065], -[19.426, 0.507, 0.506], -[21.481, 0.571, 0.556], -[40.204, 1.353, 1.283], -[39.541, 0.624, 0.642], -[4.633, 0.192, 0.193], -[2.313, 0.174, 0.170], -[5.278, 0.196, 0.197], -[19.189, 0.503, 0.490], -[15.698, 0.724, 0.701], -[1.088, 1.064, 1.059], -[3.822, 0.467, 0.427], -[9.807, 0.712, 0.675], -[9.042, 3.844, 3.828], -[19.440, 2.069, 1.911], -[19.342, 1.989, 1.950], -[0.836, 0.754, 0.775], -[0.152, 0.111, 0.122], -[0.069, 0.046, 0.042], -[0.070, 0.040, 0.051], -[0.300, 0.228, 0.225], -[0.053, 0.015, 0.034], -[0.030, 0.011, 0.013], -[0.015, 0.003, 0.012] - ] - } -] diff --git a/website/benchmark/versions/results/21.04.7.3_adaptive.json b/website/benchmark/versions/results/21.04.7.3_adaptive.json deleted file mode 100644 index bb5edb4e447..00000000000 --- a/website/benchmark/versions/results/21.04.7.3_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2021-05-20 21.4", - "system_full": "ClickHouse 21.4.7.3 2021-05-20(adaptive)", - "version": "21.4.7.3", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.021, 0.004, 0.004], -[0.013, 0.003, 0.003], -[0.016, 0.005, 0.005], -[0.010, 0.003, 0.003], -[0.014, 0.003, 0.003], -[0.010, 0.003, 0.003], -[0.017, 0.002, 0.002], -[0.060, 0.027, 0.026], -[0.015, 0.012, 0.009], -[0.009, 0.002, 0.002], -[1.193, 0.327, 0.326], -[0.633, 0.344, 0.327], -[0.032, 0.005, 0.004], -[0.032, 0.015, 0.015], -[0.121, 0.105, 0.103], -[3.414, 0.098, 0.102], -[11.208, 0.217, 0.209], -[0.237, 0.072, 0.074], -[1.411, 0.051, 0.126], -[4.444, 0.218, 0.203], -[4.219, 0.175, 0.175], -[1.788, 0.233, 0.236], -[22.316, 1.279, 1.214], -[25.837, 1.361, 1.323], -[12.063, 1.031, 1.059], -[0.299, 0.169, 0.160], -[0.114, 0.078, 0.083], -[0.945, 0.061, 0.059], -[0.011, 0.002, 0.002], -[0.030, 0.010, 0.008], -[0.058, 0.022, 0.021], -[1.447, 0.034, 0.030], -[1.518, 0.107, 0.095], -[2.377, 0.218, 0.217], -[0.028, 0.013, 0.013], -[0.019, 0.010, 0.010], -[1.541, 0.324, 0.330], -[1.872, 0.376, 0.379], -[1.032, 0.130, 0.139], -[1.472, 0.157, 0.150], -[1.846, 0.445, 0.432], -[3.153, 0.560, 0.556], -[1.698, 0.529, 0.543], -[1.213, 0.569, 0.543], -[3.571, 1.416, 1.398], -[2.978, 0.853, 0.810], -[7.312, 3.041, 2.953], -[0.916, 0.101, 0.058], -[19.296, 0.431, 0.426], -[2.597, 0.519, 0.501], -[21.286, 1.229, 1.181], -[18.186, 0.611, 0.571], -[3.249, 0.207, 0.173], -[1.183, 0.161, 0.163], -[3.153, 0.194, 0.185], -[0.764, 0.432, 0.429], -[15.719, 0.706, 0.686], -[1.082, 1.065, 1.064], -[2.955, 0.455, 0.444], -[8.856, 0.658, 0.626], -[5.234, 3.545, 3.447], -[1.934, 1.909, 1.853], -[1.853, 1.938, 1.840], -[0.802, 0.772, 0.765], -[0.130, 0.109, 0.108], -[0.062, 0.051, 0.053], -[0.054, 0.042, 0.043], -[0.239, 0.242, 0.214], -[0.039, 0.012, 0.011], -[0.031, 0.010, 0.018], -[0.020, 0.014, 0.004] - ] - } -] diff --git a/website/benchmark/versions/results/21.05.9.4_adaptive.json b/website/benchmark/versions/results/21.05.9.4_adaptive.json deleted file mode 100644 index 05f7bfa2a0b..00000000000 --- a/website/benchmark/versions/results/21.05.9.4_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2021-07-10 21.5", - "system_full": "ClickHouse 21.5.9.4 2021-07-10(adaptive)", - "version": "21.5.9.4", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.022, 0.004, 0.004], -[0.014, 0.002, 0.003], -[0.016, 0.004, 0.004], -[0.009, 0.002, 0.003], -[0.013, 0.003, 0.003], -[0.011, 0.003, 0.003], -[0.017, 0.002, 0.002], -[0.072, 0.026, 0.026], -[0.016, 0.007, 0.007], -[0.008, 0.002, 0.002], -[1.156, 0.347, 0.318], -[0.608, 0.336, 0.324], -[0.033, 0.004, 0.004], -[0.029, 0.015, 0.014], -[0.112, 0.102, 0.095], -[3.432, 0.115, 0.114], -[11.038, 0.224, 0.225], -[2.938, 0.086, 0.084], -[1.367, 0.044, 0.047], -[4.466, 0.223, 0.214], -[4.230, 0.195, 0.181], -[1.826, 0.228, 0.223], -[22.627, 1.270, 1.250], -[26.679, 1.423, 1.379], -[28.615, 1.147, 1.084], -[0.607, 0.139, 0.155], -[0.112, 0.079, 0.080], -[1.520, 0.063, 0.056], -[0.010, 0.002, 0.002], -[0.026, 0.010, 0.009], -[0.426, 0.023, 0.020], -[1.734, 0.036, 0.031], -[1.696, 0.093, 0.084], -[2.596, 0.236, 0.224], -[0.030, 0.015, 0.015], -[0.020, 0.011, 0.010], -[1.970, 0.314, 0.313], -[2.073, 0.367, 0.358], -[1.068, 0.163, 0.136], -[1.622, 0.174, 0.169], -[2.539, 0.467, 0.469], -[3.845, 0.562, 0.563], -[2.331, 0.540, 0.539], -[1.217, 0.570, 0.551], -[4.257, 1.407, 1.449], -[3.663, 0.813, 0.832], -[8.009, 2.986, 3.047], -[0.922, 0.141, 0.066], -[19.407, 0.458, 0.454], -[21.493, 0.551, 0.540], -[40.210, 1.275, 1.246], -[39.569, 0.604, 0.596], -[4.654, 0.213, 0.170], -[2.342, 0.157, 0.167], -[5.301, 0.190, 0.200], -[19.220, 0.498, 0.482], -[15.816, 0.692, 0.692], -[1.096, 1.063, 1.059], -[3.858, 0.461, 0.431], -[9.850, 0.673, 0.637], -[9.063, 3.608, 3.629], -[19.417, 1.977, 1.908], -[19.431, 2.004, 1.907], -[0.838, 0.800, 0.777], -[0.175, 0.110, 0.108], -[0.076, 0.046, 0.054], -[0.080, 0.042, 0.035], -[0.327, 0.250, 0.235], -[0.037, 0.017, 0.011], -[0.031, 0.021, 0.009], -[0.014, 0.015, 0.017] - ] - } -] diff --git a/website/benchmark/versions/results/21.06.9.7_adaptive.json b/website/benchmark/versions/results/21.06.9.7_adaptive.json deleted file mode 100644 index 5ddb79105e4..00000000000 --- a/website/benchmark/versions/results/21.06.9.7_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2021-09-03 21.6", - "system_full": "ClickHouse 21.6.9.7 2021-09-03(adaptive)", - "version": "21.6.9.7", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.021, 0.003, 0.003], -[0.012, 0.002, 0.002], -[0.018, 0.004, 0.004], -[0.010, 0.002, 0.002], -[0.014, 0.003, 0.002], -[0.014, 0.003, 0.003], -[0.014, 0.002, 0.002], -[0.066, 0.028, 0.027], -[0.059, 0.009, 0.007], -[0.010, 0.002, 0.002], -[1.226, 0.350, 0.349], -[0.573, 0.341, 0.322], -[0.032, 0.005, 0.006], -[0.033, 0.015, 0.015], -[0.112, 0.095, 0.099], -[3.431, 0.112, 0.143], -[3.106, 0.231, 0.226], -[2.623, 0.072, 0.060], -[1.397, 0.058, 0.056], -[4.432, 0.237, 0.225], -[4.207, 0.208, 0.196], -[1.801, 0.233, 0.250], -[63.562, 2.649, 2.593], -[42.495, 1.661, 1.672], -[3.054, 0.292, 0.266], -[1.139, 0.176, 0.177], -[0.130, 0.070, 0.088], -[4.663, 0.108, 0.126], -[0.010, 0.001, 0.002], -[0.026, 0.009, 0.009], -[0.323, 0.023, 0.022], -[1.709, 0.034, 0.033], -[1.703, 0.091, 0.092], -[2.612, 0.234, 0.237], -[0.027, 0.014, 0.014], -[0.029, 0.009, 0.009], -[1.967, 0.330, 0.323], -[2.053, 0.371, 0.358], -[1.059, 0.140, 0.130], -[1.663, 0.165, 0.149], -[2.534, 0.450, 0.448], -[3.803, 0.575, 0.549], -[2.410, 0.557, 0.532], -[1.230, 0.557, 0.563], -[4.250, 1.433, 1.387], -[3.695, 0.832, 0.823], -[8.005, 3.048, 2.991], -[0.925, 0.108, 0.052], -[19.516, 0.509, 0.473], -[21.482, 0.587, 0.571], -[40.055, 1.356, 1.337], -[39.576, 0.644, 0.607], -[4.633, 0.201, 0.207], -[2.300, 0.173, 0.178], -[5.286, 0.205, 0.201], -[19.282, 0.497, 0.506], -[15.767, 0.751, 0.724], -[1.095, 1.065, 1.065], -[3.838, 0.448, 0.448], -[9.826, 0.674, 0.645], -[9.114, 3.490, 3.507], -[19.456, 1.993, 1.956], -[19.353, 2.015, 1.956], -[0.824, 0.785, 0.779], -[0.166, 0.116, 0.106], -[0.091, 0.052, 0.066], -[0.076, 0.039, 0.048], -[0.305, 0.240, 0.251], -[0.046, 0.027, 0.027], -[0.056, 0.023, 0.011], -[0.017, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/21.07.11.3_adaptive.json b/website/benchmark/versions/results/21.07.11.3_adaptive.json deleted file mode 100644 index 76b181ad253..00000000000 --- a/website/benchmark/versions/results/21.07.11.3_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2021-09-24 21.7", - "system_full": "ClickHouse 21.7.11.3 2021-09-24(adaptive)", - "version": "2021-09-24", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.017, 0.004, 0.003], -[0.011, 0.002, 0.002], -[0.016, 0.004, 0.004], -[0.010, 0.002, 0.002], -[0.014, 0.002, 0.002], -[0.013, 0.003, 0.003], -[0.016, 0.003, 0.002], -[0.056, 0.025, 0.029], -[0.013, 0.007, 0.012], -[0.010, 0.002, 0.002], -[1.197, 0.338, 0.385], -[0.744, 0.328, 0.309], -[0.030, 0.005, 0.004], -[0.030, 0.017, 0.015], -[0.117, 0.092, 0.104], -[3.411, 0.106, 0.101], -[2.986, 0.233, 0.229], -[2.630, 0.065, 0.066], -[1.400, 0.057, 0.054], -[4.443, 0.230, 0.226], -[4.219, 0.199, 0.205], -[1.785, 0.229, 0.238], -[63.427, 2.479, 2.411], -[42.498, 1.582, 1.532], -[3.005, 0.297, 0.279], -[1.045, 0.157, 0.189], -[0.118, 0.082, 0.089], -[4.653, 0.136, 0.098], -[0.010, 0.001, 0.001], -[0.025, 0.012, 0.009], -[0.319, 0.023, 0.023], -[1.723, 0.032, 0.032], -[1.682, 0.092, 0.091], -[2.607, 0.242, 0.225], -[0.027, 0.014, 0.014], -[0.019, 0.010, 0.009], -[1.980, 0.323, 0.328], -[2.042, 0.363, 0.371], -[1.064, 0.135, 0.143], -[1.659, 0.172, 0.172], -[2.533, 0.484, 0.440], -[3.839, 0.598, 0.547], -[2.378, 0.583, 0.562], -[1.223, 0.562, 0.556], -[4.259, 1.447, 1.399], -[3.713, 0.862, 0.825], -[8.055, 3.074, 3.056], -[0.921, 0.124, 0.044], -[19.450, 0.493, 0.476], -[21.501, 0.585, 0.568], -[40.294, 1.348, 1.326], -[39.583, 0.622, 0.615], -[4.634, 0.220, 0.196], -[2.309, 0.173, 0.176], -[5.275, 0.214, 0.206], -[19.186, 0.524, 0.479], -[15.773, 0.744, 0.727], -[1.093, 1.071, 1.070], -[3.858, 0.469, 0.437], -[9.827, 0.661, 0.650], -[9.028, 3.542, 3.558], -[19.424, 2.010, 1.953], -[19.434, 2.045, 1.965], -[0.814, 0.792, 0.775], -[0.158, 0.113, 0.119], -[0.069, 0.043, 0.047], -[0.073, 0.045, 0.056], -[0.301, 0.243, 0.231], -[0.047, 0.013, 0.018], -[0.037, 0.015, 0.011], -[0.013, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/21.08.15.7_adaptive.json b/website/benchmark/versions/results/21.08.15.7_adaptive.json deleted file mode 100644 index 8607d494cb4..00000000000 --- a/website/benchmark/versions/results/21.08.15.7_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2022-02-25 21.8", - "system_full": "ClickHouse 21.8.15.7 2022-02-25(adaptive)", - "version": "21.8.15.7", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.019, 0.004, 0.003], -[0.010, 0.003, 0.002], -[0.017, 0.004, 0.004], -[0.010, 0.002, 0.002], -[0.014, 0.003, 0.002], -[0.012, 0.003, 0.003], -[0.016, 0.002, 0.003], -[0.060, 0.029, 0.025], -[0.014, 0.007, 0.007], -[0.009, 0.002, 0.002], -[1.137, 0.343, 0.351], -[0.752, 0.350, 0.329], -[0.030, 0.006, 0.004], -[0.031, 0.016, 0.015], -[0.113, 0.098, 0.095], -[3.412, 0.112, 0.102], -[2.999, 0.230, 0.224], -[2.620, 0.068, 0.073], -[1.394, 0.055, 0.056], -[4.439, 0.232, 0.242], -[4.215, 0.218, 0.187], -[1.819, 0.230, 0.226], -[63.446, 2.475, 2.432], -[42.542, 1.559, 1.545], -[2.991, 0.297, 0.279], -[1.020, 0.154, 0.144], -[0.124, 0.078, 0.083], -[4.689, 0.123, 0.118], -[0.012, 0.001, 0.001], -[0.027, 0.011, 0.009], -[0.286, 0.025, 0.022], -[1.709, 0.034, 0.032], -[1.692, 0.092, 0.095], -[2.611, 0.230, 0.226], -[0.026, 0.013, 0.014], -[0.020, 0.010, 0.010], -[1.973, 0.339, 0.326], -[2.014, 0.380, 0.372], -[1.034, 0.139, 0.145], -[1.661, 0.162, 0.164], -[2.537, 0.475, 0.450], -[3.815, 0.588, 0.544], -[2.368, 0.559, 0.550], -[1.201, 0.575, 0.563], -[4.254, 1.424, 1.416], -[3.701, 0.841, 0.837], -[8.060, 3.123, 3.095], -[0.922, 0.176, 0.068], -[19.380, 0.486, 0.476], -[21.535, 0.622, 0.582], -[40.306, 1.364, 1.329], -[39.580, 0.622, 0.605], -[4.635, 0.199, 0.194], -[2.337, 0.174, 0.165], -[5.270, 0.194, 0.196], -[19.226, 0.522, 0.508], -[15.791, 0.734, 0.727], -[1.093, 1.067, 1.070], -[3.841, 0.479, 0.452], -[9.837, 0.660, 0.670], -[9.048, 3.597, 3.576], -[19.469, 1.964, 1.962], -[19.474, 1.983, 1.981], -[0.839, 0.781, 0.772], -[0.149, 0.112, 0.110], -[0.072, 0.059, 0.053], -[0.086, 0.047, 0.041], -[0.308, 0.227, 0.234], -[0.047, 0.023, 0.013], -[0.029, 0.012, 0.014], -[0.026, 0.004, 0.005] - ] - } -] diff --git a/website/benchmark/versions/results/21.09.6.24_adaptive.json b/website/benchmark/versions/results/21.09.6.24_adaptive.json deleted file mode 100644 index 447f7bef853..00000000000 --- a/website/benchmark/versions/results/21.09.6.24_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2021-12-02 21.9", - "system_full": "ClickHouse 21.9.6.24 2021-12-02(adaptive)", - "version": "2021-12-02", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.022, 0.004, 0.003], -[0.014, 0.002, 0.002], -[0.018, 0.004, 0.004], -[0.010, 0.002, 0.002], -[0.015, 0.003, 0.002], -[0.012, 0.003, 0.003], -[0.017, 0.002, 0.002], -[0.071, 0.035, 0.032], -[0.017, 0.007, 0.007], -[0.045, 0.002, 0.002], -[0.920, 0.346, 0.332], -[0.573, 0.286, 0.285], -[0.032, 0.006, 0.005], -[0.028, 0.014, 0.018], -[0.116, 0.099, 0.101], -[3.420, 0.110, 0.099], -[2.968, 0.295, 0.235], -[2.711, 0.067, 0.064], -[1.415, 0.065, 0.060], -[4.427, 0.222, 0.241], -[4.183, 0.180, 0.184], -[1.854, 0.233, 0.244], -[63.467, 2.360, 2.306], -[42.527, 1.533, 1.488], -[3.013, 0.286, 0.275], -[1.009, 0.163, 0.178], -[0.150, 0.078, 0.084], -[4.553, 0.123, 0.097], -[0.011, 0.001, 0.001], -[0.026, 0.010, 0.008], -[0.249, 0.023, 0.022], -[1.679, 0.031, 0.032], -[1.660, 0.096, 0.097], -[2.579, 0.228, 0.215], -[0.031, 0.012, 0.013], -[0.022, 0.009, 0.009], -[1.960, 0.321, 0.322], -[2.028, 0.348, 0.357], -[1.069, 0.126, 0.143], -[1.648, 0.157, 0.156], -[2.569, 0.454, 0.427], -[3.799, 0.571, 0.563], -[2.363, 0.552, 0.529], -[1.174, 0.511, 0.501], -[4.261, 1.418, 1.387], -[3.693, 0.856, 0.806], -[7.945, 2.932, 2.995], -[0.919, 0.120, 0.082], -[19.420, 0.502, 0.473], -[21.480, 0.609, 0.590], -[40.279, 1.340, 1.318], -[39.461, 0.624, 0.604], -[4.635, 0.206, 0.202], -[2.313, 0.164, 0.166], -[5.325, 0.201, 0.191], -[19.250, 0.491, 0.481], -[15.833, 0.713, 0.710], -[1.100, 1.070, 1.067], -[3.816, 0.426, 0.424], -[9.758, 0.568, 0.546], -[8.369, 2.686, 2.746], -[19.439, 1.971, 1.923], -[19.436, 1.985, 1.931], -[0.781, 0.723, 0.728], -[0.150, 0.143, 0.108], -[0.078, 0.054, 0.048], -[0.071, 0.050, 0.075], -[0.313, 0.224, 0.214], -[0.044, 0.016, 0.025], -[0.032, 0.014, 0.020], -[0.022, 0.015, 0.010] - ] - } -] diff --git a/website/benchmark/versions/results/21.10.6.2_adaptive.json b/website/benchmark/versions/results/21.10.6.2_adaptive.json deleted file mode 100644 index bc77e0e8889..00000000000 --- a/website/benchmark/versions/results/21.10.6.2_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2022-01-24 21.10", - "system_full": "ClickHouse 21.10.6.2 2022-01-24(adaptive)", - "version": "21.10.6.2", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.021, 0.004, 0.004], -[0.012, 0.002, 0.002], -[0.016, 0.004, 0.004], -[0.010, 0.002, 0.002], -[0.013, 0.003, 0.002], -[0.011, 0.003, 0.003], -[0.015, 0.002, 0.002], -[0.062, 0.032, 0.035], -[0.019, 0.007, 0.007], -[0.045, 0.002, 0.002], -[1.173, 0.329, 0.302], -[0.622, 0.276, 0.278], -[0.033, 0.004, 0.006], -[0.035, 0.015, 0.014], -[0.125, 0.094, 0.099], -[3.431, 0.106, 0.098], -[2.989, 0.241, 0.226], -[2.769, 0.062, 0.059], -[1.413, 0.051, 0.048], -[4.433, 0.245, 0.211], -[4.198, 0.183, 0.172], -[1.837, 0.223, 0.232], -[63.368, 2.339, 2.324], -[42.447, 1.474, 1.469], -[3.007, 0.296, 0.274], -[1.002, 0.167, 0.176], -[0.143, 0.093, 0.082], -[4.599, 0.118, 0.098], -[0.010, 0.001, 0.001], -[0.026, 0.009, 0.007], -[0.321, 0.023, 0.023], -[1.734, 0.031, 0.030], -[1.696, 0.090, 0.100], -[2.586, 0.217, 0.222], -[0.026, 0.013, 0.013], -[0.020, 0.008, 0.009], -[2.026, 0.322, 0.312], -[2.059, 0.355, 0.348], -[1.109, 0.130, 0.137], -[1.656, 0.152, 0.148], -[2.565, 0.420, 0.410], -[3.843, 0.565, 0.536], -[2.362, 0.510, 0.521], -[1.177, 0.494, 0.480], -[4.206, 1.336, 1.315], -[3.694, 0.828, 0.802], -[7.829, 2.826, 2.838], -[0.933, 0.101, 0.050], -[19.482, 0.505, 0.470], -[21.289, 0.582, 0.565], -[39.997, 1.372, 1.307], -[39.358, 0.656, 0.609], -[4.636, 0.200, 0.177], -[2.328, 0.155, 0.167], -[5.278, 0.198, 0.177], -[19.227, 0.519, 0.507], -[15.788, 0.749, 0.717], -[1.093, 1.063, 1.060], -[3.848, 0.412, 0.418], -[9.738, 0.557, 0.528], -[8.344, 2.681, 2.646], -[19.421, 1.944, 1.914], -[19.217, 1.937, 1.929], -[0.798, 0.711, 0.704], -[0.165, 0.111, 0.109], -[0.081, 0.045, 0.040], -[0.071, 0.060, 0.052], -[0.294, 0.245, 0.223], -[0.053, 0.025, 0.016], -[0.030, 0.018, 0.023], -[0.017, 0.005, 0.012] - ] - } -] diff --git a/website/benchmark/versions/results/21.11.11.1_adaptive.json b/website/benchmark/versions/results/21.11.11.1_adaptive.json deleted file mode 100644 index 9228e607e77..00000000000 --- a/website/benchmark/versions/results/21.11.11.1_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2022-01-23 21.11", - "system_full": "ClickHouse 21.11.11.1 2022-01-23(adaptive)", - "version": "21.11.11.1", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.031, 0.004, 0.003], -[0.017, 0.002, 0.002], -[0.024, 0.004, 0.004], -[0.015, 0.002, 0.002], -[0.019, 0.002, 0.002], -[0.019, 0.003, 0.003], -[0.020, 0.002, 0.002], -[0.060, 0.023, 0.022], -[0.023, 0.007, 0.007], -[0.052, 0.002, 0.002], -[1.080, 0.333, 0.317], -[0.795, 0.262, 0.279], -[0.038, 0.004, 0.004], -[0.037, 0.014, 0.016], -[0.116, 0.099, 0.094], -[3.492, 0.085, 0.074], -[2.783, 0.223, 0.218], -[2.676, 0.055, 0.051], -[1.449, 0.054, 0.052], -[4.442, 0.202, 0.196], -[4.269, 0.180, 0.170], -[1.907, 0.225, 0.213], -[63.380, 2.320, 2.283], -[42.459, 1.482, 1.417], -[2.926, 0.288, 0.279], -[1.156, 0.140, 0.141], -[0.135, 0.080, 0.069], -[3.156, 0.090, 0.092], -[0.018, 0.002, 0.001], -[0.031, 0.008, 0.007], -[0.396, 0.020, 0.021], -[1.745, 0.030, 0.030], -[1.695, 0.086, 0.084], -[2.610, 0.220, 0.204], -[0.031, 0.011, 0.011], -[0.027, 0.008, 0.008], -[2.071, 0.313, 0.306], -[2.075, 0.333, 0.334], -[1.133, 0.118, 0.113], -[1.693, 0.136, 0.131], -[2.607, 0.391, 0.372], -[3.839, 0.520, 0.506], -[2.428, 0.521, 0.494], -[1.176, 0.473, 0.463], -[4.198, 1.345, 1.298], -[3.697, 0.809, 0.768], -[7.942, 2.859, 2.861], -[0.935, 0.064, 0.031], -[19.534, 0.490, 0.468], -[21.514, 0.556, 0.536], -[40.022, 1.329, 1.267], -[33.001, 0.673, 0.575], -[4.655, 0.167, 0.153], -[2.383, 0.135, 0.128], -[5.388, 0.163, 0.155], -[19.305, 0.509, 0.484], -[15.801, 0.738, 0.714], -[1.080, 1.059, 1.061], -[3.817, 0.371, 0.364], -[9.735, 0.504, 0.484], -[8.324, 2.674, 2.645], -[19.419, 1.884, 1.860], -[19.383, 1.888, 1.861], -[0.785, 0.691, 0.691], -[0.141, 0.108, 0.106], -[0.078, 0.040, 0.050], -[0.085, 0.048, 0.039], -[0.292, 0.232, 0.240], -[0.052, 0.025, 0.022], -[0.036, 0.020, 0.013], -[0.022, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/21.12.4.1_adaptive.json b/website/benchmark/versions/results/21.12.4.1_adaptive.json deleted file mode 100644 index d5d4ff0db8d..00000000000 --- a/website/benchmark/versions/results/21.12.4.1_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2022-01-23 21.12", - "system_full": "ClickHouse 21.12.4.1 2022-01-23(adaptive)", - "version": "21.12.4.1", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.036, 0.003, 0.003], -[0.020, 0.002, 0.002], -[0.025, 0.004, 0.004], -[0.019, 0.002, 0.002], -[0.020, 0.002, 0.002], -[0.020, 0.003, 0.003], -[0.022, 0.002, 0.002], -[0.067, 0.023, 0.027], -[0.024, 0.007, 0.007], -[0.060, 0.002, 0.002], -[1.121, 0.334, 0.318], -[0.607, 0.259, 0.260], -[0.038, 0.004, 0.004], -[0.039, 0.018, 0.014], -[0.120, 0.107, 0.096], -[3.493, 0.078, 0.075], -[2.812, 0.229, 0.226], -[2.654, 0.058, 0.052], -[1.437, 0.051, 0.051], -[4.449, 0.213, 0.200], -[4.273, 0.176, 0.166], -[1.864, 0.222, 0.213], -[63.700, 2.301, 2.246], -[42.592, 1.463, 1.410], -[2.933, 0.307, 0.283], -[1.117, 0.163, 0.134], -[0.135, 0.083, 0.077], -[3.151, 0.083, 0.080], -[0.019, 0.001, 0.001], -[0.033, 0.008, 0.008], -[0.407, 0.022, 0.021], -[1.755, 0.035, 0.030], -[1.719, 0.084, 0.087], -[2.633, 0.224, 0.212], -[0.032, 0.011, 0.010], -[0.028, 0.008, 0.008], -[2.071, 0.311, 0.300], -[2.107, 0.331, 0.327], -[1.158, 0.115, 0.112], -[1.707, 0.133, 0.128], -[2.621, 0.378, 0.369], -[3.826, 0.522, 0.488], -[2.445, 0.522, 0.521], -[1.181, 0.466, 0.461], -[4.246, 1.319, 1.293], -[3.692, 0.805, 0.761], -[7.928, 2.828, 2.907], -[0.920, 0.071, 0.033], -[19.573, 0.501, 0.472], -[21.520, 0.565, 0.550], -[40.245, 1.305, 1.263], -[33.018, 0.620, 0.574], -[4.656, 0.157, 0.151], -[2.426, 0.149, 0.134], -[5.345, 0.169, 0.154], -[19.347, 0.520, 0.493], -[15.793, 0.753, 0.728], -[1.082, 1.058, 1.057], -[3.851, 0.355, 0.359], -[9.758, 0.508, 0.485], -[8.379, 2.657, 2.712], -[19.461, 1.884, 1.886], -[19.397, 1.896, 1.854], -[0.785, 0.696, 0.689], -[0.137, 0.116, 0.117], -[0.076, 0.040, 0.060], -[0.080, 0.043, 0.043], -[0.320, 0.253, 0.235], -[0.064, 0.014, 0.022], -[0.048, 0.014, 0.030], -[0.020, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/22.1.4.30_adaptive.json b/website/benchmark/versions/results/22.1.4.30_adaptive.json deleted file mode 100644 index 0cc2ea8f48a..00000000000 --- a/website/benchmark/versions/results/22.1.4.30_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2022-02-25 22.1", - "system_full": "ClickHouse 22.1.4.30 2022-02-25(adaptive)", - "version": "22.1.4.30", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.026, 0.003, 0.003], -[0.021, 0.002, 0.002], -[0.024, 0.004, 0.004], -[0.019, 0.002, 0.002], -[0.020, 0.002, 0.002], -[0.020, 0.003, 0.003], -[0.021, 0.002, 0.002], -[0.076, 0.026, 0.023], -[0.026, 0.007, 0.008], -[0.057, 0.002, 0.002], -[1.235, 0.335, 0.323], -[0.605, 0.295, 0.259], -[0.037, 0.006, 0.006], -[0.039, 0.016, 0.016], -[0.134, 0.102, 0.105], -[3.485, 0.082, 0.074], -[2.802, 0.224, 0.232], -[2.682, 0.062, 0.056], -[1.436, 0.051, 0.051], -[4.452, 0.225, 0.190], -[4.245, 0.189, 0.172], -[1.951, 0.225, 0.212], -[63.456, 2.290, 2.253], -[42.478, 1.453, 1.417], -[2.930, 0.301, 0.280], -[1.145, 0.144, 0.137], -[0.136, 0.079, 0.078], -[3.136, 0.105, 0.081], -[0.020, 0.001, 0.001], -[0.035, 0.008, 0.008], -[0.391, 0.021, 0.021], -[1.751, 0.032, 0.031], -[1.697, 0.083, 0.085], -[2.610, 0.208, 0.212], -[0.033, 0.012, 0.012], -[0.026, 0.008, 0.008], -[1.972, 0.319, 0.304], -[2.083, 0.335, 0.328], -[1.087, 0.127, 0.113], -[1.670, 0.128, 0.123], -[2.615, 0.381, 0.364], -[3.817, 0.521, 0.486], -[2.390, 0.511, 0.502], -[1.148, 0.472, 0.463], -[4.492, 1.327, 1.318], -[3.722, 0.828, 0.818], -[8.008, 2.936, 2.951], -[0.941, 0.058, 0.031], -[19.524, 0.518, 0.500], -[21.530, 0.565, 0.549], -[40.236, 1.326, 1.276], -[32.973, 0.629, 0.603], -[4.656, 0.161, 0.158], -[2.387, 0.138, 0.127], -[5.362, 0.164, 0.154], -[19.264, 0.502, 0.487], -[15.836, 0.733, 0.726], -[1.083, 1.061, 1.059], -[3.832, 0.359, 0.350], -[9.755, 0.511, 0.485], -[8.436, 2.740, 2.725], -[19.561, 2.010, 2.005], -[19.328, 2.023, 2.008], -[0.774, 0.702, 0.714], -[0.152, 0.113, 0.111], -[0.069, 0.050, 0.056], -[0.086, 0.052, 0.041], -[0.328, 0.247, 0.264], -[0.056, 0.015, 0.016], -[0.040, 0.016, 0.017], -[0.019, 0.003, 0.003] - ] - } -] diff --git a/website/benchmark/versions/results/22.2.3.5_adaptive.json b/website/benchmark/versions/results/22.2.3.5_adaptive.json deleted file mode 100644 index d7c61f7fe03..00000000000 --- a/website/benchmark/versions/results/22.2.3.5_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2022-02-25 22.2", - "system_full": "ClickHouse 22.2.3.5 2022-02-25(adaptive)", - "version": "22.2.3.5", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.028, 0.004, 0.004], -[0.021, 0.003, 0.003], -[0.025, 0.004, 0.005], -[0.018, 0.003, 0.003], -[0.020, 0.003, 0.003], -[0.021, 0.004, 0.004], -[0.024, 0.002, 0.002], -[0.086, 0.036, 0.032], -[0.024, 0.011, 0.009], -[0.055, 0.002, 0.002], -[1.146, 0.330, 0.323], -[0.605, 0.282, 0.259], -[0.040, 0.007, 0.006], -[0.040, 0.014, 0.014], -[0.131, 0.112, 0.091], -[3.459, 0.083, 0.078], -[2.788, 0.227, 0.220], -[2.657, 0.063, 0.053], -[1.441, 0.050, 0.058], -[4.444, 0.207, 0.197], -[4.273, 0.178, 0.177], -[1.864, 0.217, 0.218], -[63.697, 2.299, 2.228], -[42.575, 1.462, 1.420], -[2.934, 0.314, 0.286], -[1.139, 0.151, 0.156], -[0.136, 0.074, 0.082], -[3.131, 0.088, 0.083], -[0.022, 0.002, 0.001], -[0.035, 0.009, 0.009], -[0.371, 0.023, 0.021], -[1.697, 0.034, 0.034], -[1.706, 0.087, 0.082], -[2.618, 0.209, 0.195], -[0.019, 0.002, 0.001], -[0.027, 0.009, 0.008], -[2.084, 0.314, 0.303], -[2.126, 0.344, 0.329], -[1.116, 0.117, 0.115], -[1.682, 0.137, 0.131], -[2.646, 0.386, 0.371], -[3.832, 0.514, 0.487], -[2.412, 0.505, 0.492], -[1.179, 0.457, 0.463], -[4.264, 1.362, 1.369], -[3.729, 0.856, 0.796], -[7.967, 3.005, 2.935], -[0.937, 0.059, 0.033], -[19.577, 0.544, 0.523], -[21.519, 0.593, 0.556], -[40.278, 1.335, 1.281], -[33.029, 0.672, 0.644], -[4.674, 0.161, 0.158], -[2.398, 0.136, 0.130], -[5.391, 0.175, 0.157], -[19.313, 0.515, 0.496], -[15.716, 0.736, 0.713], -[1.089, 1.067, 1.066], -[3.787, 0.366, 0.346], -[9.775, 0.510, 0.497], -[8.451, 2.676, 2.772], -[19.577, 2.037, 2.001], -[19.571, 2.024, 2.008], -[0.814, 0.718, 0.712], -[0.158, 0.107, 0.112], -[0.082, 0.041, 0.045], -[0.096, 0.068, 0.045], -[0.305, 0.250, 0.250], -[0.055, 0.027, 0.015], -[0.037, 0.013, 0.012], -[0.025, 0.004, 0.005] - ] - } -] diff --git a/website/benchmark/versions/results/22.3.3.44_adaptive.json b/website/benchmark/versions/results/22.3.3.44_adaptive.json deleted file mode 100644 index bc236a438d6..00000000000 --- a/website/benchmark/versions/results/22.3.3.44_adaptive.json +++ /dev/null @@ -1,85 +0,0 @@ - -[ - { - "system": "2022-04-06 22.3", - "system_full": "ClickHouse 22.3.3.44 2022-04-06(adaptive)", - "version": "22.3.3.44", - "kind": "adaptive", - "comments": "", - "result": - [ - -[0.031, 0.004, 0.004], -[0.021, 0.003, 0.003], -[0.026, 0.005, 0.005], -[0.019, 0.002, 0.003], -[0.021, 0.003, 0.003], -[0.024, 0.004, 0.004], -[0.023, 0.002, 0.002], -[0.130, 0.032, 0.029], -[0.027, 0.008, 0.008], -[0.057, 0.002, 0.002], -[1.145, 0.328, 0.313], -[0.635, 0.267, 0.297], -[0.039, 0.006, 0.009], -[0.038, 0.017, 0.015], -[0.129, 0.106, 0.104], -[3.447, 0.081, 0.070], -[2.784, 0.233, 0.223], -[2.670, 0.057, 0.056], -[1.449, 0.054, 0.052], -[4.461, 0.217, 0.196], -[4.270, 0.179, 0.171], -[1.845, 0.220, 0.205], -[63.657, 2.305, 2.263], -[42.601, 1.471, 1.432], -[2.952, 0.294, 0.280], -[1.143, 0.142, 0.146], -[0.134, 0.084, 0.080], -[3.128, 0.085, 0.082], -[0.021, 0.002, 0.002], -[0.034, 0.008, 0.008], -[0.365, 0.022, 0.021], -[1.751, 0.033, 0.032], -[1.691, 0.088, 0.088], -[2.620, 0.216, 0.203], -[0.018, 0.002, 0.001], -[0.026, 0.008, 0.008], -[2.045, 0.318, 0.309], -[2.074, 0.343, 0.336], -[1.119, 0.120, 0.119], -[1.674, 0.141, 0.138], -[2.637, 0.389, 0.374], -[3.842, 0.510, 0.488], -[2.378, 0.516, 0.495], -[1.185, 0.468, 0.457], -[4.261, 1.369, 1.354], -[3.745, 0.826, 0.791], -[8.026, 2.927, 2.951], -[0.908, 0.060, 0.039], -[19.572, 0.550, 0.522], -[21.498, 0.584, 0.549], -[40.296, 1.307, 1.275], -[33.134, 0.662, 0.630], -[4.665, 0.163, 0.154], -[2.389, 0.143, 0.142], -[5.373, 0.167, 0.165], -[19.294, 0.513, 0.480], -[15.836, 0.749, 0.722], -[1.083, 1.064, 1.066], -[3.812, 0.370, 0.360], -[9.763, 0.513, 0.487], -[8.477, 2.733, 2.725], -[19.572, 2.006, 2.019], -[19.497, 2.026, 2.032], -[0.784, 0.708, 0.716], -[0.148, 0.109, 0.126], -[0.073, 0.047, 0.041], -[0.077, 0.042, 0.054], -[0.317, 0.247, 0.257], -[0.061, 0.022, 0.014], -[0.038, 0.017, 0.020], -[0.021, 0.004, 0.005] - ] - } -] diff --git a/website/benchmark/versions/scripts/benchmarks.sh b/website/benchmark/versions/scripts/benchmarks.sh deleted file mode 100644 index 4e6baff42ef..00000000000 --- a/website/benchmark/versions/scripts/benchmarks.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash - -CH_QUERIES_FILE="ch_queries.sql" -SSB_QUERIES_FILE="ssb_queries.sql" -BROWN_QUERIES_FILE="brown_queries.sql" -CH_TABLE="hits_100m_obfuscated" -TRIES=3 - -if [ -x ./clickhouse ] -then - CLICKHOUSE_CLIENT="./clickhouse client" -elif command -v clickhouse-client >/dev/null 2>&1 -then - CLICKHOUSE_CLIENT="clickhouse-client" -else - echo "clickhouse-client is not found" - exit 1 -fi - -${CLICKHOUSE_CLIENT} --query 'SELECT version();' - -echo "Brown Benchmark:" - -cat "$BROWN_QUERIES_FILE" | while read query; do - sync - echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null - - echo -n "[" - for i in $(seq 1 $TRIES); do - RES=$(${CLICKHOUSE_CLIENT} --time --format=Null --max_memory_usage=100G --query="$query" 2>&1) - [[ "$?" == "0" ]] && echo -n "${RES}" || echo -n "null" - [[ "$i" != $TRIES ]] && echo -n ", " - done - echo "]," -done - -echo "SSB Benchmark:" - -cat "$SSB_QUERIES_FILE" | while read query; do - sync - echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null - - echo -n "[" - for i in $(seq 1 $TRIES); do - RES=$(${CLICKHOUSE_CLIENT} --time --format=Null --max_memory_usage=100G --query="$query" 2>&1) - [[ "$?" == "0" ]] && echo -n "${RES}" || echo -n "null" - [[ "$i" != $TRIES ]] && echo -n ", " - done - echo "]," -done - - -echo "ClickHouse Benchmark:" - -cat "$CH_QUERIES_FILE" | sed "s/{table}/${CH_TABLE}/g" | while read query; do - sync - echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null - - echo -n "[" - for i in $(seq 1 $TRIES); do - RES=$(${CLICKHOUSE_CLIENT} --time --format=Null --max_memory_usage=100G --query="$query" 2>&1) - [[ "$?" == "0" ]] && echo -n "${RES}" || echo -n "null" - [[ "$i" != $TRIES ]] && echo -n ", " - done - echo "]," -done diff --git a/website/benchmark/versions/scripts/brown_queries.sql b/website/benchmark/versions/scripts/brown_queries.sql deleted file mode 100644 index f22175646b9..00000000000 --- a/website/benchmark/versions/scripts/brown_queries.sql +++ /dev/null @@ -1,16 +0,0 @@ -SELECT machine_name, MIN(cpu) AS cpu_min, MAX(cpu) AS cpu_max, AVG(cpu) AS cpu_avg, MIN(net_in) AS net_in_min, MAX(net_in) AS net_in_max, AVG(net_in) AS net_in_avg, MIN(net_out) AS net_out_min, MAX(net_out) AS net_out_max, AVG(net_out) AS net_out_avg FROM ( SELECT machine_name, ifNull(cpu_user, 0.0) AS cpu, ifNull(bytes_in, 0.0) AS net_in, ifNull(bytes_out, 0.0) AS net_out FROM mgbench.logs1 WHERE machine_name IN ('anansi','aragog','urd') AND log_time >= toDateTime('2017-01-11 00:00:00')) AS r GROUP BY machine_name; -- Q1.1: What is the CPU/network utilization for each web server since midnight? -SELECT machine_name, log_time FROM mgbench.logs1 WHERE (machine_name LIKE 'cslab%' OR machine_name LIKE 'mslab%') AND load_one IS NULL AND log_time >= toDateTime('2017-01-10 00:00:00') ORDER BY machine_name, log_time; -- Q1.2: Which computer lab machines have been offline in the past day? -SELECT dt, hr, AVG(load_fifteen) AS load_fifteen_avg, AVG(load_five) AS load_five_avg, AVG(load_one) AS load_one_avg, AVG(mem_free) AS mem_free_avg, AVG(swap_free) AS swap_free_avg FROM ( SELECT CAST(log_time AS DATE) AS dt, toHour(log_time) AS hr, load_fifteen, load_five, load_one, mem_free, swap_free FROM mgbench.logs1 WHERE machine_name = 'babbage' AND load_fifteen IS NOT NULL AND load_five IS NOT NULL AND load_one IS NOT NULL AND mem_free IS NOT NULL AND swap_free IS NOT NULL AND log_time >= toDateTime('2017-01-01 00:00:00')) AS r GROUP BY dt, hr ORDER BY dt, hr; -- Q1.3: What are the hourly average metrics during the past 10 days for a specific workstation? -SELECT machine_name, COUNT(*) AS spikes FROM mgbench.logs1 WHERE machine_group = 'Servers' AND cpu_wio > 0.99 AND log_time >= toDateTime('2016-12-01 00:00:00') AND log_time < toDateTime('2017-01-01 00:00:00') GROUP BY machine_name ORDER BY spikes DESC LIMIT 10; -- Q1.4: Over 1 month, how often was each server blocked on disk I/O? -SELECT machine_name, dt, MIN(mem_free) AS mem_free_min FROM ( SELECT machine_name, CAST(log_time AS DATE) AS dt, mem_free FROM mgbench.logs1 WHERE machine_group = 'DMZ' AND mem_free IS NOT NULL ) AS r GROUP BY machine_name, dt HAVING MIN(mem_free) < 10000 ORDER BY machine_name, dt; -- Q1.5: Which externally reachable VMs have run low on memory? -SELECT dt, hr, SUM(net_in) AS net_in_sum, SUM(net_out) AS net_out_sum, SUM(net_in) + SUM(net_out) AS both_sum FROM ( SELECT CAST(log_time AS DATE) AS dt, toHour(log_time) AS hr, ifNull(bytes_in, 0.0) / 1000000000.0 AS net_in, ifNull(bytes_out, 0.0) / 1000000000.0 AS net_out FROM mgbench.logs1 WHERE machine_name IN ('allsorts','andes','bigred','blackjack','bonbon','cadbury','chiclets','cotton','crows','dove','fireball','hearts','huey','lindt','milkduds','milkyway','mnm','necco','nerds','orbit','peeps','poprocks','razzles','runts','smarties','smuggler','spree','stride','tootsie','trident','wrigley','york') ) AS r GROUP BY dt, hr ORDER BY both_sum DESC LIMIT 10; -- Q1.6: What is the total hourly network traffic across all file servers? -SELECT * FROM mgbench.logs2 WHERE status_code >= 500 AND log_time >= toDateTime('2012-12-18 00:00:00') ORDER BY log_time; -- Q2.1: Which requests have caused server errors within the past 2 weeks? -SELECT * FROM mgbench.logs2 WHERE status_code >= 200 AND status_code < 300 AND request LIKE '%/etc/passwd%' AND log_time >= toDateTime('2012-05-06 00:00:00') AND log_time < toDateTime('2012-05-20 00:00:00'); -- Q2.3: What was the average path depth for top-level requests in the past month? -SELECT top_level, AVG(length(request) - length(replaceOne(request, '/',''))) AS depth_avg FROM ( SELECT substring(request, 1, len) AS top_level, request FROM ( SELECT position('/', substring(request, 2)) AS len, request FROM mgbench.logs2 WHERE status_code >= 200 AND status_code < 300 AND log_time >= toDateTime('2012-12-01 00:00:00')) AS r WHERE len > 0 ) AS s WHERE top_level IN ('/about','/courses','/degrees','/events','/grad','/industry','/news','/people','/publications','/research','/teaching','/ugrad') GROUP BY top_level ORDER BY top_level; -- Q2.2: During a specific 2-week period, was the user password file leaked? -SELECT client_ip, COUNT(*) AS num_requests FROM mgbench.logs2 WHERE log_time >= toDateTime('2012-10-01 00:00:00') GROUP BY client_ip HAVING COUNT(*) >= 100000 ORDER BY num_requests DESC; -- Q2.4: During the last 3 months, which clients have made an excessive number of requests? -SELECT dt, COUNT(DISTINCT client_ip) FROM ( SELECT CAST(log_time AS DATE) AS dt, client_ip FROM mgbench.logs2) AS r GROUP BY dt ORDER BY dt; -- Q2.5: What are the daily unique visitors? -SELECT AVG(transfer) / 125000000.0 AS transfer_avg, MAX(transfer) / 125000000.0 AS transfer_max FROM ( SELECT log_time, SUM(object_size) AS transfer FROM mgbench.logs2 GROUP BY log_time) AS r; -- Q2.6: What are the average and maximum data transfer rates (Gbps)? -SELECT * FROM mgbench.logs3 WHERE event_type = 'temperature' AND event_value <= 32.0 AND log_time >= '2019-11-29 17:00:00'; -- Q3.1: Did the indoor temperature reach freezing over the weekend? -SELECT device_name, device_floor, COUNT(*) AS ct FROM mgbench.logs3 WHERE event_type = 'door_open' AND log_time >= '2019-06-01 00:00:00' GROUP BY device_name, device_floor ORDER BY ct DESC; -- Q3.4: Over the past 6 months, how frequently were each door opened? -SELECT DISTINCT device_name, device_type, device_floor, if (dt >= toDate('2018-12-01') AND dt < toDate('2019-03-01'), 'WINTER', 'SUMMER') FROM (SELECT dt, device_name, device_type, device_floor FROM ( SELECT dt, hr, device_name, device_type, device_floor, AVG(event_value) AS temperature_hourly_avg FROM ( SELECT CAST(log_time AS DATE) AS dt, toHour(log_time) AS hr, device_name, device_type, device_floor, event_value FROM mgbench.logs3 WHERE event_type = 'temperature' ) AS r GROUP BY dt, hr, device_name, device_type, device_floor ) AS s GROUP BY dt, device_name, device_type, device_floor HAVING MAX(temperature_hourly_avg) - MIN(temperature_hourly_avg) >= 25.0 ) -- Q3.5: Where in the building do large temperature variations occur in winter and summer? -SELECT yr, mo, SUM(coffee_hourly_avg) AS coffee_monthly_sum, AVG(coffee_hourly_avg) AS coffee_monthly_avg, SUM(printer_hourly_avg) AS printer_monthly_sum, AVG(printer_hourly_avg) AS printer_monthly_avg, SUM(projector_hourly_avg) AS projector_monthly_sum, AVG(projector_hourly_avg) AS projector_monthly_avg, SUM(vending_hourly_avg) AS vending_monthly_sum, AVG(vending_hourly_avg) AS vending_monthly_avg FROM ( SELECT dt, yr, mo, hr, AVG(coffee) AS coffee_hourly_avg, AVG(printer) AS printer_hourly_avg, AVG(projector) AS projector_hourly_avg, AVG(vending) AS vending_hourly_avg FROM ( SELECT CAST(log_time AS DATE) AS dt, toYear(log_time) AS yr, EXTRACT(MONTH FROM log_time) AS mo, toHour(log_time) AS hr, CASE WHEN device_name LIKE 'coffee%' THEN event_value END AS coffee, CASE WHEN device_name LIKE 'printer%' THEN event_value END AS printer, CASE WHEN device_name LIKE 'projector%' THEN event_value END AS projector, CASE WHEN device_name LIKE 'vending%' THEN event_value END AS vending FROM mgbench.logs3 WHERE device_type = 'meter' ) AS r GROUP BY dt, yr, mo, hr ) AS s GROUP BY yr, mo ORDER BY yr, mo; -- Q3.6: For each device category, what are the monthly power consumption metrics? \ No newline at end of file diff --git a/website/benchmark/versions/scripts/ch_queries.sql b/website/benchmark/versions/scripts/ch_queries.sql deleted file mode 100644 index 89c4616c642..00000000000 --- a/website/benchmark/versions/scripts/ch_queries.sql +++ /dev/null @@ -1,43 +0,0 @@ -SELECT count() FROM {table}; -SELECT count() FROM {table} WHERE AdvEngineID != 0; -SELECT sum(AdvEngineID), count(), avg(ResolutionWidth) FROM {table} ; -SELECT sum(UserID) FROM {table} ; -SELECT uniq(UserID) FROM {table} ; -SELECT uniq(SearchPhrase) FROM {table} ; -SELECT min(EventDate), max(EventDate) FROM {table} ; -SELECT AdvEngineID, count() FROM {table} WHERE AdvEngineID != 0 GROUP BY AdvEngineID ORDER BY count() DESC; -SELECT RegionID, uniq(UserID) AS u FROM {table} GROUP BY RegionID ORDER BY u DESC LIMIT 10; -SELECT RegionID, sum(AdvEngineID), count() AS c, avg(ResolutionWidth), uniq(UserID) FROM {table} GROUP BY RegionID ORDER BY c DESC LIMIT 10; -SELECT MobilePhoneModel, uniq(UserID) AS u FROM {table} WHERE MobilePhoneModel != '' GROUP BY MobilePhoneModel ORDER BY u DESC LIMIT 10; -SELECT MobilePhone, MobilePhoneModel, uniq(UserID) AS u FROM {table} WHERE MobilePhoneModel != '' GROUP BY MobilePhone, MobilePhoneModel ORDER BY u DESC LIMIT 10; -SELECT SearchPhrase, count() AS c FROM {table} WHERE SearchPhrase != '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10; -SELECT SearchPhrase, uniq(UserID) AS u FROM {table} WHERE SearchPhrase != '' GROUP BY SearchPhrase ORDER BY u DESC LIMIT 10; -SELECT SearchEngineID, SearchPhrase, count() AS c FROM {table} WHERE SearchPhrase != '' GROUP BY SearchEngineID, SearchPhrase ORDER BY c DESC LIMIT 10; -SELECT UserID, count() FROM {table} GROUP BY UserID ORDER BY count() DESC LIMIT 10; -SELECT UserID, SearchPhrase, count() FROM {table} GROUP BY UserID, SearchPhrase ORDER BY count() DESC LIMIT 10; -SELECT UserID, SearchPhrase, count() FROM {table} GROUP BY UserID, SearchPhrase LIMIT 10; -SELECT UserID, toMinute(EventTime) AS m, SearchPhrase, count() FROM {table} GROUP BY UserID, m, SearchPhrase ORDER BY count() DESC LIMIT 10; -SELECT UserID FROM {table} WHERE UserID = 12345678901234567890; -SELECT count() FROM {table} WHERE URL LIKE '%metrika%'; -SELECT SearchPhrase, any(URL), count() AS c FROM {table} WHERE URL LIKE '%metrika%' AND SearchPhrase != '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10; -SELECT SearchPhrase, any(URL), any(Title), count() AS c, uniq(UserID) FROM {table} WHERE Title LIKE '%Яндекс%' AND URL NOT LIKE '%.yandex.%' AND SearchPhrase != '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10; -SELECT * FROM {table} WHERE URL LIKE '%metrika%' ORDER BY EventTime LIMIT 10; -SELECT SearchPhrase FROM {table} WHERE SearchPhrase != '' ORDER BY EventTime LIMIT 10; -SELECT SearchPhrase FROM {table} WHERE SearchPhrase != '' ORDER BY SearchPhrase LIMIT 10; -SELECT SearchPhrase FROM {table} WHERE SearchPhrase != '' ORDER BY EventTime, SearchPhrase LIMIT 10; -SELECT CounterID, avg(length(URL)) AS l, count() AS c FROM {table} WHERE URL != '' GROUP BY CounterID HAVING c > 100000 ORDER BY l DESC LIMIT 25; -SELECT domainWithoutWWW(Referer) AS key, avg(length(Referer)) AS l, count() AS c, any(Referer) FROM {table} WHERE Referer != '' GROUP BY key HAVING c > 100000 ORDER BY l DESC LIMIT 25; -SELECT sum(ResolutionWidth), sum(ResolutionWidth + 1), sum(ResolutionWidth + 2), sum(ResolutionWidth + 3), sum(ResolutionWidth + 4), sum(ResolutionWidth + 5), sum(ResolutionWidth + 6), sum(ResolutionWidth + 7), sum(ResolutionWidth + 8), sum(ResolutionWidth + 9), sum(ResolutionWidth + 10), sum(ResolutionWidth + 11), sum(ResolutionWidth + 12), sum(ResolutionWidth + 13), sum(ResolutionWidth + 14), sum(ResolutionWidth + 15), sum(ResolutionWidth + 16), sum(ResolutionWidth + 17), sum(ResolutionWidth + 18), sum(ResolutionWidth + 19), sum(ResolutionWidth + 20), sum(ResolutionWidth + 21), sum(ResolutionWidth + 22), sum(ResolutionWidth + 23), sum(ResolutionWidth + 24), sum(ResolutionWidth + 25), sum(ResolutionWidth + 26), sum(ResolutionWidth + 27), sum(ResolutionWidth + 28), sum(ResolutionWidth + 29), sum(ResolutionWidth + 30), sum(ResolutionWidth + 31), sum(ResolutionWidth + 32), sum(ResolutionWidth + 33), sum(ResolutionWidth + 34), sum(ResolutionWidth + 35), sum(ResolutionWidth + 36), sum(ResolutionWidth + 37), sum(ResolutionWidth + 38), sum(ResolutionWidth + 39), sum(ResolutionWidth + 40), sum(ResolutionWidth + 41), sum(ResolutionWidth + 42), sum(ResolutionWidth + 43), sum(ResolutionWidth + 44), sum(ResolutionWidth + 45), sum(ResolutionWidth + 46), sum(ResolutionWidth + 47), sum(ResolutionWidth + 48), sum(ResolutionWidth + 49), sum(ResolutionWidth + 50), sum(ResolutionWidth + 51), sum(ResolutionWidth + 52), sum(ResolutionWidth + 53), sum(ResolutionWidth + 54), sum(ResolutionWidth + 55), sum(ResolutionWidth + 56), sum(ResolutionWidth + 57), sum(ResolutionWidth + 58), sum(ResolutionWidth + 59), sum(ResolutionWidth + 60), sum(ResolutionWidth + 61), sum(ResolutionWidth + 62), sum(ResolutionWidth + 63), sum(ResolutionWidth + 64), sum(ResolutionWidth + 65), sum(ResolutionWidth + 66), sum(ResolutionWidth + 67), sum(ResolutionWidth + 68), sum(ResolutionWidth + 69), sum(ResolutionWidth + 70), sum(ResolutionWidth + 71), sum(ResolutionWidth + 72), sum(ResolutionWidth + 73), sum(ResolutionWidth + 74), sum(ResolutionWidth + 75), sum(ResolutionWidth + 76), sum(ResolutionWidth + 77), sum(ResolutionWidth + 78), sum(ResolutionWidth + 79), sum(ResolutionWidth + 80), sum(ResolutionWidth + 81), sum(ResolutionWidth + 82), sum(ResolutionWidth + 83), sum(ResolutionWidth + 84), sum(ResolutionWidth + 85), sum(ResolutionWidth + 86), sum(ResolutionWidth + 87), sum(ResolutionWidth + 88), sum(ResolutionWidth + 89) FROM {table}; -SELECT SearchEngineID, ClientIP, count() AS c, sum(Refresh), avg(ResolutionWidth) FROM {table} WHERE SearchPhrase != '' GROUP BY SearchEngineID, ClientIP ORDER BY c DESC LIMIT 10; -SELECT WatchID, ClientIP, count() AS c, sum(Refresh), avg(ResolutionWidth) FROM {table} WHERE SearchPhrase != '' GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10; -SELECT WatchID, ClientIP, count() AS c, sum(Refresh), avg(ResolutionWidth) FROM {table} GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10; -SELECT URL, count() AS c FROM {table} GROUP BY URL ORDER BY c DESC LIMIT 10; -SELECT 1, URL, count() AS c FROM {table} GROUP BY 1, URL ORDER BY c DESC LIMIT 10; -SELECT ClientIP AS x, x - 1, x - 2, x - 3, count() AS c FROM {table} GROUP BY x, x - 1, x - 2, x - 3 ORDER BY c DESC LIMIT 10; -SELECT URL, count() AS PageViews FROM {table} WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND NOT DontCountHits AND NOT Refresh AND notEmpty(URL) GROUP BY URL ORDER BY PageViews DESC LIMIT 10; -SELECT Title, count() AS PageViews FROM {table} WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND NOT DontCountHits AND NOT Refresh AND notEmpty(Title) GROUP BY Title ORDER BY PageViews DESC LIMIT 10; -SELECT URL, count() AS PageViews FROM {table} WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND NOT Refresh AND IsLink AND NOT IsDownload GROUP BY URL ORDER BY PageViews DESC LIMIT 1000; -SELECT TraficSourceID, SearchEngineID, AdvEngineID, ((SearchEngineID = 0 AND AdvEngineID = 0) ? Referer : '') AS Src, URL AS Dst, count() AS PageViews FROM {table} WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND NOT Refresh GROUP BY TraficSourceID, SearchEngineID, AdvEngineID, Src, Dst ORDER BY PageViews DESC LIMIT 1000; -SELECT URLHash, EventDate, count() AS PageViews FROM {table} WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND NOT Refresh AND TraficSourceID IN (-1, 6) AND RefererHash = halfMD5('http://example.ru/') GROUP BY URLHash, EventDate ORDER BY PageViews DESC LIMIT 100; -SELECT WindowClientWidth, WindowClientHeight, count() AS PageViews FROM {table} WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND NOT Refresh AND NOT DontCountHits AND URLHash = halfMD5('http://example.ru/') GROUP BY WindowClientWidth, WindowClientHeight ORDER BY PageViews DESC LIMIT 10000; -SELECT toStartOfMinute(EventTime) AS Minute, count() AS PageViews FROM {table} WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-02' AND NOT Refresh AND NOT DontCountHits GROUP BY Minute ORDER BY Minute; diff --git a/website/benchmark/versions/scripts/runner.sh b/website/benchmark/versions/scripts/runner.sh deleted file mode 100644 index 7678738b6cb..00000000000 --- a/website/benchmark/versions/scripts/runner.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -echo "VERSION = $1" -echo "DATE = $2" \ No newline at end of file diff --git a/website/benchmark/versions/scripts/ssb_queries.sql b/website/benchmark/versions/scripts/ssb_queries.sql deleted file mode 100644 index 375cf511489..00000000000 --- a/website/benchmark/versions/scripts/ssb_queries.sql +++ /dev/null @@ -1,13 +0,0 @@ -SELECT sum(LO_EXTENDEDPRICE * LO_DISCOUNT) AS revenue FROM lineorder_flat WHERE F_YEAR = 1993 AND LO_DISCOUNT BETWEEN 1 AND 3 AND LO_QUANTITY < 25; -- Q1.1 -SELECT sum(LO_EXTENDEDPRICE * LO_DISCOUNT) AS revenue FROM lineorder_flat WHERE toYYYYMM(LO_ORDERDATE) = 199401 AND LO_DISCOUNT BETWEEN 4 AND 6 AND LO_QUANTITY BETWEEN 26 AND 35; -- Q1.2 -SELECT sum(LO_EXTENDEDPRICE * LO_DISCOUNT) AS revenue FROM lineorder_flat WHERE toRelativeWeekNum(LO_ORDERDATE) - toRelativeWeekNum(toDate('1994-01-01')) = 6 AND F_YEAR = 1994 AND LO_DISCOUNT BETWEEN 5 AND 7 AND LO_QUANTITY BETWEEN 26 AND 35; -- Q1.3 -SELECT sum(LO_REVENUE), F_YEAR AS year, P_BRAND FROM lineorder_flat WHERE P_CATEGORY = 'MFGR#12' AND S_REGION = 'AMERICA' GROUP BY year, P_BRAND ORDER BY year, P_BRAND; -- Q2.1 -SELECT sum(LO_REVENUE), F_YEAR AS year, P_BRAND FROM lineorder_flat WHERE P_BRAND >= 'MFGR#2221' AND P_BRAND <= 'MFGR#2228' AND S_REGION = 'ASIA' GROUP BY year, P_BRAND ORDER BY year, P_BRAND; -- Q2.2 -SELECT sum(LO_REVENUE), F_YEAR AS year, P_BRAND FROM lineorder_flat WHERE P_BRAND = 'MFGR#2239' AND S_REGION = 'EUROPE' GROUP BY year, P_BRAND ORDER BY year, P_BRAND; -- Q2.3 -SELECT C_NATION, S_NATION, F_YEAR AS year, sum(LO_REVENUE) AS revenue FROM lineorder_flat WHERE C_REGION = 'ASIA' AND S_REGION = 'ASIA' AND year >= 1992 AND year <= 1997 GROUP BY C_NATION, S_NATION, year ORDER BY year ASC, revenue DESC; -- Q3.1 -SELECT C_CITY, S_CITY, F_YEAR AS year, sum(LO_REVENUE) AS revenue FROM lineorder_flat WHERE C_NATION = 'UNITED STATES' AND S_NATION = 'UNITED STATES' AND year >= 1992 AND year <= 1997 GROUP BY C_CITY, S_CITY, year ORDER BY year ASC, revenue DESC; -- Q3.2 -SELECT C_CITY, S_CITY, F_YEAR AS year, sum(LO_REVENUE) AS revenue FROM lineorder_flat WHERE (C_CITY = 'UNITED KI1' OR C_CITY = 'UNITED KI5') AND (S_CITY = 'UNITED KI1' OR S_CITY = 'UNITED KI5') AND year >= 1992 AND year <= 1997 GROUP BY C_CITY, S_CITY, year ORDER BY year ASC, revenue DESC; -- Q3.3 -SELECT C_CITY, S_CITY, F_YEAR AS year, sum(LO_REVENUE) AS revenue FROM lineorder_flat WHERE (C_CITY = 'UNITED KI1' OR C_CITY = 'UNITED KI5') AND (S_CITY = 'UNITED KI1' OR S_CITY = 'UNITED KI5') AND toYYYYMM(LO_ORDERDATE) = 199712 GROUP BY C_CITY, S_CITY, year ORDER BY year ASC, revenue DESC; -- Q3.4 -SELECT F_YEAR AS year, C_NATION, sum(LO_REVENUE - LO_SUPPLYCOST) AS profit FROM lineorder_flat WHERE C_REGION = 'AMERICA' AND S_REGION = 'AMERICA' AND (P_MFGR = 'MFGR#1' OR P_MFGR = 'MFGR#2') GROUP BY year, C_NATION ORDER BY year ASC, C_NATION ASC; -- Q4.1 -SELECT F_YEAR AS year, S_NATION, P_CATEGORY, sum(LO_REVENUE - LO_SUPPLYCOST) AS profit FROM lineorder_flat WHERE C_REGION = 'AMERICA' AND S_REGION = 'AMERICA' AND (year = 1997 OR year = 1998) AND (P_MFGR = 'MFGR#1' OR P_MFGR = 'MFGR#2') GROUP BY year, S_NATION, P_CATEGORY ORDER BY year ASC, S_NATION ASC, P_CATEGORY ASC; -- Q4.2 -SELECT F_YEAR AS year, S_CITY, P_BRAND, sum(LO_REVENUE - LO_SUPPLYCOST) AS profit FROM lineorder_flat WHERE S_NATION = 'UNITED STATES' AND (year = 1997 OR year = 1998) AND P_CATEGORY = 'MFGR#14' GROUP BY year, S_CITY, P_BRAND ORDER BY year ASC, S_CITY ASC, P_BRAND ASC; -- Q4. \ No newline at end of file From 47b0d05c7193735ea9718d2a1013523ed25dacb1 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 8 Aug 2022 01:06:47 +0200 Subject: [PATCH 402/672] Removed useless code from the website --- .github/workflows/docs_release.yml | 1 - docs/tools/build.py | 7 - docs/tools/deploy-to-test.sh | 22 - docs/tools/easy_edit.sh | 27 - docs/tools/util.py | 1 - docs/tools/website.py | 171 - website/404.html | 15 - website/css/base.css | 203 - website/css/blog.css | 0 website/css/bootstrap.css | 6 - website/css/docs.css | 155 - website/css/docsearch.css | 0 website/css/highlight.css | 0 website/css/main.css | 1 - website/google419fbd824d7ff97d.html | 1 - website/index.html | 70 - website/js/base.js | 100 - website/js/bootstrap.js | 4521 ------- website/js/docs.js | 173 - website/js/docsearch.js | 10137 -------------- website/js/externs.js | 6 - website/js/index.js | 42 - website/js/jquery.js | 10872 ---------------- website/js/main.js | 1 - website/js/popper.js | 2624 ---- website/js/sentry.js | 5699 -------- website/main.html | 74 - website/opensearch.xml | 12 - website/robots.txt | 5 - website/sitemap-index.xml | 24 - website/sitemap-static.xml | 23 - website/src/js/components/case-study-card.js | 40 - website/src/js/main.js | 3 - website/src/js/utilities/equalize-heights.js | 35 - website/src/js/utilities/greenhouse.js | 16 - website/src/scss/_variables.scss | 316 - website/src/scss/bootstrap.scss | 2 - website/src/scss/components/_btn.scss | 81 - website/src/scss/components/_btns.scss | 32 - website/src/scss/components/_card.scss | 75 - .../src/scss/components/_case-study-card.scss | 206 - website/src/scss/components/_footer.scss | 6 - website/src/scss/components/_form.scss | 56 - website/src/scss/components/_hero.scss | 58 - website/src/scss/components/_hr.scss | 40 - website/src/scss/components/_icon.scss | 15 - website/src/scss/components/_img.scss | 3 - website/src/scss/components/_kicker.scss | 8 - website/src/scss/components/_lead.scss | 5 - website/src/scss/components/_logo.scss | 8 - website/src/scss/components/_navbar.scss | 165 - website/src/scss/components/_page.scss | 4 - website/src/scss/components/_photo-frame.scss | 30 - website/src/scss/components/_pullquote.scss | 47 - website/src/scss/components/_section.scss | 25 - .../src/scss/components/_severity-table.scss | 12 - .../src/scss/components/_social-icons.scss | 58 - website/src/scss/components/_tabs.scss | 106 - .../src/scss/components/_trailing-link.scss | 32 - website/src/scss/components/_ul.scss | 23 - website/src/scss/main.scss | 3 - website/src/scss/utilities/_bg.scss | 35 - website/src/scss/utilities/_border.scss | 4 - website/src/scss/utilities/_font.scss | 31 - website/src/scss/utilities/_overflow.scss | 3 - website/src/scss/utilities/_text.scss | 7 - website/templates/base.html | 18 - website/templates/blog/content.html | 69 - website/templates/blog/footer.html | 9 - website/templates/blog/nav.html | 54 - website/templates/blog/rss.xml | 23 - website/templates/careers/greenhouse.html | 8 - website/templates/careers/hero.html | 10 - website/templates/careers/overview.html | 11 - website/templates/common_css.html | 5 - website/templates/common_fonts.html | 3 - website/templates/common_js.html | 9 - website/templates/common_meta.html | 53 - website/templates/company/contact.html | 53 - website/templates/company/founders.html | 55 - website/templates/company/hero.html | 10 - website/templates/company/investors.html | 64 - website/templates/company/overview.html | 17 - website/templates/company/press.html | 109 - website/templates/company/team.html | 776 -- website/templates/contact-thank-you/hero.html | 14 - .../templates/contact-thank-you/overview.html | 8 - website/templates/docs/amp.html | 63 - website/templates/docs/content.html | 44 - website/templates/docs/footer.html | 19 - website/templates/docs/ld_json.html | 38 - .../templates/docs/machine-translated.html | 11 - website/templates/docs/nav.html | 68 - website/templates/docs/sidebar-item.html | 20 - website/templates/docs/sidebar.html | 23 - website/templates/docs/toc-item.html | 13 - website/templates/docs/toc.html | 16 - website/templates/footer.html | 19 - website/templates/global/banner.html | 6 - website/templates/global/github_stars.html | 1 - website/templates/global/nav.html | 60 - website/templates/global/newsletter.html | 40 - website/templates/index/community.html | 165 - website/templates/index/hero.html | 39 - website/templates/index/performance.html | 56 - website/templates/index/pullquote.html | 18 - website/templates/index/quickstart.html | 75 - website/templates/index/success.html | 246 - website/templates/index/why.html | 41 - .../templates/support/agreement-content.html | 120 - website/templates/support/agreement-hero.html | 10 - website/templates/support/form.html | 79 - website/templates/support/hero.html | 10 - website/templates/support/overview.html | 13 - website/templates/support/policy-content.html | 55 - website/templates/support/policy-hero.html | 10 - .../templates/trademark-policy/content.html | 238 - website/templates/trademark-policy/hero.html | 11 - website/yandex_75d07a6cd96f49df.html | 6 - website/yandex_fffaa30ee00426bb.html | 6 - 120 files changed, 39600 deletions(-) delete mode 100755 docs/tools/deploy-to-test.sh delete mode 100755 docs/tools/easy_edit.sh delete mode 100644 website/404.html delete mode 100644 website/css/base.css delete mode 100644 website/css/blog.css delete mode 100644 website/css/bootstrap.css delete mode 100644 website/css/docs.css delete mode 100644 website/css/docsearch.css delete mode 100644 website/css/highlight.css delete mode 100644 website/css/main.css delete mode 100644 website/google419fbd824d7ff97d.html delete mode 100644 website/index.html delete mode 100644 website/js/base.js delete mode 100644 website/js/bootstrap.js delete mode 100644 website/js/docs.js delete mode 100644 website/js/docsearch.js delete mode 100644 website/js/externs.js delete mode 100644 website/js/index.js delete mode 100644 website/js/jquery.js delete mode 100644 website/js/main.js delete mode 100644 website/js/popper.js delete mode 100644 website/js/sentry.js delete mode 100644 website/main.html delete mode 100644 website/opensearch.xml delete mode 100644 website/robots.txt delete mode 100644 website/sitemap-index.xml delete mode 100644 website/sitemap-static.xml delete mode 100644 website/src/js/components/case-study-card.js delete mode 100644 website/src/js/main.js delete mode 100644 website/src/js/utilities/equalize-heights.js delete mode 100644 website/src/js/utilities/greenhouse.js delete mode 100644 website/src/scss/_variables.scss delete mode 100644 website/src/scss/bootstrap.scss delete mode 100644 website/src/scss/components/_btn.scss delete mode 100644 website/src/scss/components/_btns.scss delete mode 100644 website/src/scss/components/_card.scss delete mode 100644 website/src/scss/components/_case-study-card.scss delete mode 100644 website/src/scss/components/_footer.scss delete mode 100644 website/src/scss/components/_form.scss delete mode 100644 website/src/scss/components/_hero.scss delete mode 100644 website/src/scss/components/_hr.scss delete mode 100644 website/src/scss/components/_icon.scss delete mode 100644 website/src/scss/components/_img.scss delete mode 100644 website/src/scss/components/_kicker.scss delete mode 100644 website/src/scss/components/_lead.scss delete mode 100644 website/src/scss/components/_logo.scss delete mode 100644 website/src/scss/components/_navbar.scss delete mode 100644 website/src/scss/components/_page.scss delete mode 100644 website/src/scss/components/_photo-frame.scss delete mode 100644 website/src/scss/components/_pullquote.scss delete mode 100644 website/src/scss/components/_section.scss delete mode 100644 website/src/scss/components/_severity-table.scss delete mode 100644 website/src/scss/components/_social-icons.scss delete mode 100644 website/src/scss/components/_tabs.scss delete mode 100644 website/src/scss/components/_trailing-link.scss delete mode 100644 website/src/scss/components/_ul.scss delete mode 100644 website/src/scss/main.scss delete mode 100644 website/src/scss/utilities/_bg.scss delete mode 100644 website/src/scss/utilities/_border.scss delete mode 100644 website/src/scss/utilities/_font.scss delete mode 100644 website/src/scss/utilities/_overflow.scss delete mode 100644 website/src/scss/utilities/_text.scss delete mode 100644 website/templates/base.html delete mode 100644 website/templates/blog/content.html delete mode 100644 website/templates/blog/footer.html delete mode 100644 website/templates/blog/nav.html delete mode 100644 website/templates/blog/rss.xml delete mode 100644 website/templates/careers/greenhouse.html delete mode 100644 website/templates/careers/hero.html delete mode 100644 website/templates/careers/overview.html delete mode 100644 website/templates/common_css.html delete mode 100644 website/templates/common_fonts.html delete mode 100644 website/templates/common_js.html delete mode 100644 website/templates/common_meta.html delete mode 100644 website/templates/company/contact.html delete mode 100644 website/templates/company/founders.html delete mode 100644 website/templates/company/hero.html delete mode 100644 website/templates/company/investors.html delete mode 100644 website/templates/company/overview.html delete mode 100644 website/templates/company/press.html delete mode 100644 website/templates/company/team.html delete mode 100644 website/templates/contact-thank-you/hero.html delete mode 100644 website/templates/contact-thank-you/overview.html delete mode 100644 website/templates/docs/amp.html delete mode 100644 website/templates/docs/content.html delete mode 100644 website/templates/docs/footer.html delete mode 100644 website/templates/docs/ld_json.html delete mode 100644 website/templates/docs/machine-translated.html delete mode 100644 website/templates/docs/nav.html delete mode 100644 website/templates/docs/sidebar-item.html delete mode 100644 website/templates/docs/sidebar.html delete mode 100644 website/templates/docs/toc-item.html delete mode 100644 website/templates/docs/toc.html delete mode 100644 website/templates/footer.html delete mode 100644 website/templates/global/banner.html delete mode 100644 website/templates/global/github_stars.html delete mode 100644 website/templates/global/nav.html delete mode 100644 website/templates/global/newsletter.html delete mode 100644 website/templates/index/community.html delete mode 100644 website/templates/index/hero.html delete mode 100644 website/templates/index/performance.html delete mode 100644 website/templates/index/pullquote.html delete mode 100644 website/templates/index/quickstart.html delete mode 100644 website/templates/index/success.html delete mode 100644 website/templates/index/why.html delete mode 100644 website/templates/support/agreement-content.html delete mode 100644 website/templates/support/agreement-hero.html delete mode 100644 website/templates/support/form.html delete mode 100644 website/templates/support/hero.html delete mode 100644 website/templates/support/overview.html delete mode 100644 website/templates/support/policy-content.html delete mode 100644 website/templates/support/policy-hero.html delete mode 100644 website/templates/trademark-policy/content.html delete mode 100644 website/templates/trademark-policy/hero.html delete mode 100644 website/yandex_75d07a6cd96f49df.html delete mode 100644 website/yandex_fffaa30ee00426bb.html diff --git a/.github/workflows/docs_release.yml b/.github/workflows/docs_release.yml index aed691844da..e0fdb0c2f7b 100644 --- a/.github/workflows/docs_release.yml +++ b/.github/workflows/docs_release.yml @@ -13,7 +13,6 @@ concurrency: - master paths: - '.github/**' - - 'benchmark/**' - 'docker/docs/release/**' - 'docs/**' - 'utils/list-versions/version_date.tsv' diff --git a/docs/tools/build.py b/docs/tools/build.py index 7c872435092..7f78af5e203 100755 --- a/docs/tools/build.py +++ b/docs/tools/build.py @@ -19,9 +19,6 @@ def build(args): if not args.skip_website: website.build_website(args) - - if not args.skip_website: - website.minify_website(args) redirects.build_static_redirects(args) @@ -73,9 +70,5 @@ if __name__ == "__main__": new_args = sys.executable + " " + " ".join(new_args) server = livereload.Server() - server.watch( - args.website_dir + "**/*", - livereload.shell(new_args, cwd="tools", shell=True), - ) server.serve(root=args.output_dir, host="0.0.0.0", port=args.livereload) sys.exit(0) diff --git a/docs/tools/deploy-to-test.sh b/docs/tools/deploy-to-test.sh deleted file mode 100755 index a7a922137d5..00000000000 --- a/docs/tools/deploy-to-test.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -# -# README: -# This script deploys ClickHouse website to your personal test subdomain. -# -# Before first use of this script: -# 1) Set up building documentation according to https://github.com/ClickHouse/ClickHouse/tree/master/docs/tools#use-buildpy-use-build-py -# 2) Create https://github.com/GIT_USER/clickhouse.github.io repo (replace GIT_USER with your GitHub login) -# 3) Enable GitHub Pages in settings of this repo -# 4) Add file named CNAME in root of this repo with "GIT_USER-test.clickhouse.com" content (without quotes) -# 5) Send email on address from https://clickhouse.com/#contacts asking to create GIT_USER-test.clickhouse.com domain -# -set -ex - -BASE_DIR=$(dirname "$(readlink -f "$0")") -GIT_USER=${GIT_USER:-$USER} - -GIT_PROD_URI=git@github.com:${GIT_USER}/clickhouse.github.io.git \ - BASE_DOMAIN=${GIT_USER}-test.clickhouse.com \ - EXTRA_BUILD_ARGS="${*}" \ - CLOUDFLARE_TOKEN="" \ - "${BASE_DIR}/release.sh" diff --git a/docs/tools/easy_edit.sh b/docs/tools/easy_edit.sh deleted file mode 100755 index ed8a43fead7..00000000000 --- a/docs/tools/easy_edit.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# Creates symlinks to docs in ClickHouse/docs/edit/ -# that are easy to open in both languages simultaneously -# for example, with `vim -O docs/edit/my_article/*` - -set -ex -BASE_DIR="$(dirname $(readlink -f $0))/.." -DOCS_DIR="${BASE_DIR}" -EDIT_DIR="${BASE_DIR}/edit" - -pushd "${DOCS_DIR}/en" -ARTICLES=$(find . -name '*.md' | sed -e 's/\.md$//g' -e 's/^\.\/en\///g') -popd - -rm -rf "${EDIT_DIR}" || true - -for DOCS_LANG in en ru zh ja fa -do - for ARTICLE in ${ARTICLES} - do - ARTICLE_DIR="${EDIT_DIR}/${ARTICLE}" - mkdir -p $ARTICLE_DIR || true - ln -s "${DOCS_DIR}/${DOCS_LANG}/${ARTICLE}.md" "${ARTICLE_DIR}/${DOCS_LANG}.md" - done -done - - diff --git a/docs/tools/util.py b/docs/tools/util.py index a5ebb1b11b2..dc9fb640b47 100644 --- a/docs/tools/util.py +++ b/docs/tools/util.py @@ -113,7 +113,6 @@ def init_jinja2_filters(env): env.filters["chunks"] = lambda line: [ line[i : i + chunk_size] for i in range(0, len(line), chunk_size) ] - env.filters["adjust_markdown_html"] = website.adjust_markdown_html env.filters["to_rfc882"] = lambda d: datetime.datetime.strptime( d, "%Y-%m-%d" ).strftime("%a, %d %b %Y %H:%M:%S GMT") diff --git a/docs/tools/website.py b/docs/tools/website.py index d6954fafa55..2a34458fd29 100644 --- a/docs/tools/website.py +++ b/docs/tools/website.py @@ -5,122 +5,9 @@ import os import shutil import subprocess -import bs4 - import util -def handle_iframe(iframe, soup): - allowed_domains = ["https://www.youtube.com/"] - illegal_domain = True - iframe_src = iframe.attrs["src"] - for domain in allowed_domains: - if iframe_src.startswith(domain): - illegal_domain = False - break - if illegal_domain: - raise RuntimeError(f"iframe from illegal domain: {iframe_src}") - wrapper = soup.new_tag("div") - wrapper.attrs["class"] = ["embed-responsive", "embed-responsive-16by9"] - iframe.insert_before(wrapper) - iframe.extract() - wrapper.insert(0, iframe) - if "width" in iframe.attrs: - del iframe.attrs["width"] - if "height" in iframe.attrs: - del iframe.attrs["height"] - iframe.attrs[ - "allow" - ] = "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" - iframe.attrs["class"] = "embed-responsive-item" - iframe.attrs["frameborder"] = "0" - iframe.attrs["allowfullscreen"] = "1" - - -def adjust_markdown_html(content): - soup = bs4.BeautifulSoup(content, features="html.parser") - - for a in soup.find_all("a"): - a_class = a.attrs.get("class") - a_href = a.attrs.get("href") - if a_class and "headerlink" in a_class: - a.string = "\xa0" - if a_href and a_href.startswith("http"): - a.attrs["target"] = "_blank" - - for code in soup.find_all("code"): - code_class = code.attrs.get("class") - if code_class: - code.attrs["class"] = code_class + ["syntax"] - else: - code.attrs["class"] = "syntax" - - for iframe in soup.find_all("iframe"): - handle_iframe(iframe, soup) - - for img in soup.find_all("img"): - if img.attrs.get("alt") == "iframe": - img.name = "iframe" - img.string = "" - handle_iframe(img, soup) - continue - img_class = img.attrs.get("class") - if img_class: - img.attrs["class"] = img_class + ["img-fluid"] - else: - img.attrs["class"] = "img-fluid" - - for details in soup.find_all("details"): - for summary in details.find_all("summary"): - if summary.parent != details: - summary.extract() - details.insert(0, summary) - - for dd in soup.find_all("dd"): - dd_class = dd.attrs.get("class") - if dd_class: - dd.attrs["class"] = dd_class + ["pl-3"] - else: - dd.attrs["class"] = "pl-3" - - for div in soup.find_all("div"): - div_class = div.attrs.get("class") - is_admonition = div_class and "admonition" in div.attrs.get("class") - if is_admonition: - for a in div.find_all("a"): - a_class = a.attrs.get("class") - if a_class: - a.attrs["class"] = a_class + ["alert-link"] - else: - a.attrs["class"] = "alert-link" - - for p in div.find_all("p"): - p_class = p.attrs.get("class") - if is_admonition and p_class and ("admonition-title" in p_class): - p.attrs["class"] = p_class + [ - "alert-heading", - "display-4", - "text-reset", - "mb-2", - ] - - if is_admonition: - div.attrs["role"] = "alert" - if ("info" in div_class) or ("note" in div_class): - mode = "alert-primary" - elif ("attention" in div_class) or ("warning" in div_class): - mode = "alert-warning" - elif "important" in div_class: - mode = "alert-danger" - elif "tip" in div_class: - mode = "alert-info" - else: - mode = "alert-secondary" - div.attrs["class"] = div_class + ["alert", "pb-0", "mb-4", mode] - - return str(soup) - - def build_website(args): logging.info("Building website") env = util.init_jinja2_env(args) @@ -145,11 +32,6 @@ def build_website(args): ), ) - shutil.copytree( - os.path.join(args.website_dir, "images"), - os.path.join(args.output_dir, "docs", "images"), - ) - # This file can be requested to check for available ClickHouse releases. shutil.copy2( os.path.join(args.src_dir, "utils", "list-versions", "version_date.tsv"), @@ -179,56 +61,3 @@ def build_website(args): with open(path, "wb") as f: f.write(content.encode("utf-8")) - - -def get_css_in(args): - return [ - f"'{args.website_dir}/css/bootstrap.css'", - f"'{args.website_dir}/css/docsearch.css'", - f"'{args.website_dir}/css/base.css'", - f"'{args.website_dir}/css/blog.css'", - f"'{args.website_dir}/css/docs.css'", - f"'{args.website_dir}/css/highlight.css'", - f"'{args.website_dir}/css/main.css'", - ] - - -def get_js_in(args): - return [ - f"'{args.website_dir}/js/jquery.js'", - f"'{args.website_dir}/js/popper.js'", - f"'{args.website_dir}/js/bootstrap.js'", - f"'{args.website_dir}/js/sentry.js'", - f"'{args.website_dir}/js/base.js'", - f"'{args.website_dir}/js/index.js'", - f"'{args.website_dir}/js/docsearch.js'", - f"'{args.website_dir}/js/docs.js'", - f"'{args.website_dir}/js/main.js'", - ] - - -def minify_website(args): - css_in = " ".join(get_css_in(args)) - css_out = f"{args.output_dir}/docs/css/base.css" - os.makedirs(f"{args.output_dir}/docs/css") - - command = f"cat {css_in}" - output = subprocess.check_output(command, shell=True) - with open(css_out, "wb+") as f: - f.write(output) - - with open(css_out, "rb") as f: - css_digest = hashlib.sha3_224(f.read()).hexdigest()[0:8] - - js_in = " ".join(get_js_in(args)) - js_out = f"{args.output_dir}/docs/js/base.js" - os.makedirs(f"{args.output_dir}/docs/js") - - command = f"cat {js_in}" - output = subprocess.check_output(command, shell=True) - with open(js_out, "wb+") as f: - f.write(output) - - with open(js_out, "rb") as f: - js_digest = hashlib.sha3_224(f.read()).hexdigest()[0:8] - logging.info(js_digest) diff --git a/website/404.html b/website/404.html deleted file mode 100644 index 31aaf6646df..00000000000 --- a/website/404.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "templates/base.html" %} -{% set no_footer = True %} - -{% block content %} - -{% endblock %} diff --git a/website/css/base.css b/website/css/base.css deleted file mode 100644 index d81b231789a..00000000000 --- a/website/css/base.css +++ /dev/null @@ -1,203 +0,0 @@ -.blog a:link, .blog a:visited, -.docs a:link, .docs a:visited { - color: #f14600; - text-decoration: none; -} - -.blog a:hover, .blog a:active, -.docs a:hover, .docs a:active { - text-decoration: underline; -} - -.btn.disabled, .btn:disabled { - cursor: default; - opacity: 0.4; -} - -.fake-btn { - display: inline-block; - padding: 0.375rem 0.75rem; - text-align: center; - font-size: 1rem; - line-height: 1.5; -} - -#logo-icon, #docs-logo-icon { - width: 64px; -} - -#logo-text { - width: 180px; - margin-left: 12px; -} - -.display-5 { - font-size: 2rem; - font-weight: 300; - line-height: 1.2; -} - -.display-6 { - font-size: 1.75rem; - font-weight: 300; - line-height: 1.2; -} - -.bg-dark-alt, .bg-dark-alt:focus { - background: #36363F; -} - -.bg-secondary-alt, .bg-secondary-alt:focus { - background: #444451; -} - -.bg-orange, .bg-orange:focus { - background: #f14600; -} - -.text-dark-alt { - color: #36363F; -} - -.btn-dark-alt { - background: #36363F; -} - -.btn-dark-alt:focus { - background: #36363F; -} - -.btn-outline-orange { - border-color: #f14600; - color: #f14600; -} - -.btn-orange, .btn-outline-orange:hover { - background: #f14600; - color: #fff; -} - -a.btn-outline-yellow { - border-color: #fc0; - color: #fc0; -} - -.btn-yellow, .btn-outline-yellow:hover { - background: #fc0; - color: #000; -} - -.btn-yellow:hover { - background: #ffe100; -} - -.btn-yellow:hover, .btn-yellow:link, .btn-yellow:visited { - color: #000; -} - -.btn:hover, .btn:active { - text-decoration: none; -} - -.text-red { - color: #ff3939; -} - -.text-orange { - color: #f14600; -} - -.text-yellow { - color: #fc0; -} - -.bg-number { - position: absolute; - font-size: 900%; - font-weight: bold; - color: rgba(241, 70, 0, 0.1); - line-height: 1; - margin-top: -1rem; -} - -.dropdown-item { - color: #e1e1e1 !important; -} - -.dropdown-item:hover, -.dropdown-item:focus { - background-color: #1e1e1e; -} - -.nav .dropdown-toggle::after { - vertical-align: 0 !important; -} -.nav .dropdown-toggle[aria-expanded=false]::after { - display: inline-block; - margin-right: .255em; - vertical-align: 0; - content: ""; - border-top: .3em solid transparent; - border-right: .3em solid; - border-bottom: .3em solid transparent; -} - -.dots-lb { - background: #fff url('/images/dots.svg') no-repeat 0 100%; -} - -.dots-cb { - background: #fff url('/images/dots.svg') no-repeat 50% 100%; -} - -.dots-rb { - background: #fff url('/images/dots.svg') no-repeat 100% 100%; -} - -.dots-rhb { - background: #fff url('/images/dots.svg') no-repeat 75% 100%; -} - -.dots-cc { - background: #fff url('/images/dots.svg') repeat-y 50% -17.5%; -} - -.benchmark-query-cell { - width: 20rem; - white-space: pre; - overflow-x: hidden; -} - -.benchmark-query-cell:hover { - width: auto; - background-color: #efefef; - position: absolute; - padding: 0.5rem; - margin: -0.5rem 0 0 -0.5rem; - overflow-x: auto; - white-space: normal; -} - -.benchmark-query-cell-wrapper { - width: 22rem; -} - -.w-15 { - width: 15% !important; -} - -#feedback_email { - white-space: nowrap; -} - -.height-20 { - height: 20px; -} - -ol.default{ - margin-top: 10px; -} - -ol.default li{ - margin-bottom: 10px; -} diff --git a/website/css/blog.css b/website/css/blog.css deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/website/css/bootstrap.css b/website/css/bootstrap.css deleted file mode 100644 index eb26dce70d7..00000000000 --- a/website/css/bootstrap.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * Bootstrap v4.4.1 (https://getbootstrap.com/) - * Copyright 2011-2019 The Bootstrap Authors - * Copyright 2011-2019 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--gray:#6c757d;--gray-dark:#343a40;--brand-primary:#fc0;--brand-secondary:#ff3939;--primary-accent-yellow:#fc0;--primary-accent-light-yellow:#fffaf0;--primary-accent-blue:#257af4;--primary-accent-light-blue:#e3f1fe;--secondary-accent-orange:#ff8c00;--secondary-accent-light-orange:#ffe4b5;--secondary-accent-red:#ff3939;--secondary-accent-light-red:#ffe4e1;--primary:#fc0;--secondary:#212529;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f1f6f9;--dark:#495057;--primary-light:#fffaf0;--secondary-light:#fff;--tertiary:#257af4;--tertiary-light:#e3f1fe;--white:#fff;--black:#212529;--blue:#257af4;--light-blue:#e3f1fe;--yellow:#fc0;--light-yellow:#fffaf0;--orange:#ff8c00;--light-orange:#ffe4b5;--red:#ff3939;--light-red:#ffe4e1;--medium:#d6dbdf;--breakpoint-xxs:0;--breakpoint-xs:400px;--breakpoint-sm:616px;--breakpoint-md:768px;--breakpoint-lg:980px;--breakpoint-xl:1240px;--font-family-sans-serif:"Noto Sans",sans-serif;--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,:after,:before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(33,37,41,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:Noto Sans,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:16px}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{font-style:normal;line-height:inherit}address,dl,ol,ul{margin-bottom:1rem}dl,ol,ul{margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{text-decoration:none;background-color:transparent}a,a:hover{color:#ff8c00}a:hover{text-decoration:underline}a:not([href]),a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{border-style:none}img,svg{vertical-align:middle}svg{overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}@media(max-width:1200px){legend{font-size:calc(1.275rem + .3vw)}}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:16px;font-family:Hind Siliguri,sans-serif;font-weight:500;line-height:1.125}.h1,h1{font-size:2.5rem}@media(max-width:1200px){.h1,h1{font-size:calc(1.375rem + 1.5vw)}}.h2,h2{font-size:2rem}@media(max-width:1200px){.h2,h2{font-size:calc(1.325rem + .9vw)}}.h3,h3{font-size:1.75rem}@media(max-width:1200px){.h3,h3{font-size:calc(1.3rem + .6vw)}}.h4,h4{font-size:1.5rem}@media(max-width:1200px){.h4,h4{font-size:calc(1.275rem + .3vw)}}.h5,h5{font-size:1.125rem}.h6,h6{font-size:.875rem}.lead{font-size:1.375rem;font-weight:400}@media(max-width:1200px){.lead{font-size:calc(1.2625rem + .15vw)}}.display-1{font-size:4rem;font-weight:600;line-height:1.125}@media(max-width:1200px){.display-1{font-size:calc(1.525rem + 3.3vw)}}.display-2{font-size:2.5rem;font-weight:600;line-height:1.125}@media(max-width:1200px){.display-2{font-size:calc(1.375rem + 1.5vw)}}.display-3{font-size:2rem;font-weight:500;line-height:1.125}@media(max-width:1200px){.display-3{font-size:calc(1.325rem + .9vw)}}.display-4{font-size:1.75rem;font-weight:500;line-height:1.125}@media(max-width:1200px){.display-4{font-size:calc(1.3rem + .6vw)}}hr{margin-top:8px;margin-bottom:8px;border:0;border-top:1px solid rgba(33,37,41,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-inline,.list-unstyled{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:8px;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer:before{content:"— "}.img-fluid,.img-thumbnail{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:8px}.figure{display:inline-block}.figure-img{margin-bottom:4px;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#495057;border-radius:4px}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#495057}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:20px;padding-left:20px;margin-right:auto;margin-left:auto}@media(min-width:400px){.container{max-width:576px}}@media(min-width:616px){.container{max-width:576px}}@media(min-width:768px){.container{max-width:958px}}@media(min-width:980px){.container{max-width:1008px}}@media(min-width:1240px){.container{max-width:1118px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xs{width:100%;padding-right:20px;padding-left:20px;margin-right:auto;margin-left:auto}@media(min-width:400px){.container,.container-xs{max-width:576px}}@media(min-width:616px){.container,.container-sm,.container-xs{max-width:576px}}@media(min-width:768px){.container,.container-md,.container-sm,.container-xs{max-width:958px}}@media(min-width:980px){.container,.container-lg,.container-md,.container-sm,.container-xs{max-width:1008px}}@media(min-width:1240px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xs{max-width:1118px}}.row{display:flex;flex-wrap:wrap;margin-right:-20px;margin-left:-20px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col-auto,.col-lg,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-auto,.col-md,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md-auto,.col-sm,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-auto,.col-xs,.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-auto{position:relative;width:100%;padding-right:20px;padding-left:20px}.col{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-1>*{flex:0 0 100%;max-width:100%}.row-cols-2>*{flex:0 0 50%;max-width:50%}.row-cols-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-4>*{flex:0 0 25%;max-width:25%}.row-cols-5>*{flex:0 0 20%;max-width:20%}.row-cols-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto;max-width:100%}.col-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.3333333333%}.offset-2{margin-left:16.6666666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333333333%}.offset-5{margin-left:41.6666666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333333333%}.offset-8{margin-left:66.6666666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333333333%}.offset-11{margin-left:91.6666666667%}@media(min-width:400px){.col-xs{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-xs-1>*{flex:0 0 100%;max-width:100%}.row-cols-xs-2>*{flex:0 0 50%;max-width:50%}.row-cols-xs-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-xs-4>*{flex:0 0 25%;max-width:25%}.row-cols-xs-5>*{flex:0 0 20%;max-width:20%}.row-cols-xs-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xs-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xs-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-xs-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xs-3{flex:0 0 25%;max-width:25%}.col-xs-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-xs-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-xs-6{flex:0 0 50%;max-width:50%}.col-xs-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-xs-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-xs-9{flex:0 0 75%;max-width:75%}.col-xs-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-xs-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-xs-12{flex:0 0 100%;max-width:100%}.order-xs-first{order:-1}.order-xs-last{order:13}.order-xs-0{order:0}.order-xs-1{order:1}.order-xs-2{order:2}.order-xs-3{order:3}.order-xs-4{order:4}.order-xs-5{order:5}.order-xs-6{order:6}.order-xs-7{order:7}.order-xs-8{order:8}.order-xs-9{order:9}.order-xs-10{order:10}.order-xs-11{order:11}.order-xs-12{order:12}.offset-xs-0{margin-left:0}.offset-xs-1{margin-left:8.3333333333%}.offset-xs-2{margin-left:16.6666666667%}.offset-xs-3{margin-left:25%}.offset-xs-4{margin-left:33.3333333333%}.offset-xs-5{margin-left:41.6666666667%}.offset-xs-6{margin-left:50%}.offset-xs-7{margin-left:58.3333333333%}.offset-xs-8{margin-left:66.6666666667%}.offset-xs-9{margin-left:75%}.offset-xs-10{margin-left:83.3333333333%}.offset-xs-11{margin-left:91.6666666667%}}@media(min-width:616px){.col-sm{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-sm-1>*{flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-sm-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-sm-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-sm-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-sm-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.3333333333%}.offset-sm-2{margin-left:16.6666666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333333333%}.offset-sm-5{margin-left:41.6666666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333333333%}.offset-sm-8{margin-left:66.6666666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333333333%}.offset-sm-11{margin-left:91.6666666667%}}@media(min-width:768px){.col-md{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-md-1>*{flex:0 0 100%;max-width:100%}.row-cols-md-2>*{flex:0 0 50%;max-width:50%}.row-cols-md-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-md-4>*{flex:0 0 25%;max-width:25%}.row-cols-md-5>*{flex:0 0 20%;max-width:20%}.row-cols-md-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto;max-width:100%}.col-md-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-md-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-md-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-md-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-md-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.3333333333%}.offset-md-2{margin-left:16.6666666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333333333%}.offset-md-5{margin-left:41.6666666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333333333%}.offset-md-8{margin-left:66.6666666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333333333%}.offset-md-11{margin-left:91.6666666667%}}@media(min-width:980px){.col-lg{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-lg-1>*{flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-lg-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-lg-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-lg-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-lg-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.3333333333%}.offset-lg-2{margin-left:16.6666666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333333333%}.offset-lg-5{margin-left:41.6666666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333333333%}.offset-lg-8{margin-left:66.6666666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333333333%}.offset-lg-11{margin-left:91.6666666667%}}@media(min-width:1240px){.col-xl{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-xl-1>*{flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-xl-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-xl-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-xl-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-xl-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.3333333333%}.offset-xl-2{margin-left:16.6666666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333333333%}.offset-xl-5{margin-left:41.6666666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333333333%}.offset-xl-8{margin-left:66.6666666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333333333%}.offset-xl-11{margin-left:91.6666666667%}}.table{width:100%;margin-bottom:8px;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #d6dbdf}.table thead th{vertical-align:bottom;border-bottom:2px solid #d6dbdf}.table tbody+tbody{border-top:2px solid #d6dbdf}.table-sm td,.table-sm th{padding:.3rem}.table-bordered,.table-bordered td,.table-bordered th{border:1px solid #d6dbdf}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(33,37,41,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(33,37,41,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#fff1b8}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#ffe47a}.table-hover .table-primary:hover,.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#ffec9f}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#c1c2c3}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#8c8e90}.table-hover .table-secondary:hover,.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#b4b5b6}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover,.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover,.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover,.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover,.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fbfcfd}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#f8fafc}.table-hover .table-light:hover,.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#eaeff5}.table-dark,.table-dark>td,.table-dark>th{background-color:#ccced0}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#a0a4a8}.table-hover .table-dark:hover,.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#bfc1c4}.table-primary-light,.table-primary-light>td,.table-primary-light>th{background-color:#fffefb}.table-primary-light tbody+tbody,.table-primary-light td,.table-primary-light th,.table-primary-light thead th{border-color:#fffcf7}.table-hover .table-primary-light:hover,.table-hover .table-primary-light:hover>td,.table-hover .table-primary-light:hover>th{background-color:#fff8e2}.table-secondary-light,.table-secondary-light>td,.table-secondary-light>th{background-color:#fff}.table-secondary-light tbody+tbody,.table-secondary-light td,.table-secondary-light th,.table-secondary-light thead th{border-color:#fff}.table-hover .table-secondary-light:hover,.table-hover .table-secondary-light:hover>td,.table-hover .table-secondary-light:hover>th{background-color:#f2f2f2}.table-tertiary,.table-tertiary>td,.table-tertiary>th{background-color:#c2dafc}.table-tertiary tbody+tbody,.table-tertiary td,.table-tertiary th,.table-tertiary thead th{border-color:#8ebaf9}.table-hover .table-tertiary:hover,.table-hover .table-tertiary:hover>td,.table-hover .table-tertiary:hover>th{background-color:#aacbfb}.table-tertiary-light,.table-tertiary-light>td,.table-tertiary-light>th{background-color:#f7fbff}.table-tertiary-light tbody+tbody,.table-tertiary-light td,.table-tertiary-light th,.table-tertiary-light thead th{border-color:#f0f8fe}.table-hover .table-tertiary-light:hover,.table-hover .table-tertiary-light:hover>td,.table-hover .table-tertiary-light:hover>th{background-color:#deeeff}.table-white,.table-white>td,.table-white>th{background-color:#fff}.table-white tbody+tbody,.table-white td,.table-white th,.table-white thead th{border-color:#fff}.table-hover .table-white:hover,.table-hover .table-white:hover>td,.table-hover .table-white:hover>th{background-color:#f2f2f2}.table-black,.table-black>td,.table-black>th{background-color:#c1c2c3}.table-black tbody+tbody,.table-black td,.table-black th,.table-black thead th{border-color:#8c8e90}.table-hover .table-black:hover,.table-hover .table-black:hover>td,.table-hover .table-black:hover>th{background-color:#b4b5b6}.table-blue,.table-blue>td,.table-blue>th{background-color:#c2dafc}.table-blue tbody+tbody,.table-blue td,.table-blue th,.table-blue thead th{border-color:#8ebaf9}.table-hover .table-blue:hover,.table-hover .table-blue:hover>td,.table-hover .table-blue:hover>th{background-color:#aacbfb}.table-light-blue,.table-light-blue>td,.table-light-blue>th{background-color:#f7fbff}.table-light-blue tbody+tbody,.table-light-blue td,.table-light-blue th,.table-light-blue thead th{border-color:#f0f8fe}.table-hover .table-light-blue:hover,.table-hover .table-light-blue:hover>td,.table-hover .table-light-blue:hover>th{background-color:#deeeff}.table-yellow,.table-yellow>td,.table-yellow>th{background-color:#fff1b8}.table-yellow tbody+tbody,.table-yellow td,.table-yellow th,.table-yellow thead th{border-color:#ffe47a}.table-hover .table-yellow:hover,.table-hover .table-yellow:hover>td,.table-hover .table-yellow:hover>th{background-color:#ffec9f}.table-light-yellow,.table-light-yellow>td,.table-light-yellow>th{background-color:#fffefb}.table-light-yellow tbody+tbody,.table-light-yellow td,.table-light-yellow th,.table-light-yellow thead th{border-color:#fffcf7}.table-hover .table-light-yellow:hover,.table-hover .table-light-yellow:hover>td,.table-hover .table-light-yellow:hover>th{background-color:#fff8e2}.table-orange,.table-orange>td,.table-orange>th{background-color:#ffdfb8}.table-orange tbody+tbody,.table-orange td,.table-orange th,.table-orange thead th{border-color:#ffc37a}.table-hover .table-orange:hover,.table-hover .table-orange:hover>td,.table-hover .table-orange:hover>th{background-color:#ffd49f}.table-light-orange,.table-light-orange>td,.table-light-orange>th{background-color:#fff7ea}.table-light-orange tbody+tbody,.table-light-orange td,.table-light-orange th,.table-light-orange thead th{border-color:#fff1d9}.table-hover .table-light-orange:hover,.table-hover .table-light-orange:hover>td,.table-hover .table-light-orange:hover>th{background-color:#ffedd1}.table-red,.table-red>td,.table-red>th{background-color:#ffc8c8}.table-red tbody+tbody,.table-red td,.table-red th,.table-red thead th{border-color:#ff9898}.table-hover .table-red:hover,.table-hover .table-red:hover>td,.table-hover .table-red:hover>th{background-color:#ffafaf}.table-light-red,.table-light-red>td,.table-light-red>th{background-color:#fff7f7}.table-light-red tbody+tbody,.table-light-red td,.table-light-red th,.table-light-red thead th{border-color:#fff1ef}.table-hover .table-light-red:hover,.table-hover .table-light-red:hover>td,.table-hover .table-light-red:hover>th{background-color:#ffdede}.table-medium,.table-medium>td,.table-medium>th{background-color:#f4f5f6}.table-medium tbody+tbody,.table-medium td,.table-medium th,.table-medium thead th{border-color:#eaecee}.table-hover .table-medium:hover,.table-hover .table-medium:hover>td,.table-hover .table-medium:hover>th{background-color:#e6e8eb}.table-active,.table-active>td,.table-active>th{background-color:rgba(33,37,41,.075)}.table-hover .table-active:hover,.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(22,24,27,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#6c757d;background-color:#e9ecef;border-color:#d6dbdf}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:hsla(0,0%,100%,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:hsla(0,0%,100%,.075)}@media(max-width:399.98px){.table-responsive-xs{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xs>.table-bordered{border:0}}@media(max-width:615.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media(max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media(max-width:979.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media(max-width:1239.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#6c757d;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:8px;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #6c757d}.form-control:focus{color:#6c757d;background-color:#fff;border-color:#ffe680;outline:0;box-shadow:0 0 0 .2rem rgba(255,204,0,.25)}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#6c757d;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.125rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:4px}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.125rem;line-height:1.5;border-radius:8px}select.form-control[multiple],select.form-control[size],textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:inline-flex;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:8px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label:before,.was-validated .custom-control-input:valid~.custom-control-label:before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label:before,.was-validated .custom-control-input:valid:checked~.custom-control-label:before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label:before,.was-validated .custom-control-input:valid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label:before,.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:8px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3E%3C/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label:before,.was-validated .custom-control-input:invalid~.custom-control-label:before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label:before,.was-validated .custom-control-input:invalid:checked~.custom-control-label:before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label:before,.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:flex;flex-flow:row wrap;align-items:center}.form-inline .form-check{width:100%}@media(min-width:616px){.form-inline label{justify-content:center}.form-inline .form-group,.form-inline label{display:flex;align-items:center;margin-bottom:0}.form-inline .form-group{flex:0 0 auto;flex-flow:row wrap}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:flex;align-items:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-family:inherit;font-weight:700;color:#212529;text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:12px 32px;font-size:.875rem;line-height:20px;border-radius:8px;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:none}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#495057;background-color:#fc0;border-color:#fc0}.btn-primary.focus,.btn-primary:focus,.btn-primary:hover{color:#495057;background-color:#d9ad00;border-color:#cca300}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 0 rgba(228,185,13,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#495057;background-color:#fc0;border-color:#fc0}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#495057;background-color:#cca300;border-color:#bf9900}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(228,185,13,.5)}.btn-secondary{color:#fff;background-color:#212529;border-color:#212529}.btn-secondary.focus,.btn-secondary:focus,.btn-secondary:hover{color:#fff;background-color:#101214;border-color:#0a0c0d}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 0 rgba(66,70,73,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#0a0c0d;border-color:#050506}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(66,70,73,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success.focus,.btn-success:focus,.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 0 rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info.focus,.btn-info:focus,.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 0 rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(58,176,195,.5)}.btn-warning{color:#495057;background-color:#ffc107;border-color:#ffc107}.btn-warning.focus,.btn-warning:focus,.btn-warning:hover{color:#495057;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 0 rgba(228,176,19,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#495057;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#495057;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(228,176,19,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger.focus,.btn-danger:focus,.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 0 rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(225,83,97,.5)}.btn-light{color:#495057;background-color:#f1f6f9;border-color:#f1f6f9}.btn-light.focus,.btn-light:focus,.btn-light:hover{color:#495057;background-color:#d6e5ee;border-color:#cddfea}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 0 rgba(216,221,225,.5)}.btn-light.disabled,.btn-light:disabled{color:#495057;background-color:#f1f6f9;border-color:#f1f6f9}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#495057;background-color:#cddfea;border-color:#c4d9e6}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(216,221,225,.5)}.btn-dark{color:#fff;background-color:#495057;border-color:#495057}.btn-dark.focus,.btn-dark:focus,.btn-dark:hover{color:#fff;background-color:#383d42;border-color:#32373b}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 0 rgba(100,106,112,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#495057;border-color:#495057}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#32373b;border-color:#2c3034}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(100,106,112,.5)}.btn-primary-light{color:#495057;background-color:#fffaf0;border-color:#fffaf0}.btn-primary-light.focus,.btn-primary-light:focus,.btn-primary-light:hover{color:#495057;background-color:#ffedca;border-color:#ffe9bd}.btn-primary-light.focus,.btn-primary-light:focus{box-shadow:0 0 0 0 rgba(228,225,217,.5)}.btn-primary-light.disabled,.btn-primary-light:disabled{color:#495057;background-color:#fffaf0;border-color:#fffaf0}.btn-primary-light:not(:disabled):not(.disabled).active,.btn-primary-light:not(:disabled):not(.disabled):active,.show>.btn-primary-light.dropdown-toggle{color:#495057;background-color:#ffe9bd;border-color:#ffe5b0}.btn-primary-light:not(:disabled):not(.disabled).active:focus,.btn-primary-light:not(:disabled):not(.disabled):active:focus,.show>.btn-primary-light.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(228,225,217,.5)}.btn-secondary-light{color:#495057;background-color:#fff;border-color:#fff}.btn-secondary-light.focus,.btn-secondary-light:focus,.btn-secondary-light:hover{color:#495057;background-color:#ececec;border-color:#e6e6e6}.btn-secondary-light.focus,.btn-secondary-light:focus{box-shadow:0 0 0 0 rgba(228,229,230,.5)}.btn-secondary-light.disabled,.btn-secondary-light:disabled{color:#495057;background-color:#fff;border-color:#fff}.btn-secondary-light:not(:disabled):not(.disabled).active,.btn-secondary-light:not(:disabled):not(.disabled):active,.show>.btn-secondary-light.dropdown-toggle{color:#495057;background-color:#e6e6e6;border-color:#dfdfdf}.btn-secondary-light:not(:disabled):not(.disabled).active:focus,.btn-secondary-light:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary-light.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(228,229,230,.5)}.btn-tertiary{color:#fff;background-color:#257af4;border-color:#257af4}.btn-tertiary.focus,.btn-tertiary:focus,.btn-tertiary:hover{color:#fff;background-color:#0c66e7;border-color:#0b60db}.btn-tertiary.focus,.btn-tertiary:focus{box-shadow:0 0 0 0 rgba(70,142,246,.5)}.btn-tertiary.disabled,.btn-tertiary:disabled{color:#fff;background-color:#257af4;border-color:#257af4}.btn-tertiary:not(:disabled):not(.disabled).active,.btn-tertiary:not(:disabled):not(.disabled):active,.show>.btn-tertiary.dropdown-toggle{color:#fff;background-color:#0b60db;border-color:#0a5bcf}.btn-tertiary:not(:disabled):not(.disabled).active:focus,.btn-tertiary:not(:disabled):not(.disabled):active:focus,.show>.btn-tertiary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(70,142,246,.5)}.btn-tertiary-light{color:#495057;background-color:#e3f1fe;border-color:#e3f1fe}.btn-tertiary-light.focus,.btn-tertiary-light:focus,.btn-tertiary-light:hover{color:#495057;background-color:#bedffd;border-color:#b2d8fc}.btn-tertiary-light.focus,.btn-tertiary-light:focus{box-shadow:0 0 0 0 rgba(204,217,229,.5)}.btn-tertiary-light.disabled,.btn-tertiary-light:disabled{color:#495057;background-color:#e3f1fe;border-color:#e3f1fe}.btn-tertiary-light:not(:disabled):not(.disabled).active,.btn-tertiary-light:not(:disabled):not(.disabled):active,.show>.btn-tertiary-light.dropdown-toggle{color:#495057;background-color:#b2d8fc;border-color:#a5d2fc}.btn-tertiary-light:not(:disabled):not(.disabled).active:focus,.btn-tertiary-light:not(:disabled):not(.disabled):active:focus,.show>.btn-tertiary-light.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(204,217,229,.5)}.btn-white{color:#495057;background-color:#fff;border-color:#fff}.btn-white.focus,.btn-white:focus,.btn-white:hover{color:#495057;background-color:#ececec;border-color:#e6e6e6}.btn-white.focus,.btn-white:focus{box-shadow:0 0 0 0 rgba(228,229,230,.5)}.btn-white.disabled,.btn-white:disabled{color:#495057;background-color:#fff;border-color:#fff}.btn-white:not(:disabled):not(.disabled).active,.btn-white:not(:disabled):not(.disabled):active,.show>.btn-white.dropdown-toggle{color:#495057;background-color:#e6e6e6;border-color:#dfdfdf}.btn-white:not(:disabled):not(.disabled).active:focus,.btn-white:not(:disabled):not(.disabled):active:focus,.show>.btn-white.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(228,229,230,.5)}.btn-black{color:#fff;background-color:#212529;border-color:#212529}.btn-black.focus,.btn-black:focus,.btn-black:hover{color:#fff;background-color:#101214;border-color:#0a0c0d}.btn-black.focus,.btn-black:focus{box-shadow:0 0 0 0 rgba(66,70,73,.5)}.btn-black.disabled,.btn-black:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-black:not(:disabled):not(.disabled).active,.btn-black:not(:disabled):not(.disabled):active,.show>.btn-black.dropdown-toggle{color:#fff;background-color:#0a0c0d;border-color:#050506}.btn-black:not(:disabled):not(.disabled).active:focus,.btn-black:not(:disabled):not(.disabled):active:focus,.show>.btn-black.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(66,70,73,.5)}.btn-blue{color:#fff;background-color:#257af4;border-color:#257af4}.btn-blue.focus,.btn-blue:focus,.btn-blue:hover{color:#fff;background-color:#0c66e7;border-color:#0b60db}.btn-blue.focus,.btn-blue:focus{box-shadow:0 0 0 0 rgba(70,142,246,.5)}.btn-blue.disabled,.btn-blue:disabled{color:#fff;background-color:#257af4;border-color:#257af4}.btn-blue:not(:disabled):not(.disabled).active,.btn-blue:not(:disabled):not(.disabled):active,.show>.btn-blue.dropdown-toggle{color:#fff;background-color:#0b60db;border-color:#0a5bcf}.btn-blue:not(:disabled):not(.disabled).active:focus,.btn-blue:not(:disabled):not(.disabled):active:focus,.show>.btn-blue.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(70,142,246,.5)}.btn-light-blue{color:#495057;background-color:#e3f1fe;border-color:#e3f1fe}.btn-light-blue.focus,.btn-light-blue:focus,.btn-light-blue:hover{color:#495057;background-color:#bedffd;border-color:#b2d8fc}.btn-light-blue.focus,.btn-light-blue:focus{box-shadow:0 0 0 0 rgba(204,217,229,.5)}.btn-light-blue.disabled,.btn-light-blue:disabled{color:#495057;background-color:#e3f1fe;border-color:#e3f1fe}.btn-light-blue:not(:disabled):not(.disabled).active,.btn-light-blue:not(:disabled):not(.disabled):active,.show>.btn-light-blue.dropdown-toggle{color:#495057;background-color:#b2d8fc;border-color:#a5d2fc}.btn-light-blue:not(:disabled):not(.disabled).active:focus,.btn-light-blue:not(:disabled):not(.disabled):active:focus,.show>.btn-light-blue.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(204,217,229,.5)}.btn-yellow{color:#495057;background-color:#fc0;border-color:#fc0}.btn-yellow.focus,.btn-yellow:focus,.btn-yellow:hover{color:#495057;background-color:#d9ad00;border-color:#cca300}.btn-yellow.focus,.btn-yellow:focus{box-shadow:0 0 0 0 rgba(228,185,13,.5)}.btn-yellow.disabled,.btn-yellow:disabled{color:#495057;background-color:#fc0;border-color:#fc0}.btn-yellow:not(:disabled):not(.disabled).active,.btn-yellow:not(:disabled):not(.disabled):active,.show>.btn-yellow.dropdown-toggle{color:#495057;background-color:#cca300;border-color:#bf9900}.btn-yellow:not(:disabled):not(.disabled).active:focus,.btn-yellow:not(:disabled):not(.disabled):active:focus,.show>.btn-yellow.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(228,185,13,.5)}.btn-light-yellow{color:#495057;background-color:#fffaf0;border-color:#fffaf0}.btn-light-yellow.focus,.btn-light-yellow:focus,.btn-light-yellow:hover{color:#495057;background-color:#ffedca;border-color:#ffe9bd}.btn-light-yellow.focus,.btn-light-yellow:focus{box-shadow:0 0 0 0 rgba(228,225,217,.5)}.btn-light-yellow.disabled,.btn-light-yellow:disabled{color:#495057;background-color:#fffaf0;border-color:#fffaf0}.btn-light-yellow:not(:disabled):not(.disabled).active,.btn-light-yellow:not(:disabled):not(.disabled):active,.show>.btn-light-yellow.dropdown-toggle{color:#495057;background-color:#ffe9bd;border-color:#ffe5b0}.btn-light-yellow:not(:disabled):not(.disabled).active:focus,.btn-light-yellow:not(:disabled):not(.disabled):active:focus,.show>.btn-light-yellow.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(228,225,217,.5)}.btn-orange{color:#495057;background-color:#ff8c00;border-color:#ff8c00}.btn-orange.focus,.btn-orange:focus,.btn-orange:hover{color:#fff;background-color:#d97700;border-color:#cc7000}.btn-orange.focus,.btn-orange:focus{box-shadow:0 0 0 0 rgba(228,131,13,.5)}.btn-orange.disabled,.btn-orange:disabled{color:#495057;background-color:#ff8c00;border-color:#ff8c00}.btn-orange:not(:disabled):not(.disabled).active,.btn-orange:not(:disabled):not(.disabled):active,.show>.btn-orange.dropdown-toggle{color:#fff;background-color:#cc7000;border-color:#bf6900}.btn-orange:not(:disabled):not(.disabled).active:focus,.btn-orange:not(:disabled):not(.disabled):active:focus,.show>.btn-orange.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(228,131,13,.5)}.btn-light-orange{color:#495057;background-color:#ffe4b5;border-color:#ffe4b5}.btn-light-orange.focus,.btn-light-orange:focus,.btn-light-orange:hover{color:#495057;background-color:#ffd68f;border-color:#ffd182}.btn-light-orange.focus,.btn-light-orange:focus{box-shadow:0 0 0 0 rgba(228,206,167,.5)}.btn-light-orange.disabled,.btn-light-orange:disabled{color:#495057;background-color:#ffe4b5;border-color:#ffe4b5}.btn-light-orange:not(:disabled):not(.disabled).active,.btn-light-orange:not(:disabled):not(.disabled):active,.show>.btn-light-orange.dropdown-toggle{color:#495057;background-color:#ffd182;border-color:#ffcd75}.btn-light-orange:not(:disabled):not(.disabled).active:focus,.btn-light-orange:not(:disabled):not(.disabled):active:focus,.show>.btn-light-orange.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(228,206,167,.5)}.btn-red{color:#fff;background-color:#ff3939;border-color:#ff3939}.btn-red.focus,.btn-red:focus,.btn-red:hover{color:#fff;background-color:#ff1313;border-color:#ff0606}.btn-red.focus,.btn-red:focus{box-shadow:0 0 0 0 rgba(255,87,87,.5)}.btn-red.disabled,.btn-red:disabled{color:#fff;background-color:#ff3939;border-color:#ff3939}.btn-red:not(:disabled):not(.disabled).active,.btn-red:not(:disabled):not(.disabled):active,.show>.btn-red.dropdown-toggle{color:#fff;background-color:#ff0606;border-color:#f80000}.btn-red:not(:disabled):not(.disabled).active:focus,.btn-red:not(:disabled):not(.disabled):active:focus,.show>.btn-red.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,87,87,.5)}.btn-light-red{color:#495057;background-color:#ffe4e1;border-color:#ffe4e1}.btn-light-red.focus,.btn-light-red:focus,.btn-light-red:hover{color:#495057;background-color:#ffc2bb;border-color:#ffb6ae}.btn-light-red.focus,.btn-light-red:focus{box-shadow:0 0 0 0 rgba(228,206,204,.5)}.btn-light-red.disabled,.btn-light-red:disabled{color:#495057;background-color:#ffe4e1;border-color:#ffe4e1}.btn-light-red:not(:disabled):not(.disabled).active,.btn-light-red:not(:disabled):not(.disabled):active,.show>.btn-light-red.dropdown-toggle{color:#495057;background-color:#ffb6ae;border-color:#ffaba1}.btn-light-red:not(:disabled):not(.disabled).active:focus,.btn-light-red:not(:disabled):not(.disabled):active:focus,.show>.btn-light-red.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(228,206,204,.5)}.btn-medium{color:#495057;background-color:#d6dbdf;border-color:#d6dbdf}.btn-medium.focus,.btn-medium:focus,.btn-medium:hover{color:#495057;background-color:#c1c8ce;border-color:#b9c2c9}.btn-medium.focus,.btn-medium:focus{box-shadow:0 0 0 0 rgba(193,198,203,.5)}.btn-medium.disabled,.btn-medium:disabled{color:#495057;background-color:#d6dbdf;border-color:#d6dbdf}.btn-medium:not(:disabled):not(.disabled).active,.btn-medium:not(:disabled):not(.disabled):active,.show>.btn-medium.dropdown-toggle{color:#495057;background-color:#b9c2c9;border-color:#b2bcc3}.btn-medium:not(:disabled):not(.disabled).active:focus,.btn-medium:not(:disabled):not(.disabled):active:focus,.show>.btn-medium.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(193,198,203,.5)}.btn-outline-primary{color:#fc0;border-color:#fc0}.btn-outline-primary:hover{color:#495057;background-color:#fc0;border-color:#fc0}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 0 rgba(255,204,0,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#fc0;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#495057;background-color:#fc0;border-color:#fc0}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,204,0,.5)}.btn-outline-secondary{color:#212529;border-color:#212529}.btn-outline-secondary:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 0 rgba(33,37,41,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#212529;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(33,37,41,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 0 rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 0 rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#495057;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 0 rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#495057;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 0 rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(220,53,69,.5)}.btn-outline-light{color:#f1f6f9;border-color:#f1f6f9}.btn-outline-light:hover{color:#495057;background-color:#f1f6f9;border-color:#f1f6f9}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 0 rgba(241,246,249,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f1f6f9;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#495057;background-color:#f1f6f9;border-color:#f1f6f9}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(241,246,249,.5)}.btn-outline-dark{color:#495057;border-color:#495057}.btn-outline-dark:hover{color:#fff;background-color:#495057;border-color:#495057}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 0 rgba(73,80,87,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#495057;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#495057;border-color:#495057}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(73,80,87,.5)}.btn-outline-primary-light{color:#fffaf0;border-color:#fffaf0}.btn-outline-primary-light:hover{color:#495057;background-color:#fffaf0;border-color:#fffaf0}.btn-outline-primary-light.focus,.btn-outline-primary-light:focus{box-shadow:0 0 0 0 rgba(255,250,240,.5)}.btn-outline-primary-light.disabled,.btn-outline-primary-light:disabled{color:#fffaf0;background-color:transparent}.btn-outline-primary-light:not(:disabled):not(.disabled).active,.btn-outline-primary-light:not(:disabled):not(.disabled):active,.show>.btn-outline-primary-light.dropdown-toggle{color:#495057;background-color:#fffaf0;border-color:#fffaf0}.btn-outline-primary-light:not(:disabled):not(.disabled).active:focus,.btn-outline-primary-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary-light.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,250,240,.5)}.btn-outline-secondary-light{color:#fff;border-color:#fff}.btn-outline-secondary-light:hover{color:#495057;background-color:#fff;border-color:#fff}.btn-outline-secondary-light.focus,.btn-outline-secondary-light:focus{box-shadow:0 0 0 0 hsla(0,0%,100%,.5)}.btn-outline-secondary-light.disabled,.btn-outline-secondary-light:disabled{color:#fff;background-color:transparent}.btn-outline-secondary-light:not(:disabled):not(.disabled).active,.btn-outline-secondary-light:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary-light.dropdown-toggle{color:#495057;background-color:#fff;border-color:#fff}.btn-outline-secondary-light:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary-light.dropdown-toggle:focus{box-shadow:0 0 0 0 hsla(0,0%,100%,.5)}.btn-outline-tertiary{color:#257af4;border-color:#257af4}.btn-outline-tertiary:hover{color:#fff;background-color:#257af4;border-color:#257af4}.btn-outline-tertiary.focus,.btn-outline-tertiary:focus{box-shadow:0 0 0 0 rgba(37,122,244,.5)}.btn-outline-tertiary.disabled,.btn-outline-tertiary:disabled{color:#257af4;background-color:transparent}.btn-outline-tertiary:not(:disabled):not(.disabled).active,.btn-outline-tertiary:not(:disabled):not(.disabled):active,.show>.btn-outline-tertiary.dropdown-toggle{color:#fff;background-color:#257af4;border-color:#257af4}.btn-outline-tertiary:not(:disabled):not(.disabled).active:focus,.btn-outline-tertiary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-tertiary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(37,122,244,.5)}.btn-outline-tertiary-light{color:#e3f1fe;border-color:#e3f1fe}.btn-outline-tertiary-light:hover{color:#495057;background-color:#e3f1fe;border-color:#e3f1fe}.btn-outline-tertiary-light.focus,.btn-outline-tertiary-light:focus{box-shadow:0 0 0 0 rgba(227,241,254,.5)}.btn-outline-tertiary-light.disabled,.btn-outline-tertiary-light:disabled{color:#e3f1fe;background-color:transparent}.btn-outline-tertiary-light:not(:disabled):not(.disabled).active,.btn-outline-tertiary-light:not(:disabled):not(.disabled):active,.show>.btn-outline-tertiary-light.dropdown-toggle{color:#495057;background-color:#e3f1fe;border-color:#e3f1fe}.btn-outline-tertiary-light:not(:disabled):not(.disabled).active:focus,.btn-outline-tertiary-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-tertiary-light.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(227,241,254,.5)}.btn-outline-white{color:#fff;border-color:#fff}.btn-outline-white:hover{color:#495057;background-color:#fff;border-color:#fff}.btn-outline-white.focus,.btn-outline-white:focus{box-shadow:0 0 0 0 hsla(0,0%,100%,.5)}.btn-outline-white.disabled,.btn-outline-white:disabled{color:#fff;background-color:transparent}.btn-outline-white:not(:disabled):not(.disabled).active,.btn-outline-white:not(:disabled):not(.disabled):active,.show>.btn-outline-white.dropdown-toggle{color:#495057;background-color:#fff;border-color:#fff}.btn-outline-white:not(:disabled):not(.disabled).active:focus,.btn-outline-white:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-white.dropdown-toggle:focus{box-shadow:0 0 0 0 hsla(0,0%,100%,.5)}.btn-outline-black{color:#212529;border-color:#212529}.btn-outline-black:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-black.focus,.btn-outline-black:focus{box-shadow:0 0 0 0 rgba(33,37,41,.5)}.btn-outline-black.disabled,.btn-outline-black:disabled{color:#212529;background-color:transparent}.btn-outline-black:not(:disabled):not(.disabled).active,.btn-outline-black:not(:disabled):not(.disabled):active,.show>.btn-outline-black.dropdown-toggle{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-black:not(:disabled):not(.disabled).active:focus,.btn-outline-black:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-black.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(33,37,41,.5)}.btn-outline-blue{color:#257af4;border-color:#257af4}.btn-outline-blue:hover{color:#fff;background-color:#257af4;border-color:#257af4}.btn-outline-blue.focus,.btn-outline-blue:focus{box-shadow:0 0 0 0 rgba(37,122,244,.5)}.btn-outline-blue.disabled,.btn-outline-blue:disabled{color:#257af4;background-color:transparent}.btn-outline-blue:not(:disabled):not(.disabled).active,.btn-outline-blue:not(:disabled):not(.disabled):active,.show>.btn-outline-blue.dropdown-toggle{color:#fff;background-color:#257af4;border-color:#257af4}.btn-outline-blue:not(:disabled):not(.disabled).active:focus,.btn-outline-blue:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-blue.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(37,122,244,.5)}.btn-outline-light-blue{color:#e3f1fe;border-color:#e3f1fe}.btn-outline-light-blue:hover{color:#495057;background-color:#e3f1fe;border-color:#e3f1fe}.btn-outline-light-blue.focus,.btn-outline-light-blue:focus{box-shadow:0 0 0 0 rgba(227,241,254,.5)}.btn-outline-light-blue.disabled,.btn-outline-light-blue:disabled{color:#e3f1fe;background-color:transparent}.btn-outline-light-blue:not(:disabled):not(.disabled).active,.btn-outline-light-blue:not(:disabled):not(.disabled):active,.show>.btn-outline-light-blue.dropdown-toggle{color:#495057;background-color:#e3f1fe;border-color:#e3f1fe}.btn-outline-light-blue:not(:disabled):not(.disabled).active:focus,.btn-outline-light-blue:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light-blue.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(227,241,254,.5)}.btn-outline-yellow{color:#fc0;border-color:#fc0}.btn-outline-yellow:hover{color:#495057;background-color:#fc0;border-color:#fc0}.btn-outline-yellow.focus,.btn-outline-yellow:focus{box-shadow:0 0 0 0 rgba(255,204,0,.5)}.btn-outline-yellow.disabled,.btn-outline-yellow:disabled{color:#fc0;background-color:transparent}.btn-outline-yellow:not(:disabled):not(.disabled).active,.btn-outline-yellow:not(:disabled):not(.disabled):active,.show>.btn-outline-yellow.dropdown-toggle{color:#495057;background-color:#fc0;border-color:#fc0}.btn-outline-yellow:not(:disabled):not(.disabled).active:focus,.btn-outline-yellow:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-yellow.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,204,0,.5)}.btn-outline-light-yellow{color:#fffaf0;border-color:#fffaf0}.btn-outline-light-yellow:hover{color:#495057;background-color:#fffaf0;border-color:#fffaf0}.btn-outline-light-yellow.focus,.btn-outline-light-yellow:focus{box-shadow:0 0 0 0 rgba(255,250,240,.5)}.btn-outline-light-yellow.disabled,.btn-outline-light-yellow:disabled{color:#fffaf0;background-color:transparent}.btn-outline-light-yellow:not(:disabled):not(.disabled).active,.btn-outline-light-yellow:not(:disabled):not(.disabled):active,.show>.btn-outline-light-yellow.dropdown-toggle{color:#495057;background-color:#fffaf0;border-color:#fffaf0}.btn-outline-light-yellow:not(:disabled):not(.disabled).active:focus,.btn-outline-light-yellow:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light-yellow.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,250,240,.5)}.btn-outline-orange{color:#ff8c00;border-color:#ff8c00}.btn-outline-orange:hover{color:#495057;background-color:#ff8c00;border-color:#ff8c00}.btn-outline-orange.focus,.btn-outline-orange:focus{box-shadow:0 0 0 0 rgba(255,140,0,.5)}.btn-outline-orange.disabled,.btn-outline-orange:disabled{color:#ff8c00;background-color:transparent}.btn-outline-orange:not(:disabled):not(.disabled).active,.btn-outline-orange:not(:disabled):not(.disabled):active,.show>.btn-outline-orange.dropdown-toggle{color:#495057;background-color:#ff8c00;border-color:#ff8c00}.btn-outline-orange:not(:disabled):not(.disabled).active:focus,.btn-outline-orange:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-orange.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,140,0,.5)}.btn-outline-light-orange{color:#ffe4b5;border-color:#ffe4b5}.btn-outline-light-orange:hover{color:#495057;background-color:#ffe4b5;border-color:#ffe4b5}.btn-outline-light-orange.focus,.btn-outline-light-orange:focus{box-shadow:0 0 0 0 rgba(255,228,181,.5)}.btn-outline-light-orange.disabled,.btn-outline-light-orange:disabled{color:#ffe4b5;background-color:transparent}.btn-outline-light-orange:not(:disabled):not(.disabled).active,.btn-outline-light-orange:not(:disabled):not(.disabled):active,.show>.btn-outline-light-orange.dropdown-toggle{color:#495057;background-color:#ffe4b5;border-color:#ffe4b5}.btn-outline-light-orange:not(:disabled):not(.disabled).active:focus,.btn-outline-light-orange:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light-orange.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,228,181,.5)}.btn-outline-red{color:#ff3939;border-color:#ff3939}.btn-outline-red:hover{color:#fff;background-color:#ff3939;border-color:#ff3939}.btn-outline-red.focus,.btn-outline-red:focus{box-shadow:0 0 0 0 rgba(255,57,57,.5)}.btn-outline-red.disabled,.btn-outline-red:disabled{color:#ff3939;background-color:transparent}.btn-outline-red:not(:disabled):not(.disabled).active,.btn-outline-red:not(:disabled):not(.disabled):active,.show>.btn-outline-red.dropdown-toggle{color:#fff;background-color:#ff3939;border-color:#ff3939}.btn-outline-red:not(:disabled):not(.disabled).active:focus,.btn-outline-red:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-red.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,57,57,.5)}.btn-outline-light-red{color:#ffe4e1;border-color:#ffe4e1}.btn-outline-light-red:hover{color:#495057;background-color:#ffe4e1;border-color:#ffe4e1}.btn-outline-light-red.focus,.btn-outline-light-red:focus{box-shadow:0 0 0 0 rgba(255,228,225,.5)}.btn-outline-light-red.disabled,.btn-outline-light-red:disabled{color:#ffe4e1;background-color:transparent}.btn-outline-light-red:not(:disabled):not(.disabled).active,.btn-outline-light-red:not(:disabled):not(.disabled):active,.show>.btn-outline-light-red.dropdown-toggle{color:#495057;background-color:#ffe4e1;border-color:#ffe4e1}.btn-outline-light-red:not(:disabled):not(.disabled).active:focus,.btn-outline-light-red:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light-red.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,228,225,.5)}.btn-outline-medium{color:#d6dbdf;border-color:#d6dbdf}.btn-outline-medium:hover{color:#495057;background-color:#d6dbdf;border-color:#d6dbdf}.btn-outline-medium.focus,.btn-outline-medium:focus{box-shadow:0 0 0 0 rgba(214,219,223,.5)}.btn-outline-medium.disabled,.btn-outline-medium:disabled{color:#d6dbdf;background-color:transparent}.btn-outline-medium:not(:disabled):not(.disabled).active,.btn-outline-medium:not(:disabled):not(.disabled):active,.show>.btn-outline-medium.dropdown-toggle{color:#495057;background-color:#d6dbdf;border-color:#d6dbdf}.btn-outline-medium:not(:disabled):not(.disabled).active:focus,.btn-outline-medium:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-medium.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(214,219,223,.5)}.btn-link{font-weight:400;color:#ff8c00;text-decoration:none}.btn-link:hover{color:#ff8c00;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#d6dbdf;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:16px 32px;font-size:1.125rem;line-height:26px;border-radius:8px}.btn-group-sm>.btn,.btn-sm{padding:6px 12px;font-size:.75rem;line-height:16px;border-radius:4px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:24px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media(prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(33,37,41,.15);border-radius:8px}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media(min-width:400px){.dropdown-menu-xs-left{right:auto;left:0}.dropdown-menu-xs-right{right:0;left:auto}}@media(min-width:616px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media(min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media(min-width:980px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media(min-width:1240px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-toggle:after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";display:none}.dropleft .dropdown-toggle:before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty:after{margin-left:0}.dropleft .dropdown-toggle:before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:4px 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#495057;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#3d4349;text-decoration:none;background-color:#f1f6f9}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#fc0}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#495057}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:24px;padding-left:24px}.dropdown-toggle-split:after,.dropright .dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after{margin-left:0}.dropleft .dropdown-toggle-split:before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:9px;padding-left:9px}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:24px;padding-left:24px}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio],.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;flex:1 1 0%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:flex;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label:after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#6c757d;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:8px}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.125rem;line-height:1.5;border-radius:8px}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:4px}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label:before{color:#fff;border-color:#fc0;background-color:#fc0}.custom-control-input:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(255,204,0,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label:before{border-color:#ffe680}.custom-control-input:not(:disabled):active~.custom-control-label:before{color:#fff;background-color:#fff0b3;border-color:#fff0b3}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label:before,.custom-control-input[disabled]~.custom-control-label:before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label:before{pointer-events:none;background-color:#fff;border:1px solid #d6dbdf}.custom-control-label:after,.custom-control-label:before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:""}.custom-control-label:after{background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label:before{border-radius:8px}.custom-checkbox .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:before{border-color:#fc0;background-color:#fc0}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(255,204,0,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label:before{background-color:rgba(255,204,0,.5)}.custom-radio .custom-control-label:before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(255,204,0,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label:before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label:after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#d6dbdf;border-radius:.5rem;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.custom-switch .custom-control-label:after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label:after{background-color:#fff;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(255,204,0,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#6c757d;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center/8px 10px;border:1px solid #ced4da;border-radius:8px;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#ffe680;outline:0;box-shadow:0 0 0 .2rem rgba(255,204,0,.25)}.custom-select:focus::-ms-value{color:#6c757d;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #6c757d}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.125rem}.custom-file{display:inline-block;margin-bottom:0}.custom-file,.custom-file-input{position:relative;width:100%;height:calc(1.5em + .75rem + 2px)}.custom-file-input{z-index:2;margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#ffe680;box-shadow:0 0 0 .2rem rgba(255,204,0,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label:after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]:after{content:attr(data-browse)}.custom-file-label{left:0;z-index:1;height:calc(1.5em + .75rem + 2px);font-weight:400;background-color:#fff;border:1px solid #ced4da;border-radius:8px}.custom-file-label,.custom-file-label:after{position:absolute;top:0;right:0;padding:.375rem .75rem;line-height:1.5;color:#6c757d}.custom-file-label:after{bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 8px 8px 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:none}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(255,204,0,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(255,204,0,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(255,204,0,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#fc0;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media(prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#fff0b3}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#fc0;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media(prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#fff0b3}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#fc0;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media(prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#fff0b3}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower,.custom-range::-ms-fill-upper{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px}.custom-range:disabled::-webkit-slider-thumb{background-color:#d6dbdf}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#d6dbdf}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#d6dbdf}.custom-control-label:before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.custom-control-label:before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:0}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#d6dbdf;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #6c757d}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:8px;border-top-right-radius:8px}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:transparent}.nav-tabs .nav-link.disabled{color:#d6dbdf;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#257af4;background-color:#fff;border-color:#6c757d}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:8px}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#fc0}.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;padding:24px 0}.navbar,.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl,.navbar .container-xs{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:-.09375rem;padding-bottom:-.09375rem;margin-right:0;font-size:1.125rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:0;padding-bottom:0}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.125rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:8px}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat 50%;background-size:100% 100%}@media(max-width:399.98px){.navbar-expand-xs>.container,.navbar-expand-xs>.container-fluid,.navbar-expand-xs>.container-lg,.navbar-expand-xs>.container-md,.navbar-expand-xs>.container-sm,.navbar-expand-xs>.container-xl,.navbar-expand-xs>.container-xs{padding-right:0;padding-left:0}}@media(min-width:400px){.navbar-expand-xs{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xs .navbar-nav{flex-direction:row}.navbar-expand-xs .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xs .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xs>.container,.navbar-expand-xs>.container-fluid,.navbar-expand-xs>.container-lg,.navbar-expand-xs>.container-md,.navbar-expand-xs>.container-sm,.navbar-expand-xs>.container-xl,.navbar-expand-xs>.container-xs{flex-wrap:nowrap}.navbar-expand-xs .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xs .navbar-toggler{display:none}}@media(max-width:615.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl,.navbar-expand-sm>.container-xs{padding-right:0;padding-left:0}}@media(min-width:616px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl,.navbar-expand-sm>.container-xs{flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media(max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl,.navbar-expand-md>.container-xs{padding-right:0;padding-left:0}}@media(min-width:768px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl,.navbar-expand-md>.container-xs{flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media(max-width:979.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl,.navbar-expand-lg>.container-xs{padding-right:0;padding-left:0}}@media(min-width:980px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl,.navbar-expand-lg>.container-xs{flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media(max-width:1239.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl,.navbar-expand-xl>.container-xs{padding-right:0;padding-left:0}}@media(min-width:1240px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl,.navbar-expand-xl>.container-xs{flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl,.navbar-expand>.container-xs{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl,.navbar-expand>.container-xs{flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand,.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(33,37,41,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(33,37,41,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(33,37,41,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(33,37,41,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(33,37,41,.9)}.navbar-light .navbar-toggler{color:rgba(33,37,41,.5);border-color:rgba(33,37,41,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(33, 37, 41, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(33,37,41,.5)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(33,37,41,.9)}.navbar-dark .navbar-brand,.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:hsla(0,0%,100%,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:hsla(0,0%,100%,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:hsla(0,0%,100%,.5);border-color:hsla(0,0%,100%,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid #d6dbdf;border-radius:8px}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:8px;border-top-right-radius:8px}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:8px;border-bottom-left-radius:8px}.card-body{flex:1 1 auto;min-height:1px;padding:24px}.card-title{margin-bottom:24px}.card-subtitle{margin-top:-12px}.card-subtitle,.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:24px}.card-header{padding:24px;margin-bottom:0;background-color:#f1f6f9;border-bottom:1px solid #d6dbdf}.card-header:first-child{border-radius:subtract(8px,1px) subtract(8px,1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:24px;background-color:#f1f6f9;border-top:1px solid #d6dbdf}.card-footer:last-child{border-radius:0 0 subtract(8px,1px) subtract(8px,1px)}.card-header-tabs{margin-bottom:-24px;border-bottom:0}.card-header-pills,.card-header-tabs{margin-right:-12px;margin-left:-12px}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:24px}.card-img,.card-img-bottom,.card-img-top{flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:subtract(8px,1px);border-top-right-radius:subtract(8px,1px)}.card-img,.card-img-bottom{border-bottom-right-radius:subtract(8px,1px);border-bottom-left-radius:subtract(8px,1px)}.card-deck .card{margin-bottom:20px}@media(min-width:616px){.card-deck{display:flex;flex-flow:row wrap;margin-right:-20px;margin-left:-20px}.card-deck .card{flex:1 0 0%;margin-right:20px;margin-bottom:0;margin-left:20px}}.card-group>.card{margin-bottom:20px}@media(min-width:616px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:40px}@media(min-width:616px){.card-columns{-moz-column-count:3;column-count:3;-moz-column-gap:40px;column-gap:40px;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:flex;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:8px}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item:before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover:before{text-decoration:underline;text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none;border-radius:8px}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#ff8c00;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#ff8c00;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(255,204,0,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:8px;border-bottom-left-radius:8px}.page-item:last-child .page-link{border-top-right-radius:8px;border-bottom-right-radius:8px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#fc0;border-color:#fc0}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.125rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:8px;border-bottom-left-radius:8px}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:8px;border-bottom-right-radius:8px}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:4px;border-bottom-right-radius:4px}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:8px;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#495057;background-color:#fc0}a.badge-primary:focus,a.badge-primary:hover{color:#495057;background-color:#cca300}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,204,0,.5)}.badge-secondary{color:#fff;background-color:#212529}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#0a0c0d}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(33,37,41,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#495057;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#495057;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#495057;background-color:#f1f6f9}a.badge-light:focus,a.badge-light:hover{color:#495057;background-color:#cddfea}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(241,246,249,.5)}.badge-dark{color:#fff;background-color:#495057}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#32373b}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(73,80,87,.5)}.badge-primary-light{color:#495057;background-color:#fffaf0}a.badge-primary-light:focus,a.badge-primary-light:hover{color:#495057;background-color:#ffe9bd}a.badge-primary-light.focus,a.badge-primary-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,250,240,.5)}.badge-secondary-light{color:#495057;background-color:#fff}a.badge-secondary-light:focus,a.badge-secondary-light:hover{color:#495057;background-color:#e6e6e6}a.badge-secondary-light.focus,a.badge-secondary-light:focus{outline:0;box-shadow:0 0 0 .2rem hsla(0,0%,100%,.5)}.badge-tertiary{color:#fff;background-color:#257af4}a.badge-tertiary:focus,a.badge-tertiary:hover{color:#fff;background-color:#0b60db}a.badge-tertiary.focus,a.badge-tertiary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(37,122,244,.5)}.badge-tertiary-light{color:#495057;background-color:#e3f1fe}a.badge-tertiary-light:focus,a.badge-tertiary-light:hover{color:#495057;background-color:#b2d8fc}a.badge-tertiary-light.focus,a.badge-tertiary-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(227,241,254,.5)}.badge-white{color:#495057;background-color:#fff}a.badge-white:focus,a.badge-white:hover{color:#495057;background-color:#e6e6e6}a.badge-white.focus,a.badge-white:focus{outline:0;box-shadow:0 0 0 .2rem hsla(0,0%,100%,.5)}.badge-black{color:#fff;background-color:#212529}a.badge-black:focus,a.badge-black:hover{color:#fff;background-color:#0a0c0d}a.badge-black.focus,a.badge-black:focus{outline:0;box-shadow:0 0 0 .2rem rgba(33,37,41,.5)}.badge-blue{color:#fff;background-color:#257af4}a.badge-blue:focus,a.badge-blue:hover{color:#fff;background-color:#0b60db}a.badge-blue.focus,a.badge-blue:focus{outline:0;box-shadow:0 0 0 .2rem rgba(37,122,244,.5)}.badge-light-blue{color:#495057;background-color:#e3f1fe}a.badge-light-blue:focus,a.badge-light-blue:hover{color:#495057;background-color:#b2d8fc}a.badge-light-blue.focus,a.badge-light-blue:focus{outline:0;box-shadow:0 0 0 .2rem rgba(227,241,254,.5)}.badge-yellow{color:#495057;background-color:#fc0}a.badge-yellow:focus,a.badge-yellow:hover{color:#495057;background-color:#cca300}a.badge-yellow.focus,a.badge-yellow:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,204,0,.5)}.badge-light-yellow{color:#495057;background-color:#fffaf0}a.badge-light-yellow:focus,a.badge-light-yellow:hover{color:#495057;background-color:#ffe9bd}a.badge-light-yellow.focus,a.badge-light-yellow:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,250,240,.5)}.badge-orange{color:#495057;background-color:#ff8c00}a.badge-orange:focus,a.badge-orange:hover{color:#495057;background-color:#cc7000}a.badge-orange.focus,a.badge-orange:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,140,0,.5)}.badge-light-orange{color:#495057;background-color:#ffe4b5}a.badge-light-orange:focus,a.badge-light-orange:hover{color:#495057;background-color:#ffd182}a.badge-light-orange.focus,a.badge-light-orange:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,228,181,.5)}.badge-red{color:#fff;background-color:#ff3939}a.badge-red:focus,a.badge-red:hover{color:#fff;background-color:#ff0606}a.badge-red.focus,a.badge-red:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,57,57,.5)}.badge-light-red{color:#495057;background-color:#ffe4e1}a.badge-light-red:focus,a.badge-light-red:hover{color:#495057;background-color:#ffb6ae}a.badge-light-red.focus,a.badge-light-red:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,228,225,.5)}.badge-medium{color:#495057;background-color:#d6dbdf}a.badge-medium:focus,a.badge-medium:hover{color:#495057;background-color:#b9c2c9}a.badge-medium.focus,a.badge-medium:focus{outline:0;box-shadow:0 0 0 .2rem rgba(214,219,223,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:8px}@media(min-width:616px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:8px}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#947c14;background-color:#fff5cc;border-color:#fff1b8}.alert-primary hr{border-top-color:#ffec9f}.alert-primary .alert-link{color:#67560e}.alert-secondary{color:#212529;background-color:#d3d3d4;border-color:#c1c2c3}.alert-secondary hr{border-top-color:#b4b5b6}.alert-secondary .alert-link{color:#0a0c0d}.alert-success{color:#256938;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#184324}.alert-info{color:#1c6673;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#12424a}.alert-warning{color:#947617;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#685310}.alert-danger{color:#822d38;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#5c2028}.alert-light{color:#8d9295;background-color:#fcfdfe;border-color:#fbfcfd}.alert-light hr{border-top-color:#eaeff5}.alert-light .alert-link{color:#73797c}.alert-dark{color:#363b41;background-color:#dbdcdd;border-color:#ccced0}.alert-dark hr{border-top-color:#bfc1c4}.alert-dark .alert-link{color:#1f2225}.alert-primary-light{color:#949490;background-color:#fffefc;border-color:#fffefb}.alert-primary-light hr{border-top-color:#fff8e2}.alert-primary-light .alert-link{color:#7b7b76}.alert-secondary-light{color:#949698;background-color:#fff;border-color:#fff}.alert-secondary-light hr{border-top-color:#f2f2f2}.alert-secondary-light .alert-link{color:#7a7d7f}.alert-tertiary{color:#235193;background-color:#d3e4fd;border-color:#c2dafc}.alert-tertiary hr{border-top-color:#aacbfb}.alert-tertiary .alert-link{color:#193a6a}.alert-tertiary-light{color:#868f98;background-color:#f9fcff;border-color:#f7fbff}.alert-tertiary-light hr{border-top-color:#deeeff}.alert-tertiary-light .alert-link{color:#6c767f}.alert-white{color:#949698;background-color:#fff;border-color:#fff}.alert-white hr{border-top-color:#f2f2f2}.alert-white .alert-link{color:#7a7d7f}.alert-black{color:#212529;background-color:#d3d3d4;border-color:#c1c2c3}.alert-black hr{border-top-color:#b4b5b6}.alert-black .alert-link{color:#0a0c0d}.alert-blue{color:#235193;background-color:#d3e4fd;border-color:#c2dafc}.alert-blue hr{border-top-color:#aacbfb}.alert-blue .alert-link{color:#193a6a}.alert-light-blue{color:#868f98;background-color:#f9fcff;border-color:#f7fbff}.alert-light-blue hr{border-top-color:#deeeff}.alert-light-blue .alert-link{color:#6c767f}.alert-yellow{color:#947c14;background-color:#fff5cc;border-color:#fff1b8}.alert-yellow hr{border-top-color:#ffec9f}.alert-yellow .alert-link{color:#67560e}.alert-light-yellow{color:#949490;background-color:#fffefc;border-color:#fffefb}.alert-light-yellow hr{border-top-color:#fff8e2}.alert-light-yellow .alert-link{color:#7b7b76}.alert-orange{color:#945b14;background-color:#ffe8cc;border-color:#ffdfb8}.alert-orange hr{border-top-color:#ffd49f}.alert-orange .alert-link{color:#673f0e}.alert-light-orange{color:#948872;background-color:#fffaf0;border-color:#fff7ea}.alert-light-orange hr{border-top-color:#ffedd1}.alert-light-orange .alert-link{color:#786e5b}.alert-red{color:#942f31;background-color:#ffd7d7;border-color:#ffc8c8}.alert-red hr{border-top-color:#ffafaf}.alert-red .alert-link{color:#6d2324}.alert-light-red{color:#948889;background-color:#fffaf9;border-color:#fff7f7}.alert-light-red hr{border-top-color:#ffdede}.alert-light-red .alert-link{color:#7b6e6f}.alert-medium{color:#7f8488;background-color:#f7f8f9;border-color:#f4f5f6}.alert-medium hr{border-top-color:#e6e8eb}.alert-medium .alert-link{color:#666a6e}@-webkit-keyframes progress-bar-stripes{0%{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{0%{background-position:1rem 0}to{background-position:0 0}}.progress{height:1rem;font-size:.75rem;background-color:#e9ecef;border-radius:8px}.progress,.progress-bar{display:flex;overflow:hidden}.progress-bar{flex-direction:column;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#fc0;transition:width .6s ease}@media(prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media(prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:flex;align-items:flex-start}.media-body{flex:1}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#6c757d;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#6c757d;text-decoration:none;background-color:#f1f6f9}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(33,37,41,.125)}.list-group-item:first-child{border-top-left-radius:8px;border-top-right-radius:8px}.list-group-item:last-child{border-bottom-right-radius:8px;border-bottom-left-radius:8px}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#fc0;border-color:#fc0}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal .list-group-item:first-child{border-bottom-left-radius:8px;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{border-top-right-radius:8px;border-bottom-left-radius:0}.list-group-horizontal .list-group-item.active{margin-top:0}.list-group-horizontal .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media(min-width:400px){.list-group-horizontal-xs{flex-direction:row}.list-group-horizontal-xs .list-group-item:first-child{border-bottom-left-radius:8px;border-top-right-radius:0}.list-group-horizontal-xs .list-group-item:last-child{border-top-right-radius:8px;border-bottom-left-radius:0}.list-group-horizontal-xs .list-group-item.active{margin-top:0}.list-group-horizontal-xs .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xs .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width:616px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm .list-group-item:first-child{border-bottom-left-radius:8px;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{border-top-right-radius:8px;border-bottom-left-radius:0}.list-group-horizontal-sm .list-group-item.active{margin-top:0}.list-group-horizontal-sm .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md .list-group-item:first-child{border-bottom-left-radius:8px;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{border-top-right-radius:8px;border-bottom-left-radius:0}.list-group-horizontal-md .list-group-item.active{margin-top:0}.list-group-horizontal-md .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width:980px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg .list-group-item:first-child{border-bottom-left-radius:8px;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{border-top-right-radius:8px;border-bottom-left-radius:0}.list-group-horizontal-lg .list-group-item.active{margin-top:0}.list-group-horizontal-lg .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width:1240px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl .list-group-item:first-child{border-bottom-left-radius:8px;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{border-top-right-radius:8px;border-bottom-left-radius:0}.list-group-horizontal-xl .list-group-item.active{margin-top:0}.list-group-horizontal-xl .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush .list-group-item{border-right-width:0;border-left-width:0;border-radius:0}.list-group-flush .list-group-item:first-child{border-top-width:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#947c14;background-color:#fff1b8}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#947c14;background-color:#ffec9f}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#947c14;border-color:#947c14}.list-group-item-secondary{color:#212529;background-color:#c1c2c3}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#212529;background-color:#b4b5b6}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#212529;border-color:#212529}.list-group-item-success{color:#256938;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#256938;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#256938;border-color:#256938}.list-group-item-info{color:#1c6673;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#1c6673;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#1c6673;border-color:#1c6673}.list-group-item-warning{color:#947617;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#947617;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#947617;border-color:#947617}.list-group-item-danger{color:#822d38;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#822d38;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#822d38;border-color:#822d38}.list-group-item-light{color:#8d9295;background-color:#fbfcfd}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#8d9295;background-color:#eaeff5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#8d9295;border-color:#8d9295}.list-group-item-dark{color:#363b41;background-color:#ccced0}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#363b41;background-color:#bfc1c4}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#363b41;border-color:#363b41}.list-group-item-primary-light{color:#949490;background-color:#fffefb}.list-group-item-primary-light.list-group-item-action:focus,.list-group-item-primary-light.list-group-item-action:hover{color:#949490;background-color:#fff8e2}.list-group-item-primary-light.list-group-item-action.active{color:#fff;background-color:#949490;border-color:#949490}.list-group-item-secondary-light{color:#949698;background-color:#fff}.list-group-item-secondary-light.list-group-item-action:focus,.list-group-item-secondary-light.list-group-item-action:hover{color:#949698;background-color:#f2f2f2}.list-group-item-secondary-light.list-group-item-action.active{color:#fff;background-color:#949698;border-color:#949698}.list-group-item-tertiary{color:#235193;background-color:#c2dafc}.list-group-item-tertiary.list-group-item-action:focus,.list-group-item-tertiary.list-group-item-action:hover{color:#235193;background-color:#aacbfb}.list-group-item-tertiary.list-group-item-action.active{color:#fff;background-color:#235193;border-color:#235193}.list-group-item-tertiary-light{color:#868f98;background-color:#f7fbff}.list-group-item-tertiary-light.list-group-item-action:focus,.list-group-item-tertiary-light.list-group-item-action:hover{color:#868f98;background-color:#deeeff}.list-group-item-tertiary-light.list-group-item-action.active{color:#fff;background-color:#868f98;border-color:#868f98}.list-group-item-white{color:#949698;background-color:#fff}.list-group-item-white.list-group-item-action:focus,.list-group-item-white.list-group-item-action:hover{color:#949698;background-color:#f2f2f2}.list-group-item-white.list-group-item-action.active{color:#fff;background-color:#949698;border-color:#949698}.list-group-item-black{color:#212529;background-color:#c1c2c3}.list-group-item-black.list-group-item-action:focus,.list-group-item-black.list-group-item-action:hover{color:#212529;background-color:#b4b5b6}.list-group-item-black.list-group-item-action.active{color:#fff;background-color:#212529;border-color:#212529}.list-group-item-blue{color:#235193;background-color:#c2dafc}.list-group-item-blue.list-group-item-action:focus,.list-group-item-blue.list-group-item-action:hover{color:#235193;background-color:#aacbfb}.list-group-item-blue.list-group-item-action.active{color:#fff;background-color:#235193;border-color:#235193}.list-group-item-light-blue{color:#868f98;background-color:#f7fbff}.list-group-item-light-blue.list-group-item-action:focus,.list-group-item-light-blue.list-group-item-action:hover{color:#868f98;background-color:#deeeff}.list-group-item-light-blue.list-group-item-action.active{color:#fff;background-color:#868f98;border-color:#868f98}.list-group-item-yellow{color:#947c14;background-color:#fff1b8}.list-group-item-yellow.list-group-item-action:focus,.list-group-item-yellow.list-group-item-action:hover{color:#947c14;background-color:#ffec9f}.list-group-item-yellow.list-group-item-action.active{color:#fff;background-color:#947c14;border-color:#947c14}.list-group-item-light-yellow{color:#949490;background-color:#fffefb}.list-group-item-light-yellow.list-group-item-action:focus,.list-group-item-light-yellow.list-group-item-action:hover{color:#949490;background-color:#fff8e2}.list-group-item-light-yellow.list-group-item-action.active{color:#fff;background-color:#949490;border-color:#949490}.list-group-item-orange{color:#945b14;background-color:#ffdfb8}.list-group-item-orange.list-group-item-action:focus,.list-group-item-orange.list-group-item-action:hover{color:#945b14;background-color:#ffd49f}.list-group-item-orange.list-group-item-action.active{color:#fff;background-color:#945b14;border-color:#945b14}.list-group-item-light-orange{color:#948872;background-color:#fff7ea}.list-group-item-light-orange.list-group-item-action:focus,.list-group-item-light-orange.list-group-item-action:hover{color:#948872;background-color:#ffedd1}.list-group-item-light-orange.list-group-item-action.active{color:#fff;background-color:#948872;border-color:#948872}.list-group-item-red{color:#942f31;background-color:#ffc8c8}.list-group-item-red.list-group-item-action:focus,.list-group-item-red.list-group-item-action:hover{color:#942f31;background-color:#ffafaf}.list-group-item-red.list-group-item-action.active{color:#fff;background-color:#942f31;border-color:#942f31}.list-group-item-light-red{color:#948889;background-color:#fff7f7}.list-group-item-light-red.list-group-item-action:focus,.list-group-item-light-red.list-group-item-action:hover{color:#948889;background-color:#ffdede}.list-group-item-light-red.list-group-item-action.active{color:#fff;background-color:#948889;border-color:#948889}.list-group-item-medium{color:#7f8488;background-color:#f4f5f6}.list-group-item-medium.list-group-item-action:focus,.list-group-item-medium.list-group-item-action:hover{color:#7f8488;background-color:#e6e8eb}.list-group-item-medium.list-group-item-action.active{color:#fff;background-color:#7f8488;border-color:#7f8488}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#212529;text-shadow:0 1px 0 #fff;opacity:.5}@media(max-width:1200px){.close{font-size:calc(1.275rem + .3vw)}}.close:hover{color:#212529;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:hsla(0,0%,100%,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(33,37,41,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:flex;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:hsla(0,0%,100%,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translateY(-50px)}@media(prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered:before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable:before{content:none}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(33,37,41,.2);border-radius:8px;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#212529}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:1rem;border-bottom:1px solid #d6dbdf;border-top-left-radius:7px;border-top-right-radius:7px}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #d6dbdf;border-bottom-right-radius:7px;border-bottom-left-radius:7px}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media(min-width:616px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered:before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media(min-width:980px){.modal-lg,.modal-xl{max-width:800px}}@media(min-width:1240px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:Noto Sans,sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow:before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow:before,.bs-tooltip-top .arrow:before{top:0;border-width:.4rem .4rem 0;border-top-color:#212529}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow:before,.bs-tooltip-right .arrow:before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#212529}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow:before,.bs-tooltip-bottom .arrow:before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#212529}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow:before,.bs-tooltip-left .arrow:before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#212529}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#212529;border-radius:8px}.popover{top:0;left:0;z-index:1060;max-width:276px;font-family:Noto Sans,sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(33,37,41,.2);border-radius:8px}.popover,.popover .arrow{position:absolute;display:block}.popover .arrow{width:1rem;height:.5rem;margin:0 8px}.popover .arrow:after,.popover .arrow:before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow:before,.bs-popover-top>.arrow:before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(33,37,41,.25)}.bs-popover-auto[x-placement^=top]>.arrow:after,.bs-popover-top>.arrow:after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:8px 0}.bs-popover-auto[x-placement^=right]>.arrow:before,.bs-popover-right>.arrow:before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(33,37,41,.25)}.bs-popover-auto[x-placement^=right]>.arrow:after,.bs-popover-right>.arrow:after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow:before,.bs-popover-bottom>.arrow:before{top:0;border-width:0 .5rem .5rem;border-bottom-color:rgba(33,37,41,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow:after,.bs-popover-bottom>.arrow:after{top:1px;border-width:0 .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header:before,.bs-popover-bottom .popover-header:before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:8px 0}.bs-popover-auto[x-placement^=left]>.arrow:before,.bs-popover-left>.arrow:before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(33,37,41,.25)}.bs-popover-auto[x-placement^=left]>.arrow:after,.bs-popover-left>.arrow:after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:7px;border-top-right-radius:7px}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner:after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{transform:rotate(1turn)}}@keyframes spinner-border{to{transform:rotate(1turn)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid;border-right:.25em solid transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#fc0!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#cca300!important}.bg-secondary{background-color:#212529!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#0a0c0d!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f1f6f9!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#cddfea!important}.bg-dark{background-color:#495057!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#32373b!important}.bg-primary-light{background-color:#fffaf0!important}a.bg-primary-light:focus,a.bg-primary-light:hover,button.bg-primary-light:focus,button.bg-primary-light:hover{background-color:#ffe9bd!important}.bg-secondary-light{background-color:#fff!important}a.bg-secondary-light:focus,a.bg-secondary-light:hover,button.bg-secondary-light:focus,button.bg-secondary-light:hover{background-color:#e6e6e6!important}.bg-tertiary{background-color:#257af4!important}a.bg-tertiary:focus,a.bg-tertiary:hover,button.bg-tertiary:focus,button.bg-tertiary:hover{background-color:#0b60db!important}.bg-tertiary-light{background-color:#e3f1fe!important}a.bg-tertiary-light:focus,a.bg-tertiary-light:hover,button.bg-tertiary-light:focus,button.bg-tertiary-light:hover{background-color:#b2d8fc!important}a.bg-white:focus,a.bg-white:hover,button.bg-white:focus,button.bg-white:hover{background-color:#e6e6e6!important}.bg-black{background-color:#212529!important}a.bg-black:focus,a.bg-black:hover,button.bg-black:focus,button.bg-black:hover{background-color:#0a0c0d!important}.bg-blue{background-color:#257af4!important}a.bg-blue:focus,a.bg-blue:hover,button.bg-blue:focus,button.bg-blue:hover{background-color:#0b60db!important}.bg-light-blue{background-color:#e3f1fe!important}a.bg-light-blue:focus,a.bg-light-blue:hover,button.bg-light-blue:focus,button.bg-light-blue:hover{background-color:#b2d8fc!important}.bg-yellow{background-color:#fc0!important}a.bg-yellow:focus,a.bg-yellow:hover,button.bg-yellow:focus,button.bg-yellow:hover{background-color:#cca300!important}.bg-light-yellow{background-color:#fffaf0!important}a.bg-light-yellow:focus,a.bg-light-yellow:hover,button.bg-light-yellow:focus,button.bg-light-yellow:hover{background-color:#ffe9bd!important}.bg-orange{background-color:#ff8c00!important}a.bg-orange:focus,a.bg-orange:hover,button.bg-orange:focus,button.bg-orange:hover{background-color:#cc7000!important}.bg-light-orange{background-color:#ffe4b5!important}a.bg-light-orange:focus,a.bg-light-orange:hover,button.bg-light-orange:focus,button.bg-light-orange:hover{background-color:#ffd182!important}.bg-red{background-color:#ff3939!important}a.bg-red:focus,a.bg-red:hover,button.bg-red:focus,button.bg-red:hover{background-color:#ff0606!important}.bg-light-red{background-color:#ffe4e1!important}a.bg-light-red:focus,a.bg-light-red:hover,button.bg-light-red:focus,button.bg-light-red:hover{background-color:#ffb6ae!important}.bg-medium{background-color:#d6dbdf!important}a.bg-medium:focus,a.bg-medium:hover,button.bg-medium:focus,button.bg-medium:hover{background-color:#b9c2c9!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #d6dbdf!important}.border-top{border-top:1px solid #d6dbdf!important}.border-right{border-right:1px solid #d6dbdf!important}.border-bottom{border-bottom:1px solid #d6dbdf!important}.border-left{border-left:1px solid #d6dbdf!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#fc0!important}.border-secondary{border-color:#212529!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f1f6f9!important}.border-dark{border-color:#495057!important}.border-primary-light{border-color:#fffaf0!important}.border-secondary-light{border-color:#fff!important}.border-tertiary{border-color:#257af4!important}.border-tertiary-light{border-color:#e3f1fe!important}.border-black{border-color:#212529!important}.border-blue{border-color:#257af4!important}.border-light-blue{border-color:#e3f1fe!important}.border-yellow{border-color:#fc0!important}.border-light-yellow{border-color:#fffaf0!important}.border-orange{border-color:#ff8c00!important}.border-light-orange{border-color:#ffe4b5!important}.border-red{border-color:#ff3939!important}.border-light-red{border-color:#ffe4e1!important}.border-medium{border-color:#d6dbdf!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:4px!important}.rounded{border-radius:8px!important}.rounded-top{border-top-left-radius:8px!important}.rounded-right,.rounded-top{border-top-right-radius:8px!important}.rounded-bottom,.rounded-right{border-bottom-right-radius:8px!important}.rounded-bottom,.rounded-left{border-bottom-left-radius:8px!important}.rounded-left{border-top-left-radius:8px!important}.rounded-lg{border-radius:8px!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix:after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media(min-width:400px){.d-xs-none{display:none!important}.d-xs-inline{display:inline!important}.d-xs-inline-block{display:inline-block!important}.d-xs-block{display:block!important}.d-xs-table{display:table!important}.d-xs-table-row{display:table-row!important}.d-xs-table-cell{display:table-cell!important}.d-xs-flex{display:flex!important}.d-xs-inline-flex{display:inline-flex!important}}@media(min-width:616px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media(min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media(min-width:980px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media(min-width:1240px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive:before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9:before{padding-top:42.8571428571%}.embed-responsive-16by9:before{padding-top:56.25%}.embed-responsive-4by3:before{padding-top:75%}.embed-responsive-1by1:before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media(min-width:400px){.flex-xs-row{flex-direction:row!important}.flex-xs-column{flex-direction:column!important}.flex-xs-row-reverse{flex-direction:row-reverse!important}.flex-xs-column-reverse{flex-direction:column-reverse!important}.flex-xs-wrap{flex-wrap:wrap!important}.flex-xs-nowrap{flex-wrap:nowrap!important}.flex-xs-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xs-fill{flex:1 1 auto!important}.flex-xs-grow-0{flex-grow:0!important}.flex-xs-grow-1{flex-grow:1!important}.flex-xs-shrink-0{flex-shrink:0!important}.flex-xs-shrink-1{flex-shrink:1!important}.justify-content-xs-start{justify-content:flex-start!important}.justify-content-xs-end{justify-content:flex-end!important}.justify-content-xs-center{justify-content:center!important}.justify-content-xs-between{justify-content:space-between!important}.justify-content-xs-around{justify-content:space-around!important}.align-items-xs-start{align-items:flex-start!important}.align-items-xs-end{align-items:flex-end!important}.align-items-xs-center{align-items:center!important}.align-items-xs-baseline{align-items:baseline!important}.align-items-xs-stretch{align-items:stretch!important}.align-content-xs-start{align-content:flex-start!important}.align-content-xs-end{align-content:flex-end!important}.align-content-xs-center{align-content:center!important}.align-content-xs-between{align-content:space-between!important}.align-content-xs-around{align-content:space-around!important}.align-content-xs-stretch{align-content:stretch!important}.align-self-xs-auto{align-self:auto!important}.align-self-xs-start{align-self:flex-start!important}.align-self-xs-end{align-self:flex-end!important}.align-self-xs-center{align-self:center!important}.align-self-xs-baseline{align-self:baseline!important}.align-self-xs-stretch{align-self:stretch!important}}@media(min-width:616px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media(min-width:768px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media(min-width:980px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media(min-width:1240px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media(min-width:400px){.float-xs-left{float:left!important}.float-xs-right{float:right!important}.float-xs-none{float:none!important}}@media(min-width:616px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media(min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media(min-width:980px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media(min-width:1240px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{top:0}.fixed-bottom,.fixed-top{position:fixed;right:0;left:0;z-index:1030}.fixed-bottom{bottom:0}@supports(position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 2px 14px rgba(108,117,125,.2)!important}.shadow{box-shadow:0 8px 20px rgba(108,117,125,.2)!important}.shadow-lg{box-shadow:0 12px 32px rgba(108,117,125,.2)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.stretched-link:after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:transparent}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:8px!important}.mt-1,.my-1{margin-top:8px!important}.mr-1,.mx-1{margin-right:8px!important}.mb-1,.my-1{margin-bottom:8px!important}.ml-1,.mx-1{margin-left:8px!important}.m-2{margin:16px!important}.mt-2,.my-2{margin-top:16px!important}.mr-2,.mx-2{margin-right:16px!important}.mb-2,.my-2{margin-bottom:16px!important}.ml-2,.mx-2{margin-left:16px!important}.m-3{margin:24px!important}.mt-3,.my-3{margin-top:24px!important}.mr-3,.mx-3{margin-right:24px!important}.mb-3,.my-3{margin-bottom:24px!important}.ml-3,.mx-3{margin-left:24px!important}.m-4{margin:32px!important}.mt-4,.my-4{margin-top:32px!important}.mr-4,.mx-4{margin-right:32px!important}.mb-4,.my-4{margin-bottom:32px!important}.ml-4,.mx-4{margin-left:32px!important}.m-5{margin:40px!important}.mt-5,.my-5{margin-top:40px!important}.mr-5,.mx-5{margin-right:40px!important}.mb-5,.my-5{margin-bottom:40px!important}.ml-5,.mx-5{margin-left:40px!important}.m-6{margin:48px!important}.mt-6,.my-6{margin-top:48px!important}.mr-6,.mx-6{margin-right:48px!important}.mb-6,.my-6{margin-bottom:48px!important}.ml-6,.mx-6{margin-left:48px!important}.m-7{margin:56px!important}.mt-7,.my-7{margin-top:56px!important}.mr-7,.mx-7{margin-right:56px!important}.mb-7,.my-7{margin-bottom:56px!important}.ml-7,.mx-7{margin-left:56px!important}.m-8{margin:64px!important}.mt-8,.my-8{margin-top:64px!important}.mr-8,.mx-8{margin-right:64px!important}.mb-8,.my-8{margin-bottom:64px!important}.ml-8,.mx-8{margin-left:64px!important}.m-9{margin:72px!important}.mt-9,.my-9{margin-top:72px!important}.mr-9,.mx-9{margin-right:72px!important}.mb-9,.my-9{margin-bottom:72px!important}.ml-9,.mx-9{margin-left:72px!important}.m-10{margin:80px!important}.mt-10,.my-10{margin-top:80px!important}.mr-10,.mx-10{margin-right:80px!important}.mb-10,.my-10{margin-bottom:80px!important}.ml-10,.mx-10{margin-left:80px!important}.m-12{margin:96px!important}.mt-12,.my-12{margin-top:96px!important}.mr-12,.mx-12{margin-right:96px!important}.mb-12,.my-12{margin-bottom:96px!important}.ml-12,.mx-12{margin-left:96px!important}.m-15{margin:120px!important}.mt-15,.my-15{margin-top:120px!important}.mr-15,.mx-15{margin-right:120px!important}.mb-15,.my-15{margin-bottom:120px!important}.ml-15,.mx-15{margin-left:120px!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:8px!important}.pt-1,.py-1{padding-top:8px!important}.pr-1,.px-1{padding-right:8px!important}.pb-1,.py-1{padding-bottom:8px!important}.pl-1,.px-1{padding-left:8px!important}.p-2{padding:16px!important}.pt-2,.py-2{padding-top:16px!important}.pr-2,.px-2{padding-right:16px!important}.pb-2,.py-2{padding-bottom:16px!important}.pl-2,.px-2{padding-left:16px!important}.p-3{padding:24px!important}.pt-3,.py-3{padding-top:24px!important}.pr-3,.px-3{padding-right:24px!important}.pb-3,.py-3{padding-bottom:24px!important}.pl-3,.px-3{padding-left:24px!important}.p-4{padding:32px!important}.pt-4,.py-4{padding-top:32px!important}.pr-4,.px-4{padding-right:32px!important}.pb-4,.py-4{padding-bottom:32px!important}.pl-4,.px-4{padding-left:32px!important}.p-5{padding:40px!important}.pt-5,.py-5{padding-top:40px!important}.pr-5,.px-5{padding-right:40px!important}.pb-5,.py-5{padding-bottom:40px!important}.pl-5,.px-5{padding-left:40px!important}.p-6{padding:48px!important}.pt-6,.py-6{padding-top:48px!important}.pr-6,.px-6{padding-right:48px!important}.pb-6,.py-6{padding-bottom:48px!important}.pl-6,.px-6{padding-left:48px!important}.p-7{padding:56px!important}.pt-7,.py-7{padding-top:56px!important}.pr-7,.px-7{padding-right:56px!important}.pb-7,.py-7{padding-bottom:56px!important}.pl-7,.px-7{padding-left:56px!important}.p-8{padding:64px!important}.pt-8,.py-8{padding-top:64px!important}.pr-8,.px-8{padding-right:64px!important}.pb-8,.py-8{padding-bottom:64px!important}.pl-8,.px-8{padding-left:64px!important}.p-9{padding:72px!important}.pt-9,.py-9{padding-top:72px!important}.pr-9,.px-9{padding-right:72px!important}.pb-9,.py-9{padding-bottom:72px!important}.pl-9,.px-9{padding-left:72px!important}.p-10{padding:80px!important}.pt-10,.py-10{padding-top:80px!important}.pr-10,.px-10{padding-right:80px!important}.pb-10,.py-10{padding-bottom:80px!important}.pl-10,.px-10{padding-left:80px!important}.p-12{padding:96px!important}.pt-12,.py-12{padding-top:96px!important}.pr-12,.px-12{padding-right:96px!important}.pb-12,.py-12{padding-bottom:96px!important}.pl-12,.px-12{padding-left:96px!important}.p-15{padding:120px!important}.pt-15,.py-15{padding-top:120px!important}.pr-15,.px-15{padding-right:120px!important}.pb-15,.py-15{padding-bottom:120px!important}.pl-15,.px-15{padding-left:120px!important}.m-n1{margin:-8px!important}.mt-n1,.my-n1{margin-top:-8px!important}.mr-n1,.mx-n1{margin-right:-8px!important}.mb-n1,.my-n1{margin-bottom:-8px!important}.ml-n1,.mx-n1{margin-left:-8px!important}.m-n2{margin:-16px!important}.mt-n2,.my-n2{margin-top:-16px!important}.mr-n2,.mx-n2{margin-right:-16px!important}.mb-n2,.my-n2{margin-bottom:-16px!important}.ml-n2,.mx-n2{margin-left:-16px!important}.m-n3{margin:-24px!important}.mt-n3,.my-n3{margin-top:-24px!important}.mr-n3,.mx-n3{margin-right:-24px!important}.mb-n3,.my-n3{margin-bottom:-24px!important}.ml-n3,.mx-n3{margin-left:-24px!important}.m-n4{margin:-32px!important}.mt-n4,.my-n4{margin-top:-32px!important}.mr-n4,.mx-n4{margin-right:-32px!important}.mb-n4,.my-n4{margin-bottom:-32px!important}.ml-n4,.mx-n4{margin-left:-32px!important}.m-n5{margin:-40px!important}.mt-n5,.my-n5{margin-top:-40px!important}.mr-n5,.mx-n5{margin-right:-40px!important}.mb-n5,.my-n5{margin-bottom:-40px!important}.ml-n5,.mx-n5{margin-left:-40px!important}.m-n6{margin:-48px!important}.mt-n6,.my-n6{margin-top:-48px!important}.mr-n6,.mx-n6{margin-right:-48px!important}.mb-n6,.my-n6{margin-bottom:-48px!important}.ml-n6,.mx-n6{margin-left:-48px!important}.m-n7{margin:-56px!important}.mt-n7,.my-n7{margin-top:-56px!important}.mr-n7,.mx-n7{margin-right:-56px!important}.mb-n7,.my-n7{margin-bottom:-56px!important}.ml-n7,.mx-n7{margin-left:-56px!important}.m-n8{margin:-64px!important}.mt-n8,.my-n8{margin-top:-64px!important}.mr-n8,.mx-n8{margin-right:-64px!important}.mb-n8,.my-n8{margin-bottom:-64px!important}.ml-n8,.mx-n8{margin-left:-64px!important}.m-n9{margin:-72px!important}.mt-n9,.my-n9{margin-top:-72px!important}.mr-n9,.mx-n9{margin-right:-72px!important}.mb-n9,.my-n9{margin-bottom:-72px!important}.ml-n9,.mx-n9{margin-left:-72px!important}.m-n10{margin:-80px!important}.mt-n10,.my-n10{margin-top:-80px!important}.mr-n10,.mx-n10{margin-right:-80px!important}.mb-n10,.my-n10{margin-bottom:-80px!important}.ml-n10,.mx-n10{margin-left:-80px!important}.m-n12{margin:-96px!important}.mt-n12,.my-n12{margin-top:-96px!important}.mr-n12,.mx-n12{margin-right:-96px!important}.mb-n12,.my-n12{margin-bottom:-96px!important}.ml-n12,.mx-n12{margin-left:-96px!important}.m-n15{margin:-120px!important}.mt-n15,.my-n15{margin-top:-120px!important}.mr-n15,.mx-n15{margin-right:-120px!important}.mb-n15,.my-n15{margin-bottom:-120px!important}.ml-n15,.mx-n15{margin-left:-120px!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media(min-width:400px){.m-xs-0{margin:0!important}.mt-xs-0,.my-xs-0{margin-top:0!important}.mr-xs-0,.mx-xs-0{margin-right:0!important}.mb-xs-0,.my-xs-0{margin-bottom:0!important}.ml-xs-0,.mx-xs-0{margin-left:0!important}.m-xs-1{margin:8px!important}.mt-xs-1,.my-xs-1{margin-top:8px!important}.mr-xs-1,.mx-xs-1{margin-right:8px!important}.mb-xs-1,.my-xs-1{margin-bottom:8px!important}.ml-xs-1,.mx-xs-1{margin-left:8px!important}.m-xs-2{margin:16px!important}.mt-xs-2,.my-xs-2{margin-top:16px!important}.mr-xs-2,.mx-xs-2{margin-right:16px!important}.mb-xs-2,.my-xs-2{margin-bottom:16px!important}.ml-xs-2,.mx-xs-2{margin-left:16px!important}.m-xs-3{margin:24px!important}.mt-xs-3,.my-xs-3{margin-top:24px!important}.mr-xs-3,.mx-xs-3{margin-right:24px!important}.mb-xs-3,.my-xs-3{margin-bottom:24px!important}.ml-xs-3,.mx-xs-3{margin-left:24px!important}.m-xs-4{margin:32px!important}.mt-xs-4,.my-xs-4{margin-top:32px!important}.mr-xs-4,.mx-xs-4{margin-right:32px!important}.mb-xs-4,.my-xs-4{margin-bottom:32px!important}.ml-xs-4,.mx-xs-4{margin-left:32px!important}.m-xs-5{margin:40px!important}.mt-xs-5,.my-xs-5{margin-top:40px!important}.mr-xs-5,.mx-xs-5{margin-right:40px!important}.mb-xs-5,.my-xs-5{margin-bottom:40px!important}.ml-xs-5,.mx-xs-5{margin-left:40px!important}.m-xs-6{margin:48px!important}.mt-xs-6,.my-xs-6{margin-top:48px!important}.mr-xs-6,.mx-xs-6{margin-right:48px!important}.mb-xs-6,.my-xs-6{margin-bottom:48px!important}.ml-xs-6,.mx-xs-6{margin-left:48px!important}.m-xs-7{margin:56px!important}.mt-xs-7,.my-xs-7{margin-top:56px!important}.mr-xs-7,.mx-xs-7{margin-right:56px!important}.mb-xs-7,.my-xs-7{margin-bottom:56px!important}.ml-xs-7,.mx-xs-7{margin-left:56px!important}.m-xs-8{margin:64px!important}.mt-xs-8,.my-xs-8{margin-top:64px!important}.mr-xs-8,.mx-xs-8{margin-right:64px!important}.mb-xs-8,.my-xs-8{margin-bottom:64px!important}.ml-xs-8,.mx-xs-8{margin-left:64px!important}.m-xs-9{margin:72px!important}.mt-xs-9,.my-xs-9{margin-top:72px!important}.mr-xs-9,.mx-xs-9{margin-right:72px!important}.mb-xs-9,.my-xs-9{margin-bottom:72px!important}.ml-xs-9,.mx-xs-9{margin-left:72px!important}.m-xs-10{margin:80px!important}.mt-xs-10,.my-xs-10{margin-top:80px!important}.mr-xs-10,.mx-xs-10{margin-right:80px!important}.mb-xs-10,.my-xs-10{margin-bottom:80px!important}.ml-xs-10,.mx-xs-10{margin-left:80px!important}.m-xs-12{margin:96px!important}.mt-xs-12,.my-xs-12{margin-top:96px!important}.mr-xs-12,.mx-xs-12{margin-right:96px!important}.mb-xs-12,.my-xs-12{margin-bottom:96px!important}.ml-xs-12,.mx-xs-12{margin-left:96px!important}.m-xs-15{margin:120px!important}.mt-xs-15,.my-xs-15{margin-top:120px!important}.mr-xs-15,.mx-xs-15{margin-right:120px!important}.mb-xs-15,.my-xs-15{margin-bottom:120px!important}.ml-xs-15,.mx-xs-15{margin-left:120px!important}.p-xs-0{padding:0!important}.pt-xs-0,.py-xs-0{padding-top:0!important}.pr-xs-0,.px-xs-0{padding-right:0!important}.pb-xs-0,.py-xs-0{padding-bottom:0!important}.pl-xs-0,.px-xs-0{padding-left:0!important}.p-xs-1{padding:8px!important}.pt-xs-1,.py-xs-1{padding-top:8px!important}.pr-xs-1,.px-xs-1{padding-right:8px!important}.pb-xs-1,.py-xs-1{padding-bottom:8px!important}.pl-xs-1,.px-xs-1{padding-left:8px!important}.p-xs-2{padding:16px!important}.pt-xs-2,.py-xs-2{padding-top:16px!important}.pr-xs-2,.px-xs-2{padding-right:16px!important}.pb-xs-2,.py-xs-2{padding-bottom:16px!important}.pl-xs-2,.px-xs-2{padding-left:16px!important}.p-xs-3{padding:24px!important}.pt-xs-3,.py-xs-3{padding-top:24px!important}.pr-xs-3,.px-xs-3{padding-right:24px!important}.pb-xs-3,.py-xs-3{padding-bottom:24px!important}.pl-xs-3,.px-xs-3{padding-left:24px!important}.p-xs-4{padding:32px!important}.pt-xs-4,.py-xs-4{padding-top:32px!important}.pr-xs-4,.px-xs-4{padding-right:32px!important}.pb-xs-4,.py-xs-4{padding-bottom:32px!important}.pl-xs-4,.px-xs-4{padding-left:32px!important}.p-xs-5{padding:40px!important}.pt-xs-5,.py-xs-5{padding-top:40px!important}.pr-xs-5,.px-xs-5{padding-right:40px!important}.pb-xs-5,.py-xs-5{padding-bottom:40px!important}.pl-xs-5,.px-xs-5{padding-left:40px!important}.p-xs-6{padding:48px!important}.pt-xs-6,.py-xs-6{padding-top:48px!important}.pr-xs-6,.px-xs-6{padding-right:48px!important}.pb-xs-6,.py-xs-6{padding-bottom:48px!important}.pl-xs-6,.px-xs-6{padding-left:48px!important}.p-xs-7{padding:56px!important}.pt-xs-7,.py-xs-7{padding-top:56px!important}.pr-xs-7,.px-xs-7{padding-right:56px!important}.pb-xs-7,.py-xs-7{padding-bottom:56px!important}.pl-xs-7,.px-xs-7{padding-left:56px!important}.p-xs-8{padding:64px!important}.pt-xs-8,.py-xs-8{padding-top:64px!important}.pr-xs-8,.px-xs-8{padding-right:64px!important}.pb-xs-8,.py-xs-8{padding-bottom:64px!important}.pl-xs-8,.px-xs-8{padding-left:64px!important}.p-xs-9{padding:72px!important}.pt-xs-9,.py-xs-9{padding-top:72px!important}.pr-xs-9,.px-xs-9{padding-right:72px!important}.pb-xs-9,.py-xs-9{padding-bottom:72px!important}.pl-xs-9,.px-xs-9{padding-left:72px!important}.p-xs-10{padding:80px!important}.pt-xs-10,.py-xs-10{padding-top:80px!important}.pr-xs-10,.px-xs-10{padding-right:80px!important}.pb-xs-10,.py-xs-10{padding-bottom:80px!important}.pl-xs-10,.px-xs-10{padding-left:80px!important}.p-xs-12{padding:96px!important}.pt-xs-12,.py-xs-12{padding-top:96px!important}.pr-xs-12,.px-xs-12{padding-right:96px!important}.pb-xs-12,.py-xs-12{padding-bottom:96px!important}.pl-xs-12,.px-xs-12{padding-left:96px!important}.p-xs-15{padding:120px!important}.pt-xs-15,.py-xs-15{padding-top:120px!important}.pr-xs-15,.px-xs-15{padding-right:120px!important}.pb-xs-15,.py-xs-15{padding-bottom:120px!important}.pl-xs-15,.px-xs-15{padding-left:120px!important}.m-xs-n1{margin:-8px!important}.mt-xs-n1,.my-xs-n1{margin-top:-8px!important}.mr-xs-n1,.mx-xs-n1{margin-right:-8px!important}.mb-xs-n1,.my-xs-n1{margin-bottom:-8px!important}.ml-xs-n1,.mx-xs-n1{margin-left:-8px!important}.m-xs-n2{margin:-16px!important}.mt-xs-n2,.my-xs-n2{margin-top:-16px!important}.mr-xs-n2,.mx-xs-n2{margin-right:-16px!important}.mb-xs-n2,.my-xs-n2{margin-bottom:-16px!important}.ml-xs-n2,.mx-xs-n2{margin-left:-16px!important}.m-xs-n3{margin:-24px!important}.mt-xs-n3,.my-xs-n3{margin-top:-24px!important}.mr-xs-n3,.mx-xs-n3{margin-right:-24px!important}.mb-xs-n3,.my-xs-n3{margin-bottom:-24px!important}.ml-xs-n3,.mx-xs-n3{margin-left:-24px!important}.m-xs-n4{margin:-32px!important}.mt-xs-n4,.my-xs-n4{margin-top:-32px!important}.mr-xs-n4,.mx-xs-n4{margin-right:-32px!important}.mb-xs-n4,.my-xs-n4{margin-bottom:-32px!important}.ml-xs-n4,.mx-xs-n4{margin-left:-32px!important}.m-xs-n5{margin:-40px!important}.mt-xs-n5,.my-xs-n5{margin-top:-40px!important}.mr-xs-n5,.mx-xs-n5{margin-right:-40px!important}.mb-xs-n5,.my-xs-n5{margin-bottom:-40px!important}.ml-xs-n5,.mx-xs-n5{margin-left:-40px!important}.m-xs-n6{margin:-48px!important}.mt-xs-n6,.my-xs-n6{margin-top:-48px!important}.mr-xs-n6,.mx-xs-n6{margin-right:-48px!important}.mb-xs-n6,.my-xs-n6{margin-bottom:-48px!important}.ml-xs-n6,.mx-xs-n6{margin-left:-48px!important}.m-xs-n7{margin:-56px!important}.mt-xs-n7,.my-xs-n7{margin-top:-56px!important}.mr-xs-n7,.mx-xs-n7{margin-right:-56px!important}.mb-xs-n7,.my-xs-n7{margin-bottom:-56px!important}.ml-xs-n7,.mx-xs-n7{margin-left:-56px!important}.m-xs-n8{margin:-64px!important}.mt-xs-n8,.my-xs-n8{margin-top:-64px!important}.mr-xs-n8,.mx-xs-n8{margin-right:-64px!important}.mb-xs-n8,.my-xs-n8{margin-bottom:-64px!important}.ml-xs-n8,.mx-xs-n8{margin-left:-64px!important}.m-xs-n9{margin:-72px!important}.mt-xs-n9,.my-xs-n9{margin-top:-72px!important}.mr-xs-n9,.mx-xs-n9{margin-right:-72px!important}.mb-xs-n9,.my-xs-n9{margin-bottom:-72px!important}.ml-xs-n9,.mx-xs-n9{margin-left:-72px!important}.m-xs-n10{margin:-80px!important}.mt-xs-n10,.my-xs-n10{margin-top:-80px!important}.mr-xs-n10,.mx-xs-n10{margin-right:-80px!important}.mb-xs-n10,.my-xs-n10{margin-bottom:-80px!important}.ml-xs-n10,.mx-xs-n10{margin-left:-80px!important}.m-xs-n12{margin:-96px!important}.mt-xs-n12,.my-xs-n12{margin-top:-96px!important}.mr-xs-n12,.mx-xs-n12{margin-right:-96px!important}.mb-xs-n12,.my-xs-n12{margin-bottom:-96px!important}.ml-xs-n12,.mx-xs-n12{margin-left:-96px!important}.m-xs-n15{margin:-120px!important}.mt-xs-n15,.my-xs-n15{margin-top:-120px!important}.mr-xs-n15,.mx-xs-n15{margin-right:-120px!important}.mb-xs-n15,.my-xs-n15{margin-bottom:-120px!important}.ml-xs-n15,.mx-xs-n15{margin-left:-120px!important}.m-xs-auto{margin:auto!important}.mt-xs-auto,.my-xs-auto{margin-top:auto!important}.mr-xs-auto,.mx-xs-auto{margin-right:auto!important}.mb-xs-auto,.my-xs-auto{margin-bottom:auto!important}.ml-xs-auto,.mx-xs-auto{margin-left:auto!important}}@media(min-width:616px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:8px!important}.mt-sm-1,.my-sm-1{margin-top:8px!important}.mr-sm-1,.mx-sm-1{margin-right:8px!important}.mb-sm-1,.my-sm-1{margin-bottom:8px!important}.ml-sm-1,.mx-sm-1{margin-left:8px!important}.m-sm-2{margin:16px!important}.mt-sm-2,.my-sm-2{margin-top:16px!important}.mr-sm-2,.mx-sm-2{margin-right:16px!important}.mb-sm-2,.my-sm-2{margin-bottom:16px!important}.ml-sm-2,.mx-sm-2{margin-left:16px!important}.m-sm-3{margin:24px!important}.mt-sm-3,.my-sm-3{margin-top:24px!important}.mr-sm-3,.mx-sm-3{margin-right:24px!important}.mb-sm-3,.my-sm-3{margin-bottom:24px!important}.ml-sm-3,.mx-sm-3{margin-left:24px!important}.m-sm-4{margin:32px!important}.mt-sm-4,.my-sm-4{margin-top:32px!important}.mr-sm-4,.mx-sm-4{margin-right:32px!important}.mb-sm-4,.my-sm-4{margin-bottom:32px!important}.ml-sm-4,.mx-sm-4{margin-left:32px!important}.m-sm-5{margin:40px!important}.mt-sm-5,.my-sm-5{margin-top:40px!important}.mr-sm-5,.mx-sm-5{margin-right:40px!important}.mb-sm-5,.my-sm-5{margin-bottom:40px!important}.ml-sm-5,.mx-sm-5{margin-left:40px!important}.m-sm-6{margin:48px!important}.mt-sm-6,.my-sm-6{margin-top:48px!important}.mr-sm-6,.mx-sm-6{margin-right:48px!important}.mb-sm-6,.my-sm-6{margin-bottom:48px!important}.ml-sm-6,.mx-sm-6{margin-left:48px!important}.m-sm-7{margin:56px!important}.mt-sm-7,.my-sm-7{margin-top:56px!important}.mr-sm-7,.mx-sm-7{margin-right:56px!important}.mb-sm-7,.my-sm-7{margin-bottom:56px!important}.ml-sm-7,.mx-sm-7{margin-left:56px!important}.m-sm-8{margin:64px!important}.mt-sm-8,.my-sm-8{margin-top:64px!important}.mr-sm-8,.mx-sm-8{margin-right:64px!important}.mb-sm-8,.my-sm-8{margin-bottom:64px!important}.ml-sm-8,.mx-sm-8{margin-left:64px!important}.m-sm-9{margin:72px!important}.mt-sm-9,.my-sm-9{margin-top:72px!important}.mr-sm-9,.mx-sm-9{margin-right:72px!important}.mb-sm-9,.my-sm-9{margin-bottom:72px!important}.ml-sm-9,.mx-sm-9{margin-left:72px!important}.m-sm-10{margin:80px!important}.mt-sm-10,.my-sm-10{margin-top:80px!important}.mr-sm-10,.mx-sm-10{margin-right:80px!important}.mb-sm-10,.my-sm-10{margin-bottom:80px!important}.ml-sm-10,.mx-sm-10{margin-left:80px!important}.m-sm-12{margin:96px!important}.mt-sm-12,.my-sm-12{margin-top:96px!important}.mr-sm-12,.mx-sm-12{margin-right:96px!important}.mb-sm-12,.my-sm-12{margin-bottom:96px!important}.ml-sm-12,.mx-sm-12{margin-left:96px!important}.m-sm-15{margin:120px!important}.mt-sm-15,.my-sm-15{margin-top:120px!important}.mr-sm-15,.mx-sm-15{margin-right:120px!important}.mb-sm-15,.my-sm-15{margin-bottom:120px!important}.ml-sm-15,.mx-sm-15{margin-left:120px!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:8px!important}.pt-sm-1,.py-sm-1{padding-top:8px!important}.pr-sm-1,.px-sm-1{padding-right:8px!important}.pb-sm-1,.py-sm-1{padding-bottom:8px!important}.pl-sm-1,.px-sm-1{padding-left:8px!important}.p-sm-2{padding:16px!important}.pt-sm-2,.py-sm-2{padding-top:16px!important}.pr-sm-2,.px-sm-2{padding-right:16px!important}.pb-sm-2,.py-sm-2{padding-bottom:16px!important}.pl-sm-2,.px-sm-2{padding-left:16px!important}.p-sm-3{padding:24px!important}.pt-sm-3,.py-sm-3{padding-top:24px!important}.pr-sm-3,.px-sm-3{padding-right:24px!important}.pb-sm-3,.py-sm-3{padding-bottom:24px!important}.pl-sm-3,.px-sm-3{padding-left:24px!important}.p-sm-4{padding:32px!important}.pt-sm-4,.py-sm-4{padding-top:32px!important}.pr-sm-4,.px-sm-4{padding-right:32px!important}.pb-sm-4,.py-sm-4{padding-bottom:32px!important}.pl-sm-4,.px-sm-4{padding-left:32px!important}.p-sm-5{padding:40px!important}.pt-sm-5,.py-sm-5{padding-top:40px!important}.pr-sm-5,.px-sm-5{padding-right:40px!important}.pb-sm-5,.py-sm-5{padding-bottom:40px!important}.pl-sm-5,.px-sm-5{padding-left:40px!important}.p-sm-6{padding:48px!important}.pt-sm-6,.py-sm-6{padding-top:48px!important}.pr-sm-6,.px-sm-6{padding-right:48px!important}.pb-sm-6,.py-sm-6{padding-bottom:48px!important}.pl-sm-6,.px-sm-6{padding-left:48px!important}.p-sm-7{padding:56px!important}.pt-sm-7,.py-sm-7{padding-top:56px!important}.pr-sm-7,.px-sm-7{padding-right:56px!important}.pb-sm-7,.py-sm-7{padding-bottom:56px!important}.pl-sm-7,.px-sm-7{padding-left:56px!important}.p-sm-8{padding:64px!important}.pt-sm-8,.py-sm-8{padding-top:64px!important}.pr-sm-8,.px-sm-8{padding-right:64px!important}.pb-sm-8,.py-sm-8{padding-bottom:64px!important}.pl-sm-8,.px-sm-8{padding-left:64px!important}.p-sm-9{padding:72px!important}.pt-sm-9,.py-sm-9{padding-top:72px!important}.pr-sm-9,.px-sm-9{padding-right:72px!important}.pb-sm-9,.py-sm-9{padding-bottom:72px!important}.pl-sm-9,.px-sm-9{padding-left:72px!important}.p-sm-10{padding:80px!important}.pt-sm-10,.py-sm-10{padding-top:80px!important}.pr-sm-10,.px-sm-10{padding-right:80px!important}.pb-sm-10,.py-sm-10{padding-bottom:80px!important}.pl-sm-10,.px-sm-10{padding-left:80px!important}.p-sm-12{padding:96px!important}.pt-sm-12,.py-sm-12{padding-top:96px!important}.pr-sm-12,.px-sm-12{padding-right:96px!important}.pb-sm-12,.py-sm-12{padding-bottom:96px!important}.pl-sm-12,.px-sm-12{padding-left:96px!important}.p-sm-15{padding:120px!important}.pt-sm-15,.py-sm-15{padding-top:120px!important}.pr-sm-15,.px-sm-15{padding-right:120px!important}.pb-sm-15,.py-sm-15{padding-bottom:120px!important}.pl-sm-15,.px-sm-15{padding-left:120px!important}.m-sm-n1{margin:-8px!important}.mt-sm-n1,.my-sm-n1{margin-top:-8px!important}.mr-sm-n1,.mx-sm-n1{margin-right:-8px!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-8px!important}.ml-sm-n1,.mx-sm-n1{margin-left:-8px!important}.m-sm-n2{margin:-16px!important}.mt-sm-n2,.my-sm-n2{margin-top:-16px!important}.mr-sm-n2,.mx-sm-n2{margin-right:-16px!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-16px!important}.ml-sm-n2,.mx-sm-n2{margin-left:-16px!important}.m-sm-n3{margin:-24px!important}.mt-sm-n3,.my-sm-n3{margin-top:-24px!important}.mr-sm-n3,.mx-sm-n3{margin-right:-24px!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-24px!important}.ml-sm-n3,.mx-sm-n3{margin-left:-24px!important}.m-sm-n4{margin:-32px!important}.mt-sm-n4,.my-sm-n4{margin-top:-32px!important}.mr-sm-n4,.mx-sm-n4{margin-right:-32px!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-32px!important}.ml-sm-n4,.mx-sm-n4{margin-left:-32px!important}.m-sm-n5{margin:-40px!important}.mt-sm-n5,.my-sm-n5{margin-top:-40px!important}.mr-sm-n5,.mx-sm-n5{margin-right:-40px!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-40px!important}.ml-sm-n5,.mx-sm-n5{margin-left:-40px!important}.m-sm-n6{margin:-48px!important}.mt-sm-n6,.my-sm-n6{margin-top:-48px!important}.mr-sm-n6,.mx-sm-n6{margin-right:-48px!important}.mb-sm-n6,.my-sm-n6{margin-bottom:-48px!important}.ml-sm-n6,.mx-sm-n6{margin-left:-48px!important}.m-sm-n7{margin:-56px!important}.mt-sm-n7,.my-sm-n7{margin-top:-56px!important}.mr-sm-n7,.mx-sm-n7{margin-right:-56px!important}.mb-sm-n7,.my-sm-n7{margin-bottom:-56px!important}.ml-sm-n7,.mx-sm-n7{margin-left:-56px!important}.m-sm-n8{margin:-64px!important}.mt-sm-n8,.my-sm-n8{margin-top:-64px!important}.mr-sm-n8,.mx-sm-n8{margin-right:-64px!important}.mb-sm-n8,.my-sm-n8{margin-bottom:-64px!important}.ml-sm-n8,.mx-sm-n8{margin-left:-64px!important}.m-sm-n9{margin:-72px!important}.mt-sm-n9,.my-sm-n9{margin-top:-72px!important}.mr-sm-n9,.mx-sm-n9{margin-right:-72px!important}.mb-sm-n9,.my-sm-n9{margin-bottom:-72px!important}.ml-sm-n9,.mx-sm-n9{margin-left:-72px!important}.m-sm-n10{margin:-80px!important}.mt-sm-n10,.my-sm-n10{margin-top:-80px!important}.mr-sm-n10,.mx-sm-n10{margin-right:-80px!important}.mb-sm-n10,.my-sm-n10{margin-bottom:-80px!important}.ml-sm-n10,.mx-sm-n10{margin-left:-80px!important}.m-sm-n12{margin:-96px!important}.mt-sm-n12,.my-sm-n12{margin-top:-96px!important}.mr-sm-n12,.mx-sm-n12{margin-right:-96px!important}.mb-sm-n12,.my-sm-n12{margin-bottom:-96px!important}.ml-sm-n12,.mx-sm-n12{margin-left:-96px!important}.m-sm-n15{margin:-120px!important}.mt-sm-n15,.my-sm-n15{margin-top:-120px!important}.mr-sm-n15,.mx-sm-n15{margin-right:-120px!important}.mb-sm-n15,.my-sm-n15{margin-bottom:-120px!important}.ml-sm-n15,.mx-sm-n15{margin-left:-120px!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media(min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:8px!important}.mt-md-1,.my-md-1{margin-top:8px!important}.mr-md-1,.mx-md-1{margin-right:8px!important}.mb-md-1,.my-md-1{margin-bottom:8px!important}.ml-md-1,.mx-md-1{margin-left:8px!important}.m-md-2{margin:16px!important}.mt-md-2,.my-md-2{margin-top:16px!important}.mr-md-2,.mx-md-2{margin-right:16px!important}.mb-md-2,.my-md-2{margin-bottom:16px!important}.ml-md-2,.mx-md-2{margin-left:16px!important}.m-md-3{margin:24px!important}.mt-md-3,.my-md-3{margin-top:24px!important}.mr-md-3,.mx-md-3{margin-right:24px!important}.mb-md-3,.my-md-3{margin-bottom:24px!important}.ml-md-3,.mx-md-3{margin-left:24px!important}.m-md-4{margin:32px!important}.mt-md-4,.my-md-4{margin-top:32px!important}.mr-md-4,.mx-md-4{margin-right:32px!important}.mb-md-4,.my-md-4{margin-bottom:32px!important}.ml-md-4,.mx-md-4{margin-left:32px!important}.m-md-5{margin:40px!important}.mt-md-5,.my-md-5{margin-top:40px!important}.mr-md-5,.mx-md-5{margin-right:40px!important}.mb-md-5,.my-md-5{margin-bottom:40px!important}.ml-md-5,.mx-md-5{margin-left:40px!important}.m-md-6{margin:48px!important}.mt-md-6,.my-md-6{margin-top:48px!important}.mr-md-6,.mx-md-6{margin-right:48px!important}.mb-md-6,.my-md-6{margin-bottom:48px!important}.ml-md-6,.mx-md-6{margin-left:48px!important}.m-md-7{margin:56px!important}.mt-md-7,.my-md-7{margin-top:56px!important}.mr-md-7,.mx-md-7{margin-right:56px!important}.mb-md-7,.my-md-7{margin-bottom:56px!important}.ml-md-7,.mx-md-7{margin-left:56px!important}.m-md-8{margin:64px!important}.mt-md-8,.my-md-8{margin-top:64px!important}.mr-md-8,.mx-md-8{margin-right:64px!important}.mb-md-8,.my-md-8{margin-bottom:64px!important}.ml-md-8,.mx-md-8{margin-left:64px!important}.m-md-9{margin:72px!important}.mt-md-9,.my-md-9{margin-top:72px!important}.mr-md-9,.mx-md-9{margin-right:72px!important}.mb-md-9,.my-md-9{margin-bottom:72px!important}.ml-md-9,.mx-md-9{margin-left:72px!important}.m-md-10{margin:80px!important}.mt-md-10,.my-md-10{margin-top:80px!important}.mr-md-10,.mx-md-10{margin-right:80px!important}.mb-md-10,.my-md-10{margin-bottom:80px!important}.ml-md-10,.mx-md-10{margin-left:80px!important}.m-md-12{margin:96px!important}.mt-md-12,.my-md-12{margin-top:96px!important}.mr-md-12,.mx-md-12{margin-right:96px!important}.mb-md-12,.my-md-12{margin-bottom:96px!important}.ml-md-12,.mx-md-12{margin-left:96px!important}.m-md-15{margin:120px!important}.mt-md-15,.my-md-15{margin-top:120px!important}.mr-md-15,.mx-md-15{margin-right:120px!important}.mb-md-15,.my-md-15{margin-bottom:120px!important}.ml-md-15,.mx-md-15{margin-left:120px!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:8px!important}.pt-md-1,.py-md-1{padding-top:8px!important}.pr-md-1,.px-md-1{padding-right:8px!important}.pb-md-1,.py-md-1{padding-bottom:8px!important}.pl-md-1,.px-md-1{padding-left:8px!important}.p-md-2{padding:16px!important}.pt-md-2,.py-md-2{padding-top:16px!important}.pr-md-2,.px-md-2{padding-right:16px!important}.pb-md-2,.py-md-2{padding-bottom:16px!important}.pl-md-2,.px-md-2{padding-left:16px!important}.p-md-3{padding:24px!important}.pt-md-3,.py-md-3{padding-top:24px!important}.pr-md-3,.px-md-3{padding-right:24px!important}.pb-md-3,.py-md-3{padding-bottom:24px!important}.pl-md-3,.px-md-3{padding-left:24px!important}.p-md-4{padding:32px!important}.pt-md-4,.py-md-4{padding-top:32px!important}.pr-md-4,.px-md-4{padding-right:32px!important}.pb-md-4,.py-md-4{padding-bottom:32px!important}.pl-md-4,.px-md-4{padding-left:32px!important}.p-md-5{padding:40px!important}.pt-md-5,.py-md-5{padding-top:40px!important}.pr-md-5,.px-md-5{padding-right:40px!important}.pb-md-5,.py-md-5{padding-bottom:40px!important}.pl-md-5,.px-md-5{padding-left:40px!important}.p-md-6{padding:48px!important}.pt-md-6,.py-md-6{padding-top:48px!important}.pr-md-6,.px-md-6{padding-right:48px!important}.pb-md-6,.py-md-6{padding-bottom:48px!important}.pl-md-6,.px-md-6{padding-left:48px!important}.p-md-7{padding:56px!important}.pt-md-7,.py-md-7{padding-top:56px!important}.pr-md-7,.px-md-7{padding-right:56px!important}.pb-md-7,.py-md-7{padding-bottom:56px!important}.pl-md-7,.px-md-7{padding-left:56px!important}.p-md-8{padding:64px!important}.pt-md-8,.py-md-8{padding-top:64px!important}.pr-md-8,.px-md-8{padding-right:64px!important}.pb-md-8,.py-md-8{padding-bottom:64px!important}.pl-md-8,.px-md-8{padding-left:64px!important}.p-md-9{padding:72px!important}.pt-md-9,.py-md-9{padding-top:72px!important}.pr-md-9,.px-md-9{padding-right:72px!important}.pb-md-9,.py-md-9{padding-bottom:72px!important}.pl-md-9,.px-md-9{padding-left:72px!important}.p-md-10{padding:80px!important}.pt-md-10,.py-md-10{padding-top:80px!important}.pr-md-10,.px-md-10{padding-right:80px!important}.pb-md-10,.py-md-10{padding-bottom:80px!important}.pl-md-10,.px-md-10{padding-left:80px!important}.p-md-12{padding:96px!important}.pt-md-12,.py-md-12{padding-top:96px!important}.pr-md-12,.px-md-12{padding-right:96px!important}.pb-md-12,.py-md-12{padding-bottom:96px!important}.pl-md-12,.px-md-12{padding-left:96px!important}.p-md-15{padding:120px!important}.pt-md-15,.py-md-15{padding-top:120px!important}.pr-md-15,.px-md-15{padding-right:120px!important}.pb-md-15,.py-md-15{padding-bottom:120px!important}.pl-md-15,.px-md-15{padding-left:120px!important}.m-md-n1{margin:-8px!important}.mt-md-n1,.my-md-n1{margin-top:-8px!important}.mr-md-n1,.mx-md-n1{margin-right:-8px!important}.mb-md-n1,.my-md-n1{margin-bottom:-8px!important}.ml-md-n1,.mx-md-n1{margin-left:-8px!important}.m-md-n2{margin:-16px!important}.mt-md-n2,.my-md-n2{margin-top:-16px!important}.mr-md-n2,.mx-md-n2{margin-right:-16px!important}.mb-md-n2,.my-md-n2{margin-bottom:-16px!important}.ml-md-n2,.mx-md-n2{margin-left:-16px!important}.m-md-n3{margin:-24px!important}.mt-md-n3,.my-md-n3{margin-top:-24px!important}.mr-md-n3,.mx-md-n3{margin-right:-24px!important}.mb-md-n3,.my-md-n3{margin-bottom:-24px!important}.ml-md-n3,.mx-md-n3{margin-left:-24px!important}.m-md-n4{margin:-32px!important}.mt-md-n4,.my-md-n4{margin-top:-32px!important}.mr-md-n4,.mx-md-n4{margin-right:-32px!important}.mb-md-n4,.my-md-n4{margin-bottom:-32px!important}.ml-md-n4,.mx-md-n4{margin-left:-32px!important}.m-md-n5{margin:-40px!important}.mt-md-n5,.my-md-n5{margin-top:-40px!important}.mr-md-n5,.mx-md-n5{margin-right:-40px!important}.mb-md-n5,.my-md-n5{margin-bottom:-40px!important}.ml-md-n5,.mx-md-n5{margin-left:-40px!important}.m-md-n6{margin:-48px!important}.mt-md-n6,.my-md-n6{margin-top:-48px!important}.mr-md-n6,.mx-md-n6{margin-right:-48px!important}.mb-md-n6,.my-md-n6{margin-bottom:-48px!important}.ml-md-n6,.mx-md-n6{margin-left:-48px!important}.m-md-n7{margin:-56px!important}.mt-md-n7,.my-md-n7{margin-top:-56px!important}.mr-md-n7,.mx-md-n7{margin-right:-56px!important}.mb-md-n7,.my-md-n7{margin-bottom:-56px!important}.ml-md-n7,.mx-md-n7{margin-left:-56px!important}.m-md-n8{margin:-64px!important}.mt-md-n8,.my-md-n8{margin-top:-64px!important}.mr-md-n8,.mx-md-n8{margin-right:-64px!important}.mb-md-n8,.my-md-n8{margin-bottom:-64px!important}.ml-md-n8,.mx-md-n8{margin-left:-64px!important}.m-md-n9{margin:-72px!important}.mt-md-n9,.my-md-n9{margin-top:-72px!important}.mr-md-n9,.mx-md-n9{margin-right:-72px!important}.mb-md-n9,.my-md-n9{margin-bottom:-72px!important}.ml-md-n9,.mx-md-n9{margin-left:-72px!important}.m-md-n10{margin:-80px!important}.mt-md-n10,.my-md-n10{margin-top:-80px!important}.mr-md-n10,.mx-md-n10{margin-right:-80px!important}.mb-md-n10,.my-md-n10{margin-bottom:-80px!important}.ml-md-n10,.mx-md-n10{margin-left:-80px!important}.m-md-n12{margin:-96px!important}.mt-md-n12,.my-md-n12{margin-top:-96px!important}.mr-md-n12,.mx-md-n12{margin-right:-96px!important}.mb-md-n12,.my-md-n12{margin-bottom:-96px!important}.ml-md-n12,.mx-md-n12{margin-left:-96px!important}.m-md-n15{margin:-120px!important}.mt-md-n15,.my-md-n15{margin-top:-120px!important}.mr-md-n15,.mx-md-n15{margin-right:-120px!important}.mb-md-n15,.my-md-n15{margin-bottom:-120px!important}.ml-md-n15,.mx-md-n15{margin-left:-120px!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media(min-width:980px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:8px!important}.mt-lg-1,.my-lg-1{margin-top:8px!important}.mr-lg-1,.mx-lg-1{margin-right:8px!important}.mb-lg-1,.my-lg-1{margin-bottom:8px!important}.ml-lg-1,.mx-lg-1{margin-left:8px!important}.m-lg-2{margin:16px!important}.mt-lg-2,.my-lg-2{margin-top:16px!important}.mr-lg-2,.mx-lg-2{margin-right:16px!important}.mb-lg-2,.my-lg-2{margin-bottom:16px!important}.ml-lg-2,.mx-lg-2{margin-left:16px!important}.m-lg-3{margin:24px!important}.mt-lg-3,.my-lg-3{margin-top:24px!important}.mr-lg-3,.mx-lg-3{margin-right:24px!important}.mb-lg-3,.my-lg-3{margin-bottom:24px!important}.ml-lg-3,.mx-lg-3{margin-left:24px!important}.m-lg-4{margin:32px!important}.mt-lg-4,.my-lg-4{margin-top:32px!important}.mr-lg-4,.mx-lg-4{margin-right:32px!important}.mb-lg-4,.my-lg-4{margin-bottom:32px!important}.ml-lg-4,.mx-lg-4{margin-left:32px!important}.m-lg-5{margin:40px!important}.mt-lg-5,.my-lg-5{margin-top:40px!important}.mr-lg-5,.mx-lg-5{margin-right:40px!important}.mb-lg-5,.my-lg-5{margin-bottom:40px!important}.ml-lg-5,.mx-lg-5{margin-left:40px!important}.m-lg-6{margin:48px!important}.mt-lg-6,.my-lg-6{margin-top:48px!important}.mr-lg-6,.mx-lg-6{margin-right:48px!important}.mb-lg-6,.my-lg-6{margin-bottom:48px!important}.ml-lg-6,.mx-lg-6{margin-left:48px!important}.m-lg-7{margin:56px!important}.mt-lg-7,.my-lg-7{margin-top:56px!important}.mr-lg-7,.mx-lg-7{margin-right:56px!important}.mb-lg-7,.my-lg-7{margin-bottom:56px!important}.ml-lg-7,.mx-lg-7{margin-left:56px!important}.m-lg-8{margin:64px!important}.mt-lg-8,.my-lg-8{margin-top:64px!important}.mr-lg-8,.mx-lg-8{margin-right:64px!important}.mb-lg-8,.my-lg-8{margin-bottom:64px!important}.ml-lg-8,.mx-lg-8{margin-left:64px!important}.m-lg-9{margin:72px!important}.mt-lg-9,.my-lg-9{margin-top:72px!important}.mr-lg-9,.mx-lg-9{margin-right:72px!important}.mb-lg-9,.my-lg-9{margin-bottom:72px!important}.ml-lg-9,.mx-lg-9{margin-left:72px!important}.m-lg-10{margin:80px!important}.mt-lg-10,.my-lg-10{margin-top:80px!important}.mr-lg-10,.mx-lg-10{margin-right:80px!important}.mb-lg-10,.my-lg-10{margin-bottom:80px!important}.ml-lg-10,.mx-lg-10{margin-left:80px!important}.m-lg-12{margin:96px!important}.mt-lg-12,.my-lg-12{margin-top:96px!important}.mr-lg-12,.mx-lg-12{margin-right:96px!important}.mb-lg-12,.my-lg-12{margin-bottom:96px!important}.ml-lg-12,.mx-lg-12{margin-left:96px!important}.m-lg-15{margin:120px!important}.mt-lg-15,.my-lg-15{margin-top:120px!important}.mr-lg-15,.mx-lg-15{margin-right:120px!important}.mb-lg-15,.my-lg-15{margin-bottom:120px!important}.ml-lg-15,.mx-lg-15{margin-left:120px!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:8px!important}.pt-lg-1,.py-lg-1{padding-top:8px!important}.pr-lg-1,.px-lg-1{padding-right:8px!important}.pb-lg-1,.py-lg-1{padding-bottom:8px!important}.pl-lg-1,.px-lg-1{padding-left:8px!important}.p-lg-2{padding:16px!important}.pt-lg-2,.py-lg-2{padding-top:16px!important}.pr-lg-2,.px-lg-2{padding-right:16px!important}.pb-lg-2,.py-lg-2{padding-bottom:16px!important}.pl-lg-2,.px-lg-2{padding-left:16px!important}.p-lg-3{padding:24px!important}.pt-lg-3,.py-lg-3{padding-top:24px!important}.pr-lg-3,.px-lg-3{padding-right:24px!important}.pb-lg-3,.py-lg-3{padding-bottom:24px!important}.pl-lg-3,.px-lg-3{padding-left:24px!important}.p-lg-4{padding:32px!important}.pt-lg-4,.py-lg-4{padding-top:32px!important}.pr-lg-4,.px-lg-4{padding-right:32px!important}.pb-lg-4,.py-lg-4{padding-bottom:32px!important}.pl-lg-4,.px-lg-4{padding-left:32px!important}.p-lg-5{padding:40px!important}.pt-lg-5,.py-lg-5{padding-top:40px!important}.pr-lg-5,.px-lg-5{padding-right:40px!important}.pb-lg-5,.py-lg-5{padding-bottom:40px!important}.pl-lg-5,.px-lg-5{padding-left:40px!important}.p-lg-6{padding:48px!important}.pt-lg-6,.py-lg-6{padding-top:48px!important}.pr-lg-6,.px-lg-6{padding-right:48px!important}.pb-lg-6,.py-lg-6{padding-bottom:48px!important}.pl-lg-6,.px-lg-6{padding-left:48px!important}.p-lg-7{padding:56px!important}.pt-lg-7,.py-lg-7{padding-top:56px!important}.pr-lg-7,.px-lg-7{padding-right:56px!important}.pb-lg-7,.py-lg-7{padding-bottom:56px!important}.pl-lg-7,.px-lg-7{padding-left:56px!important}.p-lg-8{padding:64px!important}.pt-lg-8,.py-lg-8{padding-top:64px!important}.pr-lg-8,.px-lg-8{padding-right:64px!important}.pb-lg-8,.py-lg-8{padding-bottom:64px!important}.pl-lg-8,.px-lg-8{padding-left:64px!important}.p-lg-9{padding:72px!important}.pt-lg-9,.py-lg-9{padding-top:72px!important}.pr-lg-9,.px-lg-9{padding-right:72px!important}.pb-lg-9,.py-lg-9{padding-bottom:72px!important}.pl-lg-9,.px-lg-9{padding-left:72px!important}.p-lg-10{padding:80px!important}.pt-lg-10,.py-lg-10{padding-top:80px!important}.pr-lg-10,.px-lg-10{padding-right:80px!important}.pb-lg-10,.py-lg-10{padding-bottom:80px!important}.pl-lg-10,.px-lg-10{padding-left:80px!important}.p-lg-12{padding:96px!important}.pt-lg-12,.py-lg-12{padding-top:96px!important}.pr-lg-12,.px-lg-12{padding-right:96px!important}.pb-lg-12,.py-lg-12{padding-bottom:96px!important}.pl-lg-12,.px-lg-12{padding-left:96px!important}.p-lg-15{padding:120px!important}.pt-lg-15,.py-lg-15{padding-top:120px!important}.pr-lg-15,.px-lg-15{padding-right:120px!important}.pb-lg-15,.py-lg-15{padding-bottom:120px!important}.pl-lg-15,.px-lg-15{padding-left:120px!important}.m-lg-n1{margin:-8px!important}.mt-lg-n1,.my-lg-n1{margin-top:-8px!important}.mr-lg-n1,.mx-lg-n1{margin-right:-8px!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-8px!important}.ml-lg-n1,.mx-lg-n1{margin-left:-8px!important}.m-lg-n2{margin:-16px!important}.mt-lg-n2,.my-lg-n2{margin-top:-16px!important}.mr-lg-n2,.mx-lg-n2{margin-right:-16px!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-16px!important}.ml-lg-n2,.mx-lg-n2{margin-left:-16px!important}.m-lg-n3{margin:-24px!important}.mt-lg-n3,.my-lg-n3{margin-top:-24px!important}.mr-lg-n3,.mx-lg-n3{margin-right:-24px!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-24px!important}.ml-lg-n3,.mx-lg-n3{margin-left:-24px!important}.m-lg-n4{margin:-32px!important}.mt-lg-n4,.my-lg-n4{margin-top:-32px!important}.mr-lg-n4,.mx-lg-n4{margin-right:-32px!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-32px!important}.ml-lg-n4,.mx-lg-n4{margin-left:-32px!important}.m-lg-n5{margin:-40px!important}.mt-lg-n5,.my-lg-n5{margin-top:-40px!important}.mr-lg-n5,.mx-lg-n5{margin-right:-40px!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-40px!important}.ml-lg-n5,.mx-lg-n5{margin-left:-40px!important}.m-lg-n6{margin:-48px!important}.mt-lg-n6,.my-lg-n6{margin-top:-48px!important}.mr-lg-n6,.mx-lg-n6{margin-right:-48px!important}.mb-lg-n6,.my-lg-n6{margin-bottom:-48px!important}.ml-lg-n6,.mx-lg-n6{margin-left:-48px!important}.m-lg-n7{margin:-56px!important}.mt-lg-n7,.my-lg-n7{margin-top:-56px!important}.mr-lg-n7,.mx-lg-n7{margin-right:-56px!important}.mb-lg-n7,.my-lg-n7{margin-bottom:-56px!important}.ml-lg-n7,.mx-lg-n7{margin-left:-56px!important}.m-lg-n8{margin:-64px!important}.mt-lg-n8,.my-lg-n8{margin-top:-64px!important}.mr-lg-n8,.mx-lg-n8{margin-right:-64px!important}.mb-lg-n8,.my-lg-n8{margin-bottom:-64px!important}.ml-lg-n8,.mx-lg-n8{margin-left:-64px!important}.m-lg-n9{margin:-72px!important}.mt-lg-n9,.my-lg-n9{margin-top:-72px!important}.mr-lg-n9,.mx-lg-n9{margin-right:-72px!important}.mb-lg-n9,.my-lg-n9{margin-bottom:-72px!important}.ml-lg-n9,.mx-lg-n9{margin-left:-72px!important}.m-lg-n10{margin:-80px!important}.mt-lg-n10,.my-lg-n10{margin-top:-80px!important}.mr-lg-n10,.mx-lg-n10{margin-right:-80px!important}.mb-lg-n10,.my-lg-n10{margin-bottom:-80px!important}.ml-lg-n10,.mx-lg-n10{margin-left:-80px!important}.m-lg-n12{margin:-96px!important}.mt-lg-n12,.my-lg-n12{margin-top:-96px!important}.mr-lg-n12,.mx-lg-n12{margin-right:-96px!important}.mb-lg-n12,.my-lg-n12{margin-bottom:-96px!important}.ml-lg-n12,.mx-lg-n12{margin-left:-96px!important}.m-lg-n15{margin:-120px!important}.mt-lg-n15,.my-lg-n15{margin-top:-120px!important}.mr-lg-n15,.mx-lg-n15{margin-right:-120px!important}.mb-lg-n15,.my-lg-n15{margin-bottom:-120px!important}.ml-lg-n15,.mx-lg-n15{margin-left:-120px!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media(min-width:1240px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:8px!important}.mt-xl-1,.my-xl-1{margin-top:8px!important}.mr-xl-1,.mx-xl-1{margin-right:8px!important}.mb-xl-1,.my-xl-1{margin-bottom:8px!important}.ml-xl-1,.mx-xl-1{margin-left:8px!important}.m-xl-2{margin:16px!important}.mt-xl-2,.my-xl-2{margin-top:16px!important}.mr-xl-2,.mx-xl-2{margin-right:16px!important}.mb-xl-2,.my-xl-2{margin-bottom:16px!important}.ml-xl-2,.mx-xl-2{margin-left:16px!important}.m-xl-3{margin:24px!important}.mt-xl-3,.my-xl-3{margin-top:24px!important}.mr-xl-3,.mx-xl-3{margin-right:24px!important}.mb-xl-3,.my-xl-3{margin-bottom:24px!important}.ml-xl-3,.mx-xl-3{margin-left:24px!important}.m-xl-4{margin:32px!important}.mt-xl-4,.my-xl-4{margin-top:32px!important}.mr-xl-4,.mx-xl-4{margin-right:32px!important}.mb-xl-4,.my-xl-4{margin-bottom:32px!important}.ml-xl-4,.mx-xl-4{margin-left:32px!important}.m-xl-5{margin:40px!important}.mt-xl-5,.my-xl-5{margin-top:40px!important}.mr-xl-5,.mx-xl-5{margin-right:40px!important}.mb-xl-5,.my-xl-5{margin-bottom:40px!important}.ml-xl-5,.mx-xl-5{margin-left:40px!important}.m-xl-6{margin:48px!important}.mt-xl-6,.my-xl-6{margin-top:48px!important}.mr-xl-6,.mx-xl-6{margin-right:48px!important}.mb-xl-6,.my-xl-6{margin-bottom:48px!important}.ml-xl-6,.mx-xl-6{margin-left:48px!important}.m-xl-7{margin:56px!important}.mt-xl-7,.my-xl-7{margin-top:56px!important}.mr-xl-7,.mx-xl-7{margin-right:56px!important}.mb-xl-7,.my-xl-7{margin-bottom:56px!important}.ml-xl-7,.mx-xl-7{margin-left:56px!important}.m-xl-8{margin:64px!important}.mt-xl-8,.my-xl-8{margin-top:64px!important}.mr-xl-8,.mx-xl-8{margin-right:64px!important}.mb-xl-8,.my-xl-8{margin-bottom:64px!important}.ml-xl-8,.mx-xl-8{margin-left:64px!important}.m-xl-9{margin:72px!important}.mt-xl-9,.my-xl-9{margin-top:72px!important}.mr-xl-9,.mx-xl-9{margin-right:72px!important}.mb-xl-9,.my-xl-9{margin-bottom:72px!important}.ml-xl-9,.mx-xl-9{margin-left:72px!important}.m-xl-10{margin:80px!important}.mt-xl-10,.my-xl-10{margin-top:80px!important}.mr-xl-10,.mx-xl-10{margin-right:80px!important}.mb-xl-10,.my-xl-10{margin-bottom:80px!important}.ml-xl-10,.mx-xl-10{margin-left:80px!important}.m-xl-12{margin:96px!important}.mt-xl-12,.my-xl-12{margin-top:96px!important}.mr-xl-12,.mx-xl-12{margin-right:96px!important}.mb-xl-12,.my-xl-12{margin-bottom:96px!important}.ml-xl-12,.mx-xl-12{margin-left:96px!important}.m-xl-15{margin:120px!important}.mt-xl-15,.my-xl-15{margin-top:120px!important}.mr-xl-15,.mx-xl-15{margin-right:120px!important}.mb-xl-15,.my-xl-15{margin-bottom:120px!important}.ml-xl-15,.mx-xl-15{margin-left:120px!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:8px!important}.pt-xl-1,.py-xl-1{padding-top:8px!important}.pr-xl-1,.px-xl-1{padding-right:8px!important}.pb-xl-1,.py-xl-1{padding-bottom:8px!important}.pl-xl-1,.px-xl-1{padding-left:8px!important}.p-xl-2{padding:16px!important}.pt-xl-2,.py-xl-2{padding-top:16px!important}.pr-xl-2,.px-xl-2{padding-right:16px!important}.pb-xl-2,.py-xl-2{padding-bottom:16px!important}.pl-xl-2,.px-xl-2{padding-left:16px!important}.p-xl-3{padding:24px!important}.pt-xl-3,.py-xl-3{padding-top:24px!important}.pr-xl-3,.px-xl-3{padding-right:24px!important}.pb-xl-3,.py-xl-3{padding-bottom:24px!important}.pl-xl-3,.px-xl-3{padding-left:24px!important}.p-xl-4{padding:32px!important}.pt-xl-4,.py-xl-4{padding-top:32px!important}.pr-xl-4,.px-xl-4{padding-right:32px!important}.pb-xl-4,.py-xl-4{padding-bottom:32px!important}.pl-xl-4,.px-xl-4{padding-left:32px!important}.p-xl-5{padding:40px!important}.pt-xl-5,.py-xl-5{padding-top:40px!important}.pr-xl-5,.px-xl-5{padding-right:40px!important}.pb-xl-5,.py-xl-5{padding-bottom:40px!important}.pl-xl-5,.px-xl-5{padding-left:40px!important}.p-xl-6{padding:48px!important}.pt-xl-6,.py-xl-6{padding-top:48px!important}.pr-xl-6,.px-xl-6{padding-right:48px!important}.pb-xl-6,.py-xl-6{padding-bottom:48px!important}.pl-xl-6,.px-xl-6{padding-left:48px!important}.p-xl-7{padding:56px!important}.pt-xl-7,.py-xl-7{padding-top:56px!important}.pr-xl-7,.px-xl-7{padding-right:56px!important}.pb-xl-7,.py-xl-7{padding-bottom:56px!important}.pl-xl-7,.px-xl-7{padding-left:56px!important}.p-xl-8{padding:64px!important}.pt-xl-8,.py-xl-8{padding-top:64px!important}.pr-xl-8,.px-xl-8{padding-right:64px!important}.pb-xl-8,.py-xl-8{padding-bottom:64px!important}.pl-xl-8,.px-xl-8{padding-left:64px!important}.p-xl-9{padding:72px!important}.pt-xl-9,.py-xl-9{padding-top:72px!important}.pr-xl-9,.px-xl-9{padding-right:72px!important}.pb-xl-9,.py-xl-9{padding-bottom:72px!important}.pl-xl-9,.px-xl-9{padding-left:72px!important}.p-xl-10{padding:80px!important}.pt-xl-10,.py-xl-10{padding-top:80px!important}.pr-xl-10,.px-xl-10{padding-right:80px!important}.pb-xl-10,.py-xl-10{padding-bottom:80px!important}.pl-xl-10,.px-xl-10{padding-left:80px!important}.p-xl-12{padding:96px!important}.pt-xl-12,.py-xl-12{padding-top:96px!important}.pr-xl-12,.px-xl-12{padding-right:96px!important}.pb-xl-12,.py-xl-12{padding-bottom:96px!important}.pl-xl-12,.px-xl-12{padding-left:96px!important}.p-xl-15{padding:120px!important}.pt-xl-15,.py-xl-15{padding-top:120px!important}.pr-xl-15,.px-xl-15{padding-right:120px!important}.pb-xl-15,.py-xl-15{padding-bottom:120px!important}.pl-xl-15,.px-xl-15{padding-left:120px!important}.m-xl-n1{margin:-8px!important}.mt-xl-n1,.my-xl-n1{margin-top:-8px!important}.mr-xl-n1,.mx-xl-n1{margin-right:-8px!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-8px!important}.ml-xl-n1,.mx-xl-n1{margin-left:-8px!important}.m-xl-n2{margin:-16px!important}.mt-xl-n2,.my-xl-n2{margin-top:-16px!important}.mr-xl-n2,.mx-xl-n2{margin-right:-16px!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-16px!important}.ml-xl-n2,.mx-xl-n2{margin-left:-16px!important}.m-xl-n3{margin:-24px!important}.mt-xl-n3,.my-xl-n3{margin-top:-24px!important}.mr-xl-n3,.mx-xl-n3{margin-right:-24px!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-24px!important}.ml-xl-n3,.mx-xl-n3{margin-left:-24px!important}.m-xl-n4{margin:-32px!important}.mt-xl-n4,.my-xl-n4{margin-top:-32px!important}.mr-xl-n4,.mx-xl-n4{margin-right:-32px!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-32px!important}.ml-xl-n4,.mx-xl-n4{margin-left:-32px!important}.m-xl-n5{margin:-40px!important}.mt-xl-n5,.my-xl-n5{margin-top:-40px!important}.mr-xl-n5,.mx-xl-n5{margin-right:-40px!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-40px!important}.ml-xl-n5,.mx-xl-n5{margin-left:-40px!important}.m-xl-n6{margin:-48px!important}.mt-xl-n6,.my-xl-n6{margin-top:-48px!important}.mr-xl-n6,.mx-xl-n6{margin-right:-48px!important}.mb-xl-n6,.my-xl-n6{margin-bottom:-48px!important}.ml-xl-n6,.mx-xl-n6{margin-left:-48px!important}.m-xl-n7{margin:-56px!important}.mt-xl-n7,.my-xl-n7{margin-top:-56px!important}.mr-xl-n7,.mx-xl-n7{margin-right:-56px!important}.mb-xl-n7,.my-xl-n7{margin-bottom:-56px!important}.ml-xl-n7,.mx-xl-n7{margin-left:-56px!important}.m-xl-n8{margin:-64px!important}.mt-xl-n8,.my-xl-n8{margin-top:-64px!important}.mr-xl-n8,.mx-xl-n8{margin-right:-64px!important}.mb-xl-n8,.my-xl-n8{margin-bottom:-64px!important}.ml-xl-n8,.mx-xl-n8{margin-left:-64px!important}.m-xl-n9{margin:-72px!important}.mt-xl-n9,.my-xl-n9{margin-top:-72px!important}.mr-xl-n9,.mx-xl-n9{margin-right:-72px!important}.mb-xl-n9,.my-xl-n9{margin-bottom:-72px!important}.ml-xl-n9,.mx-xl-n9{margin-left:-72px!important}.m-xl-n10{margin:-80px!important}.mt-xl-n10,.my-xl-n10{margin-top:-80px!important}.mr-xl-n10,.mx-xl-n10{margin-right:-80px!important}.mb-xl-n10,.my-xl-n10{margin-bottom:-80px!important}.ml-xl-n10,.mx-xl-n10{margin-left:-80px!important}.m-xl-n12{margin:-96px!important}.mt-xl-n12,.my-xl-n12{margin-top:-96px!important}.mr-xl-n12,.mx-xl-n12{margin-right:-96px!important}.mb-xl-n12,.my-xl-n12{margin-bottom:-96px!important}.ml-xl-n12,.mx-xl-n12{margin-left:-96px!important}.m-xl-n15{margin:-120px!important}.mt-xl-n15,.my-xl-n15{margin-top:-120px!important}.mr-xl-n15,.mx-xl-n15{margin-right:-120px!important}.mb-xl-n15,.my-xl-n15{margin-bottom:-120px!important}.ml-xl-n15,.mx-xl-n15{margin-left:-120px!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media(min-width:400px){.text-xs-left{text-align:left!important}.text-xs-right{text-align:right!important}.text-xs-center{text-align:center!important}}@media(min-width:616px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media(min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media(min-width:980px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media(min-width:1240px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-primary{color:#fc0!important}a.text-primary:focus,a.text-primary:hover{color:#b38f00!important}.text-secondary{color:#212529!important}a.text-secondary:focus,a.text-secondary:hover{color:#000!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f1f6f9!important}a.text-light:focus,a.text-light:hover{color:#bbd4e2!important}.text-dark{color:#495057!important}a.text-dark:focus,a.text-dark:hover{color:#262a2d!important}.text-primary-light{color:#fffaf0!important}a.text-primary-light:focus,a.text-primary-light:hover{color:#ffe1a4!important}.text-secondary-light{color:#fff!important}a.text-secondary-light:focus,a.text-secondary-light:hover{color:#d9d9d9!important}.text-tertiary{color:#257af4!important}a.text-tertiary:focus,a.text-tertiary:hover{color:#0a56c3!important}.text-tertiary-light{color:#e3f1fe!important}a.text-tertiary-light:focus,a.text-tertiary-light:hover{color:#99ccfb!important}.text-white{color:#fff!important}a.text-white:focus,a.text-white:hover{color:#d9d9d9!important}.text-black{color:#212529!important}a.text-black:focus,a.text-black:hover{color:#000!important}.text-blue{color:#257af4!important}a.text-blue:focus,a.text-blue:hover{color:#0a56c3!important}.text-light-blue{color:#e3f1fe!important}a.text-light-blue:focus,a.text-light-blue:hover{color:#99ccfb!important}.text-yellow{color:#fc0!important}a.text-yellow:focus,a.text-yellow:hover{color:#b38f00!important}.text-light-yellow{color:#fffaf0!important}a.text-light-yellow:focus,a.text-light-yellow:hover{color:#ffe1a4!important}.text-orange{color:#ff8c00!important}a.text-orange:focus,a.text-orange:hover{color:#b36200!important}.text-light-orange{color:#ffe4b5!important}a.text-light-orange:focus,a.text-light-orange:hover{color:#ffc869!important}.text-red{color:#ff3939!important}a.text-red:focus,a.text-red:hover{color:#ec0000!important}.text-light-red{color:#ffe4e1!important}a.text-light-red:focus,a.text-light-red:hover{color:#ff9f95!important}.text-medium{color:#d6dbdf!important}a.text-medium:focus,a.text-medium:hover{color:#abb5bd!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(33,37,41,.5)!important}.text-white-50{color:hsla(0,0%,100%,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,:after,:before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]:after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #d6dbdf;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}.container,body{min-width:980px!important}.navbar{display:none}.badge{border:1px solid #212529}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#d6dbdf}.table .thead-dark th{color:inherit;border-color:#d6dbdf}} \ No newline at end of file diff --git a/website/css/docs.css b/website/css/docs.css deleted file mode 100644 index e7d41bc28bf..00000000000 --- a/website/css/docs.css +++ /dev/null @@ -1,155 +0,0 @@ -details { - background: #444451; - color: #eee; - padding: 1rem; - margin-bottom: 1rem; - margin-top: 1rem; -} - -summary { - font-weight: bold; - color: #eee; -} - -#sidebar { - position: fixed; - z-index: 50; - left: 0; -} - -#toc.toc-right { - position: fixed; -} - -#languages-dropdown .disabled { - color: #666 !important; -} - -#search-icon { - width: 40px; -} - -.toc-right { - right: 0; -} - -#sidebar-nav .nav-link.active { - font-weight: bold; -} - -.headerlink { - text-decoration: none !important; - margin-left: .5rem; -} - -#content table { - border: 1px solid #dee2e6; - width: 100%; - margin-bottom: 1rem; - overflow-x: auto; -} - -#content thead { - background: #444451; - color: #fff; -} - -#content td, #content th { - padding: .75rem; - vertical-align: top; -} - -#content td { - border: 1px solid #dee2e6; -} - -#content th { - border: 1px solid #444451; -} - -#content code { - color: #000; - background: #eee; - padding: 0.125rem 0.25rem; -} - -#content code.syntax { - padding: 0; -} - -#content pre { - background: #efefef; - padding: 1rem; - line-height: 1.25; -} - -#toc .nav-link { - color: #333; -} - -.toc-left, #toc.toc-left .nav-link { - color: #efefef; -} - -#toc .nav { - flex-wrap: nowrap !important; -} - -.toc-muted { - color: #888 !important; -} - -#toc .nav-link:hover { - color: #f14600 !important; -} - -@media print { - body { - min-width: 0!important; - } - h1, h2, h3, h4, h5, h6, p, li, thead, tr, th, td, img, code, pre { - page-break-inside: avoid !important; - } -} - -@media (prefers-color-scheme: dark) { - body[data-spy], body.amp, body.blog, #to-full-website { - background: #1c1c1c; - color: #efefef; - } - - .invert-dark { - filter: invert(100%); - } - - #sidebar { - background: #333; - } - - #content table { - border: 1px solid #2a2b2c; - } - - #content thead { - background: #333; - } - - #content td { - border: 1px solid #444451; - } - - #content code { - background: #444; - color: #eee; - padding: 0.125rem 0.25rem; - } - - #content pre, .blog .tag, .blog .dropdown-menu { - background: #444; - color: #eee; - } - - #toc .nav-link { - color: #bbb; - } -} diff --git a/website/css/docsearch.css b/website/css/docsearch.css deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/website/css/highlight.css b/website/css/highlight.css deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/website/css/main.css b/website/css/main.css deleted file mode 100644 index 1b4f7c48830..00000000000 --- a/website/css/main.css +++ /dev/null @@ -1 +0,0 @@ -@media screen and (max-width:978.98px){.btn{padding:8px 16px}}@media screen and (max-width:978.98px){.btn-lg{padding:12px 24px}}.btn-primary,.btn-primary:active,.btn-primary:hover{color:#212529}.btn-outline-primary{background:#fffaf0;border-color:#fc0;color:#212529}.btn-outline-primary:active,.btn-outline-primary:hover{background:#fc0;border-color:#fc0;color:#212529}.btn-secondary{border-color:#212529;color:#fff}.btn-outline-secondary,.btn-secondary:active,.btn-secondary:hover{background:#fff;border-color:#212529;color:#212529}.btn-outline-secondary:active,.btn-outline-secondary:hover{background:#212529;border-color:#212529;color:#fff}.btn-tertiary{border-color:#257af4;color:#fff}.btn-tertiary:active,.btn-tertiary:hover{background:#257af4;border-color:#257af4;color:#fff}.btn-outline-tertiary{background:#e3f1fe;color:#257af4}.btn-outline-tertiary:active,.btn-outline-tertiary:hover{background:#257af4;color:#fff}.btns{align-items:center;display:grid;-moz-column-gap:24px;column-gap:24px;row-gap:16px;grid-auto-flow:column;justify-content:center}@media screen and (max-width:767.98px){.btns{grid-auto-flow:row}}.btns.btns-lg{-moz-column-gap:40px;column-gap:40px}.btns.is-2{grid-template-columns:1fr 1fr}@media screen and (max-width:767.98px){.btns.is-2{grid-template-columns:1fr}}.btns.is-3{grid-template-columns:1fr 1fr 1fr}@media screen and (max-width:767.98px){.btns.is-3{grid-template-columns:1fr}}.card{box-shadow:0 8px 20px rgba(108,117,125,.2);overflow:hidden;transition:box-shadow .2s,transform .2s;width:100%}.card,.card-body{position:relative}.card-body{z-index:10}.card.is-large .card-body{padding:40px}.card.bg-primary-light{border-color:#fc0}.card.has-dark-border{border-color:#6c757d}.card.has-pattern:after,.card.has-pattern:before{background-repeat:no-repeat;background-size:auto 100%;bottom:0;content:"";display:block;position:absolute;top:0;width:72px}.card.has-pattern:before{background-image:url(../images/backgrounds/bg-card-pattern-blue-1.png);background-position:0 0;left:0}.card.has-pattern:after{background-image:url(../images/backgrounds/bg-card-pattern-blue-2.png);background-position:100% 0;right:0}.card.has-hover:active,.card.has-hover:hover,a.card:active,a.card:hover{box-shadow:0 12px 32px rgba(108,117,125,.2);transform:translateY(-8px)}.card.has-highlight:after,.card.has-hover:after,a.card:after{content:"";display:block;height:8px;margin-top:auto;transition:background .2s;width:100%}.card.has-highlight:after,.card.has-hover:active:after,.card.has-hover:hover:after,a.card:active:after,a.card:hover:after{background:#e3f1fe}.case-study-cards{-moz-column-gap:40px;column-gap:40px;display:grid;grid-template-columns:1fr;row-gap:40px;padding-bottom:40px;position:relative}.case-study-cards>div{align-items:stretch;display:flex}.case-study-cards:before{background:#d6dbdf;bottom:0;content:"";display:block;left:20px;position:absolute;top:40px;width:100vw}@media screen and (min-width:980px){.case-study-cards{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:80px;padding-bottom:120px}.case-study-cards:before{left:-40px;top:120px}}.case-study-card{align-items:stretch;flex-direction:row;flex-shrink:0;left:0;transition:box-shadow .2s,left .4s,width .4s,z-index 0s;transition-delay:0s,.6s,.6s,0s;width:100%;z-index:2}@media screen and (max-width:979.98px){.case-study-card .row{min-height:0!important}}@media screen and (min-width:980px){.case-study-card:active,.case-study-card:hover{box-shadow:0 12px 32px rgba(108,117,125,.2)}.case-study-card:not(.is-open){cursor:pointer}.case-study-card.is-open{transform:none!important;transition-delay:0s,0s,0s,0s;width:calc(200% + 40px);z-index:10}.case-study-card.is-closing{z-index:10}.case-study-card.open-left.is-open{left:calc(-100% - 40px)}.case-study-card:before{background:no-repeat url(../images/backgrounds/bg-card-pattern-red.png);background-position:100%;background-size:contain;content:"";display:block;height:calc(100% - 80px);max-height:224px;max-width:234px;position:absolute;right:0;top:40px;transform:translateX(30%);transition:transform .4s;transition-delay:.6s;width:100%;z-index:1}}@media screen and (min-width:980px)and (min-width:1240px){.case-study-card:before{transform:translateX(50%)}}@media screen and (min-width:980px){.case-study-card.is-open:before{transform:translateX(70%);transition-delay:0s}}@media screen and (min-width:980px){.case-study-card-wrap{align-items:stretch;display:flex;flex-shrink:0;min-height:304px;position:relative;transition:width .4s;transition-delay:.6s;width:calc(200% + 42px);z-index:2}}@media screen and (min-width:980px){.case-study-card.is-open .case-study-card-wrap{transition-delay:0s;width:100%}}@media screen and (min-width:980px){.case-study-card-body{display:flex;flex-direction:column;padding-right:80px!important}.case-study-card-body>.row{align-self:stretch}}@media screen and (min-width:980px){.case-study-card-toggle{background:#fff;box-shadow:0 8px 20px rgba(108,117,125,.2);border-radius:100%;cursor:pointer;height:56px;position:relative;width:56px}.case-study-card-toggle:after,.case-study-card-toggle:before{background:#257af4;content:"";display:block;height:4px;left:calc(50% - 15px);position:absolute;top:calc(50% - 2px);transition:opacity .2s,transform .2s;width:30px}.case-study-card-toggle:after{transform:rotate(90deg)}}@media screen and (min-width:980px){.case-study-card.is-open .case-study-card-toggle:before{opacity:0;transform:rotate(-90deg)}}@media screen and (min-width:980px){.case-study-card.is-open .case-study-card-toggle:after{transform:rotate(0)}}@media screen and (min-width:980px){.case-study-card .col-lg-3{left:-60%;position:relative;transition:left .4s;transition-delay:.6s}}@media screen and (min-width:980px)and (min-width:980px){.case-study-card .col-lg-3{flex:0 0 250px;max-width:250px;width:250px}}@media screen and (min-width:980px){.case-study-card.is-open .col-lg-3{left:0;transition-delay:0s}}@media screen and (min-width:980px){.case-study-card .col-lg-auto{opacity:0;transform:translateX(24px);transition:opacity .4s,transform .4s;transition-delay:.2s}}@media screen and (min-width:980px)and (min-width:980px){.case-study-card .col-lg-auto{max-width:605px;width:calc(100% - 319px)}}@media screen and (min-width:980px){.case-study-card.is-open .col-lg-auto{opacity:1;transform:none;transition-delay:.2s}}.footer-copy,.footer-links{white-space:nowrap}form .form-group{position:relative}form .form-group.is-select:before{border-left:6px solid transparent;border-right:6px solid transparent;border-top:8px solid #6c757d;content:"";display:block;position:absolute;right:33px;top:calc(50% - 4px);z-index:10}form .form-control{border:1px solid #6c757d;border-radius:6px;height:auto;line-height:20px;min-height:44px;padding:12px 16px;width:100%}form .form-control,form .form-control:focus{box-shadow:0 8px 20px rgba(108,117,125,.2);color:#212529}form .form-control:focus{border-color:#212529}form .form-control::-moz-placeholder{color:#6c757d}form .form-control:-ms-input-placeholder{color:#6c757d}form .form-control::placeholder{color:#6c757d}form select.form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding-right:24px;white-space:pre-wrap}form select.form-control:not([data-chosen]){color:#6c757d}form .btn-secondary:active,form .btn-secondary:hover{color:#212529;background:#fc0;border-color:#fc0}.hero{overflow:visible;position:relative}.hero,.hero-bg{background-repeat:no-repeat;background-position:50%;background-size:cover}.hero-bg{display:block;height:100%;left:50%;position:absolute;top:0;transform:translateX(-50%);z-index:1}.hero>.container{position:relative;z-index:2}.hero.has-offset{margin-bottom:-160px;padding-bottom:160px}.base-hero{height:22.5vw;max-height:324px;min-height:280px}.index-hero{background-image:url(/images/backgrounds/bg-hero-home.svg);height:68vw;max-height:980px}.index-hero,.other-hero{max-width:2448px;width:170vw}.other-hero{background-image:url(/images/backgrounds/bg-hero.svg)}.bg-footer-cta{background-image:url(/images/backgrounds/bg-footer-cta.svg);width:2448px}.quickstart-bg{background-image:url(/images/backgrounds/bg-quick-start.svg);height:40vw;top:220px;width:170vw}hr{background:#f1f6f9;border:0;display:block;height:4px;margin:0;width:100%}hr.is-small{height:2px}hr.is-large{height:8px}hr.is-medium{background:#d6dbdf}hr.is-dark{background:#495057}hr.is-yellow{background:linear-gradient(90deg,#ff8c00,#ff8c00 8px,#fc0 16px,rgba(255,204,0,0));-webkit-clip-path:polygon(8px 100%,0 100%,0 0,8px 0,8px 100%,16px 100%,16px 0,100% 0,100% 100%);clip-path:polygon(8px 100%,0 100%,0 0,8px 0,8px 100%,16px 100%,16px 0,100% 0,100% 100%);height:8px}.icon{display:block;height:48px;margin-bottom:24px;-o-object-fit:contain;object-fit:contain;-o-object-position:center;object-position:center}@media screen and (min-width:576px){.icon{height:64px}}@media screen and (min-width:980px){.icon{height:80px}}img{max-width:100%}.kicker{color:#6c757d;font-family:Hind Siliguri,sans-serif;font-size:.875rem;font-weight:600;letter-spacing:1px;margin:0}@media screen and (max-width:978.98px){.lead{font-size:1.125rem}}.logo{display:block;height:36px;max-width:220px;-o-object-fit:contain;object-fit:contain;-o-object-position:center;object-position:center;width:100%}.navbar-clickhouse{border-bottom:4px solid #f1f6f9;height:142px}.navbar-clickhouse>.container{flex-wrap:wrap}.navbar-super{flex-shrink:0;width:100%}.navbar-super ul{list-style:none}.navbar-super li:not(:last-child){margin-bottom:0;margin-right:24px}.navbar-super a{align-items:center;color:#212529;display:flex;font-size:.875rem}.navbar-super a:active,.navbar-super a:hover{color:#257af4;text-decoration:none}.navbar-super img{flex-shrink:0;margin-right:4px}.navbar-brand-clickhouse{background:no-repeat url(../images/logo-clickhouse.svg);background-size:contain;flex-shrink:0;height:28px;margin-right:48px;padding:0;width:180px}.navbar-nav{align-items:center;height:46px}.navbar .nav-item:not(:last-child){margin-bottom:0;margin-right:24px}.navbar .nav-link{color:#212529}.navbar .nav-link:active,.navbar .nav-link:hover{color:#257af4}.navbar .navbar-nav{flex-direction:row}@media screen and (max-width:978.98px){.navbar>.container{padding-left:20px;padding-right:20px}.navbar .navbar-toggler{height:24px;padding:0;width:24px}.navbar .navbar-toggler:focus{outline:none}.navbar .navbar-toggler-icon{background:no-repeat url(../images/icons/icon-menu.svg);background-position:50%;background-size:contain;height:24px;width:24px}.navbar .navbar-collapse{background:#fff;border-bottom:4px solid #f1f6f9;height:56px;left:0;padding:0 20px 16px;position:absolute;right:0;top:100%}.navbar .nav-link{font-size:.875rem;white-space:nowrap}}@media screen and (max-width:615.98px){.navbar .navbar-collapse{height:auto}.navbar .navbar-nav{flex-direction:column;height:auto}.navbar .nav-item:not(:last-child){margin-bottom:16px;margin-right:0}}@media screen and (max-width:399.98px){.navbar{height:80px}}@media screen and (min-width:616px){.navbar.py-1+div .anchor-fixer :target{scroll-margin-top:62px}}@media screen and (min-width:616px){.navbar.py-2+div .anchor-fixer :target{scroll-margin-top:78px}}@media screen and (min-width:616px){.navbar.py-3+div .anchor-fixer :target{scroll-margin-top:94px}}@media screen and (min-width:616px){.navbar.py-4+div .anchor-fixer :target{scroll-margin-top:110px}}@media screen and (min-width:616px){.navbar.py-5+div .anchor-fixer :target{scroll-margin-top:126px}}@media screen and (min-width:616px){.navbar.py-6+div .anchor-fixer :target{scroll-margin-top:142px}}@media screen and (min-width:616px){.navbar.py-7+div .anchor-fixer :target{scroll-margin-top:158px}}@media screen and (min-width:616px){.navbar.py-8+div .anchor-fixer :target{scroll-margin-top:174px}}@media screen and (max-width:615.98px){.navbar+div .anchor-fixer :target{scroll-margin-top:73px}}@media screen and (max-width:399.98px){.navbar+div .anchor-fixer :target{scroll-margin-top:80px}}.photo-frame{background:hsla(0,0%,100%,.6);border-radius:100%;box-shadow:0 8px 20px rgba(108,117,125,.2);display:block;margin-bottom:24px;max-width:160px;position:relative}.photo-frame:before{content:"";display:block;padding-bottom:100%;width:100%}.photo-frame img{display:block;height:100%;left:0;-o-object-fit:contain;object-fit:contain;-o-object-position:center;object-position:center;position:absolute;top:0;width:100%}.pullquote{position:relative;width:70%}.pullquote:before{background:no-repeat url(../images/backgrounds/bg-quotes.svg);background-position:50%;background-size:100%;content:"";mix-blend-mode:multiply;right:56px;width:calc(100% - 16px);z-index:2}.pullquote-bg,.pullquote:before{bottom:0;display:block;position:absolute;top:0}.pullquote-bg{right:0;width:calc(50vw + 28.57143%);z-index:1}.pullquote-body{padding:64px 40px 64px 0;position:relative;z-index:3}.pullquote-quote{font-family:Hind Siliguri,sans-serif;font-size:32px;font-weight:700}.pullquote-citation{font-size:1.125rem}.section{overflow:visible;position:relative}.section,.section-bg{background-repeat:no-repeat;background-position:50%;background-size:cover}.section-bg{display:block;height:100%;left:50%;position:absolute;top:0;transform:translateX(-50%);z-index:1}.section>.container{position:relative;z-index:2}.severity-table th{background:#f1f6f9;font-size:.875rem;padding:8px 16px}.severity-table td{border-top:1px solid #d6dbdf;padding:16px}.social-icons{align-items:center;display:flex}.social-icons>a{aspect-ratio:24/24;background:#6c757d;display:block;height:24px;width:24px;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background .2s}.social-icons>a:active,.social-icons>a:hover{background:#212529}.social-icons>a+a{margin-left:32px}.social-icons-facebook{-webkit-mask-image:url(/images/icons/icon-facebook-gray.svg);mask-image:url(/images/icons/icon-facebook-gray.svg)}.social-icons-twitter{-webkit-mask-image:url(/images/icons/icon-twitter-gray.svg);mask-image:url(/images/icons/icon-twitter-gray.svg);width:31px}.social-icons-linkedin{-webkit-mask-image:url(/images/icons/icon-linkedin-gray.svg);mask-image:url(/images/icons/icon-linkedin-gray.svg)}.social-icons-linkedin-alt{-webkit-mask-image:url(/images/icons/icon-linkedin-alt-gray.svg);mask-image:url(/images/icons/icon-linkedin-alt-gray.svg)}.social-icons.size-small>a{height:20px;width:20px}.social-icons.size-small>a:active,.social-icons.size-small>a:hover{background:#212529}.social-icons.size-small>a+a{margin-left:16px}.tabs{position:relative}.tabs:before{background:#fff;border-radius:7px 7px 0 0;content:"";display:block;height:8px;left:1px;position:absolute;right:1px;top:68px;z-index:10}@media screen and (min-width:1240px){.tabs:before{top:76px}}.tabs-body{background:#fff;border-radius:8px;border:1px solid #6c757d;box-shadow:0 8px 20px rgba(108,117,125,.2);padding:24px}@media screen and (min-width:980px){.tabs-body{padding:32px}}@media screen and (min-width:1240px){.tabs-body{padding:40px}}.tabs .nav-tabs{border-bottom:0;flex-wrap:nowrap;height:76px;margin:-20px -20px -9px;-webkit-mask-image:linear-gradient(90deg,transparent,#000 20px,#000 calc(100% - 20px),transparent);mask-image:linear-gradient(90deg,transparent,#000 20px,#000 calc(100% - 20px),transparent);overflow:scroll;overflow-x:scroll;overflow-y:visible;padding:20px 20px 0;position:relative}@media screen and (min-width:940px){.tabs .nav-tabs{overflow:visible}}@media screen and (min-width:1240px){.tabs .nav-tabs{height:84px}}.tabs .nav-link{align-items:center;border-bottom:0;color:#6c757d;display:flex;font-size:.875rem;flex-shrink:0;height:56px;justify-content:center;padding:0 12px 8px;text-align:center;white-space:nowrap}@media screen and (min-width:1240px){.tabs .nav-link{height:64px;padding:0 16px 8px}}.tabs .nav-link.active{background:#fff;box-shadow:0 -4px 8px rgba(108,117,125,.1);font-weight:700;padding:0 16px 8px}@media screen and (min-width:980px){.tabs .nav-link.active{padding:0 24px 8px}}@media screen and (min-width:1240px){.tabs .nav-link.active{padding:0 32px 8px}}.tab-pane pre{background:#212529;border-radius:16px;color:#fff;padding:24px 16px}@media screen and (min-width:1240px){.tab-pane pre{padding:32px 24px}}.trailing-link{align-items:center;color:#212529;display:flex;font-size:.875rem;font-weight:700}.trailing-link:after{background:no-repeat url(../images/icons/icon-arrow.svg);background-position:100%;background-size:contain;content:"";display:block;height:12px;transition:transform .2s;width:20px}.trailing-link:active,.trailing-link:hover{color:#212529;text-decoration:none}.trailing-link:active:after,.trailing-link:hover:after{transform:translateX(8px)}.trailing-link.span-full:after{margin-left:auto}ul{list-style-type:square;padding-left:1.25em}ul li:not(:last-child){margin-bottom:16px}ul li::marker{color:#ff3939}ul.has-separators{list-style:none;padding:0}ul.has-separators li:not(:last-child){border-bottom:4px solid #f1f6f9;margin-bottom:24px;padding-bottom:24px}.bg-gradient-secondary{background-image:linear-gradient(58deg,#ff6443 3%,#fe561d 24%,#e32f0d 93%)}.bg-gradient-light-orange{background-image:linear-gradient(90deg,rgba(255,203,128,0),#ffcb80)}.bg-offset-right{bottom:0;left:-24px;position:absolute;top:0;width:calc(100vw + 24px);z-index:-1}@media screen and (min-width:1240px){.bg-offset-right{left:-96px;width:calc(100vw + 96px)}}.bg-inset-right{bottom:0;left:40px;position:absolute;top:0;width:calc(100vw - 40px);z-index:-1}@media screen and (min-width:980px){.bg-inset-right{left:96px;width:calc(100vw - 96px)}}.has-border-left{border-left:8px solid #f1f6f9;padding-left:16px}.font-xl{font-size:1.25rem}.font-lg{font-size:1.125rem}.font-sm{font-size:.875rem}.font-xs{font-size:.625rem}.font-weight-semibold{font-weight:600}.display-5{color:#212529;font-size:20px;font-weight:500}.display-6{color:#212529;font-size:14px;font-weight:700}.overflow-auto{overflow:auto}.text-decoration-underline{text-decoration:underline}.text-upper{text-transform:uppercase} \ No newline at end of file diff --git a/website/google419fbd824d7ff97d.html b/website/google419fbd824d7ff97d.html deleted file mode 100644 index b7e25cf48cc..00000000000 --- a/website/google419fbd824d7ff97d.html +++ /dev/null @@ -1 +0,0 @@ -google-site-verification: google419fbd824d7ff97d.html \ No newline at end of file diff --git a/website/index.html b/website/index.html deleted file mode 100644 index ad0b3a8cad2..00000000000 --- a/website/index.html +++ /dev/null @@ -1,70 +0,0 @@ -{% set prefetch_items = [ - ('/docs/en/', 'document') -] %} - -{% extends "templates/base.html" %} - -{% block extra_meta %} -{% include "templates/common_fonts.html" %} - -{% endblock %} - -{% block banner %} - -{% include "templates/global/banner.html" %} - -{% endblock %} - -{% block nav %} - -{% include "templates/global/nav.html" %} - -{% endblock %} - -{% block content %} - -{% include "templates/index/hero.html" %} -{% include "templates/index/why.html" %} -{% include "templates/index/success.html" %} - -{% if false %} - {% include "templates/index/pullquote.html" %} -{% endif %} - -{% include "templates/index/performance.html" %} -{% include "templates/index/quickstart.html" %} -{% include "templates/index/community.html" %} - -{% include "templates/global/newsletter.html" %} -{% include "templates/global/github_stars.html" %} -{% endblock %} diff --git a/website/js/base.js b/website/js/base.js deleted file mode 100644 index 1ab8f841dbe..00000000000 --- a/website/js/base.js +++ /dev/null @@ -1,100 +0,0 @@ -(function () { - Sentry.init({ - dsn: 'https://2b95b52c943f4ad99baccab7a9048e4d@o388870.ingest.sentry.io/5246103', - environment: window.location.hostname === 'clickhouse.com' ? 'prod' : 'test' - }); - $(document).click(function (event) { - var target = $(event.target); - var target_id = target.attr('id'); - var selector = target.attr('href'); - var is_tab = target.attr('role') === 'tab'; - var is_collapse = target.attr('data-toggle') === 'collapse'; - var is_rating = target.attr('role') === 'rating'; - var navbar_toggle = $('#navbar-toggle'); - navbar_toggle.collapse('hide'); - $('.algolia-autocomplete .ds-dropdown-menu').hide(); - if (target_id && target_id.startsWith('logo-')) { - selector = '#'; - } - }); - - var top_nav = $('#top-nav.sticky-top'); - if (window.location.hash.length > 1 && top_nav.length) { - var hash_destination = $(window.location.hash); - if (hash_destination.length) { - var offset = hash_destination.offset().top - top_nav.height() * 1.5; - $('html, body').animate({ - scrollTop: offset - }, 70); - } - } - - $('img').each(function() { - var src = $(this).attr('data-src'); - if (src) { - $(this).attr('src', src); - } - }); - - if (window.location.hostname.endsWith('clickhouse.com')) { - $('a.favicon').each(function () { - $(this).css({ - background: 'url(/favicon/' + this.hostname + ') left center no-repeat', - 'padding-left': '20px' - }); - }); - - function copy_to_clipboard(element) { - var temp = $(''); - $('body').append(temp); - temp.val($(element).text()); - temp.select(); - document.execCommand('copy'); - temp.remove(); - } - - $('pre').each(function(_, element) { - $(element).prepend( - 'Copy' - ); - }); - - $('.code-copy').each(function(_, element) { - element = $(element); - element.click(function() { - copy_to_clipboard(element.parent()); - }) - }); - } - - var is_single_page = $('html').attr('data-single-page') === 'true'; - if (!is_single_page) { - $('head').each(function (_, element) { - $(element).append( - '' - ); - $(element).append( - '' - ); - }); - } - - var beforePrint = function() { - var details = document.getElementsByTagName("details"); - for (var i = 0; i < details.length; ++i) { - details[i].open = 1; - } - }; - - if (window.matchMedia) { - window.matchMedia('print').addListener(function(q) { - if (q.matches) { - beforePrint(); - } - }); - if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { - $.fx.off = true; - } - } - window.onbeforeprint = beforePrint; -})(); diff --git a/website/js/bootstrap.js b/website/js/bootstrap.js deleted file mode 100644 index e2c76e94a42..00000000000 --- a/website/js/bootstrap.js +++ /dev/null @@ -1,4521 +0,0 @@ -/*! - * Bootstrap v4.4.1 (https://getbootstrap.com/) - * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('jquery'), require('popper.js')) : - typeof define === 'function' && define.amd ? define(['exports', 'jquery', 'popper.js'], factory) : - (global = global || self, factory(global.bootstrap = {}, global.jQuery, global.Popper)); -}(this, (function (exports, $, Popper) { 'use strict'; - - $ = $ && $.hasOwnProperty('default') ? $['default'] : $; - Popper = Popper && Popper.hasOwnProperty('default') ? Popper['default'] : Popper; - - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; - } - - function _defineProperty(obj, key, value) { - if (key in obj) { - Object.defineProperty(obj, key, { - value: value, - enumerable: true, - configurable: true, - writable: true - }); - } else { - obj[key] = value; - } - - return obj; - } - - function ownKeys(object, enumerableOnly) { - var keys = Object.keys(object); - - if (Object.getOwnPropertySymbols) { - var symbols = Object.getOwnPropertySymbols(object); - if (enumerableOnly) symbols = symbols.filter(function (sym) { - return Object.getOwnPropertyDescriptor(object, sym).enumerable; - }); - keys.push.apply(keys, symbols); - } - - return keys; - } - - function _objectSpread2(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i] != null ? arguments[i] : {}; - - if (i % 2) { - ownKeys(Object(source), true).forEach(function (key) { - _defineProperty(target, key, source[key]); - }); - } else if (Object.getOwnPropertyDescriptors) { - Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); - } else { - ownKeys(Object(source)).forEach(function (key) { - Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); - }); - } - } - - return target; - } - - function _inheritsLoose(subClass, superClass) { - subClass.prototype = Object.create(superClass.prototype); - subClass.prototype.constructor = subClass; - subClass.__proto__ = superClass; - } - - /** - * -------------------------------------------------------------------------- - * Bootstrap (v4.4.1): util.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * -------------------------------------------------------------------------- - */ - /** - * ------------------------------------------------------------------------ - * Private TransitionEnd Helpers - * ------------------------------------------------------------------------ - */ - - var TRANSITION_END = 'transitionend'; - var MAX_UID = 1000000; - var MILLISECONDS_MULTIPLIER = 1000; // Shoutout AngusCroll (https://goo.gl/pxwQGp) - - function toType(obj) { - return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase(); - } - - function getSpecialTransitionEndEvent() { - return { - bindType: TRANSITION_END, - delegateType: TRANSITION_END, - handle: function handle(event) { - if ($(event.target).is(this)) { - return event.handleObj.handler.apply(this, arguments); // eslint-disable-line prefer-rest-params - } - - return undefined; // eslint-disable-line no-undefined - } - }; - } - - function transitionEndEmulator(duration) { - var _this = this; - - var called = false; - $(this).one(Util.TRANSITION_END, function () { - called = true; - }); - setTimeout(function () { - if (!called) { - Util.triggerTransitionEnd(_this); - } - }, duration); - return this; - } - - function setTransitionEndSupport() { - $.fn.emulateTransitionEnd = transitionEndEmulator; - $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent(); - } - /** - * -------------------------------------------------------------------------- - * Public Util Api - * -------------------------------------------------------------------------- - */ - - - var Util = { - TRANSITION_END: 'bsTransitionEnd', - getUID: function getUID(prefix) { - do { - // eslint-disable-next-line no-bitwise - prefix += ~~(Math.random() * MAX_UID); // "~~" acts like a faster Math.floor() here - } while (document.getElementById(prefix)); - - return prefix; - }, - getSelectorFromElement: function getSelectorFromElement(element) { - var selector = element.getAttribute('data-target'); - - if (!selector || selector === '#') { - var hrefAttr = element.getAttribute('href'); - selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : ''; - } - - try { - return document.querySelector(selector) ? selector : null; - } catch (err) { - return null; - } - }, - getTransitionDurationFromElement: function getTransitionDurationFromElement(element) { - if (!element) { - return 0; - } // Get transition-duration of the element - - - var transitionDuration = $(element).css('transition-duration'); - var transitionDelay = $(element).css('transition-delay'); - var floatTransitionDuration = parseFloat(transitionDuration); - var floatTransitionDelay = parseFloat(transitionDelay); // Return 0 if element or transition duration is not found - - if (!floatTransitionDuration && !floatTransitionDelay) { - return 0; - } // If multiple durations are defined, take the first - - - transitionDuration = transitionDuration.split(',')[0]; - transitionDelay = transitionDelay.split(',')[0]; - return (parseFloat(transitionDuration) + parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER; - }, - reflow: function reflow(element) { - return element.offsetHeight; - }, - triggerTransitionEnd: function triggerTransitionEnd(element) { - $(element).trigger(TRANSITION_END); - }, - // TODO: Remove in v5 - supportsTransitionEnd: function supportsTransitionEnd() { - return Boolean(TRANSITION_END); - }, - isElement: function isElement(obj) { - return (obj[0] || obj).nodeType; - }, - typeCheckConfig: function typeCheckConfig(componentName, config, configTypes) { - for (var property in configTypes) { - if (Object.prototype.hasOwnProperty.call(configTypes, property)) { - var expectedTypes = configTypes[property]; - var value = config[property]; - var valueType = value && Util.isElement(value) ? 'element' : toType(value); - - if (!new RegExp(expectedTypes).test(valueType)) { - throw new Error(componentName.toUpperCase() + ": " + ("Option \"" + property + "\" provided type \"" + valueType + "\" ") + ("but expected type \"" + expectedTypes + "\".")); - } - } - } - }, - findShadowRoot: function findShadowRoot(element) { - if (!document.documentElement.attachShadow) { - return null; - } // Can find the shadow root otherwise it'll return the document - - - if (typeof element.getRootNode === 'function') { - var root = element.getRootNode(); - return root instanceof ShadowRoot ? root : null; - } - - if (element instanceof ShadowRoot) { - return element; - } // when we don't find a shadow root - - - if (!element.parentNode) { - return null; - } - - return Util.findShadowRoot(element.parentNode); - }, - jQueryDetection: function jQueryDetection() { - if (typeof $ === 'undefined') { - throw new TypeError('Bootstrap\'s JavaScript requires jQuery. jQuery must be included before Bootstrap\'s JavaScript.'); - } - - var version = $.fn.jquery.split(' ')[0].split('.'); - var minMajor = 1; - var ltMajor = 2; - var minMinor = 9; - var minPatch = 1; - var maxMajor = 4; - - if (version[0] < ltMajor && version[1] < minMinor || version[0] === minMajor && version[1] === minMinor && version[2] < minPatch || version[0] >= maxMajor) { - throw new Error('Bootstrap\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0'); - } - } - }; - Util.jQueryDetection(); - setTransitionEndSupport(); - - /** - * ------------------------------------------------------------------------ - * Constants - * ------------------------------------------------------------------------ - */ - - var NAME = 'alert'; - var VERSION = '4.4.1'; - var DATA_KEY = 'bs.alert'; - var EVENT_KEY = "." + DATA_KEY; - var DATA_API_KEY = '.data-api'; - var JQUERY_NO_CONFLICT = $.fn[NAME]; - var Selector = { - DISMISS: '[data-dismiss="alert"]' - }; - var Event = { - CLOSE: "close" + EVENT_KEY, - CLOSED: "closed" + EVENT_KEY, - CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY - }; - var ClassName = { - ALERT: 'alert', - FADE: 'fade', - SHOW: 'show' - }; - /** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - - var Alert = - /*#__PURE__*/ - function () { - function Alert(element) { - this._element = element; - } // Getters - - - var _proto = Alert.prototype; - - // Public - _proto.close = function close(element) { - var rootElement = this._element; - - if (element) { - rootElement = this._getRootElement(element); - } - - var customEvent = this._triggerCloseEvent(rootElement); - - if (customEvent.isDefaultPrevented()) { - return; - } - - this._removeElement(rootElement); - }; - - _proto.dispose = function dispose() { - $.removeData(this._element, DATA_KEY); - this._element = null; - } // Private - ; - - _proto._getRootElement = function _getRootElement(element) { - var selector = Util.getSelectorFromElement(element); - var parent = false; - - if (selector) { - parent = document.querySelector(selector); - } - - if (!parent) { - parent = $(element).closest("." + ClassName.ALERT)[0]; - } - - return parent; - }; - - _proto._triggerCloseEvent = function _triggerCloseEvent(element) { - var closeEvent = $.Event(Event.CLOSE); - $(element).trigger(closeEvent); - return closeEvent; - }; - - _proto._removeElement = function _removeElement(element) { - var _this = this; - - $(element).removeClass(ClassName.SHOW); - - if (!$(element).hasClass(ClassName.FADE)) { - this._destroyElement(element); - - return; - } - - var transitionDuration = Util.getTransitionDurationFromElement(element); - $(element).one(Util.TRANSITION_END, function (event) { - return _this._destroyElement(element, event); - }).emulateTransitionEnd(transitionDuration); - }; - - _proto._destroyElement = function _destroyElement(element) { - $(element).detach().trigger(Event.CLOSED).remove(); - } // Static - ; - - Alert._jQueryInterface = function _jQueryInterface(config) { - return this.each(function () { - var $element = $(this); - var data = $element.data(DATA_KEY); - - if (!data) { - data = new Alert(this); - $element.data(DATA_KEY, data); - } - - if (config === 'close') { - data[config](this); - } - }); - }; - - Alert._handleDismiss = function _handleDismiss(alertInstance) { - return function (event) { - if (event) { - event.preventDefault(); - } - - alertInstance.close(this); - }; - }; - - _createClass(Alert, null, [{ - key: "VERSION", - get: function get() { - return VERSION; - } - }]); - - return Alert; - }(); - /** - * ------------------------------------------------------------------------ - * Data Api implementation - * ------------------------------------------------------------------------ - */ - - - $(document).on(Event.CLICK_DATA_API, Selector.DISMISS, Alert._handleDismiss(new Alert())); - /** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - */ - - $.fn[NAME] = Alert._jQueryInterface; - $.fn[NAME].Constructor = Alert; - - $.fn[NAME].noConflict = function () { - $.fn[NAME] = JQUERY_NO_CONFLICT; - return Alert._jQueryInterface; - }; - - /** - * ------------------------------------------------------------------------ - * Constants - * ------------------------------------------------------------------------ - */ - - var NAME$1 = 'button'; - var VERSION$1 = '4.4.1'; - var DATA_KEY$1 = 'bs.button'; - var EVENT_KEY$1 = "." + DATA_KEY$1; - var DATA_API_KEY$1 = '.data-api'; - var JQUERY_NO_CONFLICT$1 = $.fn[NAME$1]; - var ClassName$1 = { - ACTIVE: 'active', - BUTTON: 'btn', - FOCUS: 'focus' - }; - var Selector$1 = { - DATA_TOGGLE_CARROT: '[data-toggle^="button"]', - DATA_TOGGLES: '[data-toggle="buttons"]', - DATA_TOGGLE: '[data-toggle="button"]', - DATA_TOGGLES_BUTTONS: '[data-toggle="buttons"] .btn', - INPUT: 'input:not([type="hidden"])', - ACTIVE: '.active', - BUTTON: '.btn' - }; - var Event$1 = { - CLICK_DATA_API: "click" + EVENT_KEY$1 + DATA_API_KEY$1, - FOCUS_BLUR_DATA_API: "focus" + EVENT_KEY$1 + DATA_API_KEY$1 + " " + ("blur" + EVENT_KEY$1 + DATA_API_KEY$1), - LOAD_DATA_API: "load" + EVENT_KEY$1 + DATA_API_KEY$1 - }; - /** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - - var Button = - /*#__PURE__*/ - function () { - function Button(element) { - this._element = element; - } // Getters - - - var _proto = Button.prototype; - - // Public - _proto.toggle = function toggle() { - var triggerChangeEvent = true; - var addAriaPressed = true; - var rootElement = $(this._element).closest(Selector$1.DATA_TOGGLES)[0]; - - if (rootElement) { - var input = this._element.querySelector(Selector$1.INPUT); - - if (input) { - if (input.type === 'radio') { - if (input.checked && this._element.classList.contains(ClassName$1.ACTIVE)) { - triggerChangeEvent = false; - } else { - var activeElement = rootElement.querySelector(Selector$1.ACTIVE); - - if (activeElement) { - $(activeElement).removeClass(ClassName$1.ACTIVE); - } - } - } else if (input.type === 'checkbox') { - if (this._element.tagName === 'LABEL' && input.checked === this._element.classList.contains(ClassName$1.ACTIVE)) { - triggerChangeEvent = false; - } - } else { - // if it's not a radio button or checkbox don't add a pointless/invalid checked property to the input - triggerChangeEvent = false; - } - - if (triggerChangeEvent) { - input.checked = !this._element.classList.contains(ClassName$1.ACTIVE); - $(input).trigger('change'); - } - - input.focus(); - addAriaPressed = false; - } - } - - if (!(this._element.hasAttribute('disabled') || this._element.classList.contains('disabled'))) { - if (addAriaPressed) { - this._element.setAttribute('aria-pressed', !this._element.classList.contains(ClassName$1.ACTIVE)); - } - - if (triggerChangeEvent) { - $(this._element).toggleClass(ClassName$1.ACTIVE); - } - } - }; - - _proto.dispose = function dispose() { - $.removeData(this._element, DATA_KEY$1); - this._element = null; - } // Static - ; - - Button._jQueryInterface = function _jQueryInterface(config) { - return this.each(function () { - var data = $(this).data(DATA_KEY$1); - - if (!data) { - data = new Button(this); - $(this).data(DATA_KEY$1, data); - } - - if (config === 'toggle') { - data[config](); - } - }); - }; - - _createClass(Button, null, [{ - key: "VERSION", - get: function get() { - return VERSION$1; - } - }]); - - return Button; - }(); - /** - * ------------------------------------------------------------------------ - * Data Api implementation - * ------------------------------------------------------------------------ - */ - - - $(document).on(Event$1.CLICK_DATA_API, Selector$1.DATA_TOGGLE_CARROT, function (event) { - var button = event.target; - - if (!$(button).hasClass(ClassName$1.BUTTON)) { - button = $(button).closest(Selector$1.BUTTON)[0]; - } - - if (!button || button.hasAttribute('disabled') || button.classList.contains('disabled')) { - event.preventDefault(); // work around Firefox bug #1540995 - } else { - var inputBtn = button.querySelector(Selector$1.INPUT); - - if (inputBtn && (inputBtn.hasAttribute('disabled') || inputBtn.classList.contains('disabled'))) { - event.preventDefault(); // work around Firefox bug #1540995 - - return; - } - - Button._jQueryInterface.call($(button), 'toggle'); - } - }).on(Event$1.FOCUS_BLUR_DATA_API, Selector$1.DATA_TOGGLE_CARROT, function (event) { - var button = $(event.target).closest(Selector$1.BUTTON)[0]; - $(button).toggleClass(ClassName$1.FOCUS, /^focus(in)?$/.test(event.type)); - }); - $(window).on(Event$1.LOAD_DATA_API, function () { - // ensure correct active class is set to match the controls' actual values/states - // find all checkboxes/readio buttons inside data-toggle groups - var buttons = [].slice.call(document.querySelectorAll(Selector$1.DATA_TOGGLES_BUTTONS)); - - for (var i = 0, len = buttons.length; i < len; i++) { - var button = buttons[i]; - var input = button.querySelector(Selector$1.INPUT); - - if (input.checked || input.hasAttribute('checked')) { - button.classList.add(ClassName$1.ACTIVE); - } else { - button.classList.remove(ClassName$1.ACTIVE); - } - } // find all button toggles - - - buttons = [].slice.call(document.querySelectorAll(Selector$1.DATA_TOGGLE)); - - for (var _i = 0, _len = buttons.length; _i < _len; _i++) { - var _button = buttons[_i]; - - if (_button.getAttribute('aria-pressed') === 'true') { - _button.classList.add(ClassName$1.ACTIVE); - } else { - _button.classList.remove(ClassName$1.ACTIVE); - } - } - }); - /** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - */ - - $.fn[NAME$1] = Button._jQueryInterface; - $.fn[NAME$1].Constructor = Button; - - $.fn[NAME$1].noConflict = function () { - $.fn[NAME$1] = JQUERY_NO_CONFLICT$1; - return Button._jQueryInterface; - }; - - /** - * ------------------------------------------------------------------------ - * Constants - * ------------------------------------------------------------------------ - */ - - var NAME$2 = 'carousel'; - var VERSION$2 = '4.4.1'; - var DATA_KEY$2 = 'bs.carousel'; - var EVENT_KEY$2 = "." + DATA_KEY$2; - var DATA_API_KEY$2 = '.data-api'; - var JQUERY_NO_CONFLICT$2 = $.fn[NAME$2]; - var ARROW_LEFT_KEYCODE = 37; // KeyboardEvent.which value for left arrow key - - var ARROW_RIGHT_KEYCODE = 39; // KeyboardEvent.which value for right arrow key - - var TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch - - var SWIPE_THRESHOLD = 40; - var Default = { - interval: 5000, - keyboard: true, - slide: false, - pause: 'hover', - wrap: true, - touch: true - }; - var DefaultType = { - interval: '(number|boolean)', - keyboard: 'boolean', - slide: '(boolean|string)', - pause: '(string|boolean)', - wrap: 'boolean', - touch: 'boolean' - }; - var Direction = { - NEXT: 'next', - PREV: 'prev', - LEFT: 'left', - RIGHT: 'right' - }; - var Event$2 = { - SLIDE: "slide" + EVENT_KEY$2, - SLID: "slid" + EVENT_KEY$2, - KEYDOWN: "keydown" + EVENT_KEY$2, - MOUSEENTER: "mouseenter" + EVENT_KEY$2, - MOUSELEAVE: "mouseleave" + EVENT_KEY$2, - TOUCHSTART: "touchstart" + EVENT_KEY$2, - TOUCHMOVE: "touchmove" + EVENT_KEY$2, - TOUCHEND: "touchend" + EVENT_KEY$2, - POINTERDOWN: "pointerdown" + EVENT_KEY$2, - POINTERUP: "pointerup" + EVENT_KEY$2, - DRAG_START: "dragstart" + EVENT_KEY$2, - LOAD_DATA_API: "load" + EVENT_KEY$2 + DATA_API_KEY$2, - CLICK_DATA_API: "click" + EVENT_KEY$2 + DATA_API_KEY$2 - }; - var ClassName$2 = { - CAROUSEL: 'carousel', - ACTIVE: 'active', - SLIDE: 'slide', - RIGHT: 'carousel-item-right', - LEFT: 'carousel-item-left', - NEXT: 'carousel-item-next', - PREV: 'carousel-item-prev', - ITEM: 'carousel-item', - POINTER_EVENT: 'pointer-event' - }; - var Selector$2 = { - ACTIVE: '.active', - ACTIVE_ITEM: '.active.carousel-item', - ITEM: '.carousel-item', - ITEM_IMG: '.carousel-item img', - NEXT_PREV: '.carousel-item-next, .carousel-item-prev', - INDICATORS: '.carousel-indicators', - DATA_SLIDE: '[data-slide], [data-slide-to]', - DATA_RIDE: '[data-ride="carousel"]' - }; - var PointerType = { - TOUCH: 'touch', - PEN: 'pen' - }; - /** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - - var Carousel = - /*#__PURE__*/ - function () { - function Carousel(element, config) { - this._items = null; - this._interval = null; - this._activeElement = null; - this._isPaused = false; - this._isSliding = false; - this.touchTimeout = null; - this.touchStartX = 0; - this.touchDeltaX = 0; - this._config = this._getConfig(config); - this._element = element; - this._indicatorsElement = this._element.querySelector(Selector$2.INDICATORS); - this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0; - this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent); - - this._addEventListeners(); - } // Getters - - - var _proto = Carousel.prototype; - - // Public - _proto.next = function next() { - if (!this._isSliding) { - this._slide(Direction.NEXT); - } - }; - - _proto.nextWhenVisible = function nextWhenVisible() { - // Don't call next when the page isn't visible - // or the carousel or its parent isn't visible - if (!document.hidden && $(this._element).is(':visible') && $(this._element).css('visibility') !== 'hidden') { - this.next(); - } - }; - - _proto.prev = function prev() { - if (!this._isSliding) { - this._slide(Direction.PREV); - } - }; - - _proto.pause = function pause(event) { - if (!event) { - this._isPaused = true; - } - - if (this._element.querySelector(Selector$2.NEXT_PREV)) { - Util.triggerTransitionEnd(this._element); - this.cycle(true); - } - - clearInterval(this._interval); - this._interval = null; - }; - - _proto.cycle = function cycle(event) { - if (!event) { - this._isPaused = false; - } - - if (this._interval) { - clearInterval(this._interval); - this._interval = null; - } - - if (this._config.interval && !this._isPaused) { - this._interval = setInterval((document.visibilityState ? this.nextWhenVisible : this.next).bind(this), this._config.interval); - } - }; - - _proto.to = function to(index) { - var _this = this; - - this._activeElement = this._element.querySelector(Selector$2.ACTIVE_ITEM); - - var activeIndex = this._getItemIndex(this._activeElement); - - if (index > this._items.length - 1 || index < 0) { - return; - } - - if (this._isSliding) { - $(this._element).one(Event$2.SLID, function () { - return _this.to(index); - }); - return; - } - - if (activeIndex === index) { - this.pause(); - this.cycle(); - return; - } - - var direction = index > activeIndex ? Direction.NEXT : Direction.PREV; - - this._slide(direction, this._items[index]); - }; - - _proto.dispose = function dispose() { - $(this._element).off(EVENT_KEY$2); - $.removeData(this._element, DATA_KEY$2); - this._items = null; - this._config = null; - this._element = null; - this._interval = null; - this._isPaused = null; - this._isSliding = null; - this._activeElement = null; - this._indicatorsElement = null; - } // Private - ; - - _proto._getConfig = function _getConfig(config) { - config = _objectSpread2({}, Default, {}, config); - Util.typeCheckConfig(NAME$2, config, DefaultType); - return config; - }; - - _proto._handleSwipe = function _handleSwipe() { - var absDeltax = Math.abs(this.touchDeltaX); - - if (absDeltax <= SWIPE_THRESHOLD) { - return; - } - - var direction = absDeltax / this.touchDeltaX; - this.touchDeltaX = 0; // swipe left - - if (direction > 0) { - this.prev(); - } // swipe right - - - if (direction < 0) { - this.next(); - } - }; - - _proto._addEventListeners = function _addEventListeners() { - var _this2 = this; - - if (this._config.keyboard) { - $(this._element).on(Event$2.KEYDOWN, function (event) { - return _this2._keydown(event); - }); - } - - if (this._config.pause === 'hover') { - $(this._element).on(Event$2.MOUSEENTER, function (event) { - return _this2.pause(event); - }).on(Event$2.MOUSELEAVE, function (event) { - return _this2.cycle(event); - }); - } - - if (this._config.touch) { - this._addTouchEventListeners(); - } - }; - - _proto._addTouchEventListeners = function _addTouchEventListeners() { - var _this3 = this; - - if (!this._touchSupported) { - return; - } - - var start = function start(event) { - if (_this3._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) { - _this3.touchStartX = event.originalEvent.clientX; - } else if (!_this3._pointerEvent) { - _this3.touchStartX = event.originalEvent.touches[0].clientX; - } - }; - - var move = function move(event) { - // ensure swiping with one touch and not pinching - if (event.originalEvent.touches && event.originalEvent.touches.length > 1) { - _this3.touchDeltaX = 0; - } else { - _this3.touchDeltaX = event.originalEvent.touches[0].clientX - _this3.touchStartX; - } - }; - - var end = function end(event) { - if (_this3._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) { - _this3.touchDeltaX = event.originalEvent.clientX - _this3.touchStartX; - } - - _this3._handleSwipe(); - - if (_this3._config.pause === 'hover') { - // If it's a touch-enabled device, mouseenter/leave are fired as - // part of the mouse compatibility events on first tap - the carousel - // would stop cycling until user tapped out of it; - // here, we listen for touchend, explicitly pause the carousel - // (as if it's the second time we tap on it, mouseenter compat event - // is NOT fired) and after a timeout (to allow for mouse compatibility - // events to fire) we explicitly restart cycling - _this3.pause(); - - if (_this3.touchTimeout) { - clearTimeout(_this3.touchTimeout); - } - - _this3.touchTimeout = setTimeout(function (event) { - return _this3.cycle(event); - }, TOUCHEVENT_COMPAT_WAIT + _this3._config.interval); - } - }; - - $(this._element.querySelectorAll(Selector$2.ITEM_IMG)).on(Event$2.DRAG_START, function (e) { - return e.preventDefault(); - }); - - if (this._pointerEvent) { - $(this._element).on(Event$2.POINTERDOWN, function (event) { - return start(event); - }); - $(this._element).on(Event$2.POINTERUP, function (event) { - return end(event); - }); - - this._element.classList.add(ClassName$2.POINTER_EVENT); - } else { - $(this._element).on(Event$2.TOUCHSTART, function (event) { - return start(event); - }); - $(this._element).on(Event$2.TOUCHMOVE, function (event) { - return move(event); - }); - $(this._element).on(Event$2.TOUCHEND, function (event) { - return end(event); - }); - } - }; - - _proto._keydown = function _keydown(event) { - if (/input|textarea/i.test(event.target.tagName)) { - return; - } - - switch (event.which) { - case ARROW_LEFT_KEYCODE: - event.preventDefault(); - this.prev(); - break; - - case ARROW_RIGHT_KEYCODE: - event.preventDefault(); - this.next(); - break; - } - }; - - _proto._getItemIndex = function _getItemIndex(element) { - this._items = element && element.parentNode ? [].slice.call(element.parentNode.querySelectorAll(Selector$2.ITEM)) : []; - return this._items.indexOf(element); - }; - - _proto._getItemByDirection = function _getItemByDirection(direction, activeElement) { - var isNextDirection = direction === Direction.NEXT; - var isPrevDirection = direction === Direction.PREV; - - var activeIndex = this._getItemIndex(activeElement); - - var lastItemIndex = this._items.length - 1; - var isGoingToWrap = isPrevDirection && activeIndex === 0 || isNextDirection && activeIndex === lastItemIndex; - - if (isGoingToWrap && !this._config.wrap) { - return activeElement; - } - - var delta = direction === Direction.PREV ? -1 : 1; - var itemIndex = (activeIndex + delta) % this._items.length; - return itemIndex === -1 ? this._items[this._items.length - 1] : this._items[itemIndex]; - }; - - _proto._triggerSlideEvent = function _triggerSlideEvent(relatedTarget, eventDirectionName) { - var targetIndex = this._getItemIndex(relatedTarget); - - var fromIndex = this._getItemIndex(this._element.querySelector(Selector$2.ACTIVE_ITEM)); - - var slideEvent = $.Event(Event$2.SLIDE, { - relatedTarget: relatedTarget, - direction: eventDirectionName, - from: fromIndex, - to: targetIndex - }); - $(this._element).trigger(slideEvent); - return slideEvent; - }; - - _proto._setActiveIndicatorElement = function _setActiveIndicatorElement(element) { - if (this._indicatorsElement) { - var indicators = [].slice.call(this._indicatorsElement.querySelectorAll(Selector$2.ACTIVE)); - $(indicators).removeClass(ClassName$2.ACTIVE); - - var nextIndicator = this._indicatorsElement.children[this._getItemIndex(element)]; - - if (nextIndicator) { - $(nextIndicator).addClass(ClassName$2.ACTIVE); - } - } - }; - - _proto._slide = function _slide(direction, element) { - var _this4 = this; - - var activeElement = this._element.querySelector(Selector$2.ACTIVE_ITEM); - - var activeElementIndex = this._getItemIndex(activeElement); - - var nextElement = element || activeElement && this._getItemByDirection(direction, activeElement); - - var nextElementIndex = this._getItemIndex(nextElement); - - var isCycling = Boolean(this._interval); - var directionalClassName; - var orderClassName; - var eventDirectionName; - - if (direction === Direction.NEXT) { - directionalClassName = ClassName$2.LEFT; - orderClassName = ClassName$2.NEXT; - eventDirectionName = Direction.LEFT; - } else { - directionalClassName = ClassName$2.RIGHT; - orderClassName = ClassName$2.PREV; - eventDirectionName = Direction.RIGHT; - } - - if (nextElement && $(nextElement).hasClass(ClassName$2.ACTIVE)) { - this._isSliding = false; - return; - } - - var slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName); - - if (slideEvent.isDefaultPrevented()) { - return; - } - - if (!activeElement || !nextElement) { - // Some weirdness is happening, so we bail - return; - } - - this._isSliding = true; - - if (isCycling) { - this.pause(); - } - - this._setActiveIndicatorElement(nextElement); - - var slidEvent = $.Event(Event$2.SLID, { - relatedTarget: nextElement, - direction: eventDirectionName, - from: activeElementIndex, - to: nextElementIndex - }); - - if ($(this._element).hasClass(ClassName$2.SLIDE)) { - $(nextElement).addClass(orderClassName); - Util.reflow(nextElement); - $(activeElement).addClass(directionalClassName); - $(nextElement).addClass(directionalClassName); - var nextElementInterval = parseInt(nextElement.getAttribute('data-interval'), 10); - - if (nextElementInterval) { - this._config.defaultInterval = this._config.defaultInterval || this._config.interval; - this._config.interval = nextElementInterval; - } else { - this._config.interval = this._config.defaultInterval || this._config.interval; - } - - var transitionDuration = Util.getTransitionDurationFromElement(activeElement); - $(activeElement).one(Util.TRANSITION_END, function () { - $(nextElement).removeClass(directionalClassName + " " + orderClassName).addClass(ClassName$2.ACTIVE); - $(activeElement).removeClass(ClassName$2.ACTIVE + " " + orderClassName + " " + directionalClassName); - _this4._isSliding = false; - setTimeout(function () { - return $(_this4._element).trigger(slidEvent); - }, 0); - }).emulateTransitionEnd(transitionDuration); - } else { - $(activeElement).removeClass(ClassName$2.ACTIVE); - $(nextElement).addClass(ClassName$2.ACTIVE); - this._isSliding = false; - $(this._element).trigger(slidEvent); - } - - if (isCycling) { - this.cycle(); - } - } // Static - ; - - Carousel._jQueryInterface = function _jQueryInterface(config) { - return this.each(function () { - var data = $(this).data(DATA_KEY$2); - - var _config = _objectSpread2({}, Default, {}, $(this).data()); - - if (typeof config === 'object') { - _config = _objectSpread2({}, _config, {}, config); - } - - var action = typeof config === 'string' ? config : _config.slide; - - if (!data) { - data = new Carousel(this, _config); - $(this).data(DATA_KEY$2, data); - } - - if (typeof config === 'number') { - data.to(config); - } else if (typeof action === 'string') { - if (typeof data[action] === 'undefined') { - throw new TypeError("No method named \"" + action + "\""); - } - - data[action](); - } else if (_config.interval && _config.ride) { - data.pause(); - data.cycle(); - } - }); - }; - - Carousel._dataApiClickHandler = function _dataApiClickHandler(event) { - var selector = Util.getSelectorFromElement(this); - - if (!selector) { - return; - } - - var target = $(selector)[0]; - - if (!target || !$(target).hasClass(ClassName$2.CAROUSEL)) { - return; - } - - var config = _objectSpread2({}, $(target).data(), {}, $(this).data()); - - var slideIndex = this.getAttribute('data-slide-to'); - - if (slideIndex) { - config.interval = false; - } - - Carousel._jQueryInterface.call($(target), config); - - if (slideIndex) { - $(target).data(DATA_KEY$2).to(slideIndex); - } - - event.preventDefault(); - }; - - _createClass(Carousel, null, [{ - key: "VERSION", - get: function get() { - return VERSION$2; - } - }, { - key: "Default", - get: function get() { - return Default; - } - }]); - - return Carousel; - }(); - /** - * ------------------------------------------------------------------------ - * Data Api implementation - * ------------------------------------------------------------------------ - */ - - - $(document).on(Event$2.CLICK_DATA_API, Selector$2.DATA_SLIDE, Carousel._dataApiClickHandler); - $(window).on(Event$2.LOAD_DATA_API, function () { - var carousels = [].slice.call(document.querySelectorAll(Selector$2.DATA_RIDE)); - - for (var i = 0, len = carousels.length; i < len; i++) { - var $carousel = $(carousels[i]); - - Carousel._jQueryInterface.call($carousel, $carousel.data()); - } - }); - /** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - */ - - $.fn[NAME$2] = Carousel._jQueryInterface; - $.fn[NAME$2].Constructor = Carousel; - - $.fn[NAME$2].noConflict = function () { - $.fn[NAME$2] = JQUERY_NO_CONFLICT$2; - return Carousel._jQueryInterface; - }; - - /** - * ------------------------------------------------------------------------ - * Constants - * ------------------------------------------------------------------------ - */ - - var NAME$3 = 'collapse'; - var VERSION$3 = '4.4.1'; - var DATA_KEY$3 = 'bs.collapse'; - var EVENT_KEY$3 = "." + DATA_KEY$3; - var DATA_API_KEY$3 = '.data-api'; - var JQUERY_NO_CONFLICT$3 = $.fn[NAME$3]; - var Default$1 = { - toggle: true, - parent: '' - }; - var DefaultType$1 = { - toggle: 'boolean', - parent: '(string|element)' - }; - var Event$3 = { - SHOW: "show" + EVENT_KEY$3, - SHOWN: "shown" + EVENT_KEY$3, - HIDE: "hide" + EVENT_KEY$3, - HIDDEN: "hidden" + EVENT_KEY$3, - CLICK_DATA_API: "click" + EVENT_KEY$3 + DATA_API_KEY$3 - }; - var ClassName$3 = { - SHOW: 'show', - COLLAPSE: 'collapse', - COLLAPSING: 'collapsing', - COLLAPSED: 'collapsed' - }; - var Dimension = { - WIDTH: 'width', - HEIGHT: 'height' - }; - var Selector$3 = { - ACTIVES: '.show, .collapsing', - DATA_TOGGLE: '[data-toggle="collapse"]' - }; - /** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - - var Collapse = - /*#__PURE__*/ - function () { - function Collapse(element, config) { - this._isTransitioning = false; - this._element = element; - this._config = this._getConfig(config); - this._triggerArray = [].slice.call(document.querySelectorAll("[data-toggle=\"collapse\"][href=\"#" + element.id + "\"]," + ("[data-toggle=\"collapse\"][data-target=\"#" + element.id + "\"]"))); - var toggleList = [].slice.call(document.querySelectorAll(Selector$3.DATA_TOGGLE)); - - for (var i = 0, len = toggleList.length; i < len; i++) { - var elem = toggleList[i]; - var selector = Util.getSelectorFromElement(elem); - var filterElement = [].slice.call(document.querySelectorAll(selector)).filter(function (foundElem) { - return foundElem === element; - }); - - if (selector !== null && filterElement.length > 0) { - this._selector = selector; - - this._triggerArray.push(elem); - } - } - - this._parent = this._config.parent ? this._getParent() : null; - - if (!this._config.parent) { - this._addAriaAndCollapsedClass(this._element, this._triggerArray); - } - - if (this._config.toggle) { - this.toggle(); - } - } // Getters - - - var _proto = Collapse.prototype; - - // Public - _proto.toggle = function toggle() { - if ($(this._element).hasClass(ClassName$3.SHOW)) { - this.hide(); - } else { - this.show(); - } - }; - - _proto.show = function show() { - var _this = this; - - if (this._isTransitioning || $(this._element).hasClass(ClassName$3.SHOW)) { - return; - } - - var actives; - var activesData; - - if (this._parent) { - actives = [].slice.call(this._parent.querySelectorAll(Selector$3.ACTIVES)).filter(function (elem) { - if (typeof _this._config.parent === 'string') { - return elem.getAttribute('data-parent') === _this._config.parent; - } - - return elem.classList.contains(ClassName$3.COLLAPSE); - }); - - if (actives.length === 0) { - actives = null; - } - } - - if (actives) { - activesData = $(actives).not(this._selector).data(DATA_KEY$3); - - if (activesData && activesData._isTransitioning) { - return; - } - } - - var startEvent = $.Event(Event$3.SHOW); - $(this._element).trigger(startEvent); - - if (startEvent.isDefaultPrevented()) { - return; - } - - if (actives) { - Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide'); - - if (!activesData) { - $(actives).data(DATA_KEY$3, null); - } - } - - var dimension = this._getDimension(); - - $(this._element).removeClass(ClassName$3.COLLAPSE).addClass(ClassName$3.COLLAPSING); - this._element.style[dimension] = 0; - - if (this._triggerArray.length) { - $(this._triggerArray).removeClass(ClassName$3.COLLAPSED).attr('aria-expanded', true); - } - - this.setTransitioning(true); - - var complete = function complete() { - $(_this._element).removeClass(ClassName$3.COLLAPSING).addClass(ClassName$3.COLLAPSE).addClass(ClassName$3.SHOW); - _this._element.style[dimension] = ''; - - _this.setTransitioning(false); - - $(_this._element).trigger(Event$3.SHOWN); - }; - - var capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1); - var scrollSize = "scroll" + capitalizedDimension; - var transitionDuration = Util.getTransitionDurationFromElement(this._element); - $(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); - this._element.style[dimension] = this._element[scrollSize] + "px"; - }; - - _proto.hide = function hide() { - var _this2 = this; - - if (this._isTransitioning || !$(this._element).hasClass(ClassName$3.SHOW)) { - return; - } - - var startEvent = $.Event(Event$3.HIDE); - $(this._element).trigger(startEvent); - - if (startEvent.isDefaultPrevented()) { - return; - } - - var dimension = this._getDimension(); - - this._element.style[dimension] = this._element.getBoundingClientRect()[dimension] + "px"; - Util.reflow(this._element); - $(this._element).addClass(ClassName$3.COLLAPSING).removeClass(ClassName$3.COLLAPSE).removeClass(ClassName$3.SHOW); - var triggerArrayLength = this._triggerArray.length; - - if (triggerArrayLength > 0) { - for (var i = 0; i < triggerArrayLength; i++) { - var trigger = this._triggerArray[i]; - var selector = Util.getSelectorFromElement(trigger); - - if (selector !== null) { - var $elem = $([].slice.call(document.querySelectorAll(selector))); - - if (!$elem.hasClass(ClassName$3.SHOW)) { - $(trigger).addClass(ClassName$3.COLLAPSED).attr('aria-expanded', false); - } - } - } - } - - this.setTransitioning(true); - - var complete = function complete() { - _this2.setTransitioning(false); - - $(_this2._element).removeClass(ClassName$3.COLLAPSING).addClass(ClassName$3.COLLAPSE).trigger(Event$3.HIDDEN); - }; - - this._element.style[dimension] = ''; - var transitionDuration = Util.getTransitionDurationFromElement(this._element); - $(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); - }; - - _proto.setTransitioning = function setTransitioning(isTransitioning) { - this._isTransitioning = isTransitioning; - }; - - _proto.dispose = function dispose() { - $.removeData(this._element, DATA_KEY$3); - this._config = null; - this._parent = null; - this._element = null; - this._triggerArray = null; - this._isTransitioning = null; - } // Private - ; - - _proto._getConfig = function _getConfig(config) { - config = _objectSpread2({}, Default$1, {}, config); - config.toggle = Boolean(config.toggle); // Coerce string values - - Util.typeCheckConfig(NAME$3, config, DefaultType$1); - return config; - }; - - _proto._getDimension = function _getDimension() { - var hasWidth = $(this._element).hasClass(Dimension.WIDTH); - return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT; - }; - - _proto._getParent = function _getParent() { - var _this3 = this; - - var parent; - - if (Util.isElement(this._config.parent)) { - parent = this._config.parent; // It's a jQuery object - - if (typeof this._config.parent.jquery !== 'undefined') { - parent = this._config.parent[0]; - } - } else { - parent = document.querySelector(this._config.parent); - } - - var selector = "[data-toggle=\"collapse\"][data-parent=\"" + this._config.parent + "\"]"; - var children = [].slice.call(parent.querySelectorAll(selector)); - $(children).each(function (i, element) { - _this3._addAriaAndCollapsedClass(Collapse._getTargetFromElement(element), [element]); - }); - return parent; - }; - - _proto._addAriaAndCollapsedClass = function _addAriaAndCollapsedClass(element, triggerArray) { - var isOpen = $(element).hasClass(ClassName$3.SHOW); - - if (triggerArray.length) { - $(triggerArray).toggleClass(ClassName$3.COLLAPSED, !isOpen).attr('aria-expanded', isOpen); - } - } // Static - ; - - Collapse._getTargetFromElement = function _getTargetFromElement(element) { - var selector = Util.getSelectorFromElement(element); - return selector ? document.querySelector(selector) : null; - }; - - Collapse._jQueryInterface = function _jQueryInterface(config) { - return this.each(function () { - var $this = $(this); - var data = $this.data(DATA_KEY$3); - - var _config = _objectSpread2({}, Default$1, {}, $this.data(), {}, typeof config === 'object' && config ? config : {}); - - if (!data && _config.toggle && typeof config === 'string' && /show|hide/.test(config)) { - _config.toggle = false; - } - - if (!data) { - data = new Collapse(this, _config); - $this.data(DATA_KEY$3, data); - } - - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError("No method named \"" + config + "\""); - } - - data[config](); - } - }); - }; - - _createClass(Collapse, null, [{ - key: "VERSION", - get: function get() { - return VERSION$3; - } - }, { - key: "Default", - get: function get() { - return Default$1; - } - }]); - - return Collapse; - }(); - /** - * ------------------------------------------------------------------------ - * Data Api implementation - * ------------------------------------------------------------------------ - */ - - - $(document).on(Event$3.CLICK_DATA_API, Selector$3.DATA_TOGGLE, function (event) { - // preventDefault only for elements (which change the URL) not inside the collapsible element - if (event.currentTarget.tagName === 'A') { - event.preventDefault(); - } - - var $trigger = $(this); - var selector = Util.getSelectorFromElement(this); - var selectors = [].slice.call(document.querySelectorAll(selector)); - $(selectors).each(function () { - var $target = $(this); - var data = $target.data(DATA_KEY$3); - var config = data ? 'toggle' : $trigger.data(); - - Collapse._jQueryInterface.call($target, config); - }); - }); - /** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - */ - - $.fn[NAME$3] = Collapse._jQueryInterface; - $.fn[NAME$3].Constructor = Collapse; - - $.fn[NAME$3].noConflict = function () { - $.fn[NAME$3] = JQUERY_NO_CONFLICT$3; - return Collapse._jQueryInterface; - }; - - /** - * ------------------------------------------------------------------------ - * Constants - * ------------------------------------------------------------------------ - */ - - var NAME$4 = 'dropdown'; - var VERSION$4 = '4.4.1'; - var DATA_KEY$4 = 'bs.dropdown'; - var EVENT_KEY$4 = "." + DATA_KEY$4; - var DATA_API_KEY$4 = '.data-api'; - var JQUERY_NO_CONFLICT$4 = $.fn[NAME$4]; - var ESCAPE_KEYCODE = 27; // KeyboardEvent.which value for Escape (Esc) key - - var SPACE_KEYCODE = 32; // KeyboardEvent.which value for space key - - var TAB_KEYCODE = 9; // KeyboardEvent.which value for tab key - - var ARROW_UP_KEYCODE = 38; // KeyboardEvent.which value for up arrow key - - var ARROW_DOWN_KEYCODE = 40; // KeyboardEvent.which value for down arrow key - - var RIGHT_MOUSE_BUTTON_WHICH = 3; // MouseEvent.which value for the right button (assuming a right-handed mouse) - - var REGEXP_KEYDOWN = new RegExp(ARROW_UP_KEYCODE + "|" + ARROW_DOWN_KEYCODE + "|" + ESCAPE_KEYCODE); - var Event$4 = { - HIDE: "hide" + EVENT_KEY$4, - HIDDEN: "hidden" + EVENT_KEY$4, - SHOW: "show" + EVENT_KEY$4, - SHOWN: "shown" + EVENT_KEY$4, - CLICK: "click" + EVENT_KEY$4, - CLICK_DATA_API: "click" + EVENT_KEY$4 + DATA_API_KEY$4, - KEYDOWN_DATA_API: "keydown" + EVENT_KEY$4 + DATA_API_KEY$4, - KEYUP_DATA_API: "keyup" + EVENT_KEY$4 + DATA_API_KEY$4 - }; - var ClassName$4 = { - DISABLED: 'disabled', - SHOW: 'show', - DROPUP: 'dropup', - DROPRIGHT: 'dropright', - DROPLEFT: 'dropleft', - MENURIGHT: 'dropdown-menu-right', - MENULEFT: 'dropdown-menu-left', - POSITION_STATIC: 'position-static' - }; - var Selector$4 = { - DATA_TOGGLE: '[data-toggle="dropdown"]', - FORM_CHILD: '.dropdown form', - MENU: '.dropdown-menu', - NAVBAR_NAV: '.navbar-nav', - VISIBLE_ITEMS: '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)' - }; - var AttachmentMap = { - TOP: 'top-start', - TOPEND: 'top-end', - BOTTOM: 'bottom-start', - BOTTOMEND: 'bottom-end', - RIGHT: 'right-start', - RIGHTEND: 'right-end', - LEFT: 'left-start', - LEFTEND: 'left-end' - }; - var Default$2 = { - offset: 0, - flip: true, - boundary: 'scrollParent', - reference: 'toggle', - display: 'dynamic', - popperConfig: null - }; - var DefaultType$2 = { - offset: '(number|string|function)', - flip: 'boolean', - boundary: '(string|element)', - reference: '(string|element)', - display: 'string', - popperConfig: '(null|object)' - }; - /** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - - var Dropdown = - /*#__PURE__*/ - function () { - function Dropdown(element, config) { - this._element = element; - this._popper = null; - this._config = this._getConfig(config); - this._menu = this._getMenuElement(); - this._inNavbar = this._detectNavbar(); - - this._addEventListeners(); - } // Getters - - - var _proto = Dropdown.prototype; - - // Public - _proto.toggle = function toggle() { - if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED)) { - return; - } - - var isActive = $(this._menu).hasClass(ClassName$4.SHOW); - - Dropdown._clearMenus(); - - if (isActive) { - return; - } - - this.show(true); - }; - - _proto.show = function show(usePopper) { - if (usePopper === void 0) { - usePopper = false; - } - - if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED) || $(this._menu).hasClass(ClassName$4.SHOW)) { - return; - } - - var relatedTarget = { - relatedTarget: this._element - }; - var showEvent = $.Event(Event$4.SHOW, relatedTarget); - - var parent = Dropdown._getParentFromElement(this._element); - - $(parent).trigger(showEvent); - - if (showEvent.isDefaultPrevented()) { - return; - } // Disable totally Popper.js for Dropdown in Navbar - - - if (!this._inNavbar && usePopper) { - /** - * Check for Popper dependency - * Popper - https://popper.js.org - */ - if (typeof Popper === 'undefined') { - throw new TypeError('Bootstrap\'s dropdowns require Popper.js (https://popper.js.org/)'); - } - - var referenceElement = this._element; - - if (this._config.reference === 'parent') { - referenceElement = parent; - } else if (Util.isElement(this._config.reference)) { - referenceElement = this._config.reference; // Check if it's jQuery element - - if (typeof this._config.reference.jquery !== 'undefined') { - referenceElement = this._config.reference[0]; - } - } // If boundary is not `scrollParent`, then set position to `static` - // to allow the menu to "escape" the scroll parent's boundaries - // https://github.com/twbs/bootstrap/issues/24251 - - - if (this._config.boundary !== 'scrollParent') { - $(parent).addClass(ClassName$4.POSITION_STATIC); - } - - this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig()); - } // If this is a touch-enabled device we add extra - // empty mouseover listeners to the body's immediate children; - // only needed because of broken event delegation on iOS - // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - - - if ('ontouchstart' in document.documentElement && $(parent).closest(Selector$4.NAVBAR_NAV).length === 0) { - $(document.body).children().on('mouseover', null, $.noop); - } - - this._element.focus(); - - this._element.setAttribute('aria-expanded', true); - - $(this._menu).toggleClass(ClassName$4.SHOW); - $(parent).toggleClass(ClassName$4.SHOW).trigger($.Event(Event$4.SHOWN, relatedTarget)); - }; - - _proto.hide = function hide() { - if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED) || !$(this._menu).hasClass(ClassName$4.SHOW)) { - return; - } - - var relatedTarget = { - relatedTarget: this._element - }; - var hideEvent = $.Event(Event$4.HIDE, relatedTarget); - - var parent = Dropdown._getParentFromElement(this._element); - - $(parent).trigger(hideEvent); - - if (hideEvent.isDefaultPrevented()) { - return; - } - - if (this._popper) { - this._popper.destroy(); - } - - $(this._menu).toggleClass(ClassName$4.SHOW); - $(parent).toggleClass(ClassName$4.SHOW).trigger($.Event(Event$4.HIDDEN, relatedTarget)); - }; - - _proto.dispose = function dispose() { - $.removeData(this._element, DATA_KEY$4); - $(this._element).off(EVENT_KEY$4); - this._element = null; - this._menu = null; - - if (this._popper !== null) { - this._popper.destroy(); - - this._popper = null; - } - }; - - _proto.update = function update() { - this._inNavbar = this._detectNavbar(); - - if (this._popper !== null) { - this._popper.scheduleUpdate(); - } - } // Private - ; - - _proto._addEventListeners = function _addEventListeners() { - var _this = this; - - $(this._element).on(Event$4.CLICK, function (event) { - event.preventDefault(); - event.stopPropagation(); - - _this.toggle(); - }); - }; - - _proto._getConfig = function _getConfig(config) { - config = _objectSpread2({}, this.constructor.Default, {}, $(this._element).data(), {}, config); - Util.typeCheckConfig(NAME$4, config, this.constructor.DefaultType); - return config; - }; - - _proto._getMenuElement = function _getMenuElement() { - if (!this._menu) { - var parent = Dropdown._getParentFromElement(this._element); - - if (parent) { - this._menu = parent.querySelector(Selector$4.MENU); - } - } - - return this._menu; - }; - - _proto._getPlacement = function _getPlacement() { - var $parentDropdown = $(this._element.parentNode); - var placement = AttachmentMap.BOTTOM; // Handle dropup - - if ($parentDropdown.hasClass(ClassName$4.DROPUP)) { - placement = AttachmentMap.TOP; - - if ($(this._menu).hasClass(ClassName$4.MENURIGHT)) { - placement = AttachmentMap.TOPEND; - } - } else if ($parentDropdown.hasClass(ClassName$4.DROPRIGHT)) { - placement = AttachmentMap.RIGHT; - } else if ($parentDropdown.hasClass(ClassName$4.DROPLEFT)) { - placement = AttachmentMap.LEFT; - } else if ($(this._menu).hasClass(ClassName$4.MENURIGHT)) { - placement = AttachmentMap.BOTTOMEND; - } - - return placement; - }; - - _proto._detectNavbar = function _detectNavbar() { - return $(this._element).closest('.navbar').length > 0; - }; - - _proto._getOffset = function _getOffset() { - var _this2 = this; - - var offset = {}; - - if (typeof this._config.offset === 'function') { - offset.fn = function (data) { - data.offsets = _objectSpread2({}, data.offsets, {}, _this2._config.offset(data.offsets, _this2._element) || {}); - return data; - }; - } else { - offset.offset = this._config.offset; - } - - return offset; - }; - - _proto._getPopperConfig = function _getPopperConfig() { - var popperConfig = { - placement: this._getPlacement(), - modifiers: { - offset: this._getOffset(), - flip: { - enabled: this._config.flip - }, - preventOverflow: { - boundariesElement: this._config.boundary - } - } - }; // Disable Popper.js if we have a static display - - if (this._config.display === 'static') { - popperConfig.modifiers.applyStyle = { - enabled: false - }; - } - - return _objectSpread2({}, popperConfig, {}, this._config.popperConfig); - } // Static - ; - - Dropdown._jQueryInterface = function _jQueryInterface(config) { - return this.each(function () { - var data = $(this).data(DATA_KEY$4); - - var _config = typeof config === 'object' ? config : null; - - if (!data) { - data = new Dropdown(this, _config); - $(this).data(DATA_KEY$4, data); - } - - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError("No method named \"" + config + "\""); - } - - data[config](); - } - }); - }; - - Dropdown._clearMenus = function _clearMenus(event) { - if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH || event.type === 'keyup' && event.which !== TAB_KEYCODE)) { - return; - } - - var toggles = [].slice.call(document.querySelectorAll(Selector$4.DATA_TOGGLE)); - - for (var i = 0, len = toggles.length; i < len; i++) { - var parent = Dropdown._getParentFromElement(toggles[i]); - - var context = $(toggles[i]).data(DATA_KEY$4); - var relatedTarget = { - relatedTarget: toggles[i] - }; - - if (event && event.type === 'click') { - relatedTarget.clickEvent = event; - } - - if (!context) { - continue; - } - - var dropdownMenu = context._menu; - - if (!$(parent).hasClass(ClassName$4.SHOW)) { - continue; - } - - if (event && (event.type === 'click' && /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) && $.contains(parent, event.target)) { - continue; - } - - var hideEvent = $.Event(Event$4.HIDE, relatedTarget); - $(parent).trigger(hideEvent); - - if (hideEvent.isDefaultPrevented()) { - continue; - } // If this is a touch-enabled device we remove the extra - // empty mouseover listeners we added for iOS support - - - if ('ontouchstart' in document.documentElement) { - $(document.body).children().off('mouseover', null, $.noop); - } - - toggles[i].setAttribute('aria-expanded', 'false'); - - if (context._popper) { - context._popper.destroy(); - } - - $(dropdownMenu).removeClass(ClassName$4.SHOW); - $(parent).removeClass(ClassName$4.SHOW).trigger($.Event(Event$4.HIDDEN, relatedTarget)); - } - }; - - Dropdown._getParentFromElement = function _getParentFromElement(element) { - var parent; - var selector = Util.getSelectorFromElement(element); - - if (selector) { - parent = document.querySelector(selector); - } - - return parent || element.parentNode; - } // eslint-disable-next-line complexity - ; - - Dropdown._dataApiKeydownHandler = function _dataApiKeydownHandler(event) { - // If not input/textarea: - // - And not a key in REGEXP_KEYDOWN => not a dropdown command - // If input/textarea: - // - If space key => not a dropdown command - // - If key is other than escape - // - If key is not up or down => not a dropdown command - // - If trigger inside the menu => not a dropdown command - if (/input|textarea/i.test(event.target.tagName) ? event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE && (event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE || $(event.target).closest(Selector$4.MENU).length) : !REGEXP_KEYDOWN.test(event.which)) { - return; - } - - event.preventDefault(); - event.stopPropagation(); - - if (this.disabled || $(this).hasClass(ClassName$4.DISABLED)) { - return; - } - - var parent = Dropdown._getParentFromElement(this); - - var isActive = $(parent).hasClass(ClassName$4.SHOW); - - if (!isActive && event.which === ESCAPE_KEYCODE) { - return; - } - - if (!isActive || isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) { - if (event.which === ESCAPE_KEYCODE) { - var toggle = parent.querySelector(Selector$4.DATA_TOGGLE); - $(toggle).trigger('focus'); - } - - $(this).trigger('click'); - return; - } - - var items = [].slice.call(parent.querySelectorAll(Selector$4.VISIBLE_ITEMS)).filter(function (item) { - return $(item).is(':visible'); - }); - - if (items.length === 0) { - return; - } - - var index = items.indexOf(event.target); - - if (event.which === ARROW_UP_KEYCODE && index > 0) { - // Up - index--; - } - - if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { - // Down - index++; - } - - if (index < 0) { - index = 0; - } - - items[index].focus(); - }; - - _createClass(Dropdown, null, [{ - key: "VERSION", - get: function get() { - return VERSION$4; - } - }, { - key: "Default", - get: function get() { - return Default$2; - } - }, { - key: "DefaultType", - get: function get() { - return DefaultType$2; - } - }]); - - return Dropdown; - }(); - /** - * ------------------------------------------------------------------------ - * Data Api implementation - * ------------------------------------------------------------------------ - */ - - - $(document).on(Event$4.KEYDOWN_DATA_API, Selector$4.DATA_TOGGLE, Dropdown._dataApiKeydownHandler).on(Event$4.KEYDOWN_DATA_API, Selector$4.MENU, Dropdown._dataApiKeydownHandler).on(Event$4.CLICK_DATA_API + " " + Event$4.KEYUP_DATA_API, Dropdown._clearMenus).on(Event$4.CLICK_DATA_API, Selector$4.DATA_TOGGLE, function (event) { - event.preventDefault(); - event.stopPropagation(); - - Dropdown._jQueryInterface.call($(this), 'toggle'); - }).on(Event$4.CLICK_DATA_API, Selector$4.FORM_CHILD, function (e) { - e.stopPropagation(); - }); - /** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - */ - - $.fn[NAME$4] = Dropdown._jQueryInterface; - $.fn[NAME$4].Constructor = Dropdown; - - $.fn[NAME$4].noConflict = function () { - $.fn[NAME$4] = JQUERY_NO_CONFLICT$4; - return Dropdown._jQueryInterface; - }; - - /** - * ------------------------------------------------------------------------ - * Constants - * ------------------------------------------------------------------------ - */ - - var NAME$5 = 'modal'; - var VERSION$5 = '4.4.1'; - var DATA_KEY$5 = 'bs.modal'; - var EVENT_KEY$5 = "." + DATA_KEY$5; - var DATA_API_KEY$5 = '.data-api'; - var JQUERY_NO_CONFLICT$5 = $.fn[NAME$5]; - var ESCAPE_KEYCODE$1 = 27; // KeyboardEvent.which value for Escape (Esc) key - - var Default$3 = { - backdrop: true, - keyboard: true, - focus: true, - show: true - }; - var DefaultType$3 = { - backdrop: '(boolean|string)', - keyboard: 'boolean', - focus: 'boolean', - show: 'boolean' - }; - var Event$5 = { - HIDE: "hide" + EVENT_KEY$5, - HIDE_PREVENTED: "hidePrevented" + EVENT_KEY$5, - HIDDEN: "hidden" + EVENT_KEY$5, - SHOW: "show" + EVENT_KEY$5, - SHOWN: "shown" + EVENT_KEY$5, - FOCUSIN: "focusin" + EVENT_KEY$5, - RESIZE: "resize" + EVENT_KEY$5, - CLICK_DISMISS: "click.dismiss" + EVENT_KEY$5, - KEYDOWN_DISMISS: "keydown.dismiss" + EVENT_KEY$5, - MOUSEUP_DISMISS: "mouseup.dismiss" + EVENT_KEY$5, - MOUSEDOWN_DISMISS: "mousedown.dismiss" + EVENT_KEY$5, - CLICK_DATA_API: "click" + EVENT_KEY$5 + DATA_API_KEY$5 - }; - var ClassName$5 = { - SCROLLABLE: 'modal-dialog-scrollable', - SCROLLBAR_MEASURER: 'modal-scrollbar-measure', - BACKDROP: 'modal-backdrop', - OPEN: 'modal-open', - FADE: 'fade', - SHOW: 'show', - STATIC: 'modal-static' - }; - var Selector$5 = { - DIALOG: '.modal-dialog', - MODAL_BODY: '.modal-body', - DATA_TOGGLE: '[data-toggle="modal"]', - DATA_DISMISS: '[data-dismiss="modal"]', - FIXED_CONTENT: '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top', - STICKY_CONTENT: '.sticky-top' - }; - /** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - - var Modal = - /*#__PURE__*/ - function () { - function Modal(element, config) { - this._config = this._getConfig(config); - this._element = element; - this._dialog = element.querySelector(Selector$5.DIALOG); - this._backdrop = null; - this._isShown = false; - this._isBodyOverflowing = false; - this._ignoreBackdropClick = false; - this._isTransitioning = false; - this._scrollbarWidth = 0; - } // Getters - - - var _proto = Modal.prototype; - - // Public - _proto.toggle = function toggle(relatedTarget) { - return this._isShown ? this.hide() : this.show(relatedTarget); - }; - - _proto.show = function show(relatedTarget) { - var _this = this; - - if (this._isShown || this._isTransitioning) { - return; - } - - if ($(this._element).hasClass(ClassName$5.FADE)) { - this._isTransitioning = true; - } - - var showEvent = $.Event(Event$5.SHOW, { - relatedTarget: relatedTarget - }); - $(this._element).trigger(showEvent); - - if (this._isShown || showEvent.isDefaultPrevented()) { - return; - } - - this._isShown = true; - - this._checkScrollbar(); - - this._setScrollbar(); - - this._adjustDialog(); - - this._setEscapeEvent(); - - this._setResizeEvent(); - - $(this._element).on(Event$5.CLICK_DISMISS, Selector$5.DATA_DISMISS, function (event) { - return _this.hide(event); - }); - $(this._dialog).on(Event$5.MOUSEDOWN_DISMISS, function () { - $(_this._element).one(Event$5.MOUSEUP_DISMISS, function (event) { - if ($(event.target).is(_this._element)) { - _this._ignoreBackdropClick = true; - } - }); - }); - - this._showBackdrop(function () { - return _this._showElement(relatedTarget); - }); - }; - - _proto.hide = function hide(event) { - var _this2 = this; - - if (event) { - event.preventDefault(); - } - - if (!this._isShown || this._isTransitioning) { - return; - } - - var hideEvent = $.Event(Event$5.HIDE); - $(this._element).trigger(hideEvent); - - if (!this._isShown || hideEvent.isDefaultPrevented()) { - return; - } - - this._isShown = false; - var transition = $(this._element).hasClass(ClassName$5.FADE); - - if (transition) { - this._isTransitioning = true; - } - - this._setEscapeEvent(); - - this._setResizeEvent(); - - $(document).off(Event$5.FOCUSIN); - $(this._element).removeClass(ClassName$5.SHOW); - $(this._element).off(Event$5.CLICK_DISMISS); - $(this._dialog).off(Event$5.MOUSEDOWN_DISMISS); - - if (transition) { - var transitionDuration = Util.getTransitionDurationFromElement(this._element); - $(this._element).one(Util.TRANSITION_END, function (event) { - return _this2._hideModal(event); - }).emulateTransitionEnd(transitionDuration); - } else { - this._hideModal(); - } - }; - - _proto.dispose = function dispose() { - [window, this._element, this._dialog].forEach(function (htmlElement) { - return $(htmlElement).off(EVENT_KEY$5); - }); - /** - * `document` has 2 events `Event.FOCUSIN` and `Event.CLICK_DATA_API` - * Do not move `document` in `htmlElements` array - * It will remove `Event.CLICK_DATA_API` event that should remain - */ - - $(document).off(Event$5.FOCUSIN); - $.removeData(this._element, DATA_KEY$5); - this._config = null; - this._element = null; - this._dialog = null; - this._backdrop = null; - this._isShown = null; - this._isBodyOverflowing = null; - this._ignoreBackdropClick = null; - this._isTransitioning = null; - this._scrollbarWidth = null; - }; - - _proto.handleUpdate = function handleUpdate() { - this._adjustDialog(); - } // Private - ; - - _proto._getConfig = function _getConfig(config) { - config = _objectSpread2({}, Default$3, {}, config); - Util.typeCheckConfig(NAME$5, config, DefaultType$3); - return config; - }; - - _proto._triggerBackdropTransition = function _triggerBackdropTransition() { - var _this3 = this; - - if (this._config.backdrop === 'static') { - var hideEventPrevented = $.Event(Event$5.HIDE_PREVENTED); - $(this._element).trigger(hideEventPrevented); - - if (hideEventPrevented.defaultPrevented) { - return; - } - - this._element.classList.add(ClassName$5.STATIC); - - var modalTransitionDuration = Util.getTransitionDurationFromElement(this._element); - $(this._element).one(Util.TRANSITION_END, function () { - _this3._element.classList.remove(ClassName$5.STATIC); - }).emulateTransitionEnd(modalTransitionDuration); - - this._element.focus(); - } else { - this.hide(); - } - }; - - _proto._showElement = function _showElement(relatedTarget) { - var _this4 = this; - - var transition = $(this._element).hasClass(ClassName$5.FADE); - var modalBody = this._dialog ? this._dialog.querySelector(Selector$5.MODAL_BODY) : null; - - if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) { - // Don't move modal's DOM position - document.body.appendChild(this._element); - } - - this._element.style.display = 'block'; - - this._element.removeAttribute('aria-hidden'); - - this._element.setAttribute('aria-modal', true); - - if ($(this._dialog).hasClass(ClassName$5.SCROLLABLE) && modalBody) { - modalBody.scrollTop = 0; - } else { - this._element.scrollTop = 0; - } - - if (transition) { - Util.reflow(this._element); - } - - $(this._element).addClass(ClassName$5.SHOW); - - if (this._config.focus) { - this._enforceFocus(); - } - - var shownEvent = $.Event(Event$5.SHOWN, { - relatedTarget: relatedTarget - }); - - var transitionComplete = function transitionComplete() { - if (_this4._config.focus) { - _this4._element.focus(); - } - - _this4._isTransitioning = false; - $(_this4._element).trigger(shownEvent); - }; - - if (transition) { - var transitionDuration = Util.getTransitionDurationFromElement(this._dialog); - $(this._dialog).one(Util.TRANSITION_END, transitionComplete).emulateTransitionEnd(transitionDuration); - } else { - transitionComplete(); - } - }; - - _proto._enforceFocus = function _enforceFocus() { - var _this5 = this; - - $(document).off(Event$5.FOCUSIN) // Guard against infinite focus loop - .on(Event$5.FOCUSIN, function (event) { - if (document !== event.target && _this5._element !== event.target && $(_this5._element).has(event.target).length === 0) { - _this5._element.focus(); - } - }); - }; - - _proto._setEscapeEvent = function _setEscapeEvent() { - var _this6 = this; - - if (this._isShown && this._config.keyboard) { - $(this._element).on(Event$5.KEYDOWN_DISMISS, function (event) { - if (event.which === ESCAPE_KEYCODE$1) { - _this6._triggerBackdropTransition(); - } - }); - } else if (!this._isShown) { - $(this._element).off(Event$5.KEYDOWN_DISMISS); - } - }; - - _proto._setResizeEvent = function _setResizeEvent() { - var _this7 = this; - - if (this._isShown) { - $(window).on(Event$5.RESIZE, function (event) { - return _this7.handleUpdate(event); - }); - } else { - $(window).off(Event$5.RESIZE); - } - }; - - _proto._hideModal = function _hideModal() { - var _this8 = this; - - this._element.style.display = 'none'; - - this._element.setAttribute('aria-hidden', true); - - this._element.removeAttribute('aria-modal'); - - this._isTransitioning = false; - - this._showBackdrop(function () { - $(document.body).removeClass(ClassName$5.OPEN); - - _this8._resetAdjustments(); - - _this8._resetScrollbar(); - - $(_this8._element).trigger(Event$5.HIDDEN); - }); - }; - - _proto._removeBackdrop = function _removeBackdrop() { - if (this._backdrop) { - $(this._backdrop).remove(); - this._backdrop = null; - } - }; - - _proto._showBackdrop = function _showBackdrop(callback) { - var _this9 = this; - - var animate = $(this._element).hasClass(ClassName$5.FADE) ? ClassName$5.FADE : ''; - - if (this._isShown && this._config.backdrop) { - this._backdrop = document.createElement('div'); - this._backdrop.className = ClassName$5.BACKDROP; - - if (animate) { - this._backdrop.classList.add(animate); - } - - $(this._backdrop).appendTo(document.body); - $(this._element).on(Event$5.CLICK_DISMISS, function (event) { - if (_this9._ignoreBackdropClick) { - _this9._ignoreBackdropClick = false; - return; - } - - if (event.target !== event.currentTarget) { - return; - } - - _this9._triggerBackdropTransition(); - }); - - if (animate) { - Util.reflow(this._backdrop); - } - - $(this._backdrop).addClass(ClassName$5.SHOW); - - if (!callback) { - return; - } - - if (!animate) { - callback(); - return; - } - - var backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop); - $(this._backdrop).one(Util.TRANSITION_END, callback).emulateTransitionEnd(backdropTransitionDuration); - } else if (!this._isShown && this._backdrop) { - $(this._backdrop).removeClass(ClassName$5.SHOW); - - var callbackRemove = function callbackRemove() { - _this9._removeBackdrop(); - - if (callback) { - callback(); - } - }; - - if ($(this._element).hasClass(ClassName$5.FADE)) { - var _backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop); - - $(this._backdrop).one(Util.TRANSITION_END, callbackRemove).emulateTransitionEnd(_backdropTransitionDuration); - } else { - callbackRemove(); - } - } else if (callback) { - callback(); - } - } // ---------------------------------------------------------------------- - // the following methods are used to handle overflowing modals - // todo (fat): these should probably be refactored out of modal.js - // ---------------------------------------------------------------------- - ; - - _proto._adjustDialog = function _adjustDialog() { - var isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; - - if (!this._isBodyOverflowing && isModalOverflowing) { - this._element.style.paddingLeft = this._scrollbarWidth + "px"; - } - - if (this._isBodyOverflowing && !isModalOverflowing) { - this._element.style.paddingRight = this._scrollbarWidth + "px"; - } - }; - - _proto._resetAdjustments = function _resetAdjustments() { - this._element.style.paddingLeft = ''; - this._element.style.paddingRight = ''; - }; - - _proto._checkScrollbar = function _checkScrollbar() { - var rect = document.body.getBoundingClientRect(); - this._isBodyOverflowing = rect.left + rect.right < window.innerWidth; - this._scrollbarWidth = this._getScrollbarWidth(); - }; - - _proto._setScrollbar = function _setScrollbar() { - var _this10 = this; - - if (this._isBodyOverflowing) { - // Note: DOMNode.style.paddingRight returns the actual value or '' if not set - // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set - var fixedContent = [].slice.call(document.querySelectorAll(Selector$5.FIXED_CONTENT)); - var stickyContent = [].slice.call(document.querySelectorAll(Selector$5.STICKY_CONTENT)); // Adjust fixed content padding - - $(fixedContent).each(function (index, element) { - var actualPadding = element.style.paddingRight; - var calculatedPadding = $(element).css('padding-right'); - $(element).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + _this10._scrollbarWidth + "px"); - }); // Adjust sticky content margin - - $(stickyContent).each(function (index, element) { - var actualMargin = element.style.marginRight; - var calculatedMargin = $(element).css('margin-right'); - $(element).data('margin-right', actualMargin).css('margin-right', parseFloat(calculatedMargin) - _this10._scrollbarWidth + "px"); - }); // Adjust body padding - - var actualPadding = document.body.style.paddingRight; - var calculatedPadding = $(document.body).css('padding-right'); - $(document.body).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + this._scrollbarWidth + "px"); - } - - $(document.body).addClass(ClassName$5.OPEN); - }; - - _proto._resetScrollbar = function _resetScrollbar() { - // Restore fixed content padding - var fixedContent = [].slice.call(document.querySelectorAll(Selector$5.FIXED_CONTENT)); - $(fixedContent).each(function (index, element) { - var padding = $(element).data('padding-right'); - $(element).removeData('padding-right'); - element.style.paddingRight = padding ? padding : ''; - }); // Restore sticky content - - var elements = [].slice.call(document.querySelectorAll("" + Selector$5.STICKY_CONTENT)); - $(elements).each(function (index, element) { - var margin = $(element).data('margin-right'); - - if (typeof margin !== 'undefined') { - $(element).css('margin-right', margin).removeData('margin-right'); - } - }); // Restore body padding - - var padding = $(document.body).data('padding-right'); - $(document.body).removeData('padding-right'); - document.body.style.paddingRight = padding ? padding : ''; - }; - - _proto._getScrollbarWidth = function _getScrollbarWidth() { - // thx d.walsh - var scrollDiv = document.createElement('div'); - scrollDiv.className = ClassName$5.SCROLLBAR_MEASURER; - document.body.appendChild(scrollDiv); - var scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth; - document.body.removeChild(scrollDiv); - return scrollbarWidth; - } // Static - ; - - Modal._jQueryInterface = function _jQueryInterface(config, relatedTarget) { - return this.each(function () { - var data = $(this).data(DATA_KEY$5); - - var _config = _objectSpread2({}, Default$3, {}, $(this).data(), {}, typeof config === 'object' && config ? config : {}); - - if (!data) { - data = new Modal(this, _config); - $(this).data(DATA_KEY$5, data); - } - - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError("No method named \"" + config + "\""); - } - - data[config](relatedTarget); - } else if (_config.show) { - data.show(relatedTarget); - } - }); - }; - - _createClass(Modal, null, [{ - key: "VERSION", - get: function get() { - return VERSION$5; - } - }, { - key: "Default", - get: function get() { - return Default$3; - } - }]); - - return Modal; - }(); - /** - * ------------------------------------------------------------------------ - * Data Api implementation - * ------------------------------------------------------------------------ - */ - - - $(document).on(Event$5.CLICK_DATA_API, Selector$5.DATA_TOGGLE, function (event) { - var _this11 = this; - - var target; - var selector = Util.getSelectorFromElement(this); - - if (selector) { - target = document.querySelector(selector); - } - - var config = $(target).data(DATA_KEY$5) ? 'toggle' : _objectSpread2({}, $(target).data(), {}, $(this).data()); - - if (this.tagName === 'A' || this.tagName === 'AREA') { - event.preventDefault(); - } - - var $target = $(target).one(Event$5.SHOW, function (showEvent) { - if (showEvent.isDefaultPrevented()) { - // Only register focus restorer if modal will actually get shown - return; - } - - $target.one(Event$5.HIDDEN, function () { - if ($(_this11).is(':visible')) { - _this11.focus(); - } - }); - }); - - Modal._jQueryInterface.call($(target), config, this); - }); - /** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - */ - - $.fn[NAME$5] = Modal._jQueryInterface; - $.fn[NAME$5].Constructor = Modal; - - $.fn[NAME$5].noConflict = function () { - $.fn[NAME$5] = JQUERY_NO_CONFLICT$5; - return Modal._jQueryInterface; - }; - - /** - * -------------------------------------------------------------------------- - * Bootstrap (v4.4.1): tools/sanitizer.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * -------------------------------------------------------------------------- - */ - var uriAttrs = ['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']; - var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; - var DefaultWhitelist = { - // Global attributes allowed on any supplied element below. - '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], - a: ['target', 'href', 'title', 'rel'], - area: [], - b: [], - br: [], - col: [], - code: [], - div: [], - em: [], - hr: [], - h1: [], - h2: [], - h3: [], - h4: [], - h5: [], - h6: [], - i: [], - img: ['src', 'alt', 'title', 'width', 'height'], - li: [], - ol: [], - p: [], - pre: [], - s: [], - small: [], - span: [], - sub: [], - sup: [], - strong: [], - u: [], - ul: [] - }; - /** - * A pattern that recognizes a commonly useful subset of URLs that are safe. - * - * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts - */ - - var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi; - /** - * A pattern that matches safe data URLs. Only matches image, video and audio types. - * - * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts - */ - - var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i; - - function allowedAttribute(attr, allowedAttributeList) { - var attrName = attr.nodeName.toLowerCase(); - - if (allowedAttributeList.indexOf(attrName) !== -1) { - if (uriAttrs.indexOf(attrName) !== -1) { - return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN)); - } - - return true; - } - - var regExp = allowedAttributeList.filter(function (attrRegex) { - return attrRegex instanceof RegExp; - }); // Check if a regular expression validates the attribute. - - for (var i = 0, l = regExp.length; i < l; i++) { - if (attrName.match(regExp[i])) { - return true; - } - } - - return false; - } - - function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) { - if (unsafeHtml.length === 0) { - return unsafeHtml; - } - - if (sanitizeFn && typeof sanitizeFn === 'function') { - return sanitizeFn(unsafeHtml); - } - - var domParser = new window.DOMParser(); - var createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); - var whitelistKeys = Object.keys(whiteList); - var elements = [].slice.call(createdDocument.body.querySelectorAll('*')); - - var _loop = function _loop(i, len) { - var el = elements[i]; - var elName = el.nodeName.toLowerCase(); - - if (whitelistKeys.indexOf(el.nodeName.toLowerCase()) === -1) { - el.parentNode.removeChild(el); - return "continue"; - } - - var attributeList = [].slice.call(el.attributes); - var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []); - attributeList.forEach(function (attr) { - if (!allowedAttribute(attr, whitelistedAttributes)) { - el.removeAttribute(attr.nodeName); - } - }); - }; - - for (var i = 0, len = elements.length; i < len; i++) { - var _ret = _loop(i); - - if (_ret === "continue") continue; - } - - return createdDocument.body.innerHTML; - } - - /** - * ------------------------------------------------------------------------ - * Constants - * ------------------------------------------------------------------------ - */ - - var NAME$6 = 'tooltip'; - var VERSION$6 = '4.4.1'; - var DATA_KEY$6 = 'bs.tooltip'; - var EVENT_KEY$6 = "." + DATA_KEY$6; - var JQUERY_NO_CONFLICT$6 = $.fn[NAME$6]; - var CLASS_PREFIX = 'bs-tooltip'; - var BSCLS_PREFIX_REGEX = new RegExp("(^|\\s)" + CLASS_PREFIX + "\\S+", 'g'); - var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']; - var DefaultType$4 = { - animation: 'boolean', - template: 'string', - title: '(string|element|function)', - trigger: 'string', - delay: '(number|object)', - html: 'boolean', - selector: '(string|boolean)', - placement: '(string|function)', - offset: '(number|string|function)', - container: '(string|element|boolean)', - fallbackPlacement: '(string|array)', - boundary: '(string|element)', - sanitize: 'boolean', - sanitizeFn: '(null|function)', - whiteList: 'object', - popperConfig: '(null|object)' - }; - var AttachmentMap$1 = { - AUTO: 'auto', - TOP: 'top', - RIGHT: 'right', - BOTTOM: 'bottom', - LEFT: 'left' - }; - var Default$4 = { - animation: true, - template: '', - trigger: 'hover focus', - title: '', - delay: 0, - html: false, - selector: false, - placement: 'top', - offset: 0, - container: false, - fallbackPlacement: 'flip', - boundary: 'scrollParent', - sanitize: true, - sanitizeFn: null, - whiteList: DefaultWhitelist, - popperConfig: null - }; - var HoverState = { - SHOW: 'show', - OUT: 'out' - }; - var Event$6 = { - HIDE: "hide" + EVENT_KEY$6, - HIDDEN: "hidden" + EVENT_KEY$6, - SHOW: "show" + EVENT_KEY$6, - SHOWN: "shown" + EVENT_KEY$6, - INSERTED: "inserted" + EVENT_KEY$6, - CLICK: "click" + EVENT_KEY$6, - FOCUSIN: "focusin" + EVENT_KEY$6, - FOCUSOUT: "focusout" + EVENT_KEY$6, - MOUSEENTER: "mouseenter" + EVENT_KEY$6, - MOUSELEAVE: "mouseleave" + EVENT_KEY$6 - }; - var ClassName$6 = { - FADE: 'fade', - SHOW: 'show' - }; - var Selector$6 = { - TOOLTIP: '.tooltip', - TOOLTIP_INNER: '.tooltip-inner', - ARROW: '.arrow' - }; - var Trigger = { - HOVER: 'hover', - FOCUS: 'focus', - CLICK: 'click', - MANUAL: 'manual' - }; - /** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - - var Tooltip = - /*#__PURE__*/ - function () { - function Tooltip(element, config) { - if (typeof Popper === 'undefined') { - throw new TypeError('Bootstrap\'s tooltips require Popper.js (https://popper.js.org/)'); - } // private - - - this._isEnabled = true; - this._timeout = 0; - this._hoverState = ''; - this._activeTrigger = {}; - this._popper = null; // Protected - - this.element = element; - this.config = this._getConfig(config); - this.tip = null; - - this._setListeners(); - } // Getters - - - var _proto = Tooltip.prototype; - - // Public - _proto.enable = function enable() { - this._isEnabled = true; - }; - - _proto.disable = function disable() { - this._isEnabled = false; - }; - - _proto.toggleEnabled = function toggleEnabled() { - this._isEnabled = !this._isEnabled; - }; - - _proto.toggle = function toggle(event) { - if (!this._isEnabled) { - return; - } - - if (event) { - var dataKey = this.constructor.DATA_KEY; - var context = $(event.currentTarget).data(dataKey); - - if (!context) { - context = new this.constructor(event.currentTarget, this._getDelegateConfig()); - $(event.currentTarget).data(dataKey, context); - } - - context._activeTrigger.click = !context._activeTrigger.click; - - if (context._isWithActiveTrigger()) { - context._enter(null, context); - } else { - context._leave(null, context); - } - } else { - if ($(this.getTipElement()).hasClass(ClassName$6.SHOW)) { - this._leave(null, this); - - return; - } - - this._enter(null, this); - } - }; - - _proto.dispose = function dispose() { - clearTimeout(this._timeout); - $.removeData(this.element, this.constructor.DATA_KEY); - $(this.element).off(this.constructor.EVENT_KEY); - $(this.element).closest('.modal').off('hide.bs.modal', this._hideModalHandler); - - if (this.tip) { - $(this.tip).remove(); - } - - this._isEnabled = null; - this._timeout = null; - this._hoverState = null; - this._activeTrigger = null; - - if (this._popper) { - this._popper.destroy(); - } - - this._popper = null; - this.element = null; - this.config = null; - this.tip = null; - }; - - _proto.show = function show() { - var _this = this; - - if ($(this.element).css('display') === 'none') { - throw new Error('Please use show on visible elements'); - } - - var showEvent = $.Event(this.constructor.Event.SHOW); - - if (this.isWithContent() && this._isEnabled) { - $(this.element).trigger(showEvent); - var shadowRoot = Util.findShadowRoot(this.element); - var isInTheDom = $.contains(shadowRoot !== null ? shadowRoot : this.element.ownerDocument.documentElement, this.element); - - if (showEvent.isDefaultPrevented() || !isInTheDom) { - return; - } - - var tip = this.getTipElement(); - var tipId = Util.getUID(this.constructor.NAME); - tip.setAttribute('id', tipId); - this.element.setAttribute('aria-describedby', tipId); - this.setContent(); - - if (this.config.animation) { - $(tip).addClass(ClassName$6.FADE); - } - - var placement = typeof this.config.placement === 'function' ? this.config.placement.call(this, tip, this.element) : this.config.placement; - - var attachment = this._getAttachment(placement); - - this.addAttachmentClass(attachment); - - var container = this._getContainer(); - - $(tip).data(this.constructor.DATA_KEY, this); - - if (!$.contains(this.element.ownerDocument.documentElement, this.tip)) { - $(tip).appendTo(container); - } - - $(this.element).trigger(this.constructor.Event.INSERTED); - this._popper = new Popper(this.element, tip, this._getPopperConfig(attachment)); - $(tip).addClass(ClassName$6.SHOW); // If this is a touch-enabled device we add extra - // empty mouseover listeners to the body's immediate children; - // only needed because of broken event delegation on iOS - // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - - if ('ontouchstart' in document.documentElement) { - $(document.body).children().on('mouseover', null, $.noop); - } - - var complete = function complete() { - if (_this.config.animation) { - _this._fixTransition(); - } - - var prevHoverState = _this._hoverState; - _this._hoverState = null; - $(_this.element).trigger(_this.constructor.Event.SHOWN); - - if (prevHoverState === HoverState.OUT) { - _this._leave(null, _this); - } - }; - - if ($(this.tip).hasClass(ClassName$6.FADE)) { - var transitionDuration = Util.getTransitionDurationFromElement(this.tip); - $(this.tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); - } else { - complete(); - } - } - }; - - _proto.hide = function hide(callback) { - var _this2 = this; - - var tip = this.getTipElement(); - var hideEvent = $.Event(this.constructor.Event.HIDE); - - var complete = function complete() { - if (_this2._hoverState !== HoverState.SHOW && tip.parentNode) { - tip.parentNode.removeChild(tip); - } - - _this2._cleanTipClass(); - - _this2.element.removeAttribute('aria-describedby'); - - $(_this2.element).trigger(_this2.constructor.Event.HIDDEN); - - if (_this2._popper !== null) { - _this2._popper.destroy(); - } - - if (callback) { - callback(); - } - }; - - $(this.element).trigger(hideEvent); - - if (hideEvent.isDefaultPrevented()) { - return; - } - - $(tip).removeClass(ClassName$6.SHOW); // If this is a touch-enabled device we remove the extra - // empty mouseover listeners we added for iOS support - - if ('ontouchstart' in document.documentElement) { - $(document.body).children().off('mouseover', null, $.noop); - } - - this._activeTrigger[Trigger.CLICK] = false; - this._activeTrigger[Trigger.FOCUS] = false; - this._activeTrigger[Trigger.HOVER] = false; - - if ($(this.tip).hasClass(ClassName$6.FADE)) { - var transitionDuration = Util.getTransitionDurationFromElement(tip); - $(tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); - } else { - complete(); - } - - this._hoverState = ''; - }; - - _proto.update = function update() { - if (this._popper !== null) { - this._popper.scheduleUpdate(); - } - } // Protected - ; - - _proto.isWithContent = function isWithContent() { - return Boolean(this.getTitle()); - }; - - _proto.addAttachmentClass = function addAttachmentClass(attachment) { - $(this.getTipElement()).addClass(CLASS_PREFIX + "-" + attachment); - }; - - _proto.getTipElement = function getTipElement() { - this.tip = this.tip || $(this.config.template)[0]; - return this.tip; - }; - - _proto.setContent = function setContent() { - var tip = this.getTipElement(); - this.setElementContent($(tip.querySelectorAll(Selector$6.TOOLTIP_INNER)), this.getTitle()); - $(tip).removeClass(ClassName$6.FADE + " " + ClassName$6.SHOW); - }; - - _proto.setElementContent = function setElementContent($element, content) { - if (typeof content === 'object' && (content.nodeType || content.jquery)) { - // Content is a DOM node or a jQuery - if (this.config.html) { - if (!$(content).parent().is($element)) { - $element.empty().append(content); - } - } else { - $element.text($(content).text()); - } - - return; - } - - if (this.config.html) { - if (this.config.sanitize) { - content = sanitizeHtml(content, this.config.whiteList, this.config.sanitizeFn); - } - - $element.html(content); - } else { - $element.text(content); - } - }; - - _proto.getTitle = function getTitle() { - var title = this.element.getAttribute('data-original-title'); - - if (!title) { - title = typeof this.config.title === 'function' ? this.config.title.call(this.element) : this.config.title; - } - - return title; - } // Private - ; - - _proto._getPopperConfig = function _getPopperConfig(attachment) { - var _this3 = this; - - var defaultBsConfig = { - placement: attachment, - modifiers: { - offset: this._getOffset(), - flip: { - behavior: this.config.fallbackPlacement - }, - arrow: { - element: Selector$6.ARROW - }, - preventOverflow: { - boundariesElement: this.config.boundary - } - }, - onCreate: function onCreate(data) { - if (data.originalPlacement !== data.placement) { - _this3._handlePopperPlacementChange(data); - } - }, - onUpdate: function onUpdate(data) { - return _this3._handlePopperPlacementChange(data); - } - }; - return _objectSpread2({}, defaultBsConfig, {}, this.config.popperConfig); - }; - - _proto._getOffset = function _getOffset() { - var _this4 = this; - - var offset = {}; - - if (typeof this.config.offset === 'function') { - offset.fn = function (data) { - data.offsets = _objectSpread2({}, data.offsets, {}, _this4.config.offset(data.offsets, _this4.element) || {}); - return data; - }; - } else { - offset.offset = this.config.offset; - } - - return offset; - }; - - _proto._getContainer = function _getContainer() { - if (this.config.container === false) { - return document.body; - } - - if (Util.isElement(this.config.container)) { - return $(this.config.container); - } - - return $(document).find(this.config.container); - }; - - _proto._getAttachment = function _getAttachment(placement) { - return AttachmentMap$1[placement.toUpperCase()]; - }; - - _proto._setListeners = function _setListeners() { - var _this5 = this; - - var triggers = this.config.trigger.split(' '); - triggers.forEach(function (trigger) { - if (trigger === 'click') { - $(_this5.element).on(_this5.constructor.Event.CLICK, _this5.config.selector, function (event) { - return _this5.toggle(event); - }); - } else if (trigger !== Trigger.MANUAL) { - var eventIn = trigger === Trigger.HOVER ? _this5.constructor.Event.MOUSEENTER : _this5.constructor.Event.FOCUSIN; - var eventOut = trigger === Trigger.HOVER ? _this5.constructor.Event.MOUSELEAVE : _this5.constructor.Event.FOCUSOUT; - $(_this5.element).on(eventIn, _this5.config.selector, function (event) { - return _this5._enter(event); - }).on(eventOut, _this5.config.selector, function (event) { - return _this5._leave(event); - }); - } - }); - - this._hideModalHandler = function () { - if (_this5.element) { - _this5.hide(); - } - }; - - $(this.element).closest('.modal').on('hide.bs.modal', this._hideModalHandler); - - if (this.config.selector) { - this.config = _objectSpread2({}, this.config, { - trigger: 'manual', - selector: '' - }); - } else { - this._fixTitle(); - } - }; - - _proto._fixTitle = function _fixTitle() { - var titleType = typeof this.element.getAttribute('data-original-title'); - - if (this.element.getAttribute('title') || titleType !== 'string') { - this.element.setAttribute('data-original-title', this.element.getAttribute('title') || ''); - this.element.setAttribute('title', ''); - } - }; - - _proto._enter = function _enter(event, context) { - var dataKey = this.constructor.DATA_KEY; - context = context || $(event.currentTarget).data(dataKey); - - if (!context) { - context = new this.constructor(event.currentTarget, this._getDelegateConfig()); - $(event.currentTarget).data(dataKey, context); - } - - if (event) { - context._activeTrigger[event.type === 'focusin' ? Trigger.FOCUS : Trigger.HOVER] = true; - } - - if ($(context.getTipElement()).hasClass(ClassName$6.SHOW) || context._hoverState === HoverState.SHOW) { - context._hoverState = HoverState.SHOW; - return; - } - - clearTimeout(context._timeout); - context._hoverState = HoverState.SHOW; - - if (!context.config.delay || !context.config.delay.show) { - context.show(); - return; - } - - context._timeout = setTimeout(function () { - if (context._hoverState === HoverState.SHOW) { - context.show(); - } - }, context.config.delay.show); - }; - - _proto._leave = function _leave(event, context) { - var dataKey = this.constructor.DATA_KEY; - context = context || $(event.currentTarget).data(dataKey); - - if (!context) { - context = new this.constructor(event.currentTarget, this._getDelegateConfig()); - $(event.currentTarget).data(dataKey, context); - } - - if (event) { - context._activeTrigger[event.type === 'focusout' ? Trigger.FOCUS : Trigger.HOVER] = false; - } - - if (context._isWithActiveTrigger()) { - return; - } - - clearTimeout(context._timeout); - context._hoverState = HoverState.OUT; - - if (!context.config.delay || !context.config.delay.hide) { - context.hide(); - return; - } - - context._timeout = setTimeout(function () { - if (context._hoverState === HoverState.OUT) { - context.hide(); - } - }, context.config.delay.hide); - }; - - _proto._isWithActiveTrigger = function _isWithActiveTrigger() { - for (var trigger in this._activeTrigger) { - if (this._activeTrigger[trigger]) { - return true; - } - } - - return false; - }; - - _proto._getConfig = function _getConfig(config) { - var dataAttributes = $(this.element).data(); - Object.keys(dataAttributes).forEach(function (dataAttr) { - if (DISALLOWED_ATTRIBUTES.indexOf(dataAttr) !== -1) { - delete dataAttributes[dataAttr]; - } - }); - config = _objectSpread2({}, this.constructor.Default, {}, dataAttributes, {}, typeof config === 'object' && config ? config : {}); - - if (typeof config.delay === 'number') { - config.delay = { - show: config.delay, - hide: config.delay - }; - } - - if (typeof config.title === 'number') { - config.title = config.title.toString(); - } - - if (typeof config.content === 'number') { - config.content = config.content.toString(); - } - - Util.typeCheckConfig(NAME$6, config, this.constructor.DefaultType); - - if (config.sanitize) { - config.template = sanitizeHtml(config.template, config.whiteList, config.sanitizeFn); - } - - return config; - }; - - _proto._getDelegateConfig = function _getDelegateConfig() { - var config = {}; - - if (this.config) { - for (var key in this.config) { - if (this.constructor.Default[key] !== this.config[key]) { - config[key] = this.config[key]; - } - } - } - - return config; - }; - - _proto._cleanTipClass = function _cleanTipClass() { - var $tip = $(this.getTipElement()); - var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX); - - if (tabClass !== null && tabClass.length) { - $tip.removeClass(tabClass.join('')); - } - }; - - _proto._handlePopperPlacementChange = function _handlePopperPlacementChange(popperData) { - var popperInstance = popperData.instance; - this.tip = popperInstance.popper; - - this._cleanTipClass(); - - this.addAttachmentClass(this._getAttachment(popperData.placement)); - }; - - _proto._fixTransition = function _fixTransition() { - var tip = this.getTipElement(); - var initConfigAnimation = this.config.animation; - - if (tip.getAttribute('x-placement') !== null) { - return; - } - - $(tip).removeClass(ClassName$6.FADE); - this.config.animation = false; - this.hide(); - this.show(); - this.config.animation = initConfigAnimation; - } // Static - ; - - Tooltip._jQueryInterface = function _jQueryInterface(config) { - return this.each(function () { - var data = $(this).data(DATA_KEY$6); - - var _config = typeof config === 'object' && config; - - if (!data && /dispose|hide/.test(config)) { - return; - } - - if (!data) { - data = new Tooltip(this, _config); - $(this).data(DATA_KEY$6, data); - } - - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError("No method named \"" + config + "\""); - } - - data[config](); - } - }); - }; - - _createClass(Tooltip, null, [{ - key: "VERSION", - get: function get() { - return VERSION$6; - } - }, { - key: "Default", - get: function get() { - return Default$4; - } - }, { - key: "NAME", - get: function get() { - return NAME$6; - } - }, { - key: "DATA_KEY", - get: function get() { - return DATA_KEY$6; - } - }, { - key: "Event", - get: function get() { - return Event$6; - } - }, { - key: "EVENT_KEY", - get: function get() { - return EVENT_KEY$6; - } - }, { - key: "DefaultType", - get: function get() { - return DefaultType$4; - } - }]); - - return Tooltip; - }(); - /** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - */ - - - $.fn[NAME$6] = Tooltip._jQueryInterface; - $.fn[NAME$6].Constructor = Tooltip; - - $.fn[NAME$6].noConflict = function () { - $.fn[NAME$6] = JQUERY_NO_CONFLICT$6; - return Tooltip._jQueryInterface; - }; - - /** - * ------------------------------------------------------------------------ - * Constants - * ------------------------------------------------------------------------ - */ - - var NAME$7 = 'popover'; - var VERSION$7 = '4.4.1'; - var DATA_KEY$7 = 'bs.popover'; - var EVENT_KEY$7 = "." + DATA_KEY$7; - var JQUERY_NO_CONFLICT$7 = $.fn[NAME$7]; - var CLASS_PREFIX$1 = 'bs-popover'; - var BSCLS_PREFIX_REGEX$1 = new RegExp("(^|\\s)" + CLASS_PREFIX$1 + "\\S+", 'g'); - - var Default$5 = _objectSpread2({}, Tooltip.Default, { - placement: 'right', - trigger: 'click', - content: '', - template: '' - }); - - var DefaultType$5 = _objectSpread2({}, Tooltip.DefaultType, { - content: '(string|element|function)' - }); - - var ClassName$7 = { - FADE: 'fade', - SHOW: 'show' - }; - var Selector$7 = { - TITLE: '.popover-header', - CONTENT: '.popover-body' - }; - var Event$7 = { - HIDE: "hide" + EVENT_KEY$7, - HIDDEN: "hidden" + EVENT_KEY$7, - SHOW: "show" + EVENT_KEY$7, - SHOWN: "shown" + EVENT_KEY$7, - INSERTED: "inserted" + EVENT_KEY$7, - CLICK: "click" + EVENT_KEY$7, - FOCUSIN: "focusin" + EVENT_KEY$7, - FOCUSOUT: "focusout" + EVENT_KEY$7, - MOUSEENTER: "mouseenter" + EVENT_KEY$7, - MOUSELEAVE: "mouseleave" + EVENT_KEY$7 - }; - /** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - - var Popover = - /*#__PURE__*/ - function (_Tooltip) { - _inheritsLoose(Popover, _Tooltip); - - function Popover() { - return _Tooltip.apply(this, arguments) || this; - } - - var _proto = Popover.prototype; - - // Overrides - _proto.isWithContent = function isWithContent() { - return this.getTitle() || this._getContent(); - }; - - _proto.addAttachmentClass = function addAttachmentClass(attachment) { - $(this.getTipElement()).addClass(CLASS_PREFIX$1 + "-" + attachment); - }; - - _proto.getTipElement = function getTipElement() { - this.tip = this.tip || $(this.config.template)[0]; - return this.tip; - }; - - _proto.setContent = function setContent() { - var $tip = $(this.getTipElement()); // We use append for html objects to maintain js events - - this.setElementContent($tip.find(Selector$7.TITLE), this.getTitle()); - - var content = this._getContent(); - - if (typeof content === 'function') { - content = content.call(this.element); - } - - this.setElementContent($tip.find(Selector$7.CONTENT), content); - $tip.removeClass(ClassName$7.FADE + " " + ClassName$7.SHOW); - } // Private - ; - - _proto._getContent = function _getContent() { - return this.element.getAttribute('data-content') || this.config.content; - }; - - _proto._cleanTipClass = function _cleanTipClass() { - var $tip = $(this.getTipElement()); - var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX$1); - - if (tabClass !== null && tabClass.length > 0) { - $tip.removeClass(tabClass.join('')); - } - } // Static - ; - - Popover._jQueryInterface = function _jQueryInterface(config) { - return this.each(function () { - var data = $(this).data(DATA_KEY$7); - - var _config = typeof config === 'object' ? config : null; - - if (!data && /dispose|hide/.test(config)) { - return; - } - - if (!data) { - data = new Popover(this, _config); - $(this).data(DATA_KEY$7, data); - } - - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError("No method named \"" + config + "\""); - } - - data[config](); - } - }); - }; - - _createClass(Popover, null, [{ - key: "VERSION", - // Getters - get: function get() { - return VERSION$7; - } - }, { - key: "Default", - get: function get() { - return Default$5; - } - }, { - key: "NAME", - get: function get() { - return NAME$7; - } - }, { - key: "DATA_KEY", - get: function get() { - return DATA_KEY$7; - } - }, { - key: "Event", - get: function get() { - return Event$7; - } - }, { - key: "EVENT_KEY", - get: function get() { - return EVENT_KEY$7; - } - }, { - key: "DefaultType", - get: function get() { - return DefaultType$5; - } - }]); - - return Popover; - }(Tooltip); - /** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - */ - - - $.fn[NAME$7] = Popover._jQueryInterface; - $.fn[NAME$7].Constructor = Popover; - - $.fn[NAME$7].noConflict = function () { - $.fn[NAME$7] = JQUERY_NO_CONFLICT$7; - return Popover._jQueryInterface; - }; - - /** - * ------------------------------------------------------------------------ - * Constants - * ------------------------------------------------------------------------ - */ - - var NAME$8 = 'scrollspy'; - var VERSION$8 = '4.4.1'; - var DATA_KEY$8 = 'bs.scrollspy'; - var EVENT_KEY$8 = "." + DATA_KEY$8; - var DATA_API_KEY$6 = '.data-api'; - var JQUERY_NO_CONFLICT$8 = $.fn[NAME$8]; - var Default$6 = { - offset: 10, - method: 'auto', - target: '' - }; - var DefaultType$6 = { - offset: 'number', - method: 'string', - target: '(string|element)' - }; - var Event$8 = { - ACTIVATE: "activate" + EVENT_KEY$8, - SCROLL: "scroll" + EVENT_KEY$8, - LOAD_DATA_API: "load" + EVENT_KEY$8 + DATA_API_KEY$6 - }; - var ClassName$8 = { - DROPDOWN_ITEM: 'dropdown-item', - DROPDOWN_MENU: 'dropdown-menu', - ACTIVE: 'active' - }; - var Selector$8 = { - DATA_SPY: '[data-spy="scroll"]', - ACTIVE: '.active', - NAV_LIST_GROUP: '.nav, .list-group', - NAV_LINKS: '.nav-link', - NAV_ITEMS: '.nav-item', - LIST_ITEMS: '.list-group-item', - DROPDOWN: '.dropdown', - DROPDOWN_ITEMS: '.dropdown-item', - DROPDOWN_TOGGLE: '.dropdown-toggle' - }; - var OffsetMethod = { - OFFSET: 'offset', - POSITION: 'position' - }; - /** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - - var ScrollSpy = - /*#__PURE__*/ - function () { - function ScrollSpy(element, config) { - var _this = this; - - this._element = element; - this._scrollElement = element.tagName === 'BODY' ? window : element; - this._config = this._getConfig(config); - this._selector = this._config.target + " " + Selector$8.NAV_LINKS + "," + (this._config.target + " " + Selector$8.LIST_ITEMS + ",") + (this._config.target + " " + Selector$8.DROPDOWN_ITEMS); - this._offsets = []; - this._targets = []; - this._activeTarget = null; - this._scrollHeight = 0; - $(this._scrollElement).on(Event$8.SCROLL, function (event) { - return _this._process(event); - }); - this.refresh(); - - this._process(); - } // Getters - - - var _proto = ScrollSpy.prototype; - - // Public - _proto.refresh = function refresh() { - var _this2 = this; - - var autoMethod = this._scrollElement === this._scrollElement.window ? OffsetMethod.OFFSET : OffsetMethod.POSITION; - var offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method; - var offsetBase = offsetMethod === OffsetMethod.POSITION ? this._getScrollTop() : 0; - this._offsets = []; - this._targets = []; - this._scrollHeight = this._getScrollHeight(); - var targets = [].slice.call(document.querySelectorAll(this._selector)); - targets.map(function (element) { - var target; - var targetSelector = Util.getSelectorFromElement(element); - - if (targetSelector) { - target = document.querySelector(targetSelector); - } - - if (target) { - var targetBCR = target.getBoundingClientRect(); - - if (targetBCR.width || targetBCR.height) { - // TODO (fat): remove sketch reliance on jQuery position/offset - return [$(target)[offsetMethod]().top + offsetBase, targetSelector]; - } - } - - return null; - }).filter(function (item) { - return item; - }).sort(function (a, b) { - return a[0] - b[0]; - }).forEach(function (item) { - _this2._offsets.push(item[0]); - - _this2._targets.push(item[1]); - }); - }; - - _proto.dispose = function dispose() { - $.removeData(this._element, DATA_KEY$8); - $(this._scrollElement).off(EVENT_KEY$8); - this._element = null; - this._scrollElement = null; - this._config = null; - this._selector = null; - this._offsets = null; - this._targets = null; - this._activeTarget = null; - this._scrollHeight = null; - } // Private - ; - - _proto._getConfig = function _getConfig(config) { - config = _objectSpread2({}, Default$6, {}, typeof config === 'object' && config ? config : {}); - - if (typeof config.target !== 'string') { - var id = $(config.target).attr('id'); - - if (!id) { - id = Util.getUID(NAME$8); - $(config.target).attr('id', id); - } - - config.target = "#" + id; - } - - Util.typeCheckConfig(NAME$8, config, DefaultType$6); - return config; - }; - - _proto._getScrollTop = function _getScrollTop() { - return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop; - }; - - _proto._getScrollHeight = function _getScrollHeight() { - return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); - }; - - _proto._getOffsetHeight = function _getOffsetHeight() { - return this._scrollElement === window ? window.innerHeight : this._scrollElement.getBoundingClientRect().height; - }; - - _proto._process = function _process() { - var scrollTop = this._getScrollTop() + this._config.offset; - - var scrollHeight = this._getScrollHeight(); - - var maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight(); - - if (this._scrollHeight !== scrollHeight) { - this.refresh(); - } - - if (scrollTop >= maxScroll) { - var target = this._targets[this._targets.length - 1]; - - if (this._activeTarget !== target) { - this._activate(target); - } - - return; - } - - if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) { - this._activeTarget = null; - - this._clear(); - - return; - } - - var offsetLength = this._offsets.length; - - for (var i = offsetLength; i--;) { - var isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]); - - if (isActiveTarget) { - this._activate(this._targets[i]); - } - } - }; - - _proto._activate = function _activate(target) { - this._activeTarget = target; - - this._clear(); - - var queries = this._selector.split(',').map(function (selector) { - return selector + "[data-target=\"" + target + "\"]," + selector + "[href=\"" + target + "\"]"; - }); - - var $link = $([].slice.call(document.querySelectorAll(queries.join(',')))); - - if ($link.hasClass(ClassName$8.DROPDOWN_ITEM)) { - $link.closest(Selector$8.DROPDOWN).find(Selector$8.DROPDOWN_TOGGLE).addClass(ClassName$8.ACTIVE); - $link.addClass(ClassName$8.ACTIVE); - } else { - // Set triggered link as active - $link.addClass(ClassName$8.ACTIVE); // Set triggered links parents as active - // With both
      and
diff --git a/website/templates/careers/hero.html b/website/templates/careers/hero.html deleted file mode 100644 index dd4e59aeb3a..00000000000 --- a/website/templates/careers/hero.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
-
- -

- {{ _('Careers') }} -

- -
-
diff --git a/website/templates/careers/overview.html b/website/templates/careers/overview.html deleted file mode 100644 index 1601bf4f4b3..00000000000 --- a/website/templates/careers/overview.html +++ /dev/null @@ -1,11 +0,0 @@ -
-
- -

- ClickHouse is searching  for individuals who are not just knowledgeable about what they do, but want to learn more. Our ideal candidates are thinkers and doers who are not afraid to take on various roles and responsibilities as they grow with the company. If you are looking for a place to build something new, be an agent of change, and have an opportunity to have a significant impact on the company’s success, this is the place for you. -

- -
- -
-
diff --git a/website/templates/common_css.html b/website/templates/common_css.html deleted file mode 100644 index b26b2bf973e..00000000000 --- a/website/templates/common_css.html +++ /dev/null @@ -1,5 +0,0 @@ - - -{% for src in extra_css %} - -{% endfor %} diff --git a/website/templates/common_fonts.html b/website/templates/common_fonts.html deleted file mode 100644 index eb61aa2c9b5..00000000000 --- a/website/templates/common_fonts.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/website/templates/common_js.html b/website/templates/common_js.html deleted file mode 100644 index 93e35d37918..00000000000 --- a/website/templates/common_js.html +++ /dev/null @@ -1,9 +0,0 @@ - - -{% for src in extra_js %} - -{% endfor %} - - diff --git a/website/templates/common_meta.html b/website/templates/common_meta.html deleted file mode 100644 index 07aa05d28b1..00000000000 --- a/website/templates/common_meta.html +++ /dev/null @@ -1,53 +0,0 @@ -{% set description = description or _('ClickHouse is a fast open-source column-oriented database management system that allows generating analytical data reports in real-time using SQL queries') %} - - - - - -{% if title %}{{ title }}{% else %}{{ _('ClickHouse - fast open-source OLAP DBMS') }}{% endif %} - - - - - - - -{% if page and page.meta.image %} - -{% else %} - -{% endif %} - - -{% if page and not single_page and not is_blog %} - -{% endif %} - -{% include "templates/docs/ld_json.html" %} - - -{% if page and page.meta.tags %} - -{% else %} - -{% endif %} -{% if config and config.extra.single_page %} - -{% endif %} - -{% if config and page and not is_blog %} - {% for code, name in config.extra.languages.items() %} - - {% endfor %} - -{% endif %} - -{% for prefetch_item in prefetch_items %} - -{% endfor %} - -{% if is_blog %} - -{% endif %} diff --git a/website/templates/company/contact.html b/website/templates/company/contact.html deleted file mode 100644 index 2538b184fda..00000000000 --- a/website/templates/company/contact.html +++ /dev/null @@ -1,53 +0,0 @@ -
-
- -

- {{ _('Contact Us') }} -

- -
-
- -
- - - - -
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
- -
- -
- -
- -
- -

- {{ _('If you have any more thoughts or questions, feel free to contact the ClickHouse team directly at') }} - feedback@clickhouse.com -

- -
-
diff --git a/website/templates/company/founders.html b/website/templates/company/founders.html deleted file mode 100644 index 2f6100e28f7..00000000000 --- a/website/templates/company/founders.html +++ /dev/null @@ -1,55 +0,0 @@ -
-
- -

- {{ _('Meet the Team') }} -

- -

- {{ _('ClickHouse Co-Founders') }} -

- -
-
- - - - -

- {{ _('Yury Izrailevsky') }} -

-

- {{ _('Co-Founder & President, Product and Engineering') }} -

- -
-
- - - - -

- {{ _('Aaron Katz') }} -

-

- {{ _('Co-Founder & CEO') }} -

- -
-
- - - - -

- {{ _('Alexey Milovidov') }} -

-

- {{ _('Co-Founder & CTO') }} -

- -
-
- -
-
diff --git a/website/templates/company/hero.html b/website/templates/company/hero.html deleted file mode 100644 index f7d3fbdb529..00000000000 --- a/website/templates/company/hero.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
-
- -

- {{ _('About Us') }} -

- -
-
diff --git a/website/templates/company/investors.html b/website/templates/company/investors.html deleted file mode 100644 index fbe15ccd8e9..00000000000 --- a/website/templates/company/investors.html +++ /dev/null @@ -1,64 +0,0 @@ -

- {{ _('ClickHouse Investors') }} -

- -
-
- - - -

- {{ _('Mike Volpi') }} -

-

- {{ _('Board Member') }} -

- - - -
-
- - - -

- {{ _('Peter Fenton') }} -

-

- {{ _('Board Member') }} -

- - -
-
- - - - -

- {{ _('Caryn Marooney') }} -

- - -
-
- - - - -

- {{ _('Kevin Wang') }} -

- - -
-
- -
- - - - - - -
diff --git a/website/templates/company/overview.html b/website/templates/company/overview.html deleted file mode 100644 index 2d95a57ddd8..00000000000 --- a/website/templates/company/overview.html +++ /dev/null @@ -1,17 +0,0 @@ -
-
- -

- ClickHouse is an open-source column-oriented DBMS (columnar database management system) for online analytical processing (OLAP) that allows users to generate analytical reports using SQL queries in real-time. -

-

- Its technology works 100-1000x faster than traditional database management systems and processes hundreds of millions to over a billion rows and tens of gigabytes of data per server per second. With a widespread user base around the globe, the technology has received praise for its reliability, ease of use, and fault tolerance. -

-

- In September of 2021 in San Fransisco, CA, ClickHouse incorporated to house the open source technology with an initial $50 million investment from Index Ventures and Benchmark Capital. On October 28, 2021 the company received Series B funding totaling $250 million at an evaluation of $2 billion from Coatue Management, Altimeter Capital, and other investors. The company continues to build the open source project and engineering the cloud technology. -

- -
- -
-
diff --git a/website/templates/company/press.html b/website/templates/company/press.html deleted file mode 100644 index ddb2cf584ac..00000000000 --- a/website/templates/company/press.html +++ /dev/null @@ -1,109 +0,0 @@ -
-
- -
-
- -

- {{ _('Press') }} -

- -
- -
-
-

- {{ _('10/28/21') }} -

-

- {{ _('ClickHouse Raises $250M Series B To Scale Groundbreaking OLAP Database Management System Globally') }} -

-

- {{ _('The investment was led by Coatue and Altimeter, with participation from Index Ventures, Benchmark, Lightspeed, Redpoint, Almaz, Yandex N.V., FirstMark and Lead Edge.') }} -

- {{ _('Read More') }} -
- -
- -

- {{ _('10/28/21') }} -

-

- {{ _('Bloomberg Exclusive') }} -

-

- {{ _('ClickHouse Valued at $2 Billion in Round After Yandex Spinout.') }} -

- {{ _('Read More') }} -
- -
- -

- {{ _('10/28/21') }} -

-

- {{ _('Lightspeed Perspective') }} -

-

- {{ _('Why Lightspeed invested in ClickHouse: a database built for speed.') }} -

- {{ _('Read More') }} -
- -
- -

- {{ _('9/20/21') }} -

-

- {{ _('ClickHouse, Inc. Announces Incorporation, Along With $50M In Series A Funding') }} -

-

- {{ _('New financing will allow the open source success to build a world-class, commercial-grade cloud solution that’s secure, compliant, and convenient for any customer to use.') }} -

- {{ _('Read More') }} -
-
-

- {{ _('9/20/21') }} -

-

- {{ _('Business Insider Exclusive') }} -

-

- {{ _('The creators of the popular ClickHouse project just raised $50 million from Index and Benchmark to form a company that will take on Splunk and Druid in the white-hot data space.') }} -

- {{ _('Read More') }} -
-
-

- {{ _('9/20/21') }} -

-

- {{ _('Index Ventures Perspective') }} -

-

- {{ _('Our road to ClickHouse started like a good spy novel, with a complex series of introductions over Telegram, clandestine text conversations spanning months before we finally managed to meet the team “face-to-face” (aka over Zoom).') }} -

- {{ _('Read More') }} -
- -
-

- {{ _('9/20/21') }} -

-

- {{ _('Yandex Perspective') }} -

-

- {{ _('Yandex announces the spin off of ClickHouse, Inc., a pioneering company focused on open-source column-oriented database management systems (DBMS) for analytical processing. ') }} -

- {{ _('Read More') }} -
-
-
- -
-
diff --git a/website/templates/company/team.html b/website/templates/company/team.html deleted file mode 100644 index 01341fb4a5f..00000000000 --- a/website/templates/company/team.html +++ /dev/null @@ -1,776 +0,0 @@ -
-
-
- -

- {{ _('ClickHouse Team') }} -

- -
-
- - - - -

- {{ _('Manas Alekar') }} -

-

- {{ _('Senior Cloud SWE') }} -

- -
-
- - - - -

- {{ _('Vitaly Baranov') }} -

-

- {{ _('Principal Sofware Engineer') }} -

- -
-
- - - - -

- {{ _('Marcel Birkner') }} -

-

- {{ _(' - Cloud SWE') }} -

- -
-
- - - - -

- {{ _('Ivan Blinkov') }} -

-

- {{ _('VP, Technical Program Management') }} -

- -
-
- - - - -

- {{ _('Tanya Bragin') }} -

-

- {{ _('VP, Product') }} -

- -
-
- - - - -

- {{ _('Anne Carlhoff') }} -

-

- {{ _(' - Sr Recruiter') }} -

- -
-
- - - - -

- {{ _('Jason Chan') }} -

-

- {{ _('Adviser, Security, Privacy & Compliance') }} -

- -
-
- - - - -

- {{ _('Vladimir Cherkasov') }} -

-

- {{ _('Software Engineer') }} -

- -
-
- - - - -

- {{ _('Martin Choluj') }} -

-

- {{ _('Vice President, Security') }} -

- -
-
- - - - -

- {{ _('Ryadh Dahimene') }} -

-

- {{ _('Consulting Architect') }} -

- -
-
- - - - -

- {{ _('Nikolay Degterinsky') }} -

-

- {{ _('Core SWE') }} -

- -
-
- - - - -

- {{ _('Miel Donkers') }} -

-

- {{ _('Senior Cloud Software Engineer') }} -

- -
-
- - - - -

- {{ _('Artur Filatenkov') }} -

-

- {{ _('Associate Core Software Engineer') }} -

- -
-
- - - - -

- {{ _('Kristina Frost') }} -

-

- {{ _('Senior Director, Business Technology') }} -

- -
-
- -
- -
-

- {{ _('Mikhail Fursov') }} -

-

- {{ _('Principal UX/UI Engineer') }} -

- -
-
- - - - -

- {{ _('Baird Garrett') }} -

-

- {{ _('General Counsel') }} -

- -
-
- - - - -

- {{ _('Geoffrey Genz') }} -

-

- {{ _('Principal Support Engineer') }} -

- -
-
- - - - -

- {{ _('Mihir Gokhale') }} -

-

- {{ _('Associate, Business Strategy & Ops') }} -

- -
-
- - - - -

- {{ _('Tyler Hannan') }} -

-

- {{ _('Senior Director, Developer Advocacy') }} -

- -
-
- - - - -

- {{ _('Mike Hayes') }} -

-

- {{ _('VP, Sales') }} -

- -
-
- - - - -

- {{ _('Nihat Hosgur') }} -

-

- {{ _('Principal Cloud SWE') }} -

- -
-
- - - - -

- {{ _('Brian Hunter') }} -

-

- {{ _('Account Executive, AMER') }} -

- -
-
- - - - -

- {{ _('Andy James') }} -

-

- {{ _('Business Technology Full Stack Lead') }} -

- -
-
- - - - -

- {{ _('Yossi Kahlon') }} -

-

- {{ _('Director, Engineering - Control Plane') }} -

- -
-
- - - - -

- {{ _('Maksim Kita') }} -

-

- {{ _('Senior Software Engineer') }} -

- -
-
- - - - -

- {{ _('Nikolai Kochetov') }} -

-

- {{ _('Engineering Team Lead') }} -

- -
-
- - - - -

- {{ _('Anne Krechmer') }} -

-

- {{ _('Senior Recruiter') }} -

- -
-
- - - - -

- {{ _('Pavel Kruglov') }} -

-

- {{ _('Software Engineer') }} -

- -
-
- - - - -

- {{ _('Michael Lex') }} -

-

- {{ _('Cloud SWE') }} -

- -
-
- - - - -

- {{ _('Niek Lok') }} -

-

- {{ _('Account Executive') }} -

- -
-
- - - - -

- {{ _('Claire Lucas') }} -

-

- {{ _('Director, Business Strategy & Ops') }} -

- -
-
- - - - -

- {{ _('Shavoyne McCowan') }} -

-

- {{ _('Executive Assistant') }} -

- -
-
- - - - -

- {{ _('Dale McDiarmid') }} -

-

- {{ _('Consulting Architect') }} -

- -
-
- - - - -

- {{ _('Nikita Mikhailov') }} -

-

- {{ _('Software Engineer') }} -

- -
-
- - - - -

- {{ _('Dmitry Novik') }} -

-

- {{ _('Senior Software Engineer') }} -

- -
-
- - - - -

- {{ _('Thom O’Connor') }} -

-

- {{ _('VP, Support & Services') }} -

- -
-
- - - - -

- {{ _('Melvyn Peignon') }} -

-

- {{ _('Manager, Support Services – EMEA') }} -

- -
-
- - - - -

- {{ _('Nir Peled') }} -

-

- {{ _('Principal UX/UI Engineer') }} -

- -
-
- - - - -

- {{ _('Anton Popov') }} -

-

- {{ _('Senior Software Engineer') }} -

- -
-
- - - - -

- {{ _('Rich Raposa') }} -

-

- {{ _('Director, Global Learning') }} -

- -
-
- - - - -

- {{ _('Marcelo Rodriguez') }} -

-

- {{ _('Sr Support Engineer') }} -

- -
-
- - - - -

- {{ _('Alexander Sapin') }} -

-

- {{ _('Engineering Team Lead') }} -

- -
-
- - - - -

- {{ _('Tom Schreiber') }} -

-

- {{ _('Consulting Architect – EMEA') }} -

- -
-
- - - - -

- {{ _('Mikhail Shiryaev') }} -

-

- {{ _('Site Reliability Engineer') }} -

- -
-
- - - - -

- {{ _('Bastian Spanneberg') }} -

-

- {{ _('Site Reliability Engineer') }} -

- -
-
- - - - -

- {{ _('Kseniia Sumarokova') }} -

-

- {{ _('Software Engineer') }} -

- -
-
- - - - -

- {{ _('Dorota Szeremeta') }} -

-

- {{ _('VP, Operations') }} -

- -
-
- - - - -

- {{ _('Yuko Takagi') }} -

-

- {{ _('Director, Go To Market Technology') }} -

- -
-
- - - - -

- {{ _('Roopa Tangirala') }} -

-

- {{ _('Senior Director, Engineering') }} -

- -
-
- - - - -

- {{ _('Alexander Tokmakov') }} -

-

- {{ _('Software Engineer') }} -

- -
-
- -
- -
-

- {{ _('Sergei Trifonov') }} -

-

- {{ _('Principal Core SWE') }} -

- -
-
- - - - -

- {{ _('Pascal Van den Nieuwendijk') }} -

-

- {{ _('Account Executive') }} -

- -
-
- - - - -

- {{ _('Arno Van Driel') }} -

-

- {{ _('VP, EMEA') }} -

- -
-
- - - - -

- {{ _('Elissa Weve') }} -

-

- {{ _('Customer / Partner Alliance Manager') }} -

- -
-
- - - - -

- {{ _('Christoph Wurm') }} -

-

- {{ _('Solutions Architect') }} -

- -
-
- - - - -

- {{ _('Ilya Yatsishin') }} -

-

- {{ _('Senior Technical Project Manager') }} -

- -
-
- - - - -

- {{ _('Mark Zitnik') }} -

-

- {{ _('Senior Cloud Software Engineer') }} -

- -
-
- -
-
- -

- {{ _('Join the Team') }} -

- - - -
-
- -
- - {% include "templates/company/investors.html" %} - -
-
diff --git a/website/templates/contact-thank-you/hero.html b/website/templates/contact-thank-you/hero.html deleted file mode 100644 index a1841eb433f..00000000000 --- a/website/templates/contact-thank-you/hero.html +++ /dev/null @@ -1,14 +0,0 @@ -
-
-
- -

- {{ _('Thank You') }} -

- -

- {{ _('Someone will be in contact with you shortly.') }} -

- -
-
diff --git a/website/templates/contact-thank-you/overview.html b/website/templates/contact-thank-you/overview.html deleted file mode 100644 index 67728414adc..00000000000 --- a/website/templates/contact-thank-you/overview.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
-
- -
- -
-
diff --git a/website/templates/docs/amp.html b/website/templates/docs/amp.html deleted file mode 100644 index dc7dd7acb49..00000000000 --- a/website/templates/docs/amp.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - {{ title }} - - - {% include "templates/docs/ld_json.html" %} - {% if config.extra.single_page %} - - {% endif %} - - - - - - -
-
-
- - - -
-
-
-
- {% include "templates/docs/machine-translated.html" %} - {{ page.content|adjust_markdown_html|html_to_amp }} -
-
-
-
- {% include "templates/docs/footer.html" %} -
-
-
- -
-
- - - - - diff --git a/website/templates/docs/content.html b/website/templates/docs/content.html deleted file mode 100644 index c2835dd1f39..00000000000 --- a/website/templates/docs/content.html +++ /dev/null @@ -1,44 +0,0 @@ -
-
- {% if not single_page %} - {% set ancestors = page.ancestors|reverse|list %} - {% if page.is_index %} - {% set ancestors = ancestors[:-1] %} - {% endif %} - {% if ancestors %} - {% set ancestor_ns = namespace(level=ancestors|length) %} - - {% endif %} - - {% include "templates/docs/machine-translated.html" %} - {{ page.content|adjust_markdown_html }} - {% endif %} - - {% if single_page and page.content %} - - {% endif %} -
- - {% include "templates/docs/footer.html" %} -
-{% if single_page and page.content %} - -(function() { - {% for chunk in page.content|adjust_markdown_html|chunks %} - document.write({{ chunk|tojson|safe }}); - {% endfor %} -})(); - -{% endif %} diff --git a/website/templates/docs/footer.html b/website/templates/docs/footer.html deleted file mode 100644 index a99a723c036..00000000000 --- a/website/templates/docs/footer.html +++ /dev/null @@ -1,19 +0,0 @@ -{% if not single_page %} -
-

{{ _('Rating') }}: RATING_VALUE - RATING_COUNT {{ _('votes') }}

- {% if is_amp %}{{ _('Article Rating') }}{% else %}{{ _('Was this content helpful?') }}{% endif %} -
RATING_STARS
-
- - -{% endif %} diff --git a/website/templates/docs/ld_json.html b/website/templates/docs/ld_json.html deleted file mode 100644 index 4dea9fa6cbb..00000000000 --- a/website/templates/docs/ld_json.html +++ /dev/null @@ -1,38 +0,0 @@ -{% if page and page.meta %} - -{% endif %} diff --git a/website/templates/docs/machine-translated.html b/website/templates/docs/machine-translated.html deleted file mode 100644 index c46d9ebad70..00000000000 --- a/website/templates/docs/machine-translated.html +++ /dev/null @@ -1,11 +0,0 @@ -{% if page.meta.machine_translated %} - -{% endif %} diff --git a/website/templates/docs/nav.html b/website/templates/docs/nav.html deleted file mode 100644 index afac39c2fab..00000000000 --- a/website/templates/docs/nav.html +++ /dev/null @@ -1,68 +0,0 @@ - diff --git a/website/templates/docs/sidebar-item.html b/website/templates/docs/sidebar-item.html deleted file mode 100644 index ca4b8f9a60e..00000000000 --- a/website/templates/docs/sidebar-item.html +++ /dev/null @@ -1,20 +0,0 @@ -{% set collapsed_threshold = 1 %} -{% if not nav_item.title.startswith("hidden-") %} -{% if nav_item.title.endswith('|hidden-folder') %} -{{ nav_item.title[:-14] }} -{% elif nav_item.children %} -{{ nav_item.title }} - -{% else %} -{{ nav_item.title }} -{% endif %} -{% endif %} diff --git a/website/templates/docs/sidebar.html b/website/templates/docs/sidebar.html deleted file mode 100644 index 709a2551d9c..00000000000 --- a/website/templates/docs/sidebar.html +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/website/templates/docs/toc-item.html b/website/templates/docs/toc-item.html deleted file mode 100644 index fedc0527a69..00000000000 --- a/website/templates/docs/toc-item.html +++ /dev/null @@ -1,13 +0,0 @@ - - {{ toc_item.title }} - -{% if toc_item.children %} - -{% endif %} diff --git a/website/templates/docs/toc.html b/website/templates/docs/toc.html deleted file mode 100644 index 7fbbe2fef3b..00000000000 --- a/website/templates/docs/toc.html +++ /dev/null @@ -1,16 +0,0 @@ -
- {% set toc_ = page.toc %} - {% if toc_ | first is defined and "\x3ch1 id=" in page.content %} - {% set toc_ = (toc_ | first).children %} - {% endif %} - -
diff --git a/website/templates/footer.html b/website/templates/footer.html deleted file mode 100644 index efb6b1a0299..00000000000 --- a/website/templates/footer.html +++ /dev/null @@ -1,19 +0,0 @@ -{% if not no_footer %} - -{% endif %} diff --git a/website/templates/global/banner.html b/website/templates/global/banner.html deleted file mode 100644 index ffb1abf7330..00000000000 --- a/website/templates/global/banner.html +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/website/templates/global/github_stars.html b/website/templates/global/github_stars.html deleted file mode 100644 index 668938fcf58..00000000000 --- a/website/templates/global/github_stars.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/website/templates/global/nav.html b/website/templates/global/nav.html deleted file mode 100644 index d775c88e4a5..00000000000 --- a/website/templates/global/nav.html +++ /dev/null @@ -1,60 +0,0 @@ - diff --git a/website/templates/global/newsletter.html b/website/templates/global/newsletter.html deleted file mode 100644 index ef0809760e6..00000000000 --- a/website/templates/global/newsletter.html +++ /dev/null @@ -1,40 +0,0 @@ -
- -
- -

- {{ _('Stay informed on feature releases, product roadmap, and future support and cloud offerings! ') }} -

- -
-
- -

- {{ _('Sign up for our newsletter') }} -

- -
- -
- - - - -
-
- -
-
- -
-
- -
- -
- -
-
- -
-
diff --git a/website/templates/index/community.html b/website/templates/index/community.html deleted file mode 100644 index 08ccf0c7580..00000000000 --- a/website/templates/index/community.html +++ /dev/null @@ -1,165 +0,0 @@ -
-
-
- -
-
- -

- {{ _('Join our growing ClickHouse community') }} -

- - - -
-
- -
- -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
-
-
- -
-
diff --git a/website/templates/index/hero.html b/website/templates/index/hero.html deleted file mode 100644 index da6ee77700b..00000000000 --- a/website/templates/index/hero.html +++ /dev/null @@ -1,39 +0,0 @@ -
-
-
- -

- ClickHouse v22.2 Released -

- -

- {{ _('ClickHouse® is an open-source, high performance columnar OLAP database management system for real-time analytics using SQL.') }} -

- -

- Watch the Release Webinar on YouTube -

- -
-
- -
-
- -
-
- -

ClickHouse Announces $250 Million in Funding

- -

Raising the Company’s Valuation to $2B

- - -
-
- -
-
diff --git a/website/templates/index/performance.html b/website/templates/index/performance.html deleted file mode 100644 index 1be0e4dafe2..00000000000 --- a/website/templates/index/performance.html +++ /dev/null @@ -1,56 +0,0 @@ -
-
- -
-
- -

- ClickHouse works 100-1000x faster than traditional approaches -

- -

- ClickHouse's performance exceeds comparable column-oriented database management systems that are available on the market. It processes hundreds of millions to over a billion rows and tens of gigabytes of data per server per second. -

- - - {{ _('Detailed comparison') }} - - -
- -
- -
-
diff --git a/website/templates/index/pullquote.html b/website/templates/index/pullquote.html deleted file mode 100644 index db6e2c89e0e..00000000000 --- a/website/templates/index/pullquote.html +++ /dev/null @@ -1,18 +0,0 @@ -
-
- -
-
-
-

- {{ _('"ClickHouse leading the way in real time for Engineering organizations"') }} -

-

- {{ _('Author Name') }}, - {{ _('Company Name') }} -

-
-
- -
-
diff --git a/website/templates/index/quickstart.html b/website/templates/index/quickstart.html deleted file mode 100644 index 100261ab548..00000000000 --- a/website/templates/index/quickstart.html +++ /dev/null @@ -1,75 +0,0 @@ -
-
-
- -

Quick Start

- -
- - - -
- -
-
-
{% include "install/deb.sh" %}
-
-
-
{% include "install/rpm.sh" %}
-
-
-
{% include "install/tgz.sh" %}
-
-
-
{% include "install/arm.sh" %}
-
-
-
{% include "install/mac-x86.sh" %}
-
-
-
{% include "install/mac-arm.sh" %}
-
-
-
{% include "install/freebsd.sh" %}
-
-
- -

- There's a number of alternative options to get started, most notably the - official Docker images of ClickHouse. To learn more about our future cloud offerings, contact us. -

- -

After you got connected to your ClickHouse server, you can proceed to:

- - - -
- -
-
-
diff --git a/website/templates/index/success.html b/website/templates/index/success.html deleted file mode 100644 index 7d70f4367b2..00000000000 --- a/website/templates/index/success.html +++ /dev/null @@ -1,246 +0,0 @@ -
-
- -

- ClickHouse Users -

- -
-
- -
-
- -
- - - -
-
- -
- -
-
- -

{{ _('ClickHouse was born and in production inside Yandex over a decade ago and now stores 10’s of trillions of rows of data serving a query throughput of 2TB per second for Yandex Metrica. It has also become the de facto standard inside Yandex for advertising systems, monitoring and observability data, business intelligence, recommendations platforms, OLAP, and even cars telemetry.') }}

- - {{ _('Read the Case Study') }} - -
-
- -
    -
  • Stores 10’s of trillions of rows of data
  • -
  • Query throughput of 2TB per second
  • -
  • Became de facto standard inside Yandex
  • -
- -
-
- -
- -
-
- -
-
- -
-
- -
- - - -
-
- -
- -
-
- -

{{ _('Uber moved its logging platform to ClickHouse increasing developer productivity and overall reliability of the platform while seeing 3x data compression, 10x performance increase, and ½ the reduction in hardware cost.') }}

- - {{ _('Read the Case Study') }} - -
-
- -
    -
  • 3x data compression
  • -
  • 10x performance increase
  • -
  • ½ the reduction in hardware cost
  • -
- -
-
- -
- -
-
- -
-
- -
-
- -
- - - -
-
- -
- -
-
- -

{{ _('eBay adopted ClickHouse for their real time OLAP events (Logs + Metrics) infrastructure. The simplified architecture with ClickHouse allowed them to reduce their DevOps activity and troubleshooting, reduced the overall infrastructure by 90%%, and they saw a stronger integration with Grafana and ClickHouse for visualization and alerting.') }}

- - {{ _('Read the Case Study') }} - -
-
- -
    -
  • Reduced DevOps activity and troubleshooting
  • -
  • 10 times less hardware
  • -
  • Stronger integration with Grafana
  • -
- -
-
- -
- -
-
- -
-
- -
-
- -
- - - -
-
- -
- -
-
- -

{{ _('Cloudflare was having challenges scaling their CitusDB-based system which had a high TCO and maintenance costs due to the complex architecture. By moving their HTTP analytics data to ClickHouse they were able to scale to 8M requests per second, deleted 10’s of thousands of lines of code, reduced their MTTR, and saw a 7x improvement on customer queries per second they could serve.') }}

- - {{ _('Read the Case Study') }} - -
-
- -
    -
  • Scaled to 8M requests per second
  • -
  • Reduced their MTTR
  • -
  • 7x improvement on query throughput
  • -
- -
-
- -
- -
-
- -
-
- -
-
- -
- - - -
-
- -
- -
-
- -

{{ _('Spotify\'s A/B Experimentation platform is serving thousands of sub-second queries per second on petabyte-scale datasets with Clickhouse. They reduced the amount of low-variance work by an order of magnitude and enabled feature teams to self-serve insights by introducing a unified SQL interface for Data Platform and tools for automatic decision making for Experimentation.') }}

- - {{ _('Read the Case Study') }} - -
-
- -
    -
  • Reduced the amount of low-variance work
  • -
  • Enabled feature teams to self-serve insights
  • -
  • Tools for automatic decision making
  • -
- -
-
- -
- -
-
- -
-
- -
-
- -
- - - -
-
- -
- -
-
- -

{{ _('ClickHouse helps serve the Client Analytics platform for reporting, deep data analysis as well as advanced data science to provide Deutsche Bank’s front office a clear view on their client\'s activity and profitability.') }}

- - {{ _('Read the Case Study') }} - -
-
- -
    -
  • Platform for reporting and deep data analysis
  • -
  • Advanced data science
  • -
  • Provide clear view of client's activity and profitability
  • -
- -
-
- -
- -
-
- -
-
- -
-
diff --git a/website/templates/index/why.html b/website/templates/index/why.html deleted file mode 100644 index 58bb42ffd31..00000000000 --- a/website/templates/index/why.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
- -

- Why ClickHouse -

- -

- {{ _('Our feature rich and hardware efficient OLAP data management system is the right choice for your organization.') }} -

- -
-
- -

Performance

-

ClickHouse supports best in the industry query performance, while significantly reducing storage requirements through our innovative use of columnar storage and compression.

-
-
-
- -

Scalability

-

Battle tested in production, with linear horizontal scalability from single-server deployments to clusters with many thousands of nodes.

-
-
-
-
-
- -

Reliability

-

ClickHouse deployments feature best in class availability. There are no single points of failure, with the architecture supporting multi-master replication, performing effectively in multi-region configurations.

-
-
-
- -

Security

-

ClickHouse comes with enterprise grade security features and fail-safe mechanisms protecting against data corruption from application bugs and human errors.

-
-
-
-
-
diff --git a/website/templates/support/agreement-content.html b/website/templates/support/agreement-content.html deleted file mode 100644 index 4ca64e69599..00000000000 --- a/website/templates/support/agreement-content.html +++ /dev/null @@ -1,120 +0,0 @@ -
-
- -

This ClickHouse Subscription Agreement, including all referenced URLs, which are incorporated herein by reference (collectively, this “Agreement”), is entered into as of the date on which an applicable Order Form is fully executed (“Effective Date”), by and between the ClickHouse entity ("ClickHouse") set forth on such Order Form, and the entity identified thereon as the “Customer” (“Customer”).

- -

1. DEFINITIONS
Capitalized terms used herein have the meaning ascribed below, or where such terms are first used, as applicable.

- -

1.1 "Affiliate" means, with respect to a party, any entity that controls, is controlled by, or which is under common control with, such party, where "control" means ownership of at least fifty percent (50%) of the outstanding voting shares of the entity, or the contractual right to establish policy for, and manage the operations of, the entity.

- -

1.2 "Order Form" means an ordering document provided by ClickHouse pursuant to which Customer purchases Subscriptions under this Agreement.

- -

1.3 "Qualifying PO" means a purchase order issued by customer for the purpose of purchasing a Subscription, which (i) references the number of an applicable Order Form provided to Customer by ClickHouse and (ii) clearly states the purchase order is subject to the terms and conditions of this Agreement.

- -

1.4 "Software" means the ClickHouse software of the same name that is licensed for use under the Apache 2.0 license.

- -

1.5 "Subscription" means Customer's right, for a fixed period of time, to receive Support Services, as set forth in the applicable Order Form.

- -

1.6 "Subscription Term" means the period of time for which a Subscription is valid, as further described in Section 7.1 of this Agreement.

- -

1.7 "Support Services" means maintenance and support services for the Software, as more fully described in the Support Services Policy.

- -

1.8 "Support Services Policy" means ClickHouse's support services policy as further described at https://clickhouse.com/support/policy/. ClickHouse reserves the right to reasonably modify the Support Services Policy during a Subscription Term, provided however, ClickHouse shall not materially diminish the level of Support Services during a Subscription Term. The effective date of each version of the Support Services Policy will be stated thereon, and ClickHouse agrees to archive copies of each version, and make the same available to Customer upon written request (e-mail sufficient). The parties agree that the Support Services Policy is hereby incorporated into these terms and conditions by this reference.

- -

2. AGREEMENT SCOPE AND PERFORMANCE OF SUPPORT SERVICES

- -

2.1 Agreement Scope. This Agreement includes terms and conditions applicable to Subscriptions for Support Services purchased under each Order Form entered into by the parties under Section 2.2 below, which Support Services may be used by Customer solely for Internal use and in connection with the use case(s) set forth on the applicable Order Form.

- -

2.2 Order for Support Services Subscriptions. Orders for Subscriptions may be placed by Customer through (1) the execution of Order Forms with ClickHouse or (2) issuance by Customer of a Qualifying PO, which will be deemed to constitute, for the purposes of this Agreement, the execution by Customer of the referenced Order Form.

- -

2.3 Affiliates. The parties agree that their respective Affiliates may also conduct business under this Agreement by entering into Order Forms, which in some cases may be subject to such additional and/or alternative terms and conditions to those contained in this Agreement as may be mutually agreed in the Order Form or an attachment thereto, as applicable. Accordingly, where Affiliates of the parties conduct business hereunder, references to Customer herein shall include any applicable Customer Affiliate, and references to ClickHouse herein shall include any applicable ClickHouse Affiliate. The parties agree that where either of them or one of their Affiliates enters into an Order Form with an Affiliate of the other party, that such Affiliate shall be solely responsible for performing all of its obligations under this Agreement in connection with such Order Form.

- -

2.4 Performance of Support Services. Subject to Customer’s payment of all fees (as set forth in an applicable Order Form), ClickHouse will provide Customer with Support Services for the Software during an applicable Subscription Term in accordance with this Agreement and the Support Services Policy. Customer will reasonably cooperate with ClickHouse in connection with the Support Services, including, without limitation, by providing ClickHouse reasonable remote access to its installations, server cloud (or hosting provider), Software and equipment in connection therewith. Further, Customer will designate appropriately skilled personnel to serve as ClickHouse’s central contacts in connection with the use, operation and support of the Software. Customer understands that ClickHouse’s performance of Support Services is dependent in part on Customer’s cooperation, actions, and performance. ClickHouse shall not be responsible for any delays or interruptions in its performance of Support Services, or any claims arising therefrom, due to Customer’s lack of cooperation or acts or omissions. ClickHouse may use its Affiliates or subcontractors to provide Support Services to Customer, provided that ClickHouse remains responsible to Customer for performance.

- -

3. PAYMENT AND TAXES

- -

3.1 Payment. ClickHouse will invoice Customer for the fees due under each Order Form or otherwise under this Agreement, and Customer will pay such fees within thirty (30) days after receipt of an applicable invoice. All invoices will be paid in the currency set forth on the applicable Order Form. Payments will be made without right of set-off or chargeback. Except as otherwise expressly provided in this Agreement, any and all payments made by Customer pursuant to this Agreement or any Order Form are non-refundable, and all commitments to make any payments hereunder or under any Order Form are non-cancellable.

- -

3.2 Taxes. All fees stated on an Order Form are exclusive of any applicable sales, use, value added and excise taxes levied upon the delivery or use of the taxable components, if any, of any Subscription purchased by Customer under this Agreement (collectively, “Taxes”). Taxes do not include any taxes on the net income of ClickHouse or any of its Affiliates. Unless Customer provides ClickHouse a valid state sales/use/excise tax exemption certificate or Direct Pay Permit, and provided that ClickHouse separately states any such taxes in the applicable invoice, Customer will pay and be solely responsible for all Taxes. If Customer is required by any foreign governmental authority to deduct or withhold any portion of the amount invoiced for the delivery or use of Support Services under this Agreement, Customer shall increase the sum paid to ClickHouse by an amount necessary for the total payment to ClickHouse equal to the amount originally invoiced.

- -

4. CONFIDENTIAL INFORMATION

- -

4.1 Confidential Information. Both parties acknowledge that, in the course of performing this Agreement, they may obtain information relating to products (such as goods, services, and software) of the other party, or relating to the parties themselves, which is of a confidential and proprietary nature ("Confidential Information"). Confidential Information includes materials and all communications concerning ClickHouse's or Customer's business and marketing strategies, including but not limited to employee and customer lists, customer profiles, project plans, design documents, product strategies and pricing data, research, advertising plans, leads and sources of supply, development activities, design and coding, interfaces with the Products, anything provided by either party to the other in connection with the Products and/or Support Services provided under this Agreement, including, without limitation, computer programs, technical drawings, algorithms, know-how, formulas, processes, ideas, inventions (whether patentable or not), schematics and other technical plans and other information of the parties which by its nature can be reasonably expected to be proprietary and confidential, whether it is presented in oral, printed, written, graphic or photographic or other tangible form (including information received, stored or transmitted electronically) even though specific designation as Confidential Information has not been made. Confidential Information also includes any notes, summaries, analyses of the foregoing that are prepared by the receiving party.

- -

4.2 Non-use and Non-disclosure. The parties shall at all times, both during the Term and thereafter keep in trust and confidence all Confidential Information of the other party using commercially reasonable care (but in no event less than the same degree of care that the receiving party uses to protect its own Confidential Information) and shall not use such Confidential Information other than as necessary to carry out its duties under this Agreement, nor shall either party disclose any such Confidential Information to third parties other than to Affiliates or as necessary to carry out its duties under this Agreement without the other party's prior written consent, provided that each party shall be allowed to disclose Confidential Information of the other party to the extent that such disclosure is approved in writing by such other party, or necessary to enforce its rights under this Agreement.

- -

4.3 Non-Applicability. The obligations of confidentiality shall not apply to information which (i) has entered the public domain or is otherwise publicly available, except where such entry or availability is the result of a party's breach of this Agreement; (ii) prior to disclosure hereunder was already in the receiving party's possession without restriction as evidenced by appropriate documentation; (iii) subsequent to disclosure hereunder is obtained by the receiving party on a non-confidential basis from a third party who has the right to disclose such information; or (iv) was developed by the receiving party without any use of any of the Confidential Information as evidenced by appropriate documentation.

- -

4.4 Terms of this Agreement. Except as required by law or governmental regulation, neither party shall disclose, advertise, or publish the terms and conditions of this Agreement without the prior written consent of the other party, except that either party may disclose the terms of this Agreement to potential acquirers, referral partners involved in an applicable transaction, accountants, attorneys and Affiliates pursuant to the terms of a non-disclosure or confidentiality agreement. If Customer is using a third party provider to host a Product, then such provider may also receive, subject to a confidentiality obligation, information related to the terms of this Agreement or Customer’s usage of the applicable Product.

- -

4.5 Disclosure Required by Law. Notwithstanding anything to the contrary herein, each party may disclose the other party's Confidential Information in order to comply with applicable law and/or an order from a court or other governmental body of competent jurisdiction, and, in connection with compliance with such an order only, if such party: (i) unless prohibited by law, gives the other party prior written notice to such disclosure if the time between that order and such disclosure reasonably permits or, if time does not permit, gives the other party written notice of such disclosure promptly after complying with that order and (ii) fully cooperates with the other party, at the other party's cost and expense, in seeking a protective order, or confidential treatment, or taking other measures to oppose or limit such disclosure. Each party must not release any more of the other party's Confidential Information than is, in the opinion of its counsel, reasonably necessary to comply with an applicable order.

- -

5. WARRANTIES AND DISCLAIMER OF WARRANTIES

- -

5.1 Limited Support Services Performance Warranty. ClickHouse warrants that it will perform the Support Services in a professional, workmanlike manner, consistent with generally accepted industry practice, and in accordance with the Support Services Policy. In the event of a breach of the foregoing warranty, ClickHouse’s sole obligation, and Customer’s exclusive remedy, shall be for ClickHouse to re-perform the applicable Support Services.

- -

5.2 Warranty Disclaimer. EXCEPT AS SET FORTH IN SECTION 5.1 ABOVE, THE SUPPORT SERVICES ARE PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND AND CLICKHOUSE MAKES NO ADDITIONAL WARRANTIES, WHETHER EXPRESSED, IMPLIED OR STATUTORY, REGARDING OR RELATING TO THE SUPPORT SERVICES OR ANY MATERIALS FURNISHED OR PROVIDED TO CUSTOMER UNDER THIS AGREEMENT. TO THE MAXIMUM EXTENT PERMITTED UNDER APPLICABLE LAW, CLICKHOUSE SPECIFICALLY DISCLAIMS ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT WITH RESPECT TO THE SUPPORT SERVICES AND ANY MATERIALS FURNISHED OR PROVIDED TO CUSTOMER UNDER THIS AGREEMENT. CUSTOMER UNDERSTANDS AND AGREES THAT THE SUPPORT SERVICES AND ANY MATERIALS FURNISHED OR PROVIDED TO CUSTOMER UNDER THIS AGREEMENT ARE NOT DESIGNED OR INTENDED FOR USE IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT, WEAPONS SYSTEMS, OR LIFE SUPPORT SYSTEMS.

- -

6. LIMITATION OF LIABILITY

- -

6.1 Excluded Damages. IN NO EVENT SHALL CUSTOMER OR CLICKHOUSE, OR THEIR RESPECTIVE AFFILIATES, BE LIABLE FOR ANY LOSS OF PROFITS, LOSS OF USE, BUSINESS INTERRUPTION, LOSS OF DATA, COST OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY PUNITIVE, INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND IN CONNECTION WITH OR ARISING OUT OF THE PERFORMANCE OF OR FAILURE TO PERFORM THIS AGREEMENT, WHETHER ALLEGED AS A BREACH OF CONTRACT OR TORTIOUS CONDUCT, INCLUDING NEGLIGENCE, EVEN IF A PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

- -

6.2 Damages Cap. EXCEPT WITH RESPECT TO (I) A PARTY’S BREACH OF ITS OBLIGATIONS UNDER SECTION 4, (II) AMOUNTS PAYABLE BY CUSTOMER UNDER SECTION 3 OF THIS AGREEMENT AND EACH ORDER FORM, AND (III) CUSTOMER'S VIOLATIONS OF THE USE RESTRICTIONS SET FORTH IN THIS AGREEMENT, IN NO EVENT SHALL CLICKHOUSE'S OR CUSTOMER’S TOTAL, CUMULATIVE LIABILITY UNDER ANY ORDER FORM EXCEED THE AMOUNT PAID OR PAYABLE BY CUSTOMER TO CLICKHOUSE UNDER THIS AGREEMENT FOR THE AFFECTED SUPPORT SERVICES DELIVERED AND/OR MADE AVAILABLE TO CUSTOMER UNDER SUCH ORDER FORM FOR THE TWELVE (12) MONTH PERIOD IMMEDIATELY PRIOR TO THE FIRST EVENT GIVING RISE TO LIABILITY.

- -

6.3 Basis of the Bargain. THE ALLOCATIONS OF LIABILITY IN THIS SECTION 6 REPRESENT THE AGREED AND BARGAINED FOR UNDERSTANDING OF THE PARTIES, AND THE COMPENSATION OF CLICKHOUSE FOR THE SUPPORT SERVICES PROVIDED HEREUNDER REFLECTS SUCH ALLOCATIONS. THE FOREGOING LIMITATIONS, EXCLUSIONS AND DISCLAIMERS WILL APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, EVEN IF ANY REMEDY FAILS IN ITS ESSENTIAL PURPOSE.

- -

7. TERM AND TERMINATION

- -

7.1 Subscription Term. The initial Subscription Term for each Subscription will commence and expire in accordance with the start date and end date set forth on the applicable Order Form, unless earlier terminated in in accordance with Section 7.3 below. Thereafter, each Subscription may be renewed for additional one (1) year periods upon the mutual written agreement of the parties. The initial Subscription Term, plus any subsequent renewal Subscription Term shall be the "Subscription Term".

- -

7.2 Agreement Term. This Agreement will commence on the Effective Date and, unless earlier terminated in accordance with Section 7.3(b) below, continue in force and effect for a period of two (2) years. Thereafter, the term of this Agreement shall automatically renew for additional one (1) year periods unless either party gives written notice to the other of its intention not to renew the Agreement at least thirty (30) days prior to the expiration of the then-current term. The initial term of this Agreement, plus any subsequent renewal term, shall be the "Term" of this Agreement. Notwithstanding any expiration of this Agreement, its terms will continue to apply to any Subscription that has not been terminated or for which the Subscription Term has not expired.

- -

7.3 Termination.

-
    -
  1. Subscriptions. Each party may terminate a Subscription upon giving notice in writing to the other party if the non-terminating party commits a material breach of this Agreement with respect to such Subscription, and has failed to cure such breach within thirty (30) days following a request in writing from the notifying party to do so. Upon the termination or expiration of a Subscription, the rights and obligations of the parties with respect thereto will, subject to Section 7.4 below, cease, provided that termination of a Subscription under this subsection (a) will not result in termination of any other Subscriptions.
  2. -
  3. Agreement. Either party may terminate this Agreement upon giving notice in writing to the other party if the non-terminating party commits a material breach of this Agreement with respect to any active Subscriptions hereunder, and has failed to cure such breach within thirty (30) days following a request in writing from the notifying party to do so. For the avoidance of doubt, termination of this Agreement under this subsection (b) will result in the termination of all Subscriptions and Order Forms.
  4. -
- -

7.4 Survival. Upon the expiration or termination of an Order Form or this Agreement, (i) Customer shall have no further rights under any affected Subscription(s); and (ii) any payment obligations accrued under Section 3, as well as the provisions of Sections 1, 4, 5, 6, 7, 7.4 and 9 of this Agreement will survive such expiration or termination.

- -

8. GENERAL

- -

8.1 Anti-Corruption. Each party acknowledges that it is aware of, understands and has complied and will comply with, all applicable U.S. and foreign anti-corruption laws, including without limitation, the U.S. Foreign Corrupt Practices Act of 1977 and the U.K. Bribery Act of 2010, and similarly applicable anti-corruption and anti-bribery laws ("Anti-Corruption Laws"). Each party agrees that no one acting on its behalf will give, offer, agree or promise to give, or authorize the giving directly or indirectly, of any money or other thing of value, including travel, entertainment, or gifts, to anyone as an unlawful inducement or reward for favorable action or forbearance from action or the exercise of unlawful influence (a) to any governmental official or employee (including employees of government-owned and government-controlled corporations or agencies or public international organizations), (b) to any political party, official of a political party, or candidate, (c) to an intermediary for payment to any of the foregoing, or (d) to any other person or entity in a corrupt or improper effort to obtain or retain business or any commercial advantage, such as receiving a permit or license, or directing business to any person. Improper payments, provisions, bribes, kickbacks, influence payments, or other unlawful provisions to any person are prohibited under this Agreement.

- -

8.2 Assignment. Neither party may assign this Agreement, in whole or in part, without the prior written consent of the other party, provided that no such consent will be required to assign this Agreement in its entirety to (i) an Affiliate that is able to satisfy the obligations of the assigning party under this Agreement or (ii) a successor in interest in connection with a merger, acquisition or sale of all or substantially all of the assigning party's assets. Any assignment in violation of this Section shall be void, ab initio, and of no effect. Subject to the foregoing, this Agreement is binding upon, inures to the benefit of and is enforceable by, the parties and their respective permitted successors and assigns.

- -

8.3 Attorneys' Fees. If any action or proceeding, whether regulatory, administrative, at law or in equity is commenced or instituted to enforce or interpret any of the terms or provisions of this Agreement, the prevailing party in any such action or proceeding shall be entitled to recover its reasonable attorneys' fees, expert witness fees, costs of suit and expenses, in addition to any other relief to which such prevailing party may be entitled. As used herein, "prevailing party" includes without limitation, a party who dismisses an action for recovery hereunder in exchange for payment of the sums allegedly due, performance of covenants allegedly breached, or consideration substantially equal to the relief sought in the action.

- -

8.4 California Consumer Privacy Act (CCPA). ClickHouse is a “Service Provider” as such term is defined under §1798.140(v) of the CCPA. As such ClickHouse shall not retain, use or disclose any personal information (as defined in the CCPA) received from Customer during the Term of this Agreement for any purpose other than the specific purpose of providing the Support Services specified in this Agreement or for such other business purpose as is specified in this Agreement.

- -

8.5 Customer Identification. ClickHouse may identify Customer as a user of the Support Services, on its website, through a press release issued by ClickHouse and in other promotional materials.

- -

8.6 Feedback. Customer, Customer’s Affiliates, and their respective agents, may volunteer feedback to ClickHouse, and/or its Affiliates, about the Support Services (“Feedback”). ClickHouse and its Affiliates shall be irrevocably entitled to use that Feedback, for any purpose and without any duty to account. provided that, in doing so, they may not breach their obligations of confidentiality under Section 4 of this Agreement.

- -

8.7 Force Majeure. Except with respect to payment obligations, neither party will be liable for, or be considered to be in breach of, or in default under, this Agreement, as a result of any cause or condition beyond such party's reasonable control.

- -

8.8 Governing Law, Jurisdiction and Venue.

- -
    -
  1. Customers in California. If Customer is located in California (as determined by the Customer address on the applicable Order Form), this Agreement will be governed by the laws of the State of California, without regard to its conflict of laws principles, and all suits hereunder will be brought solely in Federal Court for the Northern District of California, or if that court lacks subject matter jurisdiction, in any California State Court located in Santa Clara County.
  2. -
  3. Customers Outside of California. If Customer is located anywhere other than California (as determined by the Customer address on the applicable Order Form), this Agreement will be governed by the laws of the State of Delaware, without regard to its conflict of laws principles, and all suits hereunder will be brought solely in Federal Court for the District of Delaware, or if that court lacks subject matter jurisdiction, in any Delaware State Court located in Wilmington, Delaware.
  4. - -
  5. All Customers. This Agreement shall not be governed by the 1980 UN Convention on Contracts for the International Sale of Goods. The parties hereby irrevocably waive any and all claims and defenses either might otherwise have in any action or proceeding in any of the applicable courts set forth in (a) or (b) above, based upon any alleged lack of personal jurisdiction, improper venue, forum non conveniens, or any similar claim or defense.
  6. - -
  7. Equitable Relief. A breach or threatened breach, by either party of Section 4 may cause irreparable harm for which damages at law may not provide adequate relief, and therefore the non-breaching party shall be entitled to seek injunctive relief without being required to post a bond.
  8. - -
- -

8.9 Non-waiver. Any failure of either party to insist upon or enforce performance by the other party of any of the provisions of this Agreement or to exercise any rights or remedies under this Agreement will not be interpreted or construed as a waiver or relinquishment of such party's right to assert or rely upon such provision, right or remedy in that or any other instance.

- -

8.10 Notices. Any notice or other communication under this Agreement given by either party to the other will be deemed to be properly given if given in writing and delivered in person or by e-mail, if acknowledged received by return e-mail or followed within one day by a delivered or mailed copy of such notice, or if mailed, properly addressed and stamped with the required postage, to the intended recipient at its address specified on an Order Form. Notices to ClickHouse may also be sent to legal@ClickHouse.com. Either party may from time to time change its address for notices under this Section by giving the other party notice of the change in accordance with this Section.

- -

8.11 Relationship of the Parties. The relationship of the parties hereunder shall be that of independent contractors, and nothing herein shall be deemed or construed to create any employment, agency or fiduciary relationship between the parties. Each party shall be solely responsible for the supervision, direction, control and payment of its personnel, including, without limitation, for taxes, deductions and withholdings, compensation and benefits, and nothing herein will be deemed to result in either party having an employer-employee relationship with the personnel of the other party.

- -

8.12 Severability. If any provision of this Agreement is held to be invalid or unenforceable, the remaining portions will remain in full force and effect and such provision will be enforced to the maximum extent possible so as to give effect the intent of the parties and will be reformed to the extent necessary to make such provision valid and enforceable.

- -

8.13 Entire Agreement; Amendment. This Agreement, together with any Order Forms executed by the parties, and the Support Services Policy, each of which is hereby incorporated herein by this reference, constitutes the entire agreement between the parties concerning the subject matter hereof, and it supersedes, and its terms govern, all prior proposals, agreements, or other communications between the parties, oral or written, regarding such subject matter. This Agreement may be executed in any number of counterparts, each of which when so executed and delivered shall be deemed an original, and all of which together shall constitute one and the same agreement. Execution of a scanned copy will have the same force and effect as execution of an original, and a scanned signature will be deemed an original and valid signature. In the event of any conflict between the terms and conditions of any of the foregoing documents, the conflict shall be resolved based on the following order of precedence: (i) an applicable Order Form (but only for the transaction thereunder), (ii) an applicable Addendum (including any exhibits, attachments and addenda thereto), (iii) this Agreement, and (iv) the Support Services Policy. For the avoidance of doubt, the parties hereby expressly acknowledge and agree that if Customer issues any purchase orders or similar documents in connection with its purchase of a Subscription, it shall do so only for the purpose of Section 2.2(2) or for its own internal, administrative purposes and not with the intent to provide any contractual terms. By entering into this Agreement, whether prior to or following receipt of Customer's purchase order or any similar document, the parties are hereby expressly showing their intention not to be contractually bound by the contents of any such purchase order or similar document, which are hereby deemed rejected and extraneous to this Agreement, and ClickHouse's performance of this Agreement shall not amount to: (i) an acceptance by conduct of any terms set out or referred to in the purchase order or similar document; (ii) an amendment of this Agreement, nor (iii) an agreement to amend this Agreement. This Agreement shall not be modified except by a subsequently dated, written amendment that expressly amends this Agreement and which is signed on behalf of ClickHouse and Customer by their duly authorized representatives. The parties agree that the terms and conditions of this Agreement are a result of mutual negotiations. Therefore, the rule of construction that any ambiguity shall apply against the drafter is not applicable and will not apply to this Agreement. Any ambiguity shall be reasonably construed as to its fair meaning and not strictly for or against one party regardless of who authored the ambiguous language.

- - -
-
\ No newline at end of file diff --git a/website/templates/support/agreement-hero.html b/website/templates/support/agreement-hero.html deleted file mode 100644 index ea97fb7729a..00000000000 --- a/website/templates/support/agreement-hero.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
-
- -

- {{ _('Clickhouse, Inc.
Subscription Agreement') }} -

- -
-
\ No newline at end of file diff --git a/website/templates/support/form.html b/website/templates/support/form.html deleted file mode 100644 index b4ba3a68cdf..00000000000 --- a/website/templates/support/form.html +++ /dev/null @@ -1,79 +0,0 @@ -
-
- -
- - - - -
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
- -
- -
- -
- -

Severity Classification Level Definitions

- - - - - - - - - - - - - - - - - - - - - - - - - - -
Severity Classification LevelOrganizational ImpactDefinition
Severity 1Critical Business ImpactClickHouse DBMS LTS or stable software release in production use is not functioning or is stopped or severely impacted so that Customer cannot reasonably continue use of DBMS and no Workaround is available
Severity 2Major Business ImpactClickHouse DBMS LTS or stable software release is functioning inconsistently causing significantly impaired Customer usage and productivity, such as periodic work stoppages and feature crashes
Severity 3Minor Business Impact or General QuestionsClickHouse DBMS LTS or stable software release is functioning inconsistently causing slightly impaired Customer usage and productivity but Customer can work around such inconsistency or impairment, or Customer has a question or enhancement for the DBMS or ClickHouse not determined an Error
- -
- -
-
diff --git a/website/templates/support/hero.html b/website/templates/support/hero.html deleted file mode 100644 index 22b69256471..00000000000 --- a/website/templates/support/hero.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
-
- -

- {{ _('Support Services') }} -

- -
-
diff --git a/website/templates/support/overview.html b/website/templates/support/overview.html deleted file mode 100644 index 429b8d23c5b..00000000000 --- a/website/templates/support/overview.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
- -

- Enter Your Support Case Details -

- -

- Describe the problem you're having. Once you hit submit, we'll log a case for you and kick off an email thread to collaborate on your issue. If you have an attachment (e.g. log files) to share, respond to the case email thread and include as an attachment. -

- -
-
diff --git a/website/templates/support/policy-content.html b/website/templates/support/policy-content.html deleted file mode 100644 index d6efae2fb67..00000000000 --- a/website/templates/support/policy-content.html +++ /dev/null @@ -1,55 +0,0 @@ -
-
-

Effective Date: January 28, 2022

- -

This ClickHouse Support Services Policy sets forth ClickHouse’s policy for the provision of Support Services to its Subscription customers (each a “Customer”). Capitalized terms used but not defined herein, have the definition set forth in the applicable Subscription Agreement between ClickHouse and Customer (the “Agreement”).

- -
    -
  1. Defined Terms -
      -
    1. "Business Day" means Monday through Friday other than a day designated from time to time as a national holiday in the place from which Support Services may be provided.
    2. -
    3. Long-Term Supported Release” means the official release for the Software which has been designated as a release for long term support. ClickHouse designates its as Long-Term Supported Releases by including the letters“lts” in the release number, which are typically March and August releases.
    4. -
    5. Regular Stable Sequential Release” means the official software release for the Software which has not been designated as a Long Term Supported Release.
    6. -
    7. Support” means technical support by telephone, chat, or email provided by ClickHouse to a Support Contact concerning a Support Incident.
    8. -
    9. Support Contact” means a single named individual who is authorized to contact ClickHouse to use the Support Services.
    10. -
    11. "Support Incident" means a single issue or error with the Software that is raised with ClickHouse by a Support Contact. Each Support Incident will be assigned a unique ID by ClickHouse. In the situation where multiple similar or equivalent cases are opened for a single Support Incident, ClickHouse may choose to consolidate these into a single support case, in which case it shall promptly notify Customer.
    12. -
    -
  2. -
  3. Scope and Performance of Support Services. -

    The scope of the Support Services provided to Customer includes general assistance and support regarding the installation of the Software and basic technical configuration of the Software, as well as operational or development assistance on how to use the Software. ClickHouse shall use commercially reasonable efforts to meet the applicable target response times set forth in Section 3 below. Customer acknowledges that the time required for resolution of issues may vary depending on the specific circumstances of each problem, including, without limitation, the nature of the incident/problem, the extent and accuracy of information available about the incident/problem, and the level of Customer's cooperation and responsiveness in providing materials, information, access and support reasonably required by ClickHouse to achieve problem resolution.

    -
      -
    • Normal Business Hours: -
        -
      • 24x7x365 for Severity 1 Issues
      • -
      • Monday 08:00 CEST through Friday 17:00 US Pacific time for Severity 2 and Severity 3 issues
      • -
      -
    • -
    • Number of Support Contacts: unlimited
    • -
    • Emergency Patches: yes
    • -
    • Annual Support Incidents: unlimited
    • -
    -
  4. -
  5. Severity Levels and ClickHouse Efforts. -

    Severity Level 1

    -

    A Severity Level 1 issue is a major production error within the software that severely impacts the Customer's use of the software for production purposes, such as the loss of production data or where production systems are not functioning and no work-around exists. ClickHouse will use continuous efforts during applicable Normal Business Hours to provide a resolution for any Level 1 issues as soon as is commercially reasonable.

    -

    Severity Level 2

    -

    A Severity Level 2 issue is an error within the software where the customer's system is functioning for production purposes but in a reduced capacity, such as a problem that is causing significant impact to portions of the customer's business operations and productivity, or where the software is exposed to potential loss or interruption of service. ClickHouse will use continuous efforts during the Normal Business Hours to provide a resolution for any Severity Level 2 issues.

    -

    Severity Level 3

    -

    A Severity Level 3 issue is a medium-to-low impact error that involves partial and/or non-critical loss of functionality for production purposes or development purposes, such as a problem that impairs some operations but allows the customer's operations to continue to function. Errors for which there is limited or no loss or functionality or impact to the customer's operation and for which there is an easy work-around qualify as Severity Level 3. General questions are also Severity Level issues. ClickHouse will use reasonable efforts to provide a resolution for any Severity Level 3 issues in time for an upcoming release of the software. All inbound production email cases shall have an initial status of Severity Level 3.

    -
  6. -
  7. Customer Obligations. -

    Customer agrees to provide ClickHouse with reasonable: (i) detail of the nature of and circumstances surrounding the issue, (ii) access to Customer's environment as necessary to enable ClickHouse to provide Support Services; and (iii) cooperation in the diagnosis and resolution of any issues.

    -
  8. -
  9. Supported Versions. -

    Notwithstanding anything else, ClickHouse will support the current Regular Stable Release in conjunction with the two (2) prior Regular Stable Releases of the Software or a minimum period of three (3) months from the date of the current Regular Stable Release. ClickHouse will support the current Long-Term Supported Release in conjunction with the one (1) prior Long-Term Supported Release of the Software or a minimum period of one (1) year from the date of the current Long-Term Supported Release.

    - -
  10. -
  11. Support Service Exclusions. -

    ClickHouse will have no obligation to provide Support Services to Customer in the event that (i) the Software has been changed, modified or damaged by Customer or anyone other than ClickHouse, (ii) the problem is caused by Customer's negligence, misconduct, or misuse of the Software, a hardware malfunction, or other causes beyond the reasonable control of ClickHouse, (iii) the problem is due to third party software, (iv) the Software is being hosted by a third party that is offering the Software as a service (v) Customer has not installed or implemented any Software releases made generally available or is not running a then supported version of the Software as provided by ClickHouse or (vi) information requested by Customer could reasonably be expected to assist in the development, deployment, enablement and/or maintenance of any non-ClickHouse software that competes with ClickHouse's commercial software products. The Support Services do not cover the support of any third party software which integrates with the Software or the investigation into a potential or actual security incident in a Customer environment, including but not limited to the analysis and response to security events and signals. In addition, the Support Services do not include the following: (a) use of any version of a Software that is not designated as a production release (such as a milestone or release candidate or code contained in the sandbox or any other repository that is not packaged into a production release distribution); (b) Customer's failure to comply with operating instructions contained in the documentation for the Software; (c) installation, configuration, management and operation of Customer's applications; (d) APIs, interfaces or data formats other than those included with the Software; or (e) any training.

    -
  12. - -
-
-
\ No newline at end of file diff --git a/website/templates/support/policy-hero.html b/website/templates/support/policy-hero.html deleted file mode 100644 index dc551dc434c..00000000000 --- a/website/templates/support/policy-hero.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
-
- -

- {{ _('ClickHouse Support Services Policy') }} -

- -
-
\ No newline at end of file diff --git a/website/templates/trademark-policy/content.html b/website/templates/trademark-policy/content.html deleted file mode 100644 index 5039d06cea3..00000000000 --- a/website/templates/trademark-policy/content.html +++ /dev/null @@ -1,238 +0,0 @@ -
-
- -

- Version 1.0 dated January 17, 2022 -

- -

- This trademark policy was prepared to help you understand how to use the trademarks, service marks and logos of ClickHouse, Inc., with ClickHouse, Inc.'s open-source, high performance columnar OLAP database management system for real-time analytics using SQL. -

- -

- While some of our software is available under a free and open source software license, that copyright license does not include a license to use our trademark and logo ("marks"), and this policy is intended to explain how to use our marks consistent with background law and community expectation. -

- -

- Trademark rights protect everyone, not just the company that owns them. They help us make sure that potential customers and users understand who is providing software and services, and who is responsible for their quality control. -

- -

- This Policy covers: -

- -
    -
  1. Our word trademark and service mark: ClickHouse
  2. -
  3. Our logo:
  4. -
- -

- -

- -

- This policy covers all of ClickHouse, Inc.'s marks, whether they are registered or not. -

- -

- 1. GENERAL GUIDELINES -

- -

- Whenever you use one of our marks, you must always do so in a way that does not mislead anyone about what they are getting and from whom. For example, you cannot say you are distributing the ClickHouse software when you're distributing a modified version of it, because recipients may not understand the differences between your modified version and our own. -

- -

- You also cannot use our logo on your website in a way that suggests that your website is an official website or that we endorse your website. -

- -

- You can, though, say you like the ClickHouse software, that you participate in the ClickHouse community, that you are providing an unmodified version of the ClickHouse software. -

- -

- You may not use or register our marks, or variations of them as part of your own trademark, service mark, domain name, corporate name, trade name, product name or service name. -

- -

- Trademark law does not allow your use of names or trademarks that are too similar to ours. You therefore may not use an obvious variation of any of our marks or any phonetic equivalent, foreign language equivalent, takeoff, or abbreviation for a similar or compatible product or service. We would consider the following too similar to one of our marks: -

- -
    -
  • MyClickHouse
  • -
  • ClickHouseConnector
  • -
  • ClickHouse Cloud
  • -
  • Managed ClickHouse
  • -
  • Hosted ClickHouse
  • -
- -

- 2. ACCEPTABLE USES -

- -

- Unmodified Code -

- -

- When you redistribute an unmodified copy of our software -- the exact form in which we make it available -- you must retain the marks we have placed on the software to identify your redistribution. -

- -

- Modified Code -

- -

- If you distribute a modified version of our software, you must remove all of our logos from it, including from our source trees. You may use our word marks, but not our logos, to truthfully describe the origin of the software that you are providing. For example, if the code you are distributing is a modification of our software, you may say, "This software is derived from the source code for ClickHouse software." -

- -

- Statements About Compatibility -

- -

- You may use the word mark, but not the logo, to truthfully describe the relationship between your software and ours. Any other use may imply that we have certified or approved your software. If you wish to use our logo, please contact us at the email address below to discuss license terms. -

- -

- Naming Compatible Products -

- -

- If you wish to describe your product or service with reference to the ClickHouse software, here are the conditions under which you may do so. You may call your software XYZ (where XYZ is your product name) for ClickHouse only if: -

- -
    -
  • All versions of the ClickHouse software you deliver with your product are the exact binaries provided by us.
  • -
  • Your product is fully compatible with the then-current APIs for the ClickHouse software.
  • -
  • You use the following legend in marketing materials or product descriptions: "ClickHouse is a trademark of ClickHouse, Inc. https://clickhouse.com"
  • -
- -

- Managed Services -

- -

- We consider managed services provided by others to be a separate product from our own. That's because the quality of managed services depends on a lot more than software. So, you may refer to your own managed service providing ClickHouse software as long as your use is consistent with all relevant licenses from ClickHouse, Inc. and: -

- -
    -
  • You do not use the logo on your managed service.
  • -
  • Your product is fully compatible with the then current APIs for the ClickHouse software.
  • -
  • You make it clear that ClickHouse, Inc. is not the source of the service.
  • -
  • You do not imply that ClickHouse, Inc. sponsors or approves your service.
  • -
- -

- Therefore, you might say, "XYZ is a fully managed cloud offering based on ClickHouse software", if you use the following legend in marketing materials or product descriptions: "ClickHouse is a registered trademark of ClickHouse, Inc. https://clickhouse.com" -

- -

- Professional Services -

- -

- If you provide maintenance, consulting, training or support for ClickHouse software, you must make it clear that the services are not provided by ClickHouse. For example, you can offer "Support for ClickHouse software" but not "ClickHouse Support." -

- -

- User Groups -

- -

- You can use the word mark as part of your user group name provided that: -

- -
    -
  • The main focus of the group is our software
  • -
  • The group does not make a profit
  • -
  • Any charge to attend meetings are to cover the cost of the venue, food and drink only
  • -
- -

- You are not authorized to conduct a conference using our marks. -

- -

- No Domain Names -

- -

- You must not register any domain that includes our word mark or any variant or combination of it. If you want to register a domain name for a user group that meets the above criteria, please contact us at the email address below. -

- -

- 3. HOW TO DISPLAY OUR MARKS -

- -

- When you have the right to use our marks, here is how to display them. -

- -

- Trademark Marking and Legends -

- -

- The first or most prominent mention of a mark on a webpage, document, or documentation should be accompanied by a symbol indicating whether the mark is a registered trademark ("®") or an unregistered trademark ("™"). If you don't know which applies, contact us. -

- -

- Place the following notice at the foot of the page where you have used the mark: "ClickHouse is a registered trademark of ClickHouse, Inc. https://clickhouse.com" -

- -

- Use of Trademarks in Text -

- -

- Always use trademarks in their exact form with the correct spelling, neither abbreviated, hyphenated, or combined with any other word or words. -

- -

- Unacceptable: ClickHouse-DB -

- -

- Don't pluralize a trademark. -

- -

- Unacceptable: I have seventeen ClickHouses running in my system. -

- -

- Always use a trademark as an adjective modifying a noun. -

- -

- Unacceptable: This is a ClickHouse.
- Acceptable: This is a ClickHouse software application. -

- -

- Use of Logo -

- -

- You may not change our logo except to scale it. This means you may not add decorative elements, change the colors, change the proportions, distort it, add elements, or combine it with other logos. -

- -

- However, when the context requires the use of black-and-white graphics and the logo is color, you may reproduce the logo in a manner that produces a black-and-white image. -

- -

- 4. QUESTIONS -

- -

- If you have questions about this policy, wish to discuss a license to use our trademarks that is not consistent with this policy, or want to discuss a violation of this policy, please contact us at trademarks@clickhouse.com. -

- -

- These guidelines are based on the Model Trademark Guidelines, available at http://www.modeltrademarkguidelines.org., used under a Creative Commons Attribution 3.0 Unported license: https://creativecommons.org/licenses/by/3.0/deed.en_US. -

- -
-
diff --git a/website/templates/trademark-policy/hero.html b/website/templates/trademark-policy/hero.html deleted file mode 100644 index 16bacfd213e..00000000000 --- a/website/templates/trademark-policy/hero.html +++ /dev/null @@ -1,11 +0,0 @@ -
-
-
- -

- {{ _('ClickHouse') }}
- {{ _('Trademark Policy') }} -

- -
-
diff --git a/website/yandex_75d07a6cd96f49df.html b/website/yandex_75d07a6cd96f49df.html deleted file mode 100644 index 8febd2e3759..00000000000 --- a/website/yandex_75d07a6cd96f49df.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - Verification: 75d07a6cd96f49df - diff --git a/website/yandex_fffaa30ee00426bb.html b/website/yandex_fffaa30ee00426bb.html deleted file mode 100644 index fdecc0ad8aa..00000000000 --- a/website/yandex_fffaa30ee00426bb.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - Verification: fffaa30ee00426bb - \ No newline at end of file From bee41125389a4e910a9b52372bf2f2eda9c693be Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 8 Aug 2022 01:10:48 +0200 Subject: [PATCH 403/672] Update README --- website/README.md | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/website/README.md b/website/README.md index 0c9b13676b5..67937044ba0 100644 --- a/website/README.md +++ b/website/README.md @@ -1,28 +1 @@ -ClickHouse website is built alongside it's documentation via [docs/tools](https://github.com/ClickHouse/ClickHouse/tree/master/docs/tools), see [README.md there](https://github.com/ClickHouse/ClickHouse/tree/master/docs/tools/README.md). - -# How to quickly test the main page of the website - -``` -# If you have old OS distribution, -# Run this from repository root: - -docker run -it --rm --network host --volume $(pwd):/workspace ubuntu:20.04 /bin/bash -cd workspace/docs/tools -apt update -apt install sudo python pip git -pip3 install -r requirements.txt -git config --global --add safe.directory /workspace -./build.py --livereload 8080 -``` - -``` -cd ../docs/tools -sudo apt install python3 pip -pip3 install -r requirements.txt - -virtualenv build - -./build.py --livereload 8080 - -# Open the web browser and go to http://localhost:8080/ -``` +# This is not a website From d914ddc7669f69989b60e145d13e1dfe9bc621ec Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 8 Aug 2022 02:36:50 +0200 Subject: [PATCH 404/672] Fix Play UI --- programs/server/play.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/programs/server/play.html b/programs/server/play.html index ab04722609c..1801d243749 100644 --- a/programs/server/play.html +++ b/programs/server/play.html @@ -75,6 +75,9 @@ * { box-sizing: border-box; + /* For iPad */ + margin: 0; + border-radius: 0; } html, body @@ -501,7 +504,6 @@ const server_address = document.getElementById('url').value; - var url = server_address + (server_address.indexOf('?') >= 0 ? '&' : '?') + /// Ask server to allow cross-domain requests. @@ -641,10 +643,9 @@ clearElement('chart'); clearElement('data-unparsed'); clearElement('error'); - clearElement('hourglass'); - document.getElementById('check-mark').innerText = ''; - document.getElementById('hourglass').innerText = ''; + document.getElementById('check-mark').display = 'none'; + document.getElementById('hourglass').display = 'none'; document.getElementById('stats').innerText = ''; document.getElementById('logo-container').style.display = 'block'; } From 2d836356433b2cd8f8d5816e42d0e22bdf025e8a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 8 Aug 2022 03:57:02 +0200 Subject: [PATCH 405/672] Play UI: row numbers; cell selection; hysteresis --- programs/server/play.html | 59 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/programs/server/play.html b/programs/server/play.html index 1801d243749..a941dd62c68 100644 --- a/programs/server/play.html +++ b/programs/server/play.html @@ -278,13 +278,24 @@ display: none; } - /* When mouse pointer is over table cell, will display full text (with wrap) instead of cut. */ - td.left:hover + /* When mouse pointer is over table cell, will display full text (with wrap) instead of cut. + * We also keep it for some time on mouseout for "hysteresis" effect. + */ + td.left:hover, .td-hover-hysteresis { white-space: pre-wrap; max-width: none; } + .td-selected + { + white-space: pre-wrap; + max-width: none; + background-color: var(--table-hover-color); + border: 2px solid var(--border-color); + display: block; + } + td.transposed { max-width: none; @@ -298,6 +309,13 @@ vertical-align: middle; } + .row-number + { + text-align: right; + background-color: var(--table-header-color); + color: var(--misc-text-color); + } + div.empty-result { opacity: 10%; @@ -793,7 +811,17 @@ return; } + const should_display_row_numbers = response.data.length > 3; + let thead = document.createElement('thead'); + + if (should_display_row_numbers) { + let th = document.createElement('th'); + th.className = 'row-number'; + th.appendChild(document.createTextNode('№')); + thead.appendChild(th); + } + for (let idx in response.meta) { let th = document.createElement('th'); const name = document.createTextNode(response.meta[idx].name); @@ -803,7 +831,9 @@ /// To prevent hanging the browser, limit the number of cells in a table. /// It's important to have the limit on number of cells, not just rows, because tables may be wide or narrow. + /// Also we permit rendering of more records but only if elapsed time is not large. const max_rows = 10000 / response.meta.length; + const max_render_ms = 200; let row_num = 0; const column_is_number = response.meta.map(elem => !!elem.type.match(/^(Nullable\()?(U?Int|Decimal|Float)/)); @@ -819,18 +849,41 @@ column_need_render_bars: column_need_render_bars, }; + const start_time = performance.now(); + + function tdMouseEnter(e) { + let elem = e.target; + elem.classList.add('td-hover-hysteresis'); + elem.onmouseleave = _ => { + setTimeout(() => { elem && elem.classList.remove('td-hover-hysteresis') }, 1000); + } + } + + function tdClick(e) { + let elem = e.target; + elem.classList.add('td-selected'); + } + let tbody = document.createElement('tbody'); for (let row_idx in response.data) { let tr = document.createElement('tr'); + if (should_display_row_numbers) { + let td = document.createElement('td'); + td.className = 'row-number'; + td.appendChild(document.createTextNode(1 + +row_idx)); + tr.appendChild(td); + } for (let col_idx in response.data[row_idx]) { let cell = response.data[row_idx][col_idx]; const td = renderCell(cell, col_idx, settings); + td.onmouseenter = tdMouseEnter; + td.onclick = tdClick; tr.appendChild(td); } tbody.appendChild(tr); ++row_num; - if (row_num >= max_rows) { + if (row_num >= max_rows && performance.now() - start_time >= max_render_ms) { break; } } From 5524706b7833c180e1932211198893cc4ebfa331 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 8 Aug 2022 05:03:15 +0300 Subject: [PATCH 406/672] Revert "ColumnVector: optimize filter with AVX512VBMI2 compress store" --- src/Columns/ColumnVector.cpp | 147 ++++------------------ src/Columns/tests/gtest_column_vector.cpp | 91 -------------- src/Common/CpuId.h | 6 - src/Common/TargetSpecific.cpp | 7 +- src/Common/TargetSpecific.h | 33 +---- 5 files changed, 32 insertions(+), 252 deletions(-) delete mode 100644 src/Columns/tests/gtest_column_vector.cpp diff --git a/src/Columns/ColumnVector.cpp b/src/Columns/ColumnVector.cpp index 66274cfbf9e..9084e47c2f5 100644 --- a/src/Columns/ColumnVector.cpp +++ b/src/Columns/ColumnVector.cpp @@ -12,14 +12,12 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include @@ -27,10 +25,6 @@ # include #endif -#if USE_MULTITARGET_CODE -# include -#endif - #if USE_EMBEDDED_COMPILER #include #include @@ -477,115 +471,6 @@ void ColumnVector::insertRangeFrom(const IColumn & src, size_t start, size_t memcpy(data.data() + old_size, &src_vec.data[start], length * sizeof(data[0])); } -static inline UInt64 blsr(UInt64 mask) -{ -#ifdef __BMI__ - return _blsr_u64(mask); -#else - return mask & (mask-1); -#endif -} - -DECLARE_DEFAULT_CODE( -template -inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_aligned, const T *& data_pos, Container & res_data) -{ - while (filt_pos < filt_end_aligned) - { - UInt64 mask = bytes64MaskToBits64Mask(filt_pos); - - if (0xffffffffffffffff == mask) - { - res_data.insert(data_pos, data_pos + SIMD_BYTES); - } - else - { - while (mask) - { - size_t index = std::countr_zero(mask); - res_data.push_back(data_pos[index]); - mask = blsr(mask); - } - } - - filt_pos += SIMD_BYTES; - data_pos += SIMD_BYTES; - } -} -) - -DECLARE_AVX512VBMI2_SPECIFIC_CODE( -template -inline void compressStoreAVX512(const void *src, void *dst, const UInt64 mask) -{ - __m512i vsrc = _mm512_loadu_si512(src); - if constexpr (ELEMENT_WIDTH == 1) - _mm512_mask_compressstoreu_epi8(dst, static_cast<__mmask64>(mask), vsrc); - else if constexpr (ELEMENT_WIDTH == 2) - _mm512_mask_compressstoreu_epi16(dst, static_cast<__mmask32>(mask), vsrc); - else if constexpr (ELEMENT_WIDTH == 4) - _mm512_mask_compressstoreu_epi32(dst, static_cast<__mmask16>(mask), vsrc); - else if constexpr (ELEMENT_WIDTH == 8) - _mm512_mask_compressstoreu_epi64(dst, static_cast<__mmask8>(mask), vsrc); -} - -template -inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_aligned, const T *& data_pos, Container & res_data) -{ - static constexpr size_t VEC_LEN = 64; /// AVX512 vector length - 64 bytes - static constexpr size_t ELEMENT_WIDTH = sizeof(T); - static constexpr size_t ELEMENTS_PER_VEC = VEC_LEN / ELEMENT_WIDTH; - static constexpr UInt64 KMASK = 0xffffffffffffffff >> (64 - ELEMENTS_PER_VEC); - - size_t current_offset = res_data.size(); - size_t reserve_size = res_data.size(); - size_t alloc_size = SIMD_BYTES * 2; - - while (filt_pos < filt_end_aligned) - { - /// to avoid calling resize too frequently, resize to reserve buffer. - if (reserve_size - current_offset < SIMD_BYTES) - { - reserve_size += alloc_size; - res_data.resize(reserve_size); - alloc_size *= 2; - } - - UInt64 mask = bytes64MaskToBits64Mask(filt_pos); - - if (0xffffffffffffffff == mask) - { - for (size_t i = 0; i < SIMD_BYTES; i += ELEMENTS_PER_VEC) - _mm512_storeu_si512(reinterpret_cast(&res_data[current_offset + i]), - _mm512_loadu_si512(reinterpret_cast(data_pos + i))); - current_offset += SIMD_BYTES; - } - else - { - if (mask) - { - for (size_t i = 0; i < SIMD_BYTES; i += ELEMENTS_PER_VEC) - { - compressStoreAVX512(reinterpret_cast(data_pos + i), - reinterpret_cast(&res_data[current_offset]), mask & KMASK); - current_offset += std::popcount(mask & KMASK); - /// prepare mask for next iter, if ELEMENTS_PER_VEC = 64, no next iter - if (ELEMENTS_PER_VEC < 64) - { - mask >>= ELEMENTS_PER_VEC; - } - } - } - } - - filt_pos += SIMD_BYTES; - data_pos += SIMD_BYTES; - } - /// resize to the real size. - res_data.resize(current_offset); -} -) - template ColumnPtr ColumnVector::filter(const IColumn::Filter & filt, ssize_t result_size_hint) const { @@ -611,13 +496,31 @@ ColumnPtr ColumnVector::filter(const IColumn::Filter & filt, ssize_t result_s static constexpr size_t SIMD_BYTES = 64; const UInt8 * filt_end_aligned = filt_pos + size / SIMD_BYTES * SIMD_BYTES; -#if USE_MULTITARGET_CODE - static constexpr bool VBMI2_CAPABLE = sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8; - if (VBMI2_CAPABLE && isArchSupported(TargetArch::AVX512VBMI2)) - TargetSpecific::AVX512VBMI2::doFilterAligned(filt_pos, filt_end_aligned, data_pos, res_data); - else -#endif - TargetSpecific::Default::doFilterAligned(filt_pos, filt_end_aligned, data_pos, res_data); + while (filt_pos < filt_end_aligned) + { + UInt64 mask = bytes64MaskToBits64Mask(filt_pos); + + if (0xffffffffffffffff == mask) + { + res_data.insert(data_pos, data_pos + SIMD_BYTES); + } + else + { + while (mask) + { + size_t index = std::countr_zero(mask); + res_data.push_back(data_pos[index]); + #ifdef __BMI__ + mask = _blsr_u64(mask); + #else + mask = mask & (mask-1); + #endif + } + } + + filt_pos += SIMD_BYTES; + data_pos += SIMD_BYTES; + } while (filt_pos < filt_end) { diff --git a/src/Columns/tests/gtest_column_vector.cpp b/src/Columns/tests/gtest_column_vector.cpp deleted file mode 100644 index 5a8fd1fad60..00000000000 --- a/src/Columns/tests/gtest_column_vector.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include -#include -#include -#include -#include - - -using namespace DB; - -static pcg64 rng(randomSeed()); -static constexpr int error_code = 12345; -static constexpr size_t TEST_RUNS = 500; -static constexpr size_t MAX_ROWS = 10000; -static const std::vector filter_ratios = {1, 2, 5, 11, 32, 64, 100, 1000}; -static const size_t K = filter_ratios.size(); - -template -static MutableColumnPtr createColumn(size_t n) -{ - auto column = ColumnVector::create(); - auto & values = column->getData(); - - for (size_t i = 0; i < n; ++i) - { - values.push_back(i); - } - - return column; -} - -bool checkFilter(const PaddedPODArray &flit, const IColumn & src, const IColumn & dst) -{ - size_t n = flit.size(); - size_t dst_size = dst.size(); - size_t j = 0; /// index of dest - for (size_t i = 0; i < n; ++i) - { - if (flit[i] != 0) - { - if ((dst_size <= j) || (src.compareAt(i, j, dst, 0) != 0)) - return false; - j++; - } - } - return dst_size == j; /// filtered size check -} - -template -static void testFilter() -{ - auto test_case = [&](size_t rows, size_t filter_ratio) - { - auto vector_column = createColumn(rows); - PaddedPODArray flit(rows); - for (size_t i = 0; i < rows; ++i) - flit[i] = rng() % filter_ratio == 0; - auto res_column = vector_column->filter(flit, -1); - - if (!checkFilter(flit, *vector_column, *res_column)) - throw Exception(error_code, "VectorColumn filter failure, type: {}", typeid(T).name()); - }; - - try - { - for (size_t i = 0; i < TEST_RUNS; ++i) - { - size_t rows = rng() % MAX_ROWS + 1; - size_t filter_ratio = filter_ratios[rng() % K]; - - test_case(rows, filter_ratio); - } - } - catch (const Exception & e) - { - FAIL() << e.displayText(); - } -} - - -TEST(ColumnVector, Filter) -{ - testFilter(); - testFilter(); - testFilter(); - testFilter(); - testFilter(); - testFilter(); - testFilter(); - testFilter(); - testFilter(); -} diff --git a/src/Common/CpuId.h b/src/Common/CpuId.h index 1e54ccf62b3..167fa22faf6 100644 --- a/src/Common/CpuId.h +++ b/src/Common/CpuId.h @@ -82,7 +82,6 @@ inline bool cpuid(UInt32 op, UInt32 * res) noexcept /// NOLINT OP(AVX512BW) \ OP(AVX512VL) \ OP(AVX512VBMI) \ - OP(AVX512VBMI2) \ OP(PREFETCHWT1) \ OP(SHA) \ OP(ADX) \ @@ -303,11 +302,6 @@ bool haveAVX512VBMI() noexcept return haveAVX512F() && ((CpuInfo(0x7, 0).registers.ecx >> 1) & 1u); } -bool haveAVX512VBMI2() noexcept -{ - return haveAVX512F() && ((CpuInfo(0x7, 0).registers.ecx >> 6) & 1u); -} - bool haveRDRAND() noexcept { return CpuInfo(0x0).registers.eax >= 0x7 && ((CpuInfo(0x1).registers.ecx >> 30) & 1u); diff --git a/src/Common/TargetSpecific.cpp b/src/Common/TargetSpecific.cpp index a5fbe7de078..70b03833775 100644 --- a/src/Common/TargetSpecific.cpp +++ b/src/Common/TargetSpecific.cpp @@ -20,8 +20,6 @@ UInt32 getSupportedArchs() result |= static_cast(TargetArch::AVX512BW); if (Cpu::CpuFlagsCache::have_AVX512VBMI) result |= static_cast(TargetArch::AVX512VBMI); - if (Cpu::CpuFlagsCache::have_AVX512VBMI2) - result |= static_cast(TargetArch::AVX512VBMI2); return result; } @@ -40,9 +38,8 @@ String toString(TargetArch arch) case TargetArch::AVX: return "avx"; case TargetArch::AVX2: return "avx2"; case TargetArch::AVX512F: return "avx512f"; - case TargetArch::AVX512BW: return "avx512bw"; - case TargetArch::AVX512VBMI: return "avx512vbmi"; - case TargetArch::AVX512VBMI2: return "avx512vbmi"; + case TargetArch::AVX512BW: return "avx512bw"; + case TargetArch::AVX512VBMI: return "avx512vbmi"; } __builtin_unreachable(); diff --git a/src/Common/TargetSpecific.h b/src/Common/TargetSpecific.h index 250642f6ee4..f078c0e3ffc 100644 --- a/src/Common/TargetSpecific.h +++ b/src/Common/TargetSpecific.h @@ -31,7 +31,7 @@ * int funcImpl() { * return 2; * } - * ) // DECLARE_AVX2_SPECIFIC_CODE + * ) // DECLARE_DEFAULT_CODE * * int func() { * #if USE_MULTITARGET_CODE @@ -80,9 +80,8 @@ enum class TargetArch : UInt32 AVX = (1 << 1), AVX2 = (1 << 2), AVX512F = (1 << 3), - AVX512BW = (1 << 4), - AVX512VBMI = (1 << 5), - AVX512VBMI2 = (1 << 6), + AVX512BW = (1 << 4), + AVX512VBMI = (1 << 5), }; /// Runtime detection. @@ -101,7 +100,6 @@ String toString(TargetArch arch); #if defined(__clang__) -#define AVX512VBMI2_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,avx512vbmi2"))) #define AVX512VBMI_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi"))) #define AVX512BW_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw"))) #define AVX512_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f"))) @@ -110,8 +108,6 @@ String toString(TargetArch arch); #define SSE42_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt"))) #define DEFAULT_FUNCTION_SPECIFIC_ATTRIBUTE -# define BEGIN_AVX512VBMI2_SPECIFIC_CODE \ - _Pragma("clang attribute push(__attribute__((target(\"sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,avx512vbmi2\"))),apply_to=function)") # define BEGIN_AVX512VBMI_SPECIFIC_CODE \ _Pragma("clang attribute push(__attribute__((target(\"sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi\"))),apply_to=function)") # define BEGIN_AVX512BW_SPECIFIC_CODE \ @@ -133,7 +129,6 @@ String toString(TargetArch arch); # define DUMMY_FUNCTION_DEFINITION [[maybe_unused]] void _dummy_function_definition(); #else -#define AVX512VBMI2_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,avx512vbmi2,tune=native"))) #define AVX512VBMI_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,tune=native"))) #define AVX512BW_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,tune=native"))) #define AVX512_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,tune=native"))) @@ -142,9 +137,6 @@ String toString(TargetArch arch); #define SSE42_FUNCTION_SPECIFIC_ATTRIBUTE __attribute__((target("sse,sse2,sse3,ssse3,sse4,popcnt",tune=native))) #define DEFAULT_FUNCTION_SPECIFIC_ATTRIBUTE -# define BEGIN_AVX512VBMI2_SPECIFIC_CODE \ - _Pragma("GCC push_options") \ - _Pragma("GCC target(\"sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,avx512vbmi2,tune=native\")") # define BEGIN_AVX512VBMI_SPECIFIC_CODE \ _Pragma("GCC push_options") \ _Pragma("GCC target(\"sse,sse2,sse3,ssse3,sse4,popcnt,avx,avx2,avx512f,avx512bw,avx512vl,avx512vbmi,tune=native\")") @@ -225,16 +217,6 @@ namespace TargetSpecific::AVX512VBMI { \ } \ END_TARGET_SPECIFIC_CODE -#define DECLARE_AVX512VBMI2_SPECIFIC_CODE(...) \ -BEGIN_AVX512VBMI2_SPECIFIC_CODE \ -namespace TargetSpecific::AVX512VBMI2 { \ - DUMMY_FUNCTION_DEFINITION \ - using namespace DB::TargetSpecific::AVX512VBMI2; \ - __VA_ARGS__ \ -} \ -END_TARGET_SPECIFIC_CODE - - #else #define USE_MULTITARGET_CODE 0 @@ -247,7 +229,6 @@ END_TARGET_SPECIFIC_CODE #define DECLARE_AVX512F_SPECIFIC_CODE(...) #define DECLARE_AVX512BW_SPECIFIC_CODE(...) #define DECLARE_AVX512VBMI_SPECIFIC_CODE(...) -#define DECLARE_AVX512VBMI2_SPECIFIC_CODE(...) #endif @@ -264,9 +245,8 @@ DECLARE_SSE42_SPECIFIC_CODE (__VA_ARGS__) \ DECLARE_AVX_SPECIFIC_CODE (__VA_ARGS__) \ DECLARE_AVX2_SPECIFIC_CODE (__VA_ARGS__) \ DECLARE_AVX512F_SPECIFIC_CODE(__VA_ARGS__) \ -DECLARE_AVX512BW_SPECIFIC_CODE (__VA_ARGS__) \ -DECLARE_AVX512VBMI_SPECIFIC_CODE (__VA_ARGS__) \ -DECLARE_AVX512VBMI2_SPECIFIC_CODE (__VA_ARGS__) +DECLARE_AVX512BW_SPECIFIC_CODE(__VA_ARGS__) \ +DECLARE_AVX512VBMI_SPECIFIC_CODE(__VA_ARGS__) DECLARE_DEFAULT_CODE( constexpr auto BuildArch = TargetArch::Default; /// NOLINT @@ -296,9 +276,6 @@ DECLARE_AVX512VBMI_SPECIFIC_CODE( constexpr auto BuildArch = TargetArch::AVX512VBMI; /// NOLINT ) // DECLARE_AVX512VBMI_SPECIFIC_CODE -DECLARE_AVX512VBMI2_SPECIFIC_CODE( - constexpr auto BuildArch = TargetArch::AVX512VBMI2; /// NOLINT -) // DECLARE_AVX512VBMI2_SPECIFIC_CODE /** Runtime Dispatch helpers for class members. * From 653b86d1e698a21e375abfaea17da183541dcb13 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 8 Aug 2022 04:16:50 +0200 Subject: [PATCH 407/672] Play UI: row numbers; cell selection; hysteresis --- programs/server/play.html | 1 + 1 file changed, 1 insertion(+) diff --git a/programs/server/play.html b/programs/server/play.html index a941dd62c68..f4001ca0ac7 100644 --- a/programs/server/play.html +++ b/programs/server/play.html @@ -311,6 +311,7 @@ .row-number { + width: 1%; text-align: right; background-color: var(--table-header-color); color: var(--misc-text-color); From 64a1b0f2b84db6d9b42fc82aeae7dc208359999f Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 8 Aug 2022 04:25:03 +0200 Subject: [PATCH 408/672] Play UI: row numbers; cell selection; hysteresis --- programs/server/play.html | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/programs/server/play.html b/programs/server/play.html index f4001ca0ac7..6a412fa5c60 100644 --- a/programs/server/play.html +++ b/programs/server/play.html @@ -293,7 +293,6 @@ max-width: none; background-color: var(--table-hover-color); border: 2px solid var(--border-color); - display: block; } td.transposed @@ -852,19 +851,6 @@ const start_time = performance.now(); - function tdMouseEnter(e) { - let elem = e.target; - elem.classList.add('td-hover-hysteresis'); - elem.onmouseleave = _ => { - setTimeout(() => { elem && elem.classList.remove('td-hover-hysteresis') }, 1000); - } - } - - function tdClick(e) { - let elem = e.target; - elem.classList.add('td-selected'); - } - let tbody = document.createElement('tbody'); for (let row_idx in response.data) { let tr = document.createElement('tr'); @@ -877,8 +863,15 @@ for (let col_idx in response.data[row_idx]) { let cell = response.data[row_idx][col_idx]; const td = renderCell(cell, col_idx, settings); - td.onmouseenter = tdMouseEnter; - td.onclick = tdClick; + + td.onclick = () => { td.classList.add('td-selected') }; + td.onmouseenter = () => { + td.classList.add('td-hover-hysteresis'); + td.onmouseleave = () => { + setTimeout(() => { td && td.classList.remove('td-hover-hysteresis') }, 1000); + }; + }; + tr.appendChild(td); } tbody.appendChild(tr); From 40463e9c479039b5bb519e9ff78fcb23deccc33f Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Mon, 8 Aug 2022 00:20:09 -0400 Subject: [PATCH 409/672] add Stateful tests (release), Stateless tests (release) to Mergeable Check --- tests/ci/ci_config.py | 2 ++ tests/ci/functional_test_check.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index 12598e79a19..5b8f3b4227e 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -352,4 +352,6 @@ REQUIRED_CHECKS = [ "Style Check", "ClickHouse build check", "ClickHouse special build check", + "Stateful tests (release)", + "Stateless tests (release)", ] diff --git a/tests/ci/functional_test_check.py b/tests/ci/functional_test_check.py index e5a05b3e816..690ac3c1851 100644 --- a/tests/ci/functional_test_check.py +++ b/tests/ci/functional_test_check.py @@ -6,6 +6,7 @@ import logging import os import subprocess import sys +import atexit from github import Github @@ -22,6 +23,7 @@ from commit_status_helper import ( get_commit, override_status, post_commit_status_to_file, + update_mergeable_check, ) from clickhouse_helper import ( ClickHouseHelper, @@ -209,6 +211,8 @@ if __name__ == "__main__": pr_info = PRInfo(need_changed_files=run_changed_tests) + atexit.register(update_mergeable_check, gh, pr_info, check_name) + if not os.path.exists(temp_path): os.makedirs(temp_path) From 9d1860c3cd68cc9fb8cfe14d57b85ec809820407 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Mon, 8 Aug 2022 12:45:54 +0800 Subject: [PATCH 410/672] Fix test --- ...371_select_projection_normal_agg.reference | 4 +- .../02371_select_projection_normal_agg.sql | 61 +++++++++++++++---- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/tests/queries/0_stateless/02371_select_projection_normal_agg.reference b/tests/queries/0_stateless/02371_select_projection_normal_agg.reference index d15a861b0ec..9972842f982 100644 --- a/tests/queries/0_stateless/02371_select_projection_normal_agg.reference +++ b/tests/queries/0_stateless/02371_select_projection_normal_agg.reference @@ -1,3 +1 @@ -0 -0 -2022-07-22 01:00:00 0 0 +1 1 diff --git a/tests/queries/0_stateless/02371_select_projection_normal_agg.sql b/tests/queries/0_stateless/02371_select_projection_normal_agg.sql index 9b510625544..283aec0b122 100644 --- a/tests/queries/0_stateless/02371_select_projection_normal_agg.sql +++ b/tests/queries/0_stateless/02371_select_projection_normal_agg.sql @@ -47,6 +47,27 @@ INSERT INTO video_log SELECT FROM rng LIMIT 10; +DROP TABLE IF EXISTS video_log_result; + +CREATE TABLE video_log_result +( + `hour` DateTime, + `sum_bytes` UInt64, + `avg_duration` Float64 +) +ENGINE = MergeTree +PARTITION BY toDate(hour) +ORDER BY sum_bytes; + +INSERT INTO video_log_result SELECT + toStartOfHour(datetime) AS hour, + sum(bytes), + avg(duration) +FROM video_log +WHERE (toDate(hour) = '2022-07-22') AND (device_id = '100') --(device_id = '100') Make sure it's not good and doesn't go into prewhere. +GROUP BY hour; + + ALTER TABLE video_log ADD PROJECTION p_norm ( SELECT @@ -57,7 +78,7 @@ ALTER TABLE video_log ADD PROJECTION p_norm ORDER BY device_id ); -ALTER TABLE video_log MATERIALIZE PROJECTION p_norm; +ALTER TABLE video_log MATERIALIZE PROJECTION p_norm settings mutations_sync=1; ALTER TABLE video_log ADD PROJECTION p_agg ( @@ -71,15 +92,33 @@ ALTER TABLE video_log ADD PROJECTION p_agg domain ); -ALTER TABLE video_log MATERIALIZE PROJECTION p_agg; - -SELECT sleep(3); -SELECT sleep(3); +ALTER TABLE video_log MATERIALIZE PROJECTION p_agg settings mutations_sync=1; SELECT - toStartOfHour(datetime) AS hour, - ignore(sum(bytes)), - ignore(avg(duration)) -FROM video_log -WHERE (toDate(hour) = '2022-07-22') AND (device_id = '100') --(device_id = '100') Make sure it's not good and doesn't go into prewhere. -GROUP BY hour; + equals(sum_bytes1, sum_bytes2), + equals(avg_duration1, avg_duration2) +FROM +( + SELECT + toStartOfHour(datetime) AS hour, + sum(bytes) AS sum_bytes1, + avg(duration) AS avg_duration1 + FROM video_log + WHERE (toDate(hour) = '2022-07-22') AND (device_id = '100') --(device_id = '100') Make sure it's not good and doesn't go into prewhere. + GROUP BY hour +) +LEFT JOIN +( + SELECT + `hour`, + `sum_bytes` AS sum_bytes2, + `avg_duration` AS avg_duration2 + FROM video_log_result +) +USING (hour) settings joined_subquery_requires_alias=0; + +DROP TABLE IF EXISTS video_log; + +DROP TABLE IF EXISTS rng; + +DROP TABLE IF EXISTS video_log_result; From db85ebfaa71108ef42f89f5585534269e04e62ee Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 8 Aug 2022 06:55:41 +0200 Subject: [PATCH 411/672] Add server-side time to Progress --- src/Common/ProfileEvents.cpp | 5 +-- src/Common/ProgressIndication.cpp | 18 +++++--- src/Common/ProgressIndication.h | 6 ++- src/Core/ProtocolDefines.h | 5 ++- src/IO/Progress.cpp | 70 ++++++++++++++++++------------- src/IO/Progress.h | 64 +++++++++++++++------------- src/Server/TCPHandler.cpp | 20 ++++----- src/Server/TCPHandler.h | 2 + 8 files changed, 109 insertions(+), 81 deletions(-) diff --git a/src/Common/ProfileEvents.cpp b/src/Common/ProfileEvents.cpp index 453ed9ec37c..eb144402b52 100644 --- a/src/Common/ProfileEvents.cpp +++ b/src/Common/ProfileEvents.cpp @@ -1,7 +1,6 @@ #include #include -#include -#include + /// Available events. Add something here as you wish. #define APPLY_FOR_EVENTS(M) \ @@ -450,7 +449,7 @@ void increment(Event event, Count amount) CountersIncrement::CountersIncrement(Counters::Snapshot const & snapshot) { init(); - std::memcpy(increment_holder.get(), snapshot.counters_holder.get(), Counters::num_counters * sizeof(Increment)); + memcpy(increment_holder.get(), snapshot.counters_holder.get(), Counters::num_counters * sizeof(Increment)); } CountersIncrement::CountersIncrement(Counters::Snapshot const & after, Counters::Snapshot const & before) diff --git a/src/Common/ProgressIndication.cpp b/src/Common/ProgressIndication.cpp index 8ca1612e916..ab4ecf5c25f 100644 --- a/src/Common/ProgressIndication.cpp +++ b/src/Common/ProgressIndication.cpp @@ -8,7 +8,6 @@ #include "Common/formatReadable.h" #include #include -#include #include "IO/WriteBufferFromString.h" #include @@ -33,6 +32,13 @@ namespace namespace DB { +UInt64 ProgressIndication::getElapsedNanoseconds() const +{ + /// New server versions send server-side elapsed time, which is preferred for calculations. + UInt64 server_elapsed_ns = progress.elapsed_ns.load(std::memory_order_relaxed); + return server_elapsed_ns ? server_elapsed_ns : watch.elapsed(); +} + bool ProgressIndication::updateProgress(const Progress & value) { return progress.incrementPiecewiseAtomically(value); @@ -56,7 +62,7 @@ void ProgressIndication::resetProgress() write_progress_on_update = false; { std::lock_guard lock(profile_events_mutex); - cpu_usage_meter.reset(static_cast(clock_gettime_ns())); + cpu_usage_meter.reset(getElapsedNanoseconds()); thread_data.clear(); } } @@ -93,7 +99,7 @@ void ProgressIndication::updateThreadEventData(HostToThreadTimesMap & new_thread total_cpu_ns += aggregateCPUUsageNs(new_host_map.second); thread_data[new_host_map.first] = std::move(new_host_map.second); } - cpu_usage_meter.add(static_cast(clock_gettime_ns()), total_cpu_ns); + cpu_usage_meter.add(getElapsedNanoseconds(), total_cpu_ns); } size_t ProgressIndication::getUsedThreadsCount() const @@ -110,7 +116,7 @@ size_t ProgressIndication::getUsedThreadsCount() const double ProgressIndication::getCPUUsage() { std::lock_guard lock(profile_events_mutex); - return cpu_usage_meter.rate(clock_gettime_ns()); + return cpu_usage_meter.rate(getElapsedNanoseconds()); } ProgressIndication::MemoryUsage ProgressIndication::getMemoryUsage() const @@ -139,7 +145,7 @@ void ProgressIndication::writeFinalProgress() std::cout << "Processed " << formatReadableQuantity(progress.read_rows) << " rows, " << formatReadableSizeWithDecimalSuffix(progress.read_bytes); - size_t elapsed_ns = watch.elapsed(); + UInt64 elapsed_ns = getElapsedNanoseconds(); if (elapsed_ns) std::cout << " (" << formatReadableQuantity(progress.read_rows * 1000000000.0 / elapsed_ns) << " rows/s., " << formatReadableSizeWithDecimalSuffix(progress.read_bytes * 1000000000.0 / elapsed_ns) << "/s.)"; @@ -185,7 +191,7 @@ void ProgressIndication::writeProgress() << formatReadableQuantity(progress.read_rows) << " rows, " << formatReadableSizeWithDecimalSuffix(progress.read_bytes); - auto elapsed_ns = watch.elapsed(); + UInt64 elapsed_ns = getElapsedNanoseconds(); if (elapsed_ns) message << " (" << formatReadableQuantity(progress.read_rows * 1000000000.0 / elapsed_ns) << " rows/s., " diff --git a/src/Common/ProgressIndication.h b/src/Common/ProgressIndication.h index 588a31beca7..4f05f41b9d0 100644 --- a/src/Common/ProgressIndication.h +++ b/src/Common/ProgressIndication.h @@ -55,7 +55,7 @@ public: void setFileProgressCallback(ContextMutablePtr context, bool write_progress_on_update = false); /// How much seconds passed since query execution start. - double elapsedSeconds() const { return watch.elapsedSeconds(); } + double elapsedSeconds() const { return getElapsedNanoseconds() / 1e9; } void addThreadIdToList(String const & host, UInt64 thread_id); @@ -74,6 +74,8 @@ private: MemoryUsage getMemoryUsage() const; + UInt64 getElapsedNanoseconds() const; + /// This flag controls whether to show the progress bar. We start showing it after /// the query has been executing for 0.5 seconds, and is still less than half complete. bool show_progress_bar = false; @@ -86,7 +88,7 @@ private: /// This information is stored here. Progress progress; - /// Track query execution time. + /// Track query execution time on client. Stopwatch watch; bool write_progress_on_update = false; diff --git a/src/Core/ProtocolDefines.h b/src/Core/ProtocolDefines.h index 584720694d7..0481d25416e 100644 --- a/src/Core/ProtocolDefines.h +++ b/src/Core/ProtocolDefines.h @@ -52,10 +52,13 @@ /// NOTE: DBMS_TCP_PROTOCOL_VERSION has nothing common with VERSION_REVISION, /// later is just a number for server version (one number instead of commit SHA) /// for simplicity (sometimes it may be more convenient in some use cases). -#define DBMS_TCP_PROTOCOL_VERSION 54457 +#define DBMS_TCP_PROTOCOL_VERSION 54458 #define DBMS_MIN_PROTOCOL_VERSION_WITH_INITIAL_QUERY_START_TIME 54449 #define DBMS_MIN_PROTOCOL_VERSION_WITH_PROFILE_EVENTS_IN_INSERT 54456 #define DBMS_MIN_PROTOCOL_VERSION_WITH_VIEW_IF_PERMITTED 54457 + +/// The server will send query elapsed run time in the Progress packet. +#define DBMS_MIN_PROTOCOL_VERSION_WITH_SERVER_QUERY_TIME_IN_PROGRESS 54458 diff --git a/src/IO/Progress.cpp b/src/IO/Progress.cpp index eb6eb7fe573..e37dfdb88e3 100644 --- a/src/IO/Progress.cpp +++ b/src/IO/Progress.cpp @@ -11,38 +11,34 @@ namespace DB { void ProgressValues::read(ReadBuffer & in, UInt64 server_revision) { - size_t new_read_rows = 0; - size_t new_read_bytes = 0; - size_t new_total_rows_to_read = 0; - size_t new_written_rows = 0; - size_t new_written_bytes = 0; - - readVarUInt(new_read_rows, in); - readVarUInt(new_read_bytes, in); - readVarUInt(new_total_rows_to_read, in); + readVarUInt(read_rows, in); + readVarUInt(read_bytes, in); + readVarUInt(total_rows_to_read, in); if (server_revision >= DBMS_MIN_REVISION_WITH_CLIENT_WRITE_INFO) { - readVarUInt(new_written_rows, in); - readVarUInt(new_written_bytes, in); + readVarUInt(written_rows, in); + readVarUInt(written_bytes, in); + } + if (server_revision >= DBMS_MIN_PROTOCOL_VERSION_WITH_SERVER_QUERY_TIME_IN_PROGRESS) + { + readVarUInt(elapsed_ns, in); } - - this->read_rows = new_read_rows; - this->read_bytes = new_read_bytes; - this->total_rows_to_read = new_total_rows_to_read; - this->written_rows = new_written_rows; - this->written_bytes = new_written_bytes; } void ProgressValues::write(WriteBuffer & out, UInt64 client_revision) const { - writeVarUInt(this->read_rows, out); - writeVarUInt(this->read_bytes, out); - writeVarUInt(this->total_rows_to_read, out); + writeVarUInt(read_rows, out); + writeVarUInt(read_bytes, out); + writeVarUInt(total_rows_to_read, out); if (client_revision >= DBMS_MIN_REVISION_WITH_CLIENT_WRITE_INFO) { - writeVarUInt(this->written_rows, out); - writeVarUInt(this->written_bytes, out); + writeVarUInt(written_rows, out); + writeVarUInt(written_bytes, out); + } + if (client_revision >= DBMS_MIN_PROTOCOL_VERSION_WITH_SERVER_QUERY_TIME_IN_PROGRESS) + { + writeVarUInt(elapsed_ns, out); } } @@ -52,19 +48,21 @@ void ProgressValues::writeJSON(WriteBuffer & out) const /// of 64-bit integers after interpretation by JavaScript. writeCString("{\"read_rows\":\"", out); - writeText(this->read_rows, out); + writeText(read_rows, out); writeCString("\",\"read_bytes\":\"", out); - writeText(this->read_bytes, out); + writeText(read_bytes, out); writeCString("\",\"written_rows\":\"", out); - writeText(this->written_rows, out); + writeText(written_rows, out); writeCString("\",\"written_bytes\":\"", out); - writeText(this->written_bytes, out); + writeText(written_bytes, out); writeCString("\",\"total_rows_to_read\":\"", out); - writeText(this->total_rows_to_read, out); + writeText(total_rows_to_read, out); writeCString("\",\"result_rows\":\"", out); - writeText(this->result_rows, out); + writeText(result_rows, out); writeCString("\",\"result_bytes\":\"", out); - writeText(this->result_bytes, out); + writeText(result_bytes, out); + writeCString("\",\"elapsed_ns\":\"", out); + writeText(elapsed_ns, out); writeCString("\"}", out); } @@ -82,6 +80,8 @@ bool Progress::incrementPiecewiseAtomically(const Progress & rhs) result_rows += rhs.result_rows; result_bytes += rhs.result_bytes; + elapsed_ns += rhs.elapsed_ns; + return rhs.read_rows || rhs.written_rows; } @@ -98,6 +98,8 @@ void Progress::reset() result_rows = 0; result_bytes = 0; + + elapsed_ns = 0; } ProgressValues Progress::getValues() const @@ -116,6 +118,8 @@ ProgressValues Progress::getValues() const res.result_rows = result_rows.load(std::memory_order_relaxed); res.result_bytes = result_bytes.load(std::memory_order_relaxed); + res.elapsed_ns = elapsed_ns.load(std::memory_order_relaxed); + return res; } @@ -135,6 +139,8 @@ ProgressValues Progress::fetchValuesAndResetPiecewiseAtomically() res.result_rows = result_rows.fetch_and(0); res.result_bytes = result_bytes.fetch_and(0); + res.elapsed_ns = elapsed_ns.fetch_and(0); + return res; } @@ -154,6 +160,8 @@ Progress Progress::fetchAndResetPiecewiseAtomically() res.result_rows = result_rows.fetch_and(0); res.result_bytes = result_bytes.fetch_and(0); + res.elapsed_ns = elapsed_ns.fetch_and(0); + return res; } @@ -171,6 +179,8 @@ Progress & Progress::operator=(Progress && other) noexcept result_rows = other.result_rows.load(std::memory_order_relaxed); result_bytes = other.result_bytes.load(std::memory_order_relaxed); + elapsed_ns = other.elapsed_ns.load(std::memory_order_relaxed); + return *this; } @@ -185,6 +195,8 @@ void Progress::read(ReadBuffer & in, UInt64 server_revision) written_rows.store(values.written_rows, std::memory_order_relaxed); written_bytes.store(values.written_bytes, std::memory_order_relaxed); + + elapsed_ns.store(values.elapsed_ns, std::memory_order_relaxed); } void Progress::write(WriteBuffer & out, UInt64 client_revision) const diff --git a/src/IO/Progress.h b/src/IO/Progress.h index 8340974b03d..48da8df26b7 100644 --- a/src/IO/Progress.h +++ b/src/IO/Progress.h @@ -16,17 +16,19 @@ class WriteBuffer; /// See Progress. struct ProgressValues { - size_t read_rows; - size_t read_bytes; + UInt64 read_rows; + UInt64 read_bytes; - size_t total_rows_to_read; - size_t total_bytes_to_read; + UInt64 total_rows_to_read; + UInt64 total_bytes_to_read; - size_t written_rows; - size_t written_bytes; + UInt64 written_rows; + UInt64 written_bytes; - size_t result_rows; - size_t result_bytes; + UInt64 result_rows; + UInt64 result_bytes; + + UInt64 elapsed_ns; void read(ReadBuffer & in, UInt64 server_revision); void write(WriteBuffer & out, UInt64 client_revision) const; @@ -35,39 +37,39 @@ struct ProgressValues struct ReadProgress { - size_t read_rows; - size_t read_bytes; - size_t total_rows_to_read; + UInt64 read_rows; + UInt64 read_bytes; + UInt64 total_rows_to_read; - ReadProgress(size_t read_rows_, size_t read_bytes_, size_t total_rows_to_read_ = 0) + ReadProgress(UInt64 read_rows_, UInt64 read_bytes_, UInt64 total_rows_to_read_ = 0) : read_rows(read_rows_), read_bytes(read_bytes_), total_rows_to_read(total_rows_to_read_) {} }; struct WriteProgress { - size_t written_rows; - size_t written_bytes; + UInt64 written_rows; + UInt64 written_bytes; - WriteProgress(size_t written_rows_, size_t written_bytes_) + WriteProgress(UInt64 written_rows_, UInt64 written_bytes_) : written_rows(written_rows_), written_bytes(written_bytes_) {} }; struct ResultProgress { - size_t result_rows; - size_t result_bytes; + UInt64 result_rows; + UInt64 result_bytes; - ResultProgress(size_t result_rows_, size_t result_bytes_) + ResultProgress(UInt64 result_rows_, UInt64 result_bytes_) : result_rows(result_rows_), result_bytes(result_bytes_) {} }; struct FileProgress { /// Here read_bytes (raw bytes) - do not equal ReadProgress::read_bytes, which are calculated according to column types. - size_t read_bytes; - size_t total_bytes_to_read; + UInt64 read_bytes; + UInt64 total_bytes_to_read; - explicit FileProgress(size_t read_bytes_, size_t total_bytes_to_read_ = 0) : read_bytes(read_bytes_), total_bytes_to_read(total_bytes_to_read_) {} + explicit FileProgress(UInt64 read_bytes_, UInt64 total_bytes_to_read_ = 0) : read_bytes(read_bytes_), total_bytes_to_read(total_bytes_to_read_) {} }; @@ -77,24 +79,26 @@ struct FileProgress */ struct Progress { - std::atomic read_rows {0}; /// Rows (source) processed. - std::atomic read_bytes {0}; /// Bytes (uncompressed, source) processed. + std::atomic read_rows {0}; /// Rows (source) processed. + std::atomic read_bytes {0}; /// Bytes (uncompressed, source) processed. /** How much rows/bytes must be processed, in total, approximately. Non-zero value is sent when there is information about * some new part of job. Received values must be summed to get estimate of total rows to process. */ - std::atomic total_rows_to_read {0}; - std::atomic total_bytes_to_read {0}; + std::atomic total_rows_to_read {0}; + std::atomic total_bytes_to_read {0}; - std::atomic written_rows {0}; - std::atomic written_bytes {0}; + std::atomic written_rows {0}; + std::atomic written_bytes {0}; - std::atomic result_rows {0}; - std::atomic result_bytes {0}; + std::atomic result_rows {0}; + std::atomic result_bytes {0}; + + std::atomic elapsed_ns {0}; Progress() = default; - Progress(size_t read_rows_, size_t read_bytes_, size_t total_rows_to_read_ = 0) + Progress(UInt64 read_rows_, UInt64 read_bytes_, UInt64 total_rows_to_read_ = 0) : read_rows(read_rows_), read_bytes(read_bytes_), total_rows_to_read(total_rows_to_read_) {} explicit Progress(ReadProgress read_progress) diff --git a/src/Server/TCPHandler.cpp b/src/Server/TCPHandler.cpp index 1011524e057..39ce87ce4ae 100644 --- a/src/Server/TCPHandler.cpp +++ b/src/Server/TCPHandler.cpp @@ -195,7 +195,6 @@ void TCPHandler::runImpl() break; } - Stopwatch watch; state.reset(); /// Initialized later. @@ -374,7 +373,7 @@ void TCPHandler::runImpl() /// Send final progress /// - /// NOTE: we cannot send Progress for regular INSERT (w/ VALUES) + /// NOTE: we cannot send Progress for regular INSERT (with VALUES) /// without breaking protocol compatibility, but it can be done /// by increasing revision. sendProgress(); @@ -385,8 +384,7 @@ void TCPHandler::runImpl() /// Do it before sending end of stream, to have a chance to show log message in client. query_scope->logPeakMemoryUsage(); - watch.stop(); - LOG_DEBUG(log, "Processed in {} sec.", watch.elapsedSeconds()); + LOG_DEBUG(log, "Processed in {} sec.", state.watch.elapsedSeconds()); query_duration_already_logged = true; if (state.is_connection_closed) @@ -500,6 +498,11 @@ void TCPHandler::runImpl() LOG_WARNING(log, "Can't skip data packets after query failure."); } + if (!query_duration_already_logged) + { + LOG_DEBUG(log, "Processed in {} sec.", state.watch.elapsedSeconds()); + } + try { /// QueryState should be cleared before QueryScope, since otherwise @@ -517,12 +520,6 @@ void TCPHandler::runImpl() */ } - if (!query_duration_already_logged) - { - watch.stop(); - LOG_DEBUG(log, "Processed in {} sec.", watch.elapsedSeconds()); - } - /// It is important to destroy query context here. We do not want it to live arbitrarily longer than the query. query_context.reset(); @@ -1782,6 +1779,9 @@ void TCPHandler::sendProgress() { writeVarUInt(Protocol::Server::Progress, *out); auto increment = state.progress.fetchValuesAndResetPiecewiseAtomically(); + UInt64 current_elapsed_ns = state.watch.elapsedNanoseconds(); + increment.elapsed_ns = current_elapsed_ns - state.prev_elapsed_ns; + state.prev_elapsed_ns = current_elapsed_ns; increment.write(*out, client_tcp_protocol_version); out->next(); } diff --git a/src/Server/TCPHandler.h b/src/Server/TCPHandler.h index bea00c815c8..e9edaa7ae2a 100644 --- a/src/Server/TCPHandler.h +++ b/src/Server/TCPHandler.h @@ -102,6 +102,8 @@ struct QueryState /// To output progress, the difference after the previous sending of progress. Progress progress; + Stopwatch watch; + UInt64 prev_elapsed_ns = 0; /// Timeouts setter for current query std::unique_ptr timeout_setter; From 855e52a7c97ca004d81285c22c870cb1d0193433 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 8 Aug 2022 07:12:08 +0200 Subject: [PATCH 412/672] Send final ProfileEvents just in case --- src/Server/TCPHandler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Server/TCPHandler.cpp b/src/Server/TCPHandler.cpp index 39ce87ce4ae..d2242cb42b1 100644 --- a/src/Server/TCPHandler.cpp +++ b/src/Server/TCPHandler.cpp @@ -377,6 +377,7 @@ void TCPHandler::runImpl() /// without breaking protocol compatibility, but it can be done /// by increasing revision. sendProgress(); + sendSelectProfileEvents(); } state.io.onFinish(); From 0349d9da96e1a533012554e905ebeac82b8d1598 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 8 Aug 2022 07:25:29 +0200 Subject: [PATCH 413/672] Change font in CI reports just in case (might be worse) --- docker/test/performance-comparison/report.py | 17 +---------------- tests/ci/report.py | 15 +-------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/docker/test/performance-comparison/report.py b/docker/test/performance-comparison/report.py index 4c1b6e1dd57..960f23be95c 100755 --- a/docker/test/performance-comparison/report.py +++ b/docker/test/performance-comparison/report.py @@ -41,24 +41,9 @@ color_good = "#b0d050" header_template = """ - ClickHouse \ No newline at end of file diff --git a/website/images/clickhouse-logomark.png b/website/images/clickhouse-logomark.png deleted file mode 100644 index d521c8147f7ddfcec1505331773a83fd3b07ab8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 305 zcmeAS@N?(olHy`uVBq!ia0y~yU=#qdKd>+ZNh@(R5Jw`wC&U#<|3Ay{|1rb=J75No zeTLyb1H)$(?o~iJwvr&fV1^$o91Ydk$6v_;MGkqoIEGZ*dV6ytZ-aq|^Fe#oQnv&S zw}iGe2Yy`<6AD`8a&hmqvtQTE3%#~^+H1p2^S(-RkPJ?+oTzop6?(F}sR1j)u@O1TaS?83{1OQjGV;}$k diff --git a/website/images/clickhouse-white.svg b/website/images/clickhouse-white.svg deleted file mode 100644 index 2d4c795abe8..00000000000 --- a/website/images/clickhouse-white.svg +++ /dev/null @@ -1 +0,0 @@ -ClickHouse white \ No newline at end of file diff --git a/website/images/logo-180x180.png b/website/images/logo-180x180.png deleted file mode 100644 index d89c259e27c8aa861b82f3f0902ef9c14ff7f93a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 698 zcmeAS@N?(olHy`uVBq!ia0vp^TR@nD8Ax&oe*=;XEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD3K806XN>+43Nd}{}jXj za}574GW<_t_z&cs0VALwgaIUhvT!ys1E>X}3!)LC7jDk~|3CSH-vKRe@%^n}84_xgRT)UO@UrB~mq7cwCclFxECOure@ks_OrQq9HdwB{QuOw+3T%b3dR4NstY}`DrEPiAAXl<>lpi gnR(g8$%zH2dih1^v)|cB0TnTLy85}Sb4q9e0Q@!mpa1{> diff --git a/website/images/logo-200x120.png b/website/images/logo-200x120.png deleted file mode 100644 index 01d205bfdba67953d1886eea928f049dc29f6e08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1732 zcmai#X;c!37RONp+(=)~XKtm2B{~SCUK4o)4zObw<)(p<*O6wMerXTHpNALiWqzyEXo=iaZsdpS6e2-P*y z1pok0G6{El#|L-B(FX6#`xT;hI|h#SBjNz+FI6XMtlUX-SS0EN0D%1Li!_?>MH@RI zgiQ{>Lw*ArLhY<2(NmE-hZHi-CzSJYaXPKUD9)l_MHy*V zG>}iI<2}&QulY{00_=As3r6puHG_lg4ry`r4i65fJV?Szg-e3_wf zGX0$L0%%@R)ccFN2K5c7{9WKv3+8!V5HErU!lLBC@&?YD=BzYaVov!8K+=3CAHs(( zf#yH&RxE>Y_-TlRUYF(;5OsPC1E_xl_W1N(8(^@pB#`?u9z&WkV%8+FN=bKp9 zG=aw4?w7aGMlT%C(vHHE#$S&(Q22D11>Oaxj*M(b(?cJV?z0}<4O#BmoC9~WF2oqt5ed^P6lSKRU!E4z9a&pP{j>nwpI1w8~MmarC%y&wO|5-Hq^0-9eb zsr91%_?e^g0tU8C1kD2bfQ3ZgmN?ki2ylMyYn7sGrb7?b3?(YPpzH#`1^U3le$f$C5HE8AhyJdTUCnnPT+7 z9VRC)hCpxwoIajW_z@w`bAuC}H@R9phYn>d^3gI{d#2-$K3Z>{ZKyWY-KZ0H65=~) zvZ7&OhE!ItuX6t2!1YL#%)OPGuiWxDK2$Kt$EAnfRojY!~0c zEd}ihI>{d`D|AD=_LZN%1}p*7QhLv15HaQ()-ju{a939=g$my5`k(3t{NWn6`ZA>a zmt^#0BX%1j=uG4vb<*sJPx$N^g_~IPUMjB4S+du`&J#Vxj zgX)vE`5i*OB=Z3oTBkY*`EWI{0P(Ivz<%qc=RXhxioip9TR4mz~J-a_5a4pH)EaM?yu^x#(YGw#o@$4vwFMeyjJ*Srw z!Xw6TBY;HBn&7P^mY$H=<(otpZ*hW_ccx`moLa8l_!JI09jBRgN zKXs4NsI6@N-{B#+beXcgs=ajzZM72i{L-c!7B=K<-82>cNk9BR;Ni(DQ(}uGtfRAY zN|G&dDetX8`GKB2!CcR1>!E-SvY2kVCEA=lF(_$M#_q@{gW`@nQoYXG<*|Nb<^(Mm<=uAgx!{0mP12I3CrY@ky2 WIMw0j&x)OY10drAac#bgZ~p*!xe+J; diff --git a/website/images/logo-400x240.png b/website/images/logo-400x240.png deleted file mode 100644 index 9c39da97a878ec4cfd8d4c632d2eaa4efcc0eb74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3751 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 CjV=5D diff --git a/website/images/logo-clickhouse.svg b/website/images/logo-clickhouse.svg deleted file mode 100644 index b6bb0cd2d07..00000000000 --- a/website/images/logo-clickhouse.svg +++ /dev/null @@ -1 +0,0 @@ -ClickHouse Logo \ No newline at end of file diff --git a/website/images/logo.png b/website/images/logo.png deleted file mode 100644 index 552637796d655b238468957043f815ecf0b54424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 377 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58CaNs)Vi3hp+Jhc*vT`5gM;JtL;nXrE@y#9 zWHAGS=OqwkT;baF11Kn2;u=vBoS#-wo>-L1P+nfHmzkGcoSayYs+V7sKKq@G6i|^w zfKP}kkp6#y;r}^?|7RHf1KBV#-=@m}D92V3 \ No newline at end of file From ffde0d953034b91554c39024d6ecf18140c6fcdd Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 10 Aug 2022 04:46:43 +0200 Subject: [PATCH 494/672] Fix broken image in test-visualizer --- utils/tests-visualizer/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/tests-visualizer/index.html b/utils/tests-visualizer/index.html index 15ee221aa8e..11b2d6504e4 100644 --- a/utils/tests-visualizer/index.html +++ b/utils/tests-visualizer/index.html @@ -62,7 +62,7 @@

 

Data not load

- +

Loading (~10 seconds, ~20 MB)

From 34c8d2c3b45c49f36cab6f61f8c3d8de96ad0f1b Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 10 Aug 2022 08:33:58 +0200 Subject: [PATCH 495/672] Play: recognize tab in textarea --- programs/server/play.html | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/programs/server/play.html b/programs/server/play.html index 6a412fa5c60..c511d13cf91 100644 --- a/programs/server/play.html +++ b/programs/server/play.html @@ -78,6 +78,7 @@ /* For iPad */ margin: 0; border-radius: 0; + tab-size: 4; } html, body @@ -609,11 +610,13 @@ } } + let query_area = document.getElementById('query'); + window.onpopstate = function(event) { if (!event.state) { return; } - document.getElementById('query').value = event.state.query; + query_area.value = event.state.query; if (!event.state.response) { clear(); return; @@ -622,13 +625,13 @@ }; if (window.location.hash) { - document.getElementById('query').value = window.atob(window.location.hash.substr(1)); + query_area.value = window.atob(window.location.hash.substr(1)); } function post() { ++request_num; - let query = document.getElementById('query').value; + let query = query_area.value; postImpl(request_num, query); } @@ -645,6 +648,32 @@ } } + /// Pressing Tab in textarea will increase indentation. + /// But for accessibility reasons, we will fall back to tab navigation if the user already used Tab for that. + + let user_prefers_tab_navigation = false; + + [...document.querySelectorAll('input')].map(elem => { + elem.onkeydown = (e) => { + if (e.key == 'Tab') { user_prefers_tab_navigation = true; } + }; + }); + + query_area.onkeydown = (e) => { + if (e.key == 'Tab' && !event.shiftKey && !user_prefers_tab_navigation) { + let elem = e.target; + let selection_start = elem.selectionStart; + let selection_end = elem.selectionEnd; + + elem.value = elem.value.substring(0, elem.selectionStart) + ' ' + elem.value.substring(elem.selectionEnd); + elem.selectionStart = selection_start + 4; + elem.selectionEnd = selection_start + 4; + + e.preventDefault(); + return false; + } + }; + function clearElement(id) { let elem = document.getElementById(id); @@ -701,7 +730,7 @@ stats.innerText = `Elapsed: ${seconds} sec, read ${formatted_rows} rows, ${formatted_bytes}.`; /// We can also render graphs if user performed EXPLAIN PIPELINE graph=1 or EXPLAIN AST graph = 1 - if (response.data.length > 3 && document.getElementById('query').value.match(/^\s*EXPLAIN/i) && typeof(response.data[0][0]) === "string" && response.data[0][0].startsWith("digraph")) { + if (response.data.length > 3 && query_area.value.match(/^\s*EXPLAIN/i) && typeof(response.data[0][0]) === "string" && response.data[0][0].startsWith("digraph")) { renderGraph(response); } else { renderTable(response); From 5bc85a61780896c4ec7f7171f0a67017cbcd2ef9 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 10 Aug 2022 09:09:04 +0200 Subject: [PATCH 496/672] Do not write elapsed_ns in HTTP headers --- src/IO/Progress.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/IO/Progress.cpp b/src/IO/Progress.cpp index e37dfdb88e3..1069803633c 100644 --- a/src/IO/Progress.cpp +++ b/src/IO/Progress.cpp @@ -61,8 +61,6 @@ void ProgressValues::writeJSON(WriteBuffer & out) const writeText(result_rows, out); writeCString("\",\"result_bytes\":\"", out); writeText(result_bytes, out); - writeCString("\",\"elapsed_ns\":\"", out); - writeText(elapsed_ns, out); writeCString("\"}", out); } From c283351a5f2100c5eace627d84d2442b2c2d0861 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 10 Aug 2022 09:31:24 +0200 Subject: [PATCH 497/672] Fix error --- src/IO/Progress.h | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/IO/Progress.h b/src/IO/Progress.h index 48da8df26b7..c21b1b854b0 100644 --- a/src/IO/Progress.h +++ b/src/IO/Progress.h @@ -16,19 +16,19 @@ class WriteBuffer; /// See Progress. struct ProgressValues { - UInt64 read_rows; - UInt64 read_bytes; + UInt64 read_rows = 0; + UInt64 read_bytes = 0; - UInt64 total_rows_to_read; - UInt64 total_bytes_to_read; + UInt64 total_rows_to_read = 0; + UInt64 total_bytes_to_read = 0; - UInt64 written_rows; - UInt64 written_bytes; + UInt64 written_rows = 0; + UInt64 written_bytes = 0; - UInt64 result_rows; - UInt64 result_bytes; + UInt64 result_rows = 0; + UInt64 result_bytes = 0; - UInt64 elapsed_ns; + UInt64 elapsed_ns = 0; void read(ReadBuffer & in, UInt64 server_revision); void write(WriteBuffer & out, UInt64 client_revision) const; @@ -37,9 +37,9 @@ struct ProgressValues struct ReadProgress { - UInt64 read_rows; - UInt64 read_bytes; - UInt64 total_rows_to_read; + UInt64 read_rows = 0; + UInt64 read_bytes = 0; + UInt64 total_rows_to_read = 0; ReadProgress(UInt64 read_rows_, UInt64 read_bytes_, UInt64 total_rows_to_read_ = 0) : read_rows(read_rows_), read_bytes(read_bytes_), total_rows_to_read(total_rows_to_read_) {} @@ -47,8 +47,8 @@ struct ReadProgress struct WriteProgress { - UInt64 written_rows; - UInt64 written_bytes; + UInt64 written_rows = 0; + UInt64 written_bytes = 0; WriteProgress(UInt64 written_rows_, UInt64 written_bytes_) : written_rows(written_rows_), written_bytes(written_bytes_) {} @@ -56,8 +56,8 @@ struct WriteProgress struct ResultProgress { - UInt64 result_rows; - UInt64 result_bytes; + UInt64 result_rows = 0; + UInt64 result_bytes = 0; ResultProgress(UInt64 result_rows_, UInt64 result_bytes_) : result_rows(result_rows_), result_bytes(result_bytes_) {} @@ -66,8 +66,8 @@ struct ResultProgress struct FileProgress { /// Here read_bytes (raw bytes) - do not equal ReadProgress::read_bytes, which are calculated according to column types. - UInt64 read_bytes; - UInt64 total_bytes_to_read; + UInt64 read_bytes = 0; + UInt64 total_bytes_to_read = 0; explicit FileProgress(UInt64 read_bytes_, UInt64 total_bytes_to_read_ = 0) : read_bytes(read_bytes_), total_bytes_to_read(total_bytes_to_read_) {} }; From 810221baf21c8e055839035984aae5d687dda39a Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 10 Aug 2022 07:39:32 +0000 Subject: [PATCH 498/672] Assume unversioned server has version=0 and use tryParse() instead of from_chars() --- .../library-bridge/LibraryBridgeHandlers.cpp | 25 ++++++++--------- programs/odbc-bridge/ColumnInfoHandler.cpp | 27 ++++++++---------- .../odbc-bridge/IdentifierQuoteHandler.cpp | 27 ++++++++---------- programs/odbc-bridge/MainHandler.cpp | 26 ++++++++--------- programs/odbc-bridge/SchemaAllowedHandler.cpp | 28 +++++++++---------- 5 files changed, 60 insertions(+), 73 deletions(-) diff --git a/programs/library-bridge/LibraryBridgeHandlers.cpp b/programs/library-bridge/LibraryBridgeHandlers.cpp index bdb5f3ca02b..a28148bd1f7 100644 --- a/programs/library-bridge/LibraryBridgeHandlers.cpp +++ b/programs/library-bridge/LibraryBridgeHandlers.cpp @@ -19,7 +19,6 @@ #include #include #include -#include namespace DB @@ -92,27 +91,25 @@ void ExternalDictionaryLibraryBridgeRequestHandler::handleRequest(HTTPServerRequ LOG_TRACE(log, "Request URI: {}", request.getURI()); HTMLForm params(getContext()->getSettingsRef(), request); + size_t version; + if (!params.has("version")) - { - processError(response, "No 'version' in request URL"); - return; - } + version = 0; /// assumed version for too old servers which do not send a version else { String version_str = params.get("version"); - size_t version; - auto [_, ec] = std::from_chars(version_str.data(), version_str.data() + version_str.size(), version); - if (ec != std::errc()) + if (!tryParse(version, version_str)) { processError(response, "Unable to parse 'version' string in request URL: '" + version_str + "' Check if the server and library-bridge have the same version."); return; } - if (version != LIBRARY_BRIDGE_PROTOCOL_VERSION) - { - // backwards compatibility is for now deemed unnecessary, just let the user upgrade the server and bridge to the same version - processError(response, "Server and library-bridge have different versions: '" + std::to_string(version) + "' vs. '" + std::to_string(LIBRARY_BRIDGE_PROTOCOL_VERSION) + "'"); - return; - } + } + + if (version != LIBRARY_BRIDGE_PROTOCOL_VERSION) + { + /// backwards compatibility is considered unnecessary for now, just let the user know that the server and the bridge must be upgraded together + processError(response, "Server and library-bridge have different versions: '" + std::to_string(version) + "' vs. '" + std::to_string(LIBRARY_BRIDGE_PROTOCOL_VERSION) + "'"); + return; } if (!params.has("method")) diff --git a/programs/odbc-bridge/ColumnInfoHandler.cpp b/programs/odbc-bridge/ColumnInfoHandler.cpp index 7d2f6f57d34..0ea2495af78 100644 --- a/programs/odbc-bridge/ColumnInfoHandler.cpp +++ b/programs/odbc-bridge/ColumnInfoHandler.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -23,8 +24,6 @@ #include #include -#include - namespace DB { @@ -83,27 +82,25 @@ void ODBCColumnsInfoHandler::handleRequest(HTTPServerRequest & request, HTTPServ LOG_WARNING(log, fmt::runtime(message)); }; + size_t version; + if (!params.has("version")) - { - process_error("No 'version' in request URL"); - return; - } + version = 0; /// assumed version for too old servers which do not send a version else { String version_str = params.get("version"); - size_t version; - auto [_, ec] = std::from_chars(version_str.data(), version_str.data() + version_str.size(), version); - if (ec != std::errc()) + if (!tryParse(version, version_str)) { process_error("Unable to parse 'version' string in request URL: '" + version_str + "' Check if the server and library-bridge have the same version."); return; } - if (version != XDBC_BRIDGE_PROTOCOL_VERSION) - { - // backwards compatibility is for now deemed unnecessary, just let the user upgrade the server and bridge to the same version - process_error("Server and library-bridge have different versions: '" + std::to_string(version) + "' vs. '" + std::to_string(LIBRARY_BRIDGE_PROTOCOL_VERSION) + "'"); - return; - } + } + + if (version != XDBC_BRIDGE_PROTOCOL_VERSION) + { + /// backwards compatibility is considered unnecessary for now, just let the user know that the server and the bridge must be upgraded together + process_error("Server and library-bridge have different versions: '" + std::to_string(version) + "' vs. '" + std::to_string(LIBRARY_BRIDGE_PROTOCOL_VERSION) + "'"); + return; } if (!params.has("table")) diff --git a/programs/odbc-bridge/IdentifierQuoteHandler.cpp b/programs/odbc-bridge/IdentifierQuoteHandler.cpp index b162a2ea324..8157e3b6159 100644 --- a/programs/odbc-bridge/IdentifierQuoteHandler.cpp +++ b/programs/odbc-bridge/IdentifierQuoteHandler.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -17,8 +18,6 @@ #include "validateODBCConnectionString.h" #include "ODBCPooledConnectionFactory.h" -#include - namespace DB { @@ -35,27 +34,25 @@ void IdentifierQuoteHandler::handleRequest(HTTPServerRequest & request, HTTPServ LOG_WARNING(log, fmt::runtime(message)); }; + size_t version; + if (!params.has("version")) - { - process_error("No 'version' in request URL"); - return; - } + version = 0; /// assumed version for too old servers which do not send a version else { String version_str = params.get("version"); - size_t version; - auto [_, ec] = std::from_chars(version_str.data(), version_str.data() + version_str.size(), version); - if (ec != std::errc()) + if (!tryParse(version, version_str)) { process_error("Unable to parse 'version' string in request URL: '" + version_str + "' Check if the server and library-bridge have the same version."); return; } - if (version != XDBC_BRIDGE_PROTOCOL_VERSION) - { - // backwards compatibility is for now deemed unnecessary, just let the user upgrade the server and bridge to the same version - process_error("Server and library-bridge have different versions: '" + std::to_string(version) + "' vs. '" + std::to_string(LIBRARY_BRIDGE_PROTOCOL_VERSION) + "'"); - return; - } + } + + if (version != XDBC_BRIDGE_PROTOCOL_VERSION) + { + /// backwards compatibility is considered unnecessary for now, just let the user know that the server and the bridge must be upgraded together + process_error("Server and library-bridge have different versions: '" + std::to_string(version) + "' vs. '" + std::to_string(LIBRARY_BRIDGE_PROTOCOL_VERSION) + "'"); + return; } if (!params.has("connection_string")) diff --git a/programs/odbc-bridge/MainHandler.cpp b/programs/odbc-bridge/MainHandler.cpp index 6ece12198d3..fe22d8facfd 100644 --- a/programs/odbc-bridge/MainHandler.cpp +++ b/programs/odbc-bridge/MainHandler.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include @@ -57,29 +56,28 @@ void ODBCHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse HTMLForm params(getContext()->getSettingsRef(), request); LOG_TRACE(log, "Request URI: {}", request.getURI()); + size_t version; + if (!params.has("version")) - { - processError(response, "No 'version' in request URL"); - return; - } + version = 0; /// assumed version for too old servers which do not send a version else { String version_str = params.get("version"); - size_t version; - auto [_, ec] = std::from_chars(version_str.data(), version_str.data() + version_str.size(), version); - if (ec != std::errc()) + if (!tryParse(version, version_str)) { processError(response, "Unable to parse 'version' string in request URL: '" + version_str + "' Check if the server and library-bridge have the same version."); return; } - if (version != XDBC_BRIDGE_PROTOCOL_VERSION) - { - // backwards compatibility is for now deemed unnecessary, just let the user upgrade the server and bridge to the same version - processError(response, "Server and library-bridge have different versions: '" + std::to_string(version) + "' vs. '" + std::to_string(LIBRARY_BRIDGE_PROTOCOL_VERSION) + "'"); - return; - } } + if (version != XDBC_BRIDGE_PROTOCOL_VERSION) + { + /// backwards compatibility is considered unnecessary for now, just let the user know that the server and the bridge must be upgraded together + processError(response, "Server and library-bridge have different versions: '" + std::to_string(version) + "' vs. '" + std::to_string(LIBRARY_BRIDGE_PROTOCOL_VERSION) + "'"); + return; + } + + if (mode == "read") params.read(request.getStream()); diff --git a/programs/odbc-bridge/SchemaAllowedHandler.cpp b/programs/odbc-bridge/SchemaAllowedHandler.cpp index f70474fc898..4d20c8bc3b7 100644 --- a/programs/odbc-bridge/SchemaAllowedHandler.cpp +++ b/programs/odbc-bridge/SchemaAllowedHandler.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -14,8 +15,6 @@ #include #include -#include - namespace DB { @@ -43,29 +42,28 @@ void SchemaAllowedHandler::handleRequest(HTTPServerRequest & request, HTTPServer LOG_WARNING(log, fmt::runtime(message)); }; + size_t version; + if (!params.has("version")) - { - process_error("No 'version' in request URL"); - return; - } + version = 0; /// assumed version for too old servers which do not send a version else { String version_str = params.get("version"); - size_t version; - auto [_, ec] = std::from_chars(version_str.data(), version_str.data() + version_str.size(), version); - if (ec != std::errc()) + if (!tryParse(version, version_str)) { process_error("Unable to parse 'version' string in request URL: '" + version_str + "' Check if the server and library-bridge have the same version."); return; } - if (version != XDBC_BRIDGE_PROTOCOL_VERSION) - { - // backwards compatibility is for now deemed unnecessary, just let the user upgrade the server and bridge to the same version - process_error("Server and library-bridge have different versions: '" + std::to_string(version) + "' vs. '" + std::to_string(LIBRARY_BRIDGE_PROTOCOL_VERSION) + "'"); - return; - } } + if (version != XDBC_BRIDGE_PROTOCOL_VERSION) + { + /// backwards compatibility is considered unnecessary for now, just let the user know that the server and the bridge must be upgraded together + process_error("Server and library-bridge have different versions: '" + std::to_string(version) + "' vs. '" + std::to_string(LIBRARY_BRIDGE_PROTOCOL_VERSION) + "'"); + return; + } + + if (!params.has("connection_string")) { process_error("No 'connection_string' in request URL"); From 63812de4386bc960ad4169c1c9d0837a3be04268 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 10 Aug 2022 09:44:33 +0200 Subject: [PATCH 499/672] Add a test --- ...381_client_prints_server_side_time.reference | 1 + .../02381_client_prints_server_side_time.sh | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 tests/queries/0_stateless/02381_client_prints_server_side_time.reference create mode 100755 tests/queries/0_stateless/02381_client_prints_server_side_time.sh diff --git a/tests/queries/0_stateless/02381_client_prints_server_side_time.reference b/tests/queries/0_stateless/02381_client_prints_server_side_time.reference new file mode 100644 index 00000000000..7326d960397 --- /dev/null +++ b/tests/queries/0_stateless/02381_client_prints_server_side_time.reference @@ -0,0 +1 @@ +Ok diff --git a/tests/queries/0_stateless/02381_client_prints_server_side_time.sh b/tests/queries/0_stateless/02381_client_prints_server_side_time.sh new file mode 100755 index 00000000000..2b296828e37 --- /dev/null +++ b/tests/queries/0_stateless/02381_client_prints_server_side_time.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +# We will check that --time option of clickhouse-client will display server-side time, not client-side time. +# For this purpose, we ask it to output some amount of data that is larger than the buffers in clickhouse-client +# but smaller than the pipe buffer, and we slow down output of the query result with 'pv' command to get around one second of run time. +# If everything is works as expected, at least sometimes this command should display time less than 100ms. +while true +do + $CLICKHOUSE_CLIENT --time --query 'SELECT 1 FROM numbers(1000000) FORMAT RowBinary' 2>"${CLICKHOUSE_TMP}/time" | pv --quiet --rate-limit 1M > /dev/null + grep -q -F '0.0' "${CLICKHOUSE_TMP}/time" && break +done + +echo 'Ok' From 202b48aaa0e26643fdcda946d9f4697e92047720 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 10 Aug 2022 10:10:28 +0200 Subject: [PATCH 500/672] Add a test for query parameters in HTTP POST --- .../0_stateless/02382_query_parameters_post.reference | 1 + tests/queries/0_stateless/02382_query_parameters_post.sh | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 tests/queries/0_stateless/02382_query_parameters_post.reference create mode 100755 tests/queries/0_stateless/02382_query_parameters_post.sh diff --git a/tests/queries/0_stateless/02382_query_parameters_post.reference b/tests/queries/0_stateless/02382_query_parameters_post.reference new file mode 100644 index 00000000000..7f8f011eb73 --- /dev/null +++ b/tests/queries/0_stateless/02382_query_parameters_post.reference @@ -0,0 +1 @@ +7 diff --git a/tests/queries/0_stateless/02382_query_parameters_post.sh b/tests/queries/0_stateless/02382_query_parameters_post.sh new file mode 100755 index 00000000000..25db5043292 --- /dev/null +++ b/tests/queries/0_stateless/02382_query_parameters_post.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +${CLICKHOUSE_CURL} -X POST -F 'query=select {p1:UInt8} + {p2:UInt8}' -F 'param_p1=3' -F 'param_p2=4' "${CLICKHOUSE_URL}" From 537ba613dcc5c2f23f5763f894dcbe2b9823d32d Mon Sep 17 00:00:00 2001 From: Roman Vasin Date: Wed, 10 Aug 2022 08:14:16 +0000 Subject: [PATCH 501/672] Remove clamping from AddDaysImpl and AddWeeksImpl; fix 01921_datatype_date32 test --- src/Functions/FunctionDateOrDateTimeAddInterval.h | 8 ++++---- tests/queries/0_stateless/01921_datatype_date32.reference | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Functions/FunctionDateOrDateTimeAddInterval.h b/src/Functions/FunctionDateOrDateTimeAddInterval.h index bf2f530cb7f..fbfc9e9bc1f 100644 --- a/src/Functions/FunctionDateOrDateTimeAddInterval.h +++ b/src/Functions/FunctionDateOrDateTimeAddInterval.h @@ -288,12 +288,12 @@ struct AddDaysImpl static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &, UInt16 = 0) { - return static_cast(std::clamp(d + delta, 0L, 65535L)); + return d + delta; } static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl &, UInt16 = 0) { - return std::max(static_cast(d + delta), -static_cast(DateLUT::instance().getDayNumOffsetEpoch())); + return d + delta; } }; @@ -322,12 +322,12 @@ struct AddWeeksImpl static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int32 delta, const DateLUTImpl &, UInt16 = 0) { - return static_cast(std::clamp(d + delta * 7, 0, 65535)); + return d + delta * 7; } static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int32 delta, const DateLUTImpl &, UInt16 = 0) { - return std::max(static_cast(d + delta * 7), -static_cast(DateLUT::instance().getDayNumOffsetEpoch())); + return d + delta * 7; } }; diff --git a/tests/queries/0_stateless/01921_datatype_date32.reference b/tests/queries/0_stateless/01921_datatype_date32.reference index 8cc9cc2886f..acb0cc4ca59 100644 --- a/tests/queries/0_stateless/01921_datatype_date32.reference +++ b/tests/queries/0_stateless/01921_datatype_date32.reference @@ -248,14 +248,14 @@ 2299-12-30 23:00:00.000 2021-06-21 23:00:00.000 -------subtractDays--------- -1900-01-01 -1900-01-01 +2299-12-31 +2299-12-31 2299-12-08 2299-12-24 2021-06-15 -------subtractWeeks--------- -1900-01-01 -1900-01-01 +2299-12-31 +2299-12-31 2299-12-08 2299-12-24 2021-06-15 From 36ea40ab64430b5a93eeca8d6ed5ee56a6d66522 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 10 Aug 2022 11:21:42 +0200 Subject: [PATCH 502/672] Fix clickhouse-test hang in case of CREATE DATABASE fails Signed-off-by: Azat Khuzhin --- tests/clickhouse-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 952fc7fb0a9..96a7c929260 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -914,7 +914,7 @@ class TestCase: description_full += result.description description_full += "\n" - if result.status == TestStatus.FAIL: + if result.status == TestStatus.FAIL and self.testcase_args: description_full += "Database: " + self.testcase_args.testcase_database result.description = description_full From 6164506a5ebb50bd451c4305bbc30a54085106da Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 10 Aug 2022 10:52:58 +0200 Subject: [PATCH 503/672] tests: fix 02380_insert_mv_race for Ordinary database Signed-off-by: Azat Khuzhin --- tests/queries/0_stateless/02380_insert_mv_race.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02380_insert_mv_race.sh b/tests/queries/0_stateless/02380_insert_mv_race.sh index 3edf99fb502..725c7eacce6 100755 --- a/tests/queries/0_stateless/02380_insert_mv_race.sh +++ b/tests/queries/0_stateless/02380_insert_mv_race.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: long, race, no-ordinary-database +# Tags: long, race # Regression test for INSERT into table with MV attached, # to avoid possible errors if some table will disappears, @@ -9,6 +9,12 @@ CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CUR_DIR"/../shell_config.sh +$CLICKHOUSE_CLIENT -nm -q "ATTACH TABLE mv" |& { + # CANNOT_GET_CREATE_TABLE_QUERY -- ATTACH TABLE IF EXISTS + # TABLE_ALREADY_EXISTS -- ATTACH TABLE IF NOT EXISTS + grep -F -m1 Exception | grep -v -e CANNOT_GET_CREATE_TABLE_QUERY -e TABLE_ALREADY_EXISTS +} + $CLICKHOUSE_CLIENT -nm -q " DROP TABLE IF EXISTS null; CREATE TABLE null (key Int) ENGINE = Null; @@ -23,4 +29,8 @@ $CLICKHOUSE_CLIENT -q "INSERT INTO null SELECT * FROM numbers_mt(1000) settings } & sleep 0.05 $CLICKHOUSE_CLIENT -q "DETACH TABLE mv" + +# avoid leftovers on DROP DATABASE (force_remove_data_recursively_on_drop) for Ordinary database +$CLICKHOUSE_CLIENT -q "ATTACH TABLE mv" + wait From cdd5b32d297e1a953d1d31bfed2db78651bdb0b8 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Tue, 9 Aug 2022 16:46:23 +0800 Subject: [PATCH 504/672] fix HashMethodOneNumber with const column --- src/Common/ColumnsHashing.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Common/ColumnsHashing.h b/src/Common/ColumnsHashing.h index e921f4fbf9a..3c44c89674e 100644 --- a/src/Common/ColumnsHashing.h +++ b/src/Common/ColumnsHashing.h @@ -39,15 +39,18 @@ struct HashMethodOneNumber using Base = columns_hashing_impl::HashMethodBase; const char * vec; + ColumnPtr owned_column; /// If the keys of a fixed length then key_sizes contains their lengths, empty otherwise. HashMethodOneNumber(const ColumnRawPtrs & key_columns, const Sizes & /*key_sizes*/, const HashMethodContextPtr &) { - vec = key_columns[0]->getRawData().data; + owned_column = key_columns[0]->convertToFullColumnIfConst(); + vec = owned_column->getRawData().data; } explicit HashMethodOneNumber(const IColumn * column) { + owned_column = column->convertToFullColumnIfConst(); vec = column->getRawData().data; } From 1a08161dbfcf1fd3885c4eaff39dc7f533ec7fc9 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Tue, 9 Aug 2022 17:18:21 +0800 Subject: [PATCH 505/672] simplify solution --- src/Common/ColumnsHashing.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Common/ColumnsHashing.h b/src/Common/ColumnsHashing.h index 3c44c89674e..b558aaccc7e 100644 --- a/src/Common/ColumnsHashing.h +++ b/src/Common/ColumnsHashing.h @@ -39,19 +39,19 @@ struct HashMethodOneNumber using Base = columns_hashing_impl::HashMethodBase; const char * vec; - ColumnPtr owned_column; + bool is_column_const; /// If the keys of a fixed length then key_sizes contains their lengths, empty otherwise. HashMethodOneNumber(const ColumnRawPtrs & key_columns, const Sizes & /*key_sizes*/, const HashMethodContextPtr &) { - owned_column = key_columns[0]->convertToFullColumnIfConst(); - vec = owned_column->getRawData().data; + vec = key_columns[0]->getRawData().data; + is_column_const = isColumnConst(*key_columns[0]); } explicit HashMethodOneNumber(const IColumn * column) { - owned_column = column->convertToFullColumnIfConst(); vec = column->getRawData().data; + is_column_const = isColumnConst(*column); } /// Creates context. Method is called once and result context is used in all threads. @@ -69,7 +69,11 @@ struct HashMethodOneNumber using Base::getHash; /// (const Data & data, size_t row, Arena & pool) -> size_t /// Is used for default implementation in HashMethodBase. - FieldType getKeyHolder(size_t row, Arena &) const { return unalignedLoad(vec + row * sizeof(FieldType)); } + FieldType getKeyHolder(size_t row, Arena &) const + { + size_t pos = is_column_const ? 0 : row; + return unalignedLoad(vec + pos * sizeof(FieldType)); + } const FieldType * getKeyData() const { return reinterpret_cast(vec); } }; From 7910a93b02bb524e88bd68353e0547b6c3a26da3 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Tue, 9 Aug 2022 18:25:52 +0800 Subject: [PATCH 506/672] fix other hash methods and add tests --- src/Common/ColumnsHashing.h | 20 +++++++++++++------ .../02381_intersect_hash_method.reference | 9 +++++++++ .../02381_intersect_hash_method.sql | 3 +++ 3 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 tests/queries/0_stateless/02381_intersect_hash_method.reference create mode 100644 tests/queries/0_stateless/02381_intersect_hash_method.sql diff --git a/src/Common/ColumnsHashing.h b/src/Common/ColumnsHashing.h index b558aaccc7e..877e57db3d6 100644 --- a/src/Common/ColumnsHashing.h +++ b/src/Common/ColumnsHashing.h @@ -39,19 +39,19 @@ struct HashMethodOneNumber using Base = columns_hashing_impl::HashMethodBase; const char * vec; - bool is_column_const; + bool column_is_const = false; /// If the keys of a fixed length then key_sizes contains their lengths, empty otherwise. HashMethodOneNumber(const ColumnRawPtrs & key_columns, const Sizes & /*key_sizes*/, const HashMethodContextPtr &) { vec = key_columns[0]->getRawData().data; - is_column_const = isColumnConst(*key_columns[0]); + column_is_const = isColumnConst(*key_columns[0]); } explicit HashMethodOneNumber(const IColumn * column) { vec = column->getRawData().data; - is_column_const = isColumnConst(*column); + column_is_const = isColumnConst(*column); } /// Creates context. Method is called once and result context is used in all threads. @@ -71,7 +71,7 @@ struct HashMethodOneNumber /// Is used for default implementation in HashMethodBase. FieldType getKeyHolder(size_t row, Arena &) const { - size_t pos = is_column_const ? 0 : row; + size_t pos = column_is_const ? 0 : row; return unalignedLoad(vec + pos * sizeof(FieldType)); } @@ -89,12 +89,16 @@ struct HashMethodString const IColumn::Offset * offsets; const UInt8 * chars; + bool column_is_const = false; HashMethodString(const ColumnRawPtrs & key_columns, const Sizes & /*key_sizes*/, const HashMethodContextPtr &) { const IColumn * column = key_columns[0]; if (isColumnConst(*column)) + { + column_is_const = true; column = &assert_cast(*column).getDataColumn(); + } const ColumnString & column_string = assert_cast(*column); offsets = column_string.getOffsets().data(); @@ -103,7 +107,8 @@ struct HashMethodString auto getKeyHolder(ssize_t row, [[maybe_unused]] Arena & pool) const { - StringRef key(chars + offsets[row - 1], offsets[row] - offsets[row - 1] - 1); + ssize_t pos = column_is_const ? 0 : row; + StringRef key(chars + offsets[pos - 1], offsets[pos] - offsets[pos - 1] - 1); if constexpr (place_string_to_arena) { @@ -131,6 +136,7 @@ struct HashMethodFixedString size_t n; const ColumnFixedString::Chars * chars; + bool column_is_const = false; HashMethodFixedString(const ColumnRawPtrs & key_columns, const Sizes & /*key_sizes*/, const HashMethodContextPtr &) { @@ -138,11 +144,13 @@ struct HashMethodFixedString const ColumnFixedString & column_string = assert_cast(column); n = column_string.getN(); chars = &column_string.getChars(); + column_is_const = isColumnConst(column); } auto getKeyHolder(size_t row, [[maybe_unused]] Arena & pool) const { - StringRef key(&(*chars)[row * n], n); + size_t pos = column_is_const ? 0 : row; + StringRef key(&(*chars)[pos * n], n); if constexpr (place_string_to_arena) { diff --git a/tests/queries/0_stateless/02381_intersect_hash_method.reference b/tests/queries/0_stateless/02381_intersect_hash_method.reference new file mode 100644 index 00000000000..bb0850568bb --- /dev/null +++ b/tests/queries/0_stateless/02381_intersect_hash_method.reference @@ -0,0 +1,9 @@ +1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/tests/queries/0_stateless/02381_intersect_hash_method.sql b/tests/queries/0_stateless/02381_intersect_hash_method.sql new file mode 100644 index 00000000000..cc1967dafd7 --- /dev/null +++ b/tests/queries/0_stateless/02381_intersect_hash_method.sql @@ -0,0 +1,3 @@ +SELECT 1 FROM numbers(3) INTERSECT SELECT 1 FROM numbers(3); +SELECT toString(1) FROM numbers(3) INTERSECT SELECT toString(1) FROM numbers(3); +SELECT '1' FROM numbers(3) INTERSECT SELECT '1' FROM numbers(3); From 9e865d48ce6466287df427dac35a9dd262e88060 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Tue, 9 Aug 2022 18:50:17 +0800 Subject: [PATCH 507/672] increase test size --- .../02381_intersect_hash_method.reference | 21 +++++++++++++++++++ .../02381_intersect_hash_method.sql | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/tests/queries/0_stateless/02381_intersect_hash_method.reference b/tests/queries/0_stateless/02381_intersect_hash_method.reference index bb0850568bb..ac8f48bbb7b 100644 --- a/tests/queries/0_stateless/02381_intersect_hash_method.reference +++ b/tests/queries/0_stateless/02381_intersect_hash_method.reference @@ -7,3 +7,24 @@ 1 1 1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/tests/queries/0_stateless/02381_intersect_hash_method.sql b/tests/queries/0_stateless/02381_intersect_hash_method.sql index cc1967dafd7..1154718c686 100644 --- a/tests/queries/0_stateless/02381_intersect_hash_method.sql +++ b/tests/queries/0_stateless/02381_intersect_hash_method.sql @@ -1,3 +1,3 @@ -SELECT 1 FROM numbers(3) INTERSECT SELECT 1 FROM numbers(3); -SELECT toString(1) FROM numbers(3) INTERSECT SELECT toString(1) FROM numbers(3); -SELECT '1' FROM numbers(3) INTERSECT SELECT '1' FROM numbers(3); +SELECT 1 FROM numbers(10) INTERSECT SELECT 1 FROM numbers(10); +SELECT toString(1) FROM numbers(10) INTERSECT SELECT toString(1) FROM numbers(10); +SELECT '1' FROM numbers(10) INTERSECT SELECT '1' FROM numbers(10); From 17fa8076e32e0c0f0713bb2f4830875ad169f8f6 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Wed, 10 Aug 2022 18:56:18 +0800 Subject: [PATCH 508/672] better implementation --- src/Common/ColumnsHashing.h | 50 ++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/Common/ColumnsHashing.h b/src/Common/ColumnsHashing.h index 877e57db3d6..6aef60fffc7 100644 --- a/src/Common/ColumnsHashing.h +++ b/src/Common/ColumnsHashing.h @@ -7,6 +7,7 @@ #include #include #include "Columns/IColumn.h" +#include "Core/Field.h" #include #include @@ -15,8 +16,10 @@ #include #include +#include #include #include +#include namespace DB @@ -39,19 +42,27 @@ struct HashMethodOneNumber using Base = columns_hashing_impl::HashMethodBase; const char * vec; - bool column_is_const = false; + FieldType const_value; + std::function get_key_holder_impl; /// If the keys of a fixed length then key_sizes contains their lengths, empty otherwise. HashMethodOneNumber(const ColumnRawPtrs & key_columns, const Sizes & /*key_sizes*/, const HashMethodContextPtr &) { vec = key_columns[0]->getRawData().data; - column_is_const = isColumnConst(*key_columns[0]); + if (isColumnConst(*key_columns[0])) + { + const_value = unalignedLoad(vec); + get_key_holder_impl = [this](size_t /*row*/) { return const_value; }; + } + else + { + get_key_holder_impl = [this](size_t row) { return unalignedLoad(vec + row * sizeof(FieldType)); }; + } } explicit HashMethodOneNumber(const IColumn * column) { vec = column->getRawData().data; - column_is_const = isColumnConst(*column); } /// Creates context. Method is called once and result context is used in all threads. @@ -69,11 +80,7 @@ struct HashMethodOneNumber using Base::getHash; /// (const Data & data, size_t row, Arena & pool) -> size_t /// Is used for default implementation in HashMethodBase. - FieldType getKeyHolder(size_t row, Arena &) const - { - size_t pos = column_is_const ? 0 : row; - return unalignedLoad(vec + pos * sizeof(FieldType)); - } + FieldType getKeyHolder(size_t row, Arena &) const { return get_key_holder_impl(row); } const FieldType * getKeyData() const { return reinterpret_cast(vec); } }; @@ -89,27 +96,28 @@ struct HashMethodString const IColumn::Offset * offsets; const UInt8 * chars; - bool column_is_const = false; + std::function get_key_holder_impl; HashMethodString(const ColumnRawPtrs & key_columns, const Sizes & /*key_sizes*/, const HashMethodContextPtr &) { const IColumn * column = key_columns[0]; - if (isColumnConst(*column)) - { - column_is_const = true; + bool column_is_const = isColumnConst(*column); + if (column_is_const) column = &assert_cast(*column).getDataColumn(); - } const ColumnString & column_string = assert_cast(*column); offsets = column_string.getOffsets().data(); chars = column_string.getChars().data(); + + if (column_is_const) + get_key_holder_impl = [this](size_t /*row*/) { return StringRef(chars, offsets[0] - 1); }; + else + get_key_holder_impl = [this](size_t row) { return StringRef(chars + offsets[row - 1], offsets[row] - offsets[row - 1] - 1); }; } auto getKeyHolder(ssize_t row, [[maybe_unused]] Arena & pool) const { - ssize_t pos = column_is_const ? 0 : row; - StringRef key(chars + offsets[pos - 1], offsets[pos] - offsets[pos - 1] - 1); - + StringRef key = get_key_holder_impl(row); if constexpr (place_string_to_arena) { return ArenaKeyHolder{key, pool}; @@ -136,7 +144,7 @@ struct HashMethodFixedString size_t n; const ColumnFixedString::Chars * chars; - bool column_is_const = false; + std::function get_key_holder_impl; HashMethodFixedString(const ColumnRawPtrs & key_columns, const Sizes & /*key_sizes*/, const HashMethodContextPtr &) { @@ -144,13 +152,15 @@ struct HashMethodFixedString const ColumnFixedString & column_string = assert_cast(column); n = column_string.getN(); chars = &column_string.getChars(); - column_is_const = isColumnConst(column); + if (isColumnConst(column)) + get_key_holder_impl = [this](size_t /*row*/) { return StringRef(&(*chars)[0], n); }; + else + get_key_holder_impl = [this](size_t row) { return StringRef(&(*chars)[row * n], n); }; } auto getKeyHolder(size_t row, [[maybe_unused]] Arena & pool) const { - size_t pos = column_is_const ? 0 : row; - StringRef key(&(*chars)[pos * n], n); + StringRef key = get_key_holder_impl(row); if constexpr (place_string_to_arena) { From ec49f7b3a3b19f34afa1ac413de492c50493b3d8 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Wed, 10 Aug 2022 19:28:08 +0800 Subject: [PATCH 509/672] fix style --- src/Common/ColumnsHashing.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Common/ColumnsHashing.h b/src/Common/ColumnsHashing.h index 6aef60fffc7..e89c894c427 100644 --- a/src/Common/ColumnsHashing.h +++ b/src/Common/ColumnsHashing.h @@ -6,8 +6,8 @@ #include #include #include -#include "Columns/IColumn.h" -#include "Core/Field.h" +#include +#include #include #include @@ -110,7 +110,7 @@ struct HashMethodString chars = column_string.getChars().data(); if (column_is_const) - get_key_holder_impl = [this](size_t /*row*/) { return StringRef(chars, offsets[0] - 1); }; + get_key_holder_impl = [this](size_t /*row*/) { return StringRef(chars, offsets[0] - 1); }; else get_key_holder_impl = [this](size_t row) { return StringRef(chars + offsets[row - 1], offsets[row] - offsets[row - 1] - 1); }; } From 9ecf1c156a18e73e42b4932da772a7e0c205d99b Mon Sep 17 00:00:00 2001 From: vdimir Date: Wed, 10 Aug 2022 11:32:24 +0000 Subject: [PATCH 510/672] Skip newlines before Tags in clickhouse-test --- tests/clickhouse-test | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 952fc7fb0a9..179060be96d 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1179,15 +1179,21 @@ class TestSuite: def is_shebang(line: str) -> bool: return line.startswith("#!") + def find_tag_line(file): + for line in file: + line = line.strip() + if line and not is_shebang(line): + return line + return '' + def load_tags_from_file(filepath): + comment_sign = get_comment_sign(filepath) with open(filepath, "r", encoding="utf-8") as file: try: - line = file.readline() - if is_shebang(line): - line = file.readline() + line = find_tag_line(file) except UnicodeDecodeError: return [] - return parse_tags_from_line(line, get_comment_sign(filepath)) + return parse_tags_from_line(line, comment_sign) all_tags = {} start_time = datetime.now() From c7615e9bde579de97c0f847d66ba9026d4dbd116 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 10 Aug 2022 12:28:28 +0000 Subject: [PATCH 511/672] fix style check --- src/Columns/ColumnArray.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Columns/ColumnArray.cpp b/src/Columns/ColumnArray.cpp index bf646139b41..93bcc3eb611 100644 --- a/src/Columns/ColumnArray.cpp +++ b/src/Columns/ColumnArray.cpp @@ -56,7 +56,7 @@ ColumnArray::ColumnArray(MutableColumnPtr && nested_column, MutableColumnPtr && /// This will also prevent possible overflow in offset. if (data->size() != last_offset) - throw Exception( ErrorCodes::LOGICAL_ERROR, + throw Exception(ErrorCodes::LOGICAL_ERROR, "offsets_column has data inconsistent with nested_column. Data size: {}, last offset: {}", data->size(), last_offset); } From 4b2bba2ff1aa9a878335e3c975e648992fe76400 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 10 Aug 2022 14:37:06 +0200 Subject: [PATCH 512/672] Do not upload unnecessary lambda sources --- .../build_and_deploy_archive.sh | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/ci/team_keys_lambda/build_and_deploy_archive.sh b/tests/ci/team_keys_lambda/build_and_deploy_archive.sh index cd5b0d26e3f..defa400453f 100644 --- a/tests/ci/team_keys_lambda/build_and_deploy_archive.sh +++ b/tests/ci/team_keys_lambda/build_and_deploy_archive.sh @@ -7,18 +7,20 @@ cd "$WORKDIR" PY_EXEC=python3.9 LAMBDA_NAME=$(basename "$PWD") LAMBDA_NAME=${LAMBDA_NAME//_/-} -VENV=lambda-venv -rm -rf "$VENV" lambda-package.zip -"$PY_EXEC" -m venv "$VENV" -#virtualenv "$VENV" -# shellcheck disable=SC1091 -source "$VENV/bin/activate" -pip install -r requirements.txt PACKAGE=lambda-package rm -rf "$PACKAGE" "$PACKAGE".zip -cp -r "$VENV/lib/$PY_EXEC/site-packages" "$PACKAGE" +mkdir "$PACKAGE" cp app.py "$PACKAGE" -rm -r "$PACKAGE"/{pip,pip-*,setuptools,setuptools-*} -( cd "$PACKAGE" && zip -r ../"$PACKAGE".zip . ) +if [ -f requirements.txt ]; then + VENV=lambda-venv + rm -rf "$VENV" lambda-package.zip + "$PY_EXEC" -m venv "$VENV" + # shellcheck disable=SC1091 + source "$VENV/bin/activate" + pip install -r requirements.txt + cp -rT "$VENV/lib/$PY_EXEC/site-packages/" "$PACKAGE" + rm -r "$PACKAGE"/{pip,pip-*,setuptools,setuptools-*} +fi +( cd "$PACKAGE" && zip -9 -r ../"$PACKAGE".zip . ) aws lambda update-function-code --function-name "$LAMBDA_NAME" --zip-file fileb://"$PACKAGE".zip From 933043d84124e69f7fd2c632b37597f24fce5f07 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 10 Aug 2022 12:38:35 +0000 Subject: [PATCH 513/672] Minor fixes. --- src/Interpreters/AsynchronousMetrics.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index c1102a0652d..330afae8137 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -696,6 +696,8 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti Int64 peak = total_memory_tracker.getPeak(); Int64 rss = data.resident; + new_values["MemoryTrackingPeak"] = peak; + #if USE_JEMALLOC /// This is a memory which is kept by allocator. /// Remove it from RSS to decrease memory drift. @@ -710,7 +712,7 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti if (difference >= 1048576 || difference <= -1048576) { LOG_TRACE(log, - "MemoryTracking: allocated {}, peak {}, RSS {}, difference: {}", + "MemoryTracking: allocated {}, peak {}, RSS (adjusted) {}, difference: {}", ReadableSize(amount), ReadableSize(peak), ReadableSize(rss), From bf5e9173ad3c1351b13b69cab2790165d24528c8 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Wed, 10 Aug 2022 21:01:09 +0800 Subject: [PATCH 514/672] fix hashMethodOneNumber constructor --- src/Common/ColumnsHashing.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Common/ColumnsHashing.h b/src/Common/ColumnsHashing.h index e89c894c427..711c1c4096c 100644 --- a/src/Common/ColumnsHashing.h +++ b/src/Common/ColumnsHashing.h @@ -63,6 +63,15 @@ struct HashMethodOneNumber explicit HashMethodOneNumber(const IColumn * column) { vec = column->getRawData().data; + if (isColumnConst(*column)) + { + const_value = unalignedLoad(vec); + get_key_holder_impl = [this](size_t /*row*/) { return const_value; }; + } + else + { + get_key_holder_impl = [this](size_t row) { return unalignedLoad(vec + row * sizeof(FieldType)); }; + } } /// Creates context. Method is called once and result context is used in all threads. From 2fb68078e828256218c2bdf994b766de7f1a7185 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 10 Aug 2022 15:22:04 +0200 Subject: [PATCH 515/672] Replace S3 URLs by parameter --- tests/ci/ast_fuzzer_check.py | 3 ++- tests/ci/build_check.py | 9 ++++----- tests/ci/build_report_check.py | 3 ++- tests/ci/ccache_utils.py | 5 +++-- tests/ci/codebrowser_check.py | 11 +++++++---- tests/ci/compatibility_check.py | 4 ++-- tests/ci/docker_images_check.py | 4 ++-- tests/ci/docker_manifests_merge.py | 4 ++-- tests/ci/docker_server.py | 7 +++---- tests/ci/docs_check.py | 4 ++-- tests/ci/docs_release.py | 4 ++-- tests/ci/env_helper.py | 1 + tests/ci/fast_test_check.py | 4 ++-- tests/ci/functional_test_check.py | 6 +++--- tests/ci/integration_test_check.py | 4 ++-- tests/ci/keeper_jepsen_check.py | 6 +++--- tests/ci/performance_comparison_check.py | 6 +++--- tests/ci/push_to_artifactory.py | 4 ++-- tests/ci/s3_helper.py | 15 ++++----------- tests/ci/split_build_smoke_check.py | 4 ++-- tests/ci/stress_check.py | 6 +++--- tests/ci/style_check.py | 4 ++-- tests/ci/unit_tests_check.py | 4 ++-- 23 files changed, 60 insertions(+), 62 deletions(-) diff --git a/tests/ci/ast_fuzzer_check.py b/tests/ci/ast_fuzzer_check.py index 918e27a4e11..82e7a3271c1 100644 --- a/tests/ci/ast_fuzzer_check.py +++ b/tests/ci/ast_fuzzer_check.py @@ -12,6 +12,7 @@ from env_helper import ( GITHUB_RUN_URL, REPORTS_PATH, REPO_COPY, + S3_URL, TEMP_PATH, ) from s3_helper import S3Helper @@ -117,7 +118,7 @@ if __name__ == "__main__": "core.gz": os.path.join(workspace_path, "core.gz"), } - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) for f in paths: try: paths[f] = s3_helper.upload_test_report_to_s3(paths[f], s3_prefix + "/" + f) diff --git a/tests/ci/build_check.py b/tests/ci/build_check.py index 488fd1bbb34..2463ec669dd 100644 --- a/tests/ci/build_check.py +++ b/tests/ci/build_check.py @@ -15,6 +15,7 @@ from env_helper import ( IMAGES_PATH, REPO_COPY, S3_BUILDS_BUCKET, + S3_URL, TEMP_PATH, ) from s3_helper import S3Helper @@ -142,11 +143,9 @@ def check_for_success_run( for url in build_results: url_escaped = url.replace("+", "%2B").replace(" ", "%20") if BUILD_LOG_NAME in url: - log_url = f"https://s3.amazonaws.com/{S3_BUILDS_BUCKET}/{url_escaped}" + log_url = f"{S3_URL}/{S3_BUILDS_BUCKET}/{url_escaped}" else: - build_urls.append( - f"https://s3.amazonaws.com/{S3_BUILDS_BUCKET}/{url_escaped}" - ) + build_urls.append(f"{S3_URL}/{S3_BUILDS_BUCKET}/{url_escaped}") if not log_url: # log is uploaded the last, so if there's no log we need to rerun the build return @@ -250,7 +249,7 @@ def main(): logging.info("Repo copy path %s", REPO_COPY) - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) version = get_version_from_repo(git=Git(True)) release_or_pr, performance_pr = get_release_or_pr(pr_info, version) diff --git a/tests/ci/build_report_check.py b/tests/ci/build_report_check.py index f1f92cded1d..3155b4fd56d 100644 --- a/tests/ci/build_report_check.py +++ b/tests/ci/build_report_check.py @@ -14,6 +14,7 @@ from env_helper import ( GITHUB_RUN_URL, GITHUB_SERVER_URL, REPORTS_PATH, + S3_URL, TEMP_PATH, ) from report import create_build_html_report @@ -244,7 +245,7 @@ def main(): logging.error("No success builds, failing check") sys.exit(1) - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) branch_url = f"{GITHUB_SERVER_URL}/{GITHUB_REPOSITORY}/commits/master" branch_name = "master" diff --git a/tests/ci/ccache_utils.py b/tests/ci/ccache_utils.py index bd155b02cb4..fd3589e1bb3 100644 --- a/tests/ci/ccache_utils.py +++ b/tests/ci/ccache_utils.py @@ -7,9 +7,10 @@ import os import shutil from pathlib import Path -import requests +import requests # type: ignore from compress_files import decompress_fast, compress_fast +from env_helper import S3_URL, S3_BUILDS_BUCKET DOWNLOAD_RETRIES_COUNT = 5 @@ -73,7 +74,7 @@ def get_ccache_if_not_exists( for obj in objects: if ccache_name in obj: logging.info("Found ccache on path %s", obj) - url = "https://s3.amazonaws.com/clickhouse-builds/" + obj + url = f"{S3_URL}/{S3_BUILDS_BUCKET}/{obj}" compressed_cache = os.path.join(temp_path, os.path.basename(obj)) dowload_file_with_progress(url, compressed_cache) diff --git a/tests/ci/codebrowser_check.py b/tests/ci/codebrowser_check.py index 230a778c598..121339d9971 100644 --- a/tests/ci/codebrowser_check.py +++ b/tests/ci/codebrowser_check.py @@ -7,7 +7,7 @@ import logging from github import Github -from env_helper import IMAGES_PATH, REPO_COPY +from env_helper import IMAGES_PATH, REPO_COPY, S3_TEST_REPORTS_BUCKET, S3_URL from stopwatch import Stopwatch from upload_result_helper import upload_results from s3_helper import S3Helper @@ -23,7 +23,7 @@ def get_run_command(repo_path, output_path, image): cmd = ( "docker run " + f"--volume={repo_path}:/repo_folder " f"--volume={output_path}:/test_output " - f"-e 'DATA=https://s3.amazonaws.com/clickhouse-test-reports/codebrowser/data' {image}" + f"-e 'DATA={S3_URL}/{S3_TEST_REPORTS_BUCKET}/codebrowser/data' {image}" ) return cmd @@ -41,7 +41,7 @@ if __name__ == "__main__": os.makedirs(temp_path) docker_image = get_image_with_version(IMAGES_PATH, "clickhouse/codebrowser") - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) result_path = os.path.join(temp_path, "result_path") if not os.path.exists(result_path): @@ -69,7 +69,10 @@ if __name__ == "__main__": report_path, s3_path_prefix, "clickhouse-test-reports" ) - index_html = 'HTML report' + index_html = ( + '' + "HTML report" + ) test_results = [(index_html, "Look at the report")] diff --git a/tests/ci/compatibility_check.py b/tests/ci/compatibility_check.py index 2a1b9716189..fc7584536ef 100644 --- a/tests/ci/compatibility_check.py +++ b/tests/ci/compatibility_check.py @@ -8,7 +8,7 @@ import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import PRInfo @@ -169,7 +169,7 @@ if __name__ == "__main__": subprocess.check_call(f"sudo chown -R ubuntu:ubuntu {temp_path}", shell=True) - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) state, description, test_results, additional_logs = process_result( result_path, server_log_path ) diff --git a/tests/ci/docker_images_check.py b/tests/ci/docker_images_check.py index 8b838defa8b..76ebbb78c7b 100644 --- a/tests/ci/docker_images_check.py +++ b/tests/ci/docker_images_check.py @@ -14,7 +14,7 @@ from github import Github from clickhouse_helper import ClickHouseHelper, prepare_tests_results_for_clickhouse from commit_status_helper import post_commit_status -from env_helper import GITHUB_WORKSPACE, RUNNER_TEMP, GITHUB_RUN_URL +from env_helper import GITHUB_WORKSPACE, RUNNER_TEMP, GITHUB_RUN_URL, S3_URL from get_robot_token import get_best_robot_token, get_parameter_from_ssm from pr_info import PRInfo from s3_helper import S3Helper @@ -460,7 +460,7 @@ def main(): with open(changed_json, "w", encoding="utf-8") as images_file: json.dump(result_images, images_file) - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) s3_path_prefix = ( str(pr_info.number) + "/" + pr_info.sha + "/" + NAME.lower().replace(" ", "_") diff --git a/tests/ci/docker_manifests_merge.py b/tests/ci/docker_manifests_merge.py index 00ab0b9e77f..78f236be786 100644 --- a/tests/ci/docker_manifests_merge.py +++ b/tests/ci/docker_manifests_merge.py @@ -11,7 +11,7 @@ from github import Github from clickhouse_helper import ClickHouseHelper, prepare_tests_results_for_clickhouse from commit_status_helper import post_commit_status -from env_helper import RUNNER_TEMP +from env_helper import RUNNER_TEMP, S3_URL from get_robot_token import get_best_robot_token, get_parameter_from_ssm from pr_info import PRInfo from s3_helper import S3Helper @@ -203,7 +203,7 @@ def main(): json.dump(changed_images, ci) pr_info = PRInfo() - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) url = upload_results(s3_helper, pr_info.number, pr_info.sha, test_results, [], NAME) diff --git a/tests/ci/docker_server.py b/tests/ci/docker_server.py index 09a75206442..64172b90ebc 100644 --- a/tests/ci/docker_server.py +++ b/tests/ci/docker_server.py @@ -16,7 +16,7 @@ from build_check import get_release_or_pr from clickhouse_helper import ClickHouseHelper, prepare_tests_results_for_clickhouse from commit_status_helper import post_commit_status from docker_images_check import DockerImage -from env_helper import CI, GITHUB_RUN_URL, RUNNER_TEMP, S3_BUILDS_BUCKET +from env_helper import CI, GITHUB_RUN_URL, RUNNER_TEMP, S3_BUILDS_BUCKET, S3_URL from get_robot_token import get_best_robot_token, get_parameter_from_ssm from git_helper import Git from pr_info import PRInfo @@ -309,8 +309,7 @@ def main(): pr_info = PRInfo() release_or_pr, _ = get_release_or_pr(pr_info, args.version) args.bucket_prefix = ( - f"https://s3.amazonaws.com/{S3_BUILDS_BUCKET}/" - f"{release_or_pr}/{pr_info.sha}" + f"{S3_URL}/{S3_BUILDS_BUCKET}/{release_or_pr}/{pr_info.sha}" ) if args.push: @@ -336,7 +335,7 @@ def main(): status = "failure" pr_info = pr_info or PRInfo() - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) url = upload_results(s3_helper, pr_info.number, pr_info.sha, test_results, [], NAME) diff --git a/tests/ci/docs_check.py b/tests/ci/docs_check.py index cf4fd8da692..f260d1f1e50 100644 --- a/tests/ci/docs_check.py +++ b/tests/ci/docs_check.py @@ -6,7 +6,7 @@ import os import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY +from env_helper import TEMP_PATH, REPO_COPY, S3_URL from s3_helper import S3Helper from pr_info import PRInfo from get_robot_token import get_best_robot_token @@ -120,7 +120,7 @@ if __name__ == "__main__": else: lines.append(("Non zero exit code", "FAIL")) - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) ch_helper = ClickHouseHelper() report_url = upload_results( diff --git a/tests/ci/docs_release.py b/tests/ci/docs_release.py index 35203486fae..96b0e7048c6 100644 --- a/tests/ci/docs_release.py +++ b/tests/ci/docs_release.py @@ -7,7 +7,7 @@ import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, CLOUDFLARE_TOKEN +from env_helper import TEMP_PATH, REPO_COPY, CLOUDFLARE_TOKEN, S3_URL from s3_helper import S3Helper from pr_info import PRInfo from get_robot_token import get_best_robot_token @@ -106,7 +106,7 @@ if __name__ == "__main__": else: lines.append(("Non zero exit code", "FAIL")) - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) report_url = upload_results( s3_helper, pr_info.number, pr_info.sha, lines, additional_files, NAME diff --git a/tests/ci/env_helper.py b/tests/ci/env_helper.py index b37d38763be..b6541205ed3 100644 --- a/tests/ci/env_helper.py +++ b/tests/ci/env_helper.py @@ -22,6 +22,7 @@ IMAGES_PATH = os.getenv("IMAGES_PATH", TEMP_PATH) REPORTS_PATH = os.getenv("REPORTS_PATH", p.abspath(p.join(module_dir, "./reports"))) REPO_COPY = os.getenv("REPO_COPY", git_root) RUNNER_TEMP = os.getenv("RUNNER_TEMP", p.abspath(p.join(module_dir, "./tmp"))) +S3_URL = os.getenv("S3_URL", "https://s3.amazonaws.com") S3_BUILDS_BUCKET = os.getenv("S3_BUILDS_BUCKET", "clickhouse-builds") S3_TEST_REPORTS_BUCKET = os.getenv("S3_TEST_REPORTS_BUCKET", "clickhouse-test-reports") diff --git a/tests/ci/fast_test_check.py b/tests/ci/fast_test_check.py index 9852175ca92..84d9d3f16d8 100644 --- a/tests/ci/fast_test_check.py +++ b/tests/ci/fast_test_check.py @@ -9,7 +9,7 @@ import atexit from github import Github -from env_helper import CACHES_PATH, TEMP_PATH +from env_helper import CACHES_PATH, TEMP_PATH, S3_URL from pr_info import FORCE_TESTS_LABEL, PRInfo from s3_helper import S3Helper from get_robot_token import get_best_robot_token @@ -105,7 +105,7 @@ if __name__ == "__main__": docker_image = get_image_with_version(temp_path, "clickhouse/fasttest") - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) workspace = os.path.join(temp_path, "fasttest-workspace") if not os.path.exists(workspace): diff --git a/tests/ci/functional_test_check.py b/tests/ci/functional_test_check.py index 690ac3c1851..bcfeaa9973a 100644 --- a/tests/ci/functional_test_check.py +++ b/tests/ci/functional_test_check.py @@ -10,7 +10,7 @@ import atexit from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import FORCE_TESTS_LABEL, PRInfo @@ -88,7 +88,7 @@ def get_run_command( envs = [ f"-e MAX_RUN_TIME={int(0.9 * kill_timeout)}", - '-e S3_URL="https://clickhouse-datasets.s3.amazonaws.com"', + f'-e S3_URL="{S3_URL}/clickhouse-datasets"', ] if flaky_check: @@ -314,7 +314,7 @@ if __name__ == "__main__": subprocess.check_call(f"sudo chown -R ubuntu:ubuntu {temp_path}", shell=True) - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) state, description, test_results, additional_logs = process_results( result_path, server_log_path diff --git a/tests/ci/integration_test_check.py b/tests/ci/integration_test_check.py index 565864d576c..49a95748f6c 100644 --- a/tests/ci/integration_test_check.py +++ b/tests/ci/integration_test_check.py @@ -10,7 +10,7 @@ import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import PRInfo @@ -249,7 +249,7 @@ if __name__ == "__main__": ch_helper = ClickHouseHelper() mark_flaky_tests(ch_helper, check_name, test_results) - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) report_url = upload_results( s3_helper, pr_info.number, diff --git a/tests/ci/keeper_jepsen_check.py b/tests/ci/keeper_jepsen_check.py index 88ccf8e8828..af44b87b897 100644 --- a/tests/ci/keeper_jepsen_check.py +++ b/tests/ci/keeper_jepsen_check.py @@ -9,7 +9,7 @@ import boto3 from github import Github import requests -from env_helper import REPO_COPY, TEMP_PATH +from env_helper import REPO_COPY, TEMP_PATH, S3_BUILDS_BUCKET, S3_URL from stopwatch import Stopwatch from upload_result_helper import upload_results from s3_helper import S3Helper @@ -192,7 +192,7 @@ if __name__ == "__main__": # run (see .github/workflows/jepsen.yml) So we cannot add explicit # dependency on a build job and using busy loop on it's results. For the # same reason we are using latest docker image. - build_url = f"https://s3.amazonaws.com/clickhouse-builds/{release_or_pr}/{pr_info.sha}/{build_name}/clickhouse" + build_url = f"{S3_URL}/{S3_BUILDS_BUCKET}/{release_or_pr}/{pr_info.sha}/{build_name}/clickhouse" head = requests.head(build_url) counter = 0 while head.status_code != 200: @@ -248,7 +248,7 @@ if __name__ == "__main__": description = "No Jepsen output log" test_result = [("No Jepsen output log", "FAIL")] - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) report_url = upload_results( s3_helper, pr_info.number, diff --git a/tests/ci/performance_comparison_check.py b/tests/ci/performance_comparison_check.py index 57a52dcaa6a..ce5226aeb04 100644 --- a/tests/ci/performance_comparison_check.py +++ b/tests/ci/performance_comparison_check.py @@ -15,7 +15,7 @@ from github import Github from commit_status_helper import get_commit, post_commit_status from ci_config import CI_CONFIG from docker_pull_helper import get_image_with_version -from env_helper import GITHUB_EVENT_PATH, GITHUB_RUN_URL +from env_helper import GITHUB_EVENT_PATH, GITHUB_RUN_URL, S3_BUILDS_BUCKET, S3_URL from get_robot_token import get_best_robot_token, get_parameter_from_ssm from pr_info import PRInfo from rerun_helper import RerunHelper @@ -86,7 +86,7 @@ if __name__ == "__main__": docker_env = "" - docker_env += " -e S3_URL=https://s3.amazonaws.com/clickhouse-builds" + docker_env += f" -e S3_URL={S3_URL}/{S3_BUILDS_BUCKET}" docker_env += f" -e BUILD_NAME={required_build}" if pr_info.number == 0: @@ -197,7 +197,7 @@ if __name__ == "__main__": } s3_prefix = f"{pr_info.number}/{pr_info.sha}/{check_name_prefix}/" - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) uploaded = {} # type: Dict[str, str] for name, path in paths.items(): try: diff --git a/tests/ci/push_to_artifactory.py b/tests/ci/push_to_artifactory.py index 98de315ddae..b04fa723580 100755 --- a/tests/ci/push_to_artifactory.py +++ b/tests/ci/push_to_artifactory.py @@ -9,7 +9,7 @@ from typing import Dict, List, Tuple from artifactory import ArtifactorySaaSPath # type: ignore from build_download_helper import dowload_build_with_progress -from env_helper import RUNNER_TEMP, S3_BUILDS_BUCKET +from env_helper import RUNNER_TEMP, S3_BUILDS_BUCKET, S3_URL from git_helper import TAG_REGEXP, commit, removeprefix, removesuffix @@ -98,7 +98,7 @@ class Packages: class S3: template = ( - "https://s3.amazonaws.com/" + f"{S3_URL}" # "clickhouse-builds/" f"{S3_BUILDS_BUCKET}/" # "33333/" or "21.11/" from --release, if pull request is omitted diff --git a/tests/ci/s3_helper.py b/tests/ci/s3_helper.py index 91e67135f6f..483d6aee60e 100644 --- a/tests/ci/s3_helper.py +++ b/tests/ci/s3_helper.py @@ -9,7 +9,7 @@ from multiprocessing.dummy import Pool import boto3 # type: ignore -from env_helper import S3_TEST_REPORTS_BUCKET, S3_BUILDS_BUCKET, RUNNER_TEMP, CI +from env_helper import S3_TEST_REPORTS_BUCKET, S3_BUILDS_BUCKET, RUNNER_TEMP, CI, S3_URL from compress_files import compress_file_fast @@ -98,13 +98,8 @@ class S3Helper: logging.info("Upload %s to %s. Meta: %s", file_path, s3_path, metadata) # last two replacements are specifics of AWS urls: # https://jamesd3142.wordpress.com/2018/02/28/amazon-s3-and-the-plus-symbol/ - return ( - "https://s3.amazonaws.com/{bucket}/{path}".format( - bucket=bucket_name, path=s3_path - ) - .replace("+", "%2B") - .replace(" ", "%20") - ) + url = f"{S3_URL}/{bucket_name}/{s3_path}" + return url.replace("+", "%2B").replace(" ", "%20") def upload_test_report_to_s3(self, file_path, s3_path): if CI: @@ -175,9 +170,7 @@ class S3Helper: t = time.time() except Exception as ex: logging.critical("Failed to upload file, expcetion %s", ex) - return "https://s3.amazonaws.com/{bucket}/{path}".format( - bucket=bucket_name, path=s3_path - ) + return f"{S3_URL}/{bucket_name}/{s3_path}" p = Pool(256) diff --git a/tests/ci/split_build_smoke_check.py b/tests/ci/split_build_smoke_check.py index 87a528d2761..5052b6b362e 100644 --- a/tests/ci/split_build_smoke_check.py +++ b/tests/ci/split_build_smoke_check.py @@ -7,7 +7,7 @@ import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import PRInfo @@ -126,7 +126,7 @@ if __name__ == "__main__": ) ch_helper = ClickHouseHelper() - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) report_url = upload_results( s3_helper, pr_info.number, diff --git a/tests/ci/stress_check.py b/tests/ci/stress_check.py index e63f66e2e50..6073b03f8a6 100644 --- a/tests/ci/stress_check.py +++ b/tests/ci/stress_check.py @@ -8,7 +8,7 @@ import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import PRInfo @@ -31,7 +31,7 @@ def get_run_command( ): cmd = ( "docker run --cap-add=SYS_PTRACE " - "-e S3_URL='https://clickhouse-datasets.s3.amazonaws.com' " + f"-e S3_URL='{S3_URL}/clickhouse-datasets' " f"--volume={build_path}:/package_folder " f"--volume={result_folder}:/test_output " f"--volume={repo_tests_path}:/usr/share/clickhouse-test " @@ -148,7 +148,7 @@ if __name__ == "__main__": subprocess.check_call(f"sudo chown -R ubuntu:ubuntu {temp_path}", shell=True) - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) state, description, test_results, additional_logs = process_results( result_path, server_log_path, run_log_path ) diff --git a/tests/ci/style_check.py b/tests/ci/style_check.py index 66837ccb84e..db286ec7f6c 100644 --- a/tests/ci/style_check.py +++ b/tests/ci/style_check.py @@ -15,7 +15,7 @@ from clickhouse_helper import ( ) from commit_status_helper import post_commit_status, update_mergeable_check from docker_pull_helper import get_image_with_version -from env_helper import GITHUB_WORKSPACE, RUNNER_TEMP +from env_helper import GITHUB_WORKSPACE, RUNNER_TEMP, S3_URL from get_robot_token import get_best_robot_token from github_helper import GitHub from git_helper import git_runner @@ -166,7 +166,7 @@ if __name__ == "__main__": os.makedirs(temp_path) docker_image = get_image_with_version(temp_path, "clickhouse/style-test") - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) cmd = ( f"docker run -u $(id -u ${{USER}}):$(id -g ${{USER}}) --cap-add=SYS_PTRACE " diff --git a/tests/ci/unit_tests_check.py b/tests/ci/unit_tests_check.py index 4441709cb7b..95011b728e9 100644 --- a/tests/ci/unit_tests_check.py +++ b/tests/ci/unit_tests_check.py @@ -7,7 +7,7 @@ import subprocess from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import PRInfo @@ -147,7 +147,7 @@ if __name__ == "__main__": subprocess.check_call(f"sudo chown -R ubuntu:ubuntu {temp_path}", shell=True) - s3_helper = S3Helper("https://s3.amazonaws.com") + s3_helper = S3Helper(S3_URL) state, description, test_results, additional_logs = process_result(test_output) ch_helper = ClickHouseHelper() From b7c5c54181ac60add1e4739b55266d201c621960 Mon Sep 17 00:00:00 2001 From: vdimir Date: Wed, 10 Aug 2022 13:43:55 +0000 Subject: [PATCH 516/672] Fix build --- src/Interpreters/ActionsVisitor.cpp | 1 - .../QueryPlan/ReadFromMergeTree.cpp | 4 +-- src/Storages/Hive/StorageHive.cpp | 4 +-- src/Storages/MergeTree/KeyCondition.cpp | 29 +++++++++---------- src/Storages/MergeTree/KeyCondition.h | 11 +++++++ src/Storages/MergeTree/MergeTreeData.cpp | 4 +-- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- .../MergeTree/MergeTreeIndexMinMax.cpp | 2 +- src/Storages/MergeTree/PartitionPruner.h | 2 +- 9 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/Interpreters/ActionsVisitor.cpp b/src/Interpreters/ActionsVisitor.cpp index 9b9552f37cb..6c9e54a966d 100644 --- a/src/Interpreters/ActionsVisitor.cpp +++ b/src/Interpreters/ActionsVisitor.cpp @@ -945,7 +945,6 @@ void ActionsMatcher::visit(const ASTFunction & node, const ASTPtr & ast, Data & data.source_columns, std::make_shared(data.source_columns), data.prepared_sets, - data.subqueries_for_sets, data.no_subqueries, data.no_makeset, data.only_consts, diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index 8af2f97909f..0d6f591b43a 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -905,11 +905,11 @@ MergeTreeDataSelectAnalysisResultPtr ReadFromMergeTree::selectRangesToRead( nodes.nodes.push_back(&node); } - key_condition.emplace(std::move(nodes), query_info.syntax_analyzer_result, query_info.sets, context, primary_key_columns, primary_key.expression); + key_condition.emplace(std::move(nodes), query_info.syntax_analyzer_result, query_info.prepared_sets, context, primary_key_columns, primary_key.expression); } else { - key_condition.emplace(query_info.query, query_info.syntax_analyzer_result, query_info.sets, context, primary_key_columns, primary_key.expression); + key_condition.emplace(query_info, context, primary_key_columns, primary_key.expression); } if (settings.force_primary_key && key_condition->alwaysUnknownOrTrue()) diff --git a/src/Storages/Hive/StorageHive.cpp b/src/Storages/Hive/StorageHive.cpp index f6ac40838c4..01ee5a8c3c5 100644 --- a/src/Storages/Hive/StorageHive.cpp +++ b/src/Storages/Hive/StorageHive.cpp @@ -622,7 +622,7 @@ HiveFiles StorageHive::collectHiveFilesFromPartition( for (size_t i = 0; i < partition_names.size(); ++i) ranges.emplace_back(fields[i]); - const KeyCondition partition_key_condition(query_info.query, query_info.syntax_analyzer_result, query_info.sets, getContext(), partition_names, partition_minmax_idx_expr); + const KeyCondition partition_key_condition(query_info, getContext(), partition_names, partition_minmax_idx_expr); if (!partition_key_condition.checkInHyperrectangle(ranges, partition_types).can_be_true) return {}; } @@ -690,7 +690,7 @@ HiveFilePtr StorageHive::getHiveFileIfNeeded( if (prune_level >= PruneLevel::File) { - const KeyCondition hivefile_key_condition(query_info.query, query_info.syntax_analyzer_result, query_info.sets, getContext(), hivefile_name_types.getNames(), hivefile_minmax_idx_expr); + const KeyCondition hivefile_key_condition(query_info, getContext(), hivefile_name_types.getNames(), hivefile_minmax_idx_expr); if (hive_file->useFileMinMaxIndex()) { /// Load file level minmax index and apply diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index b8a16951269..7128558b734 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -279,14 +279,14 @@ public: } ConstSetPtr tryGetPreparedSet( - const PreparedSets & sets, + const PreparedSetsPtr & sets, const std::vector & indexes_mapping, const DataTypes & data_types) const { - if (ast) + if (sets && ast) { if (ast->as() || ast->as()) - return prepared_sets->get(PreparedSetKey::forSubquery(*set)); + return sets->get(PreparedSetKey::forSubquery(*ast)); /// We have `PreparedSetKey::forLiteral` but it is useless here as we don't have enough information /// about types in left argument of the IN operator. Instead, we manually iterate through all the sets @@ -303,26 +303,23 @@ public: return true; }; - for (const auto & set : prepared_sets->getByTreeHash(right_arg->getTreeHash())) + for (const auto & set : sets->getByTreeHash(ast->getTreeHash())) { if (types_match(set)) return set; } } - else + else if (dag->column) { - if (dag->column) - { - const IColumn * col = dag->column.get(); - if (const auto * col_const = typeid_cast(col)) - col = &col_const->getDataColumn(); + const IColumn * col = dag->column.get(); + if (const auto * col_const = typeid_cast(col)) + col = &col_const->getDataColumn(); - if (const auto * col_set = typeid_cast(col)) - { - auto set = col_set->getData(); - if (set->isCreated()) - return set; - } + if (const auto * col_set = typeid_cast(col)) + { + auto set = col_set->getData(); + if (set->isCreated()) + return set; } } diff --git a/src/Storages/MergeTree/KeyCondition.h b/src/Storages/MergeTree/KeyCondition.h index 5fb1847c8d1..3c2089a56d7 100644 --- a/src/Storages/MergeTree/KeyCondition.h +++ b/src/Storages/MergeTree/KeyCondition.h @@ -216,6 +216,17 @@ public: bool single_point_ = false, bool strict_ = false); + KeyCondition( + const SelectQueryInfo & query_info, + ContextPtr context, + const Names & key_column_names, + const ExpressionActionsPtr & key_expr_, + bool single_point_ = false, + bool strict_ = false) + : KeyCondition(query_info.query, query_info.syntax_analyzer_result, query_info.prepared_sets, + context, key_column_names, key_expr_, single_point_, strict_) + {} + KeyCondition( ActionDAGNodes dag_nodes, TreeRewriterResultPtr syntax_analyzer_result, diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 377f5e6e0fc..594b4a32f9c 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -5293,9 +5293,7 @@ Block MergeTreeData::getMinMaxCountProjectionBlock( minmax_columns_types = getMinMaxColumnsTypes(partition_key); minmax_idx_condition.emplace( - query_info.query, query_info.syntax_analyzer_result, query_info.sets, - query_context, - minmax_columns_names, + query_info, query_context, minmax_columns_names, getMinMaxExpr(partition_key, ExpressionActionsSettings::fromContext(query_context))); partition_pruner.emplace(metadata_snapshot, query_info, query_context, false /* strict */); } diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index c223e285c39..ba3505b5886 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -770,7 +770,7 @@ void MergeTreeDataSelectExecutor::filterPartsByPartition( minmax_columns_types = data.getMinMaxColumnsTypes(partition_key); minmax_idx_condition.emplace( - query_info.query, query_info.syntax_analyzer_result, query_info.sets, context, minmax_columns_names, data.getMinMaxExpr(partition_key, ExpressionActionsSettings::fromContext(context))); + query_info, context, minmax_columns_names, data.getMinMaxExpr(partition_key, ExpressionActionsSettings::fromContext(context))); partition_pruner.emplace(metadata_snapshot, query_info, context, false /* strict */); if (settings.force_index_by_date && (minmax_idx_condition->alwaysUnknownOrTrue() && partition_pruner->isUseless())) diff --git a/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp b/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp index 80f3e140c41..05319ecc62e 100644 --- a/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp @@ -161,7 +161,7 @@ MergeTreeIndexConditionMinMax::MergeTreeIndexConditionMinMax( const SelectQueryInfo & query, ContextPtr context) : index_data_types(index.data_types) - , condition(query.query, query.syntax_analyzer_result, query.sets, context, index.column_names, index.expression) + , condition(query, context, index.column_names, index.expression) { } diff --git a/src/Storages/MergeTree/PartitionPruner.h b/src/Storages/MergeTree/PartitionPruner.h index 3af52fd9a38..675fef1433d 100644 --- a/src/Storages/MergeTree/PartitionPruner.h +++ b/src/Storages/MergeTree/PartitionPruner.h @@ -27,7 +27,7 @@ public: PartitionPruner(const StorageMetadataPtr & metadata, const SelectQueryInfo & query_info, ContextPtr context, bool strict) : partition_key(MergeTreePartition::adjustPartitionKey(metadata, context)) , partition_condition( - query_info.query, query_info.syntax_analyzer_result, query_info.sets, + query_info.query, query_info.syntax_analyzer_result, query_info.prepared_sets, context, partition_key.column_names, partition_key.expression, true /* single_point */, strict) , useless(strict ? partition_condition.anyUnknownOrAlwaysTrue() : partition_condition.alwaysUnknownOrTrue()) { From fff903ee81327bbe960fa18e885f12aedac29d38 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 10 Aug 2022 15:48:56 +0200 Subject: [PATCH 517/672] fix --- src/Storages/MergeTree/DataPartsExchange.cpp | 2 ++ src/Storages/MergeTree/MergeTask.cpp | 3 ++- src/Storages/MergeTree/MergeTreeData.cpp | 7 +++++++ src/Storages/MergeTree/TemporaryParts.cpp | 4 ++-- src/Storages/MergeTree/TemporaryParts.h | 7 ++++--- tests/config/config.d/merge_tree_settings.xml | 4 ++-- .../02401_merge_tree_old_tmp_dirs_cleanup.reference | 1 + .../02401_merge_tree_old_tmp_dirs_cleanup.sql | 11 +++++++++++ 8 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 tests/queries/0_stateless/02401_merge_tree_old_tmp_dirs_cleanup.reference create mode 100644 tests/queries/0_stateless/02401_merge_tree_old_tmp_dirs_cleanup.sql diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index 687167f8dd9..5fe3ee4da28 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -582,6 +582,8 @@ MergeTreeData::MutableDataPartPtr Fetcher::fetchPart( tryLogCurrentException(log); } + temporary_directory_lock = {}; + /// Try again but without zero-copy return fetchPart(metadata_snapshot, context, part_name, replica_path, host, port, timeouts, user, password, interserver_scheme, throttler, to_detached, tmp_prefix, nullptr, false, disk); diff --git a/src/Storages/MergeTree/MergeTask.cpp b/src/Storages/MergeTree/MergeTask.cpp index 21cb2883b84..a7b04f8fa32 100644 --- a/src/Storages/MergeTree/MergeTask.cpp +++ b/src/Storages/MergeTree/MergeTask.cpp @@ -142,7 +142,8 @@ bool MergeTask::ExecuteAndFinalizeHorizontalPart::prepare() if (global_ctx->data_part_storage_builder->exists()) throw Exception("Directory " + global_ctx->data_part_storage_builder->getFullPath() + " already exists", ErrorCodes::DIRECTORY_ALREADY_EXISTS); - global_ctx->temporary_directory_lock = global_ctx->data->getTemporaryPartDirectoryHolder(local_tmp_part_basename); + if (!global_ctx->parent_part) + global_ctx->temporary_directory_lock = global_ctx->data->getTemporaryPartDirectoryHolder(local_tmp_part_basename); global_ctx->all_column_names = global_ctx->metadata_snapshot->getColumns().getNamesOfPhysical(); global_ctx->storage_columns = global_ctx->metadata_snapshot->getColumns().getAllPhysical(); diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 84d7121163c..a173b8005fb 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -2864,6 +2864,13 @@ void MergeTreeData::preparePartForCommit(MutableDataPartPtr & part, Transaction part->is_temp = false; part->setState(DataPartState::PreActive); + assert([&]() + { + String dir_name = fs::path(part->data_part_storage->getRelativePath()).filename(); + bool may_be_cleaned_up = dir_name.starts_with("tmp_") || dir_name.starts_with("tmp-fetch_"); + return !may_be_cleaned_up || temporary_parts.contains(dir_name); + }()); + part->renameTo(part->name, true, builder); data_parts_indexes.insert(part); diff --git a/src/Storages/MergeTree/TemporaryParts.cpp b/src/Storages/MergeTree/TemporaryParts.cpp index 086f63c71a8..b868531a72e 100644 --- a/src/Storages/MergeTree/TemporaryParts.cpp +++ b/src/Storages/MergeTree/TemporaryParts.cpp @@ -15,10 +15,10 @@ bool TemporaryParts::contains(const std::string & basename) const return parts.contains(basename); } -void TemporaryParts::add(std::string basename) +void TemporaryParts::add(const std::string & basename) { std::lock_guard lock(mutex); - bool inserted = parts.emplace(std::move(basename)).second; + bool inserted = parts.emplace(basename).second; if (!inserted) throw Exception(ErrorCodes::LOGICAL_ERROR, "Temporary part {} already added", basename); } diff --git a/src/Storages/MergeTree/TemporaryParts.h b/src/Storages/MergeTree/TemporaryParts.h index bc9d270856f..49615f4800f 100644 --- a/src/Storages/MergeTree/TemporaryParts.h +++ b/src/Storages/MergeTree/TemporaryParts.h @@ -18,6 +18,10 @@ private: /// NOTE: It is pretty short, so use STL is fine. std::unordered_set parts; + void add(const std::string & basename); + void remove(const std::string & basename); + + friend class MergeTreeData; public: /// Returns true if passed part name is active. /// (is the destination for one of active mutation/merge). @@ -25,9 +29,6 @@ public: /// NOTE: that it accept basename (i.e. dirname), not the path, /// since later requires canonical form. bool contains(const std::string & basename) const; - - void add(std::string basename); - void remove(const std::string & basename); }; } diff --git a/tests/config/config.d/merge_tree_settings.xml b/tests/config/config.d/merge_tree_settings.xml index eab7f2d4b27..d4691b1d787 100644 --- a/tests/config/config.d/merge_tree_settings.xml +++ b/tests/config/config.d/merge_tree_settings.xml @@ -2,7 +2,7 @@ 10 - - 0 + + 1 diff --git a/tests/queries/0_stateless/02401_merge_tree_old_tmp_dirs_cleanup.reference b/tests/queries/0_stateless/02401_merge_tree_old_tmp_dirs_cleanup.reference new file mode 100644 index 00000000000..57831ddd82e --- /dev/null +++ b/tests/queries/0_stateless/02401_merge_tree_old_tmp_dirs_cleanup.reference @@ -0,0 +1 @@ +10 45 diff --git a/tests/queries/0_stateless/02401_merge_tree_old_tmp_dirs_cleanup.sql b/tests/queries/0_stateless/02401_merge_tree_old_tmp_dirs_cleanup.sql new file mode 100644 index 00000000000..54579bca30b --- /dev/null +++ b/tests/queries/0_stateless/02401_merge_tree_old_tmp_dirs_cleanup.sql @@ -0,0 +1,11 @@ +DROP TABLE IF EXISTS test_inserts; + +CREATE TABLE test_inserts (`key` Int, `part` Int) ENGINE = MergeTree PARTITION BY part ORDER BY key +SETTINGS temporary_directories_lifetime = 0, merge_tree_clear_old_temporary_directories_interval_seconds = 0; + +INSERT INTO test_inserts SELECT sleep(1), number FROM numbers(10) +SETTINGS max_insert_delayed_streams_for_parallel_write = 100, max_insert_block_size = 1, min_insert_block_size_rows = 1; + +SELECT count(), sum(part) FROM test_inserts; + +DROP TABLE test_inserts; From c0763050a719a71845866983e77c68ca5ebeb5a6 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 10 Aug 2022 16:01:09 +0200 Subject: [PATCH 518/672] more aggressive tests --- tests/config/config.d/merge_tree_settings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/config/config.d/merge_tree_settings.xml b/tests/config/config.d/merge_tree_settings.xml index d4691b1d787..e71d0574b67 100644 --- a/tests/config/config.d/merge_tree_settings.xml +++ b/tests/config/config.d/merge_tree_settings.xml @@ -4,5 +4,7 @@ 10 1 + + 10 From 347ffbf178f6279fbfa2363525c1f8b638f4e6fe Mon Sep 17 00:00:00 2001 From: avogar Date: Wed, 10 Aug 2022 14:19:27 +0000 Subject: [PATCH 519/672] Fix special build --- contrib/arrow | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/arrow b/contrib/arrow index b41ff445294..450a5638704 160000 --- a/contrib/arrow +++ b/contrib/arrow @@ -1 +1 @@ -Subproject commit b41ff4452944d50a44ad9c6e4621b50f44e9742e +Subproject commit 450a5638704386356f8e520080468fc9bc8bcaf8 From 33473458735894068de84b96679805f88924b388 Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 7 Jul 2022 12:26:34 +0000 Subject: [PATCH 520/672] Join with dictionary uses DirectJoin --- src/Interpreters/DictionaryJoinAdapter.cpp | 94 +++++++++++++++++++ src/Interpreters/DictionaryJoinAdapter.h | 34 +++++++ src/Interpreters/DirectJoin.cpp | 28 +++++- src/Interpreters/ExpressionAnalyzer.cpp | 80 +++++++++++++--- src/Interpreters/HashJoin.cpp | 34 +------ src/Interpreters/JoinedTables.cpp | 1 + src/Interpreters/TableJoin.cpp | 10 ++ src/Interpreters/TableJoin.h | 7 ++ src/Interpreters/join_common.cpp | 30 ++++++ src/Interpreters/join_common.h | 3 + .../01115_join_with_dictionary.sql | 2 + 11 files changed, 274 insertions(+), 49 deletions(-) create mode 100644 src/Interpreters/DictionaryJoinAdapter.cpp create mode 100644 src/Interpreters/DictionaryJoinAdapter.h diff --git a/src/Interpreters/DictionaryJoinAdapter.cpp b/src/Interpreters/DictionaryJoinAdapter.cpp new file mode 100644 index 00000000000..9866a25901a --- /dev/null +++ b/src/Interpreters/DictionaryJoinAdapter.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +DictionaryJoinAdapter::DictionaryJoinAdapter( + std::shared_ptr dictionary_, const Names & result_column_names) + : IKeyValueStorage(StorageID::createEmpty()) + , dictionary(dictionary_) +{ + if (!dictionary) + throw Exception("Dictionary is not initialized", ErrorCodes::LOGICAL_ERROR); + + const auto & key_types = dictionary->getStructure().getKeyTypes(); + const auto & key_names = dictionary->getStructure().getKeysNames(); + if (key_types.size() != key_names.size()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Dictionary '{}' has invalid structure", dictionary->getFullName()); + + StorageInMemoryMetadata storage_metadata; + + for (size_t i = 0; i < key_types.size(); ++i) + { + storage_metadata.columns.add(ColumnDescription(key_names[i], key_types[i])); + } + + for (const auto & attr_name : result_column_names) + { + const auto & attr = dictionary->getStructure().getAttribute(attr_name); + storage_metadata.columns.add(ColumnDescription(attr_name, attr.type)); + + attribute_names.emplace_back(attr_name); + result_types.emplace_back(attr.type); + } + + /// Fill in memory metadata to make getSampleBlock work. + setInMemoryMetadata(storage_metadata); +} + +Names DictionaryJoinAdapter::getPrimaryKey() const +{ + return dictionary->getStructure().getKeysNames(); +} + +Chunk DictionaryJoinAdapter::getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map) const +{ + if (keys.empty()) + return {}; + + Columns key_columns; + DataTypes key_types; + for (const auto & key : keys) + { + key_columns.emplace_back(key.column); + key_types.emplace_back(key.type); + } + + { + out_null_map.clear(); + + auto mask = dictionary->hasKeys(key_columns, key_types); + const auto & mask_data = mask->getData(); + + out_null_map.resize(mask_data.size(), 0); + std::copy(mask_data.begin(), mask_data.end(), out_null_map.begin()); + } + + Columns default_cols(result_types.size()); + for (size_t i = 0; i < result_types.size(); ++i) + /// Dictinonary may have non-standart default values specified + default_cols[i] = result_types[i]->createColumnConstWithDefaultValue(out_null_map.size()); + + Columns result_columns = dictionary->getColumns(attribute_names, result_types, key_columns, key_types, default_cols); + + for (const auto & key_col : key_columns) + { + /// Insert default values for keys that were not found + ColumnPtr filtered_key_col = JoinCommon::filterWithBlanks(key_col, out_null_map); + result_columns.insert(result_columns.begin(), filtered_key_col); + } + + size_t num_rows = result_columns[0]->size(); + return Chunk(std::move(result_columns), num_rows); +} + +} diff --git a/src/Interpreters/DictionaryJoinAdapter.h b/src/Interpreters/DictionaryJoinAdapter.h new file mode 100644 index 00000000000..adfa75a996b --- /dev/null +++ b/src/Interpreters/DictionaryJoinAdapter.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + +namespace DB +{ + +/// Used in join with dictionary to provide sufficient interface to DirectJoin +class DictionaryJoinAdapter : public IKeyValueStorage +{ +public: + DictionaryJoinAdapter( + std::shared_ptr dictionary_, const Names & result_column_names); + + Names getPrimaryKey() const override; + + Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map) const override; + + std::string getName() const override + { + return dictionary->getFullName(); + } + +private: + std::shared_ptr dictionary; + + Strings attribute_names; + DataTypes result_types; +}; + +} diff --git a/src/Interpreters/DirectJoin.cpp b/src/Interpreters/DirectJoin.cpp index af6bd484753..ceacca2fdca 100644 --- a/src/Interpreters/DirectJoin.cpp +++ b/src/Interpreters/DirectJoin.cpp @@ -71,11 +71,18 @@ DirectKeyValueJoin::DirectKeyValueJoin(std::shared_ptr table_join_, throw DB::Exception(ErrorCodes::UNSUPPORTED_JOIN_KEYS, "Not supported by direct JOIN"); } - if (table_join->strictness() != JoinStrictness::All && - table_join->strictness() != JoinStrictness::Any && - table_join->strictness() != JoinStrictness::RightAny) + bool allowed_inner = isInner(table_join->kind()) && (table_join->strictness() == ASTTableJoin::Strictness::All || + table_join->strictness() == ASTTableJoin::Strictness::Any || + table_join->strictness() != JoinStrictness::RightAny); + + bool allowed_left = isLeft(table_join->kind()) && (table_join->strictness() == ASTTableJoin::Strictness::Any || + table_join->strictness() == ASTTableJoin::Strictness::All || + table_join->strictness() == ASTTableJoin::Strictness::Semi || + table_join->strictness() == ASTTableJoin::Strictness::Anti); + if (!allowed_inner && !allowed_left) { - throw DB::Exception(ErrorCodes::NOT_IMPLEMENTED, "Not supported by direct JOIN"); + throw DB::Exception(ErrorCodes::NOT_IMPLEMENTED, "Strictness {} and kind {} is not supported by direct JOIN", + table_join->strictness(), table_join->kind()); } LOG_TRACE(log, "Using direct join"); @@ -116,7 +123,18 @@ void DirectKeyValueJoin::joinBlock(Block & block, std::shared_ptr &) block.insert(std::move(col)); } - if (!isLeftOrFull(table_join->kind())) + bool is_semi_join = table_join->strictness() == ASTTableJoin::Strictness::Semi; + bool is_anti_join = table_join->strictness() == ASTTableJoin::Strictness::Anti; + + if (is_anti_join) + { + /// invert null_map + for (auto & val : null_map) + val = !val; + } + + /// Filter non joined rows + if (isInner(table_join->kind()) || (isLeft(table_join->kind()) && (is_semi_join || is_anti_join))) { MutableColumns dst_columns = block.mutateColumns(); for (auto & col : dst_columns) diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index 4f59a45b628..a6c91f7ace3 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -40,7 +41,7 @@ #include #include #include - +#include #include @@ -1014,19 +1015,15 @@ static std::shared_ptr chooseJoinAlgorithm(std::shared_ptr ana if (analyzed_join->isEnabledAlgorithm(JoinAlgorithm::DIRECT)) { - if (JoinPtr kvjoin = tryKeyValueJoin(analyzed_join, right_sample_block)) - { - /// Do not need to execute plan for right part - joined_plan.reset(); - return kvjoin; - } + JoinPtr direct_join = nullptr; + direct_join = direct_join ? direct_join : tryKeyValueJoin(analyzed_join, right_sample_block); + direct_join = direct_join ? direct_join : tryDictJoin(analyzed_join, right_sample_block, getContext()); - /// It's not a hash join actually, that's why we check JoinAlgorithm::DIRECT - /// It's would be fixed in https://github.com/ClickHouse/ClickHouse/pull/38956 - if (analyzed_join->tryInitDictJoin(right_sample_block, context)) + if (direct_join) { + /// Do not need to execute plan for right part, it's ready. joined_plan.reset(); - return std::make_shared(analyzed_join, right_sample_block); + return direct_join; } } @@ -1113,6 +1110,66 @@ static std::unique_ptr buildJoinedPlan( return joined_plan; } +std::shared_ptr tryDictJoin(std::shared_ptr analyzed_join, const Block & right_sample_block, ContextPtr context) +{ + using Strictness = ASTTableJoin::Strictness; + + bool allowed_inner = isInner(analyzed_join->kind()) && analyzed_join->strictness() == Strictness::All; + bool allowed_left = isLeft(analyzed_join->kind()) && (analyzed_join->strictness() == Strictness::Any || + analyzed_join->strictness() == Strictness::All || + analyzed_join->strictness() == Strictness::Semi || + analyzed_join->strictness() == Strictness::Anti); + if (!allowed_inner && !allowed_left) + { + LOG_TRACE(&Poco::Logger::get("tryDictJoin"), "Can't use dictionary join: {} {} is not supported", + analyzed_join->kind(), analyzed_join->strictness()); + return nullptr; + } + + if (analyzed_join->getClauses().size() != 1 || analyzed_join->getClauses()[0].key_names_right.size() != 1) + { + LOG_TRACE(&Poco::Logger::get("tryDictJoin"), "Can't use dictionary join: only one key is supported"); + return nullptr; + } + + const auto & right_key = analyzed_join->getOnlyClause().key_names_right[0]; + + const auto & dictionary_name = analyzed_join->getRightStorageName(); + if (dictionary_name.empty()) + { + LOG_TRACE(&Poco::Logger::get("tryDictJoin"), "Can't use dictionary join: dictionary was not found"); + return nullptr; + } + + FunctionDictHelper dictionary_helper(context); + + auto dictionary = dictionary_helper.getDictionary(dictionary_name); + if (!dictionary) + { + LOG_TRACE(&Poco::Logger::get("tryDictJoin"), "Can't use dictionary join: dictionary was not found"); + return nullptr; + } + + const auto & dict_keys = dictionary->getStructure().getKeysNames(); + if (dict_keys.size() != 1 || dict_keys[0] != analyzed_join->getOriginalName(right_key)) + { + LOG_TRACE(&Poco::Logger::get("tryDictJoin"), "Can't use dictionary join: join key '{}' doesn't natch to dictionary key ({})", + right_key, fmt::join(dict_keys, ", ")); + return nullptr; + } + + Names attr_names; + for (const auto & col : right_sample_block) + { + if (col.name == right_key) + continue; + attr_names.push_back(analyzed_join->getOriginalName(col.name)); + } + + auto dict_reader = std::make_shared(dictionary, attr_names); + return std::make_shared(analyzed_join, right_sample_block, dict_reader); +} + std::shared_ptr tryKeyValueJoin(std::shared_ptr analyzed_join, const Block & right_sample_block) { if (!analyzed_join->isEnabledAlgorithm(JoinAlgorithm::DIRECT)) @@ -1192,7 +1249,6 @@ JoinPtr SelectQueryExpressionAnalyzer::makeJoin( } JoinPtr join = chooseJoinAlgorithm(analyzed_join, joined_plan, getContext()); - return join; } diff --git a/src/Interpreters/HashJoin.cpp b/src/Interpreters/HashJoin.cpp index 95a0008c257..55a18cb4193 100644 --- a/src/Interpreters/HashJoin.cpp +++ b/src/Interpreters/HashJoin.cpp @@ -179,36 +179,6 @@ namespace JoinStuff } } -static ColumnPtr filterWithBlanks(ColumnPtr src_column, const IColumn::Filter & filter, bool inverse_filter = false) -{ - ColumnPtr column = src_column->convertToFullColumnIfConst(); - MutableColumnPtr mut_column = column->cloneEmpty(); - mut_column->reserve(column->size()); - - if (inverse_filter) - { - for (size_t row = 0; row < filter.size(); ++row) - { - if (filter[row]) - mut_column->insertDefault(); - else - mut_column->insertFrom(*column, row); - } - } - else - { - for (size_t row = 0; row < filter.size(); ++row) - { - if (filter[row]) - mut_column->insertFrom(*column, row); - else - mut_column->insertDefault(); - } - } - - return mut_column; -} - static ColumnWithTypeAndName correctNullability(ColumnWithTypeAndName && column, bool nullable) { if (nullable) @@ -220,7 +190,7 @@ static ColumnWithTypeAndName correctNullability(ColumnWithTypeAndName && column, /// We have to replace values masked by NULLs with defaults. if (column.column) if (const auto * nullable_column = checkAndGetColumn(*column.column)) - column.column = filterWithBlanks(column.column, nullable_column->getNullMapColumn().getData(), true); + column.column = JoinCommon::filterWithBlanks(column.column, nullable_column->getNullMapColumn().getData(), true); JoinCommon::removeColumnNullability(column); } @@ -1607,7 +1577,7 @@ void HashJoin::joinBlockImpl( const auto & col = block.getByName(left_name); bool is_nullable = JoinCommon::isNullable(right_key.type); - ColumnPtr thin_column = filterWithBlanks(col.column, filter); + ColumnPtr thin_column = JoinCommon::filterWithBlanks(col.column, filter); ColumnWithTypeAndName right_col(thin_column, col.type, right_col_name); if (right_col.type->lowCardinality() != right_key.type->lowCardinality()) diff --git a/src/Interpreters/JoinedTables.cpp b/src/Interpreters/JoinedTables.cpp index 74b54ef537f..9caaad7faa7 100644 --- a/src/Interpreters/JoinedTables.cpp +++ b/src/Interpreters/JoinedTables.cpp @@ -323,6 +323,7 @@ std::shared_ptr JoinedTables::makeTableJoin(const ASTSelectQuery & se else if (auto storage_dict = std::dynamic_pointer_cast(storage); storage_dict && join_algorithm.isSet(JoinAlgorithm::DIRECT)) { + table_join->setRightStorageName(storage_dict->getDictionaryName()); table_join->setStorageJoin(storage_dict); } else if (auto storage_kv = std::dynamic_pointer_cast(storage); diff --git a/src/Interpreters/TableJoin.cpp b/src/Interpreters/TableJoin.cpp index 5d402ca4127..53eeff49437 100644 --- a/src/Interpreters/TableJoin.cpp +++ b/src/Interpreters/TableJoin.cpp @@ -727,6 +727,16 @@ void TableJoin::setStorageJoin(std::shared_ptr storage) right_storage_join = storage; } +void TableJoin::setRightStorageName(const std::string & storage_name) +{ + right_storage_name = storage_name; +} + +const std::string & TableJoin::getRightStorageName() const +{ + return right_storage_name; +} + void TableJoin::setStorageJoin(std::shared_ptr storage) { if (right_storage_join) diff --git a/src/Interpreters/TableJoin.h b/src/Interpreters/TableJoin.h index 3bb3b00416c..4e0706712a4 100644 --- a/src/Interpreters/TableJoin.h +++ b/src/Interpreters/TableJoin.h @@ -29,6 +29,7 @@ class ASTSelectQuery; struct DatabaseAndTableWithAlias; class Block; class DictionaryReader; +class DictionaryJoinAdapter; class StorageJoin; class StorageDictionary; class IKeyValueStorage; @@ -145,6 +146,8 @@ private: std::shared_ptr right_kv_storage; + std::string right_storage_name; + Names requiredJoinedNames() const; /// Create converting actions and change key column names if required @@ -301,6 +304,10 @@ public: std::unordered_map leftToRightKeyRemap() const; + /// Remember storage name in case of joining with dictionary or another special storage + void setRightStorageName(const std::string & storage_name); + const std::string & getRightStorageName() const; + void setStorageJoin(std::shared_ptr storage); void setStorageJoin(std::shared_ptr storage); void setStorageJoin(std::shared_ptr storage); diff --git a/src/Interpreters/join_common.cpp b/src/Interpreters/join_common.cpp index c81f4a193c3..ba71445df29 100644 --- a/src/Interpreters/join_common.cpp +++ b/src/Interpreters/join_common.cpp @@ -573,6 +573,36 @@ void splitAdditionalColumns(const Names & key_names, const Block & sample_block, } } +ColumnPtr filterWithBlanks(ColumnPtr src_column, const IColumn::Filter & filter, bool inverse_filter) +{ + ColumnPtr column = src_column->convertToFullColumnIfConst(); + MutableColumnPtr mut_column = column->cloneEmpty(); + mut_column->reserve(column->size()); + + if (inverse_filter) + { + for (size_t row = 0; row < filter.size(); ++row) + { + if (filter[row]) + mut_column->insertDefault(); + else + mut_column->insertFrom(*column, row); + } + } + else + { + for (size_t row = 0; row < filter.size(); ++row) + { + if (filter[row]) + mut_column->insertFrom(*column, row); + else + mut_column->insertDefault(); + } + } + + return mut_column; +} + } NotJoinedBlocks::NotJoinedBlocks(std::unique_ptr filler_, diff --git a/src/Interpreters/join_common.h b/src/Interpreters/join_common.h index 38b431db3e0..2e26ab782a1 100644 --- a/src/Interpreters/join_common.h +++ b/src/Interpreters/join_common.h @@ -106,6 +106,9 @@ void splitAdditionalColumns(const Names & key_names, const Block & sample_block, void changeLowCardinalityInplace(ColumnWithTypeAndName & column); +/// Insert default values for rows marked in filter +ColumnPtr filterWithBlanks(ColumnPtr src_column, const IColumn::Filter & filter, bool inverse_filter = false); + } /// Creates result from right table data in RIGHT and FULL JOIN when keys are not present in left table. diff --git a/tests/queries/0_stateless/01115_join_with_dictionary.sql b/tests/queries/0_stateless/01115_join_with_dictionary.sql index 5fbfe283fea..2b38abdec0e 100644 --- a/tests/queries/0_stateless/01115_join_with_dictionary.sql +++ b/tests/queries/0_stateless/01115_join_with_dictionary.sql @@ -1,5 +1,7 @@ SET send_logs_level = 'fatal'; +DROP TABLE IF EXISTS t1; + DROP DICTIONARY IF EXISTS dict_flat; DROP DICTIONARY IF EXISTS dict_hashed; DROP DICTIONARY IF EXISTS dict_complex_cache; From 7073067d40236e8095b5876d5b86d9b561eb845e Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 7 Jul 2022 14:53:39 +0000 Subject: [PATCH 521/672] check attributes for join with dict --- src/Dictionaries/DictionaryStructure.cpp | 6 ++++++ src/Dictionaries/DictionaryStructure.h | 1 + src/Interpreters/DictionaryJoinAdapter.cpp | 6 ++++++ src/Interpreters/DirectJoin.cpp | 7 ++++++- src/Interpreters/ExpressionAnalyzer.cpp | 5 ++++- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Dictionaries/DictionaryStructure.cpp b/src/Dictionaries/DictionaryStructure.cpp index 5624f9595d7..3ba82164eb2 100644 --- a/src/Dictionaries/DictionaryStructure.cpp +++ b/src/Dictionaries/DictionaryStructure.cpp @@ -167,6 +167,12 @@ void DictionaryStructure::validateKeyTypes(const DataTypes & key_types) const } } +bool DictionaryStructure::hasAttribute(const std::string & attribute_name) const +{ + auto it = attribute_name_to_index.find(attribute_name); + return it != attribute_name_to_index.end(); +} + const DictionaryAttribute & DictionaryStructure::getAttribute(const std::string & attribute_name) const { auto it = attribute_name_to_index.find(attribute_name); diff --git a/src/Dictionaries/DictionaryStructure.h b/src/Dictionaries/DictionaryStructure.h index bb4c306affa..327606e97f7 100644 --- a/src/Dictionaries/DictionaryStructure.h +++ b/src/Dictionaries/DictionaryStructure.h @@ -127,6 +127,7 @@ struct DictionaryStructure final DataTypes getKeyTypes() const; void validateKeyTypes(const DataTypes & key_types) const; + bool hasAttribute(const std::string & attribute_name) const; const DictionaryAttribute & getAttribute(const std::string & attribute_name) const; const DictionaryAttribute & getAttribute(const std::string & attribute_name, const DataTypePtr & type) const; diff --git a/src/Interpreters/DictionaryJoinAdapter.cpp b/src/Interpreters/DictionaryJoinAdapter.cpp index 9866a25901a..e3c7deb3963 100644 --- a/src/Interpreters/DictionaryJoinAdapter.cpp +++ b/src/Interpreters/DictionaryJoinAdapter.cpp @@ -12,6 +12,11 @@ namespace DB { +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + DictionaryJoinAdapter::DictionaryJoinAdapter( std::shared_ptr dictionary_, const Names & result_column_names) : IKeyValueStorage(StorageID::createEmpty()) @@ -78,6 +83,7 @@ Chunk DictionaryJoinAdapter::getByKeys(const ColumnsWithTypeAndName & keys, Padd /// Dictinonary may have non-standart default values specified default_cols[i] = result_types[i]->createColumnConstWithDefaultValue(out_null_map.size()); + /// Result block consists of key columns and then attributes Columns result_columns = dictionary->getColumns(attribute_names, result_types, key_columns, key_types, default_cols); for (const auto & key_col : key_columns) diff --git a/src/Interpreters/DirectJoin.cpp b/src/Interpreters/DirectJoin.cpp index ceacca2fdca..46ed45f7708 100644 --- a/src/Interpreters/DirectJoin.cpp +++ b/src/Interpreters/DirectJoin.cpp @@ -30,6 +30,11 @@ static MutableColumns convertBlockStructure( MutableColumns result_columns; for (const auto & out_sample_col : result_sample_block) { + /// Some coulumns from result_sample_block may not be in source_sample_block, + /// e.g. if they will be calculated later based on joined columns + if (!source_sample_block.has(out_sample_col.name)) + continue; + auto i = source_sample_block.getPositionByName(out_sample_col.name); if (columns[i] == nullptr) { @@ -111,7 +116,7 @@ void DirectKeyValueJoin::joinBlock(Block & block, std::shared_ptr &) NullMap null_map; Chunk joined_chunk = storage->getByKeys({key_col}, null_map); - /// Expected right block may differ from structure in storage, because of `join_use_nulls` or we just select not all columns. + /// Expected right block may differ from structure in storage, because of `join_use_nulls` or we just select not all joined attributes Block original_right_block = originalRightBlock(right_sample_block, *table_join); Block sample_storage_block = storage->getInMemoryMetadataPtr()->getSampleBlock(); MutableColumns result_columns = convertBlockStructure(sample_storage_block, original_right_block, joined_chunk.mutateColumns(), null_map); diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index a6c91f7ace3..4cb90c2218a 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -1163,7 +1163,10 @@ std::shared_ptr tryDictJoin(std::shared_ptr analy { if (col.name == right_key) continue; - attr_names.push_back(analyzed_join->getOriginalName(col.name)); + + const auto & original_name = analyzed_join->getOriginalName(col.name); + if (dictionary->getStructure().hasAttribute(original_name)) + attr_names.push_back(original_name); } auto dict_reader = std::make_shared(dictionary, attr_names); From d1aea199875ffd4a5b8a7225b1c0ce01460ce482 Mon Sep 17 00:00:00 2001 From: vdimir Date: Fri, 8 Jul 2022 13:11:27 +0000 Subject: [PATCH 522/672] Remove old join with dictionary --- src/Interpreters/DictionaryReader.cpp | 172 ------------------------ src/Interpreters/DictionaryReader.h | 50 ------- src/Interpreters/ExpressionAnalyzer.cpp | 1 - src/Interpreters/HashJoin.cpp | 123 ++--------------- src/Interpreters/HashJoin.h | 14 +- src/Interpreters/JoinedTables.cpp | 1 - src/Interpreters/TableJoin.cpp | 12 +- src/Interpreters/TableJoin.h | 10 +- 8 files changed, 13 insertions(+), 370 deletions(-) delete mode 100644 src/Interpreters/DictionaryReader.cpp delete mode 100644 src/Interpreters/DictionaryReader.h diff --git a/src/Interpreters/DictionaryReader.cpp b/src/Interpreters/DictionaryReader.cpp deleted file mode 100644 index 3c66b0019ed..00000000000 --- a/src/Interpreters/DictionaryReader.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace DB -{ - -namespace ErrorCodes -{ - extern const int NUMBER_OF_COLUMNS_DOESNT_MATCH; - extern const int TYPE_MISMATCH; -} - - -DictionaryReader::FunctionWrapper::FunctionWrapper(FunctionOverloadResolverPtr resolver, const ColumnsWithTypeAndName & arguments, - Block & block, const ColumnNumbers & arg_positions_, const String & column_name, - TypeIndex expected_type) - : arg_positions(arg_positions_) - , result_pos(block.columns()) -{ - FunctionBasePtr prepared_function = resolver->build(arguments); - - ColumnWithTypeAndName result; - result.name = "get_" + column_name; - result.type = prepared_function->getResultType(); - if (result.type->getTypeId() != expected_type) - throw Exception("Type mismatch in dictionary reader for: " + column_name, ErrorCodes::TYPE_MISMATCH); - block.insert(result); - - ColumnsWithTypeAndName args; - args.reserve(arg_positions.size()); - for (auto pos : arg_positions) - args.emplace_back(block.getByPosition(pos)); - - function = prepared_function->prepare(block.getColumnsWithTypeAndName()); -} - -static constexpr const size_t key_size = 1; - -DictionaryReader::DictionaryReader(const String & dictionary_name, const Names & src_column_names, const NamesAndTypesList & result_columns, - ContextPtr context) - : result_header(makeResultBlock(result_columns)) - , key_position(key_size + result_header.columns()) -{ - if (src_column_names.size() != result_columns.size()) - throw Exception("Columns number mismatch in dictionary reader", ErrorCodes::NUMBER_OF_COLUMNS_DOESNT_MATCH); - - ColumnWithTypeAndName dict_name; - ColumnWithTypeAndName key; - ColumnWithTypeAndName column_name; - - { - dict_name.name = "dict"; - dict_name.type = std::make_shared(); - dict_name.column = dict_name.type->createColumnConst(1, dictionary_name); - - /// TODO: composite key (key_size > 1) - key.name = "key"; - key.type = std::make_shared(); - - column_name.name = "column"; - column_name.type = std::make_shared(); - } - - /// dictHas('dict_name', id) - ColumnsWithTypeAndName arguments_has; - arguments_has.push_back(dict_name); - arguments_has.push_back(key); - - /// dictGet('dict_name', 'attr_name', id) - ColumnsWithTypeAndName arguments_get; - arguments_get.push_back(dict_name); - arguments_get.push_back(column_name); - arguments_get.push_back(key); - - sample_block.insert(dict_name); - - for (const auto & columns_name : src_column_names) - { - ColumnWithTypeAndName name; - name.name = "col_" + columns_name; - name.type = std::make_shared(); - name.column = name.type->createColumnConst(1, columns_name); - - sample_block.insert(name); - } - - sample_block.insert(key); - - ColumnNumbers positions_has{0, key_position}; - function_has = std::make_unique(FunctionFactory::instance().get("dictHas", context), - arguments_has, sample_block, positions_has, "has", DataTypeUInt8().getTypeId()); - functions_get.reserve(result_header.columns()); - - for (size_t i = 0; i < result_header.columns(); ++i) - { - size_t column_name_pos = key_size + i; - auto & column = result_header.getByPosition(i); - arguments_get[1].column = DataTypeString().createColumnConst(1, src_column_names[i]); - ColumnNumbers positions_get{0, column_name_pos, key_position}; - functions_get.emplace_back( - FunctionWrapper(FunctionFactory::instance().get("dictGet", context), - arguments_get, sample_block, positions_get, column.name, column.type->getTypeId())); - } -} - -void DictionaryReader::readKeys(const IColumn & keys, Block & out_block, ColumnVector::Container & found, - std::vector & positions) const -{ - auto working_block = sample_block.getColumnsWithTypeAndName(); - size_t has_position = key_position + 1; - size_t size = keys.size(); - - /// set keys for dictHas() - ColumnWithTypeAndName & key_column = working_block[key_position]; - key_column.column = keys.cloneResized(size); /// just a copy we cannot avoid - - /// calculate and extract dictHas() - function_has->execute(working_block, size); - ColumnWithTypeAndName & has_column = working_block[has_position]; - auto mutable_has = IColumn::mutate(std::move(has_column.column)); - found.swap(typeid_cast &>(*mutable_has).getData()); - has_column.column = nullptr; - - /// set mapping from source keys to resulting rows in output block - positions.clear(); - positions.resize(size, 0); - size_t pos = 0; - for (size_t i = 0; i < size; ++i) - if (found[i]) - positions[i] = pos++; - - /// set keys for dictGet(): remove not found keys - key_column.column = key_column.column->filter(found, -1); - size_t rows = key_column.column->size(); - - /// calculate dictGet() - for (const auto & func : functions_get) - func.execute(working_block, rows); - - /// make result: copy header block with correct names and move data columns - out_block = result_header.cloneEmpty(); - size_t first_get_position = has_position + 1; - for (size_t i = 0; i < out_block.columns(); ++i) - { - auto & src_column = working_block[first_get_position + i]; - auto & dst_column = out_block.getByPosition(i); - dst_column.column = src_column.column; - src_column.column = nullptr; - } -} - -Block DictionaryReader::makeResultBlock(const NamesAndTypesList & names) -{ - Block block; - for (const auto & nm : names) - { - ColumnWithTypeAndName column{nullptr, nm.type, nm.name}; - if (column.type->isNullable()) - column.type = typeid_cast(*column.type).getNestedType(); - block.insert(std::move(column)); - } - return block; -} - -} diff --git a/src/Interpreters/DictionaryReader.h b/src/Interpreters/DictionaryReader.h deleted file mode 100644 index bfb21e2f050..00000000000 --- a/src/Interpreters/DictionaryReader.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace DB -{ - -/// Read block of required columns from Dictionary by UInt64 key column. Rename columns if needed. -/// Current implementation uses dictHas() + N * dictGet() functions. -class DictionaryReader -{ -public: - struct FunctionWrapper - { - ExecutableFunctionPtr function; - ColumnNumbers arg_positions; - size_t result_pos = 0; - - FunctionWrapper(FunctionOverloadResolverPtr resolver, const ColumnsWithTypeAndName & arguments, Block & block, - const ColumnNumbers & arg_positions_, const String & column_name, TypeIndex expected_type); - - void execute(ColumnsWithTypeAndName & columns, size_t rows) const - { - ColumnsWithTypeAndName args; - args.reserve(arg_positions.size()); - for (auto pos : arg_positions) - args.emplace_back(columns[pos]); - - columns[result_pos].column = function->execute(args, columns[result_pos].type, rows, false); - } - }; - - DictionaryReader(const String & dictionary_name, const Names & src_column_names, const NamesAndTypesList & result_columns, - ContextPtr context); - void readKeys(const IColumn & keys, Block & out_block, ColumnVector::Container & found, std::vector & positions) const; - -private: - Block result_header; - Block sample_block; /// dictionary name, column names, key, dictHas() result, dictGet() results - size_t key_position; - std::unique_ptr function_has; - std::vector functions_get; - - static Block makeResultBlock(const NamesAndTypesList & names); -}; - -} diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index 4cb90c2218a..b466fbbfcc1 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include diff --git a/src/Interpreters/HashJoin.cpp b/src/Interpreters/HashJoin.cpp index 55a18cb4193..a948769540a 100644 --- a/src/Interpreters/HashJoin.cpp +++ b/src/Interpreters/HashJoin.cpp @@ -19,9 +19,7 @@ #include #include #include -#include -#include #include #include @@ -268,16 +266,7 @@ HashJoin::HashJoin(std::shared_ptr table_join_, const Block & right_s const auto & key_names_right = clause.key_names_right; ColumnRawPtrs key_columns = JoinCommon::extractKeysForJoin(right_table_keys, key_names_right); - if (table_join->getDictionaryReader()) - { - assert(disjuncts_num == 1); - data->type = Type::DICT; - - data->maps.resize(disjuncts_num); - std::get(data->maps[0]).create(Type::DICT); - chooseMethod(kind, key_columns, key_sizes.emplace_back()); /// init key_sizes - } - else if (strictness == JoinStrictness::Asof) + if (strictness == ASTTableJoin::Strictness::Asof) { assert(disjuncts_num == 1); @@ -399,36 +388,6 @@ static KeyGetter createKeyGetter(const ColumnRawPtrs & key_columns, const Sizes template using FindResultImpl = ColumnsHashing::columns_hashing_impl::FindResultImpl; -class KeyGetterForDict -{ -public: - using Mapped = RowRef; - using FindResult = FindResultImpl; - - KeyGetterForDict(const TableJoin & table_join, const ColumnRawPtrs & key_columns) - { - assert(table_join.getDictionaryReader()); - table_join.getDictionaryReader()->readKeys(*key_columns[0], read_result, found, positions); - - for (ColumnWithTypeAndName & column : read_result) - if (table_join.rightBecomeNullable(column.type)) - JoinCommon::convertColumnToNullable(column); - } - - FindResult findKey(const TableJoin &, size_t row, const Arena &) - { - result.block = &read_result; - result.row_num = positions[row]; - return FindResult(&result, found[row], 0); - } - -private: - Block read_result; - Mapped result; - ColumnVector::Container found; - std::vector positions; -}; - /// Dummy key getter, always find nothing, used for JOIN ON NULL template class KeyGetterEmpty @@ -499,20 +458,13 @@ struct KeyGetterForType void HashJoin::dataMapInit(MapsVariant & map) { - if (data->type == Type::DICT) - return; - if (kind == JoinKind::Cross) + + if (kind == ASTTableJoin::Kind::Cross) return; joinDispatchInit(kind, strictness, map); joinDispatch(kind, strictness, map, [&](auto, auto, auto & map_) { map_.create(data->type); }); } -bool HashJoin::overDictionary() const -{ - assert(data->type != Type::DICT || table_join->getDictionaryReader()); - return data->type == Type::DICT; -} - bool HashJoin::empty() const { return data->type == Type::EMPTY; @@ -520,7 +472,7 @@ bool HashJoin::empty() const bool HashJoin::alwaysReturnsEmptySet() const { - return isInnerOrRight(getKind()) && data->empty && !overDictionary(); + return isInnerOrRight(getKind()) && data->empty; } size_t HashJoin::getTotalRowCount() const @@ -532,7 +484,7 @@ size_t HashJoin::getTotalRowCount() const for (const auto & block : data->blocks) res += block.rows(); } - else if (data->type != Type::DICT) + else { for (const auto & map : data->maps) { @@ -540,6 +492,7 @@ size_t HashJoin::getTotalRowCount() const } } + return res; } @@ -552,7 +505,7 @@ size_t HashJoin::getTotalByteCount() const for (const auto & block : data->blocks) res += block.bytes(); } - else if (data->type != Type::DICT) + else { for (const auto & map : data->maps) { @@ -663,7 +616,6 @@ namespace { case HashJoin::Type::EMPTY: return 0; case HashJoin::Type::CROSS: return 0; /// Do nothing. We have already saved block, and it is enough. - case HashJoin::Type::DICT: return 0; /// No one should call it with Type::DICT. #define M(TYPE) \ case HashJoin::Type::TYPE: \ @@ -723,9 +675,6 @@ Block HashJoin::structureRightBlock(const Block & block) const bool HashJoin::addJoinedBlock(const Block & source_block, bool check_limits) { - if (overDictionary()) - throw Exception("Logical error: insert into hash-map in HashJoin over dictionary", ErrorCodes::LOGICAL_ERROR); - /// RowRef::SizeT is uint32_t (not size_t) for hash table Cell memory efficiency. /// It's possible to split bigger blocks and insert them by parts here. But it would be a dead code. if (unlikely(source_block.rows() > std::numeric_limits::max())) @@ -1446,28 +1395,6 @@ IColumn::Filter switchJoinRightColumns( } } -template -IColumn::Filter dictionaryJoinRightColumns(const TableJoin & table_join, AddedColumns & added_columns) -{ - if constexpr (KIND == JoinKind::Left && - (STRICTNESS == JoinStrictness::Any || - STRICTNESS == JoinStrictness::Semi || - STRICTNESS == JoinStrictness::Anti)) - { - assert(added_columns.join_on_keys.size() == 1); - - std::vector maps_vector; - maps_vector.push_back(&table_join); - - JoinStuff::JoinUsedFlags flags; - std::vector key_getter_vector; - key_getter_vector.push_back(KeyGetterForDict(table_join, added_columns.join_on_keys[0].key_columns)); - return joinRightColumnsSwitchNullability(std::move(key_getter_vector), maps_vector, added_columns, flags); - } - - throw Exception(ErrorCodes::LOGICAL_ERROR, "Wrong JOIN combination: {} {}", STRICTNESS, KIND); -} - } /// nameless template @@ -1514,9 +1441,7 @@ void HashJoin::joinBlockImpl( bool has_required_right_keys = (required_right_keys.columns() != 0); added_columns.need_filter = jf.need_filter || has_required_right_keys; - IColumn::Filter row_filter = overDictionary() ? - dictionaryJoinRightColumns(*table_join, added_columns) : - switchJoinRightColumns(maps_, added_columns, data->type, used_flags); + IColumn::Filter row_filter = switchJoinRightColumns(maps_, added_columns, data->type, used_flags); for (size_t i = 0; i < added_columns.size(); ++i) block.insert(added_columns.moveColumn(i)); @@ -1772,39 +1697,7 @@ void HashJoin::joinBlock(Block & block, ExtraBlockPtr & not_processed) materializeBlockInplace(block); } - if (overDictionary()) { - auto & map = std::get(data->maps[0]); - std::vector*> maps_vector; - maps_vector.push_back(&map); - - if (kind == JoinKind::Left) - { - switch (strictness) - { - case JoinStrictness::Any: - case JoinStrictness::All: - joinBlockImpl(block, sample_block_with_columns_to_add, maps_vector); - break; - case JoinStrictness::Semi: - joinBlockImpl(block, sample_block_with_columns_to_add, maps_vector); - break; - case JoinStrictness::Anti: - joinBlockImpl(block, sample_block_with_columns_to_add, maps_vector); - break; - default: - throw Exception(ErrorCodes::LOGICAL_ERROR, "Wrong JOIN combination: dictionary + {} {}", strictness, kind); - } - } - else if (kind == JoinKind::Inner && strictness == JoinStrictness::All) - joinBlockImpl(block, sample_block_with_columns_to_add, maps_vector); - - else - throw Exception(ErrorCodes::LOGICAL_ERROR, "Wrong JOIN combination: {} {}", strictness, kind); - } - else - { - std::vectormaps[0])> * > maps_vector; for (size_t i = 0; i < table_join->getClauses().size(); ++i) maps_vector.push_back(&data->maps[i]); diff --git a/src/Interpreters/HashJoin.h b/src/Interpreters/HashJoin.h index 54c641627c0..f12997fd1c9 100644 --- a/src/Interpreters/HashJoin.h +++ b/src/Interpreters/HashJoin.h @@ -33,7 +33,6 @@ namespace DB { class TableJoin; -class DictionaryReader; namespace JoinStuff { @@ -171,13 +170,12 @@ public: /// Used by joinGet function that turns StorageJoin into a dictionary. ColumnWithTypeAndName joinGet(const Block & block, const Block & block_with_columns_to_add) const; - bool isFilled() const override { return from_storage_join || data->type == Type::DICT; } + bool isFilled() const override { return from_storage_join; } JoinPipelineType pipelineType() const override { - /// No need to process anything in the right stream if it's a dictionary will just join the left stream with it. - bool is_filled = from_storage_join || data->type == Type::DICT; - if (is_filled) + /// No need to process anything in the right stream if hash table was already filled + if (from_storage_join) return JoinPipelineType::FilledRight; /// Default pipeline processes right stream at first and then left. @@ -233,7 +231,6 @@ public: { EMPTY, CROSS, - DICT, #define M(NAME) NAME, APPLY_FOR_JOIN_VARIANTS(M) #undef M @@ -261,7 +258,6 @@ public: { case Type::EMPTY: break; case Type::CROSS: break; - case Type::DICT: break; #define M(NAME) \ case Type::NAME: NAME = std::make_unique(); break; @@ -276,7 +272,6 @@ public: { case Type::EMPTY: return 0; case Type::CROSS: return 0; - case Type::DICT: return 0; #define M(NAME) \ case Type::NAME: return NAME ? NAME->size() : 0; @@ -293,7 +288,6 @@ public: { case Type::EMPTY: return 0; case Type::CROSS: return 0; - case Type::DICT: return 0; #define M(NAME) \ case Type::NAME: return NAME ? NAME->getBufferSizeInBytes() : 0; @@ -310,7 +304,6 @@ public: { case Type::EMPTY: return 0; case Type::CROSS: return 0; - case Type::DICT: return 0; #define M(NAME) \ case Type::NAME: return NAME ? NAME->getBufferSizeInCells() : 0; @@ -424,7 +417,6 @@ private: static Type chooseMethod(JoinKind kind, const ColumnRawPtrs & key_columns, Sizes & key_sizes); bool empty() const; - bool overDictionary() const; }; } diff --git a/src/Interpreters/JoinedTables.cpp b/src/Interpreters/JoinedTables.cpp index 9caaad7faa7..7a6f624ae47 100644 --- a/src/Interpreters/JoinedTables.cpp +++ b/src/Interpreters/JoinedTables.cpp @@ -324,7 +324,6 @@ std::shared_ptr JoinedTables::makeTableJoin(const ASTSelectQuery & se storage_dict && join_algorithm.isSet(JoinAlgorithm::DIRECT)) { table_join->setRightStorageName(storage_dict->getDictionaryName()); - table_join->setStorageJoin(storage_dict); } else if (auto storage_kv = std::dynamic_pointer_cast(storage); storage_kv && join_algorithm.isSet(JoinAlgorithm::DIRECT)) diff --git a/src/Interpreters/TableJoin.cpp b/src/Interpreters/TableJoin.cpp index 53eeff49437..fccd8430f1c 100644 --- a/src/Interpreters/TableJoin.cpp +++ b/src/Interpreters/TableJoin.cpp @@ -13,7 +13,6 @@ #include -#include #include #include @@ -722,8 +721,6 @@ void TableJoin::setStorageJoin(std::shared_ptr storage) void TableJoin::setStorageJoin(std::shared_ptr storage) { - if (right_storage_dictionary) - throw DB::Exception(ErrorCodes::LOGICAL_ERROR, "StorageJoin and Dictionary join are mutually exclusive"); right_storage_join = storage; } @@ -737,13 +734,6 @@ const std::string & TableJoin::getRightStorageName() const return right_storage_name; } -void TableJoin::setStorageJoin(std::shared_ptr storage) -{ - if (right_storage_join) - throw DB::Exception(ErrorCodes::LOGICAL_ERROR, "StorageJoin and Dictionary join are mutually exclusive"); - right_storage_dictionary = storage; -} - String TableJoin::renamedRightColumnName(const String & name) const { if (const auto it = renames.find(name); it != renames.end()) @@ -828,7 +818,7 @@ void TableJoin::resetToCross() bool TableJoin::allowParallelHashJoin() const { - if (dictionary_reader || !join_algorithm.isSet(JoinAlgorithm::PARALLEL_HASH)) + if (!right_storage_name.empty() || !join_algorithm.isSet(JoinAlgorithm::PARALLEL_HASH)) return false; if (table_join.kind != JoinKind::Left && table_join.kind != JoinKind::Inner) return false; diff --git a/src/Interpreters/TableJoin.h b/src/Interpreters/TableJoin.h index 4e0706712a4..d7463f1f4b5 100644 --- a/src/Interpreters/TableJoin.h +++ b/src/Interpreters/TableJoin.h @@ -28,7 +28,6 @@ class Context; class ASTSelectQuery; struct DatabaseAndTableWithAlias; class Block; -class DictionaryReader; class DictionaryJoinAdapter; class StorageJoin; class StorageDictionary; @@ -141,9 +140,6 @@ private: std::shared_ptr right_storage_join; - std::shared_ptr right_storage_dictionary; - std::shared_ptr dictionary_reader; - std::shared_ptr right_kv_storage; std::string right_storage_name; @@ -310,14 +306,10 @@ public: void setStorageJoin(std::shared_ptr storage); void setStorageJoin(std::shared_ptr storage); - void setStorageJoin(std::shared_ptr storage); std::shared_ptr getStorageJoin() { return right_storage_join; } - bool tryInitDictJoin(const Block & sample_block, ContextPtr context); - - bool isSpecialStorage() const { return right_storage_dictionary || right_storage_join || right_kv_storage; } - const DictionaryReader * getDictionaryReader() const { return dictionary_reader.get(); } + bool isSpecialStorage() const { return !right_storage_name.empty() || right_storage_join || right_kv_storage; } std::shared_ptr getStorageKeyValue() { return right_kv_storage; } }; From 442ffb0349cafdcfa1f6d43a1ac501ada173d3df Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 4 Aug 2022 15:15:49 +0000 Subject: [PATCH 523/672] Fix build after resolving conflicts --- src/Interpreters/DirectJoin.cpp | 22 ++++---- src/Interpreters/ExpressionAnalyzer.cpp | 27 +++++----- src/Interpreters/HashJoin.cpp | 4 +- src/Interpreters/TableJoin.cpp | 69 ------------------------- 4 files changed, 27 insertions(+), 95 deletions(-) diff --git a/src/Interpreters/DirectJoin.cpp b/src/Interpreters/DirectJoin.cpp index 46ed45f7708..d47a4f7a305 100644 --- a/src/Interpreters/DirectJoin.cpp +++ b/src/Interpreters/DirectJoin.cpp @@ -69,21 +69,21 @@ DirectKeyValueJoin::DirectKeyValueJoin(std::shared_ptr table_join_, , right_sample_block(right_sample_block_) , log(&Poco::Logger::get("DirectKeyValueJoin")) { - if (!table_join->oneDisjunct() - || table_join->getOnlyClause().key_names_left.size() != 1 - || table_join->getOnlyClause().key_names_right.size() != 1) + if (!table_join->oneDisjunct() || + table_join->getOnlyClause().key_names_left.size() != 1 || + table_join->getOnlyClause().key_names_right.size() != 1) { throw DB::Exception(ErrorCodes::UNSUPPORTED_JOIN_KEYS, "Not supported by direct JOIN"); } - bool allowed_inner = isInner(table_join->kind()) && (table_join->strictness() == ASTTableJoin::Strictness::All || - table_join->strictness() == ASTTableJoin::Strictness::Any || + bool allowed_inner = isInner(table_join->kind()) && (table_join->strictness() == JoinStrictness::All || + table_join->strictness() == JoinStrictness::Any || table_join->strictness() != JoinStrictness::RightAny); - bool allowed_left = isLeft(table_join->kind()) && (table_join->strictness() == ASTTableJoin::Strictness::Any || - table_join->strictness() == ASTTableJoin::Strictness::All || - table_join->strictness() == ASTTableJoin::Strictness::Semi || - table_join->strictness() == ASTTableJoin::Strictness::Anti); + bool allowed_left = isLeft(table_join->kind()) && (table_join->strictness() == JoinStrictness::Any || + table_join->strictness() == JoinStrictness::All || + table_join->strictness() == JoinStrictness::Semi || + table_join->strictness() == JoinStrictness::Anti); if (!allowed_inner && !allowed_left) { throw DB::Exception(ErrorCodes::NOT_IMPLEMENTED, "Strictness {} and kind {} is not supported by direct JOIN", @@ -128,8 +128,8 @@ void DirectKeyValueJoin::joinBlock(Block & block, std::shared_ptr &) block.insert(std::move(col)); } - bool is_semi_join = table_join->strictness() == ASTTableJoin::Strictness::Semi; - bool is_anti_join = table_join->strictness() == ASTTableJoin::Strictness::Anti; + bool is_semi_join = table_join->strictness() == JoinStrictness::Semi; + bool is_anti_join = table_join->strictness() == JoinStrictness::Anti; if (is_anti_join) { diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index b466fbbfcc1..18fcef87d3c 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -113,6 +113,8 @@ bool allowEarlyConstantFolding(const ActionsDAG & actions, const Settings & sett return true; } +Poco::Logger * getLogger() { return &Poco::Logger::get("ExpressionAnalyzer"); } + } bool sanitizeBlock(Block & block, bool throw_if_cannot_create_column) @@ -1006,6 +1008,7 @@ static ActionsDAGPtr createJoinedBlockActions(ContextPtr context, const TableJoi return ExpressionAnalyzer(expression_list, syntax_result, context).getActionsDAG(true, false); } +std::shared_ptr tryDictJoin(std::shared_ptr analyzed_join, const Block & right_sample_block, ContextPtr context); std::shared_ptr tryKeyValueJoin(std::shared_ptr analyzed_join, const Block & right_sample_block); static std::shared_ptr chooseJoinAlgorithm(std::shared_ptr analyzed_join, std::unique_ptr & joined_plan, ContextPtr context) @@ -1016,7 +1019,7 @@ static std::shared_ptr chooseJoinAlgorithm(std::shared_ptr ana { JoinPtr direct_join = nullptr; direct_join = direct_join ? direct_join : tryKeyValueJoin(analyzed_join, right_sample_block); - direct_join = direct_join ? direct_join : tryDictJoin(analyzed_join, right_sample_block, getContext()); + direct_join = direct_join ? direct_join : tryDictJoin(analyzed_join, right_sample_block, context); if (direct_join) { @@ -1111,23 +1114,21 @@ static std::unique_ptr buildJoinedPlan( std::shared_ptr tryDictJoin(std::shared_ptr analyzed_join, const Block & right_sample_block, ContextPtr context) { - using Strictness = ASTTableJoin::Strictness; - - bool allowed_inner = isInner(analyzed_join->kind()) && analyzed_join->strictness() == Strictness::All; - bool allowed_left = isLeft(analyzed_join->kind()) && (analyzed_join->strictness() == Strictness::Any || - analyzed_join->strictness() == Strictness::All || - analyzed_join->strictness() == Strictness::Semi || - analyzed_join->strictness() == Strictness::Anti); + bool allowed_inner = isInner(analyzed_join->kind()) && analyzed_join->strictness() == JoinStrictness::All; + bool allowed_left = isLeft(analyzed_join->kind()) && (analyzed_join->strictness() == JoinStrictness::Any || + analyzed_join->strictness() == JoinStrictness::All || + analyzed_join->strictness() == JoinStrictness::Semi || + analyzed_join->strictness() == JoinStrictness::Anti); if (!allowed_inner && !allowed_left) { - LOG_TRACE(&Poco::Logger::get("tryDictJoin"), "Can't use dictionary join: {} {} is not supported", + LOG_TRACE(getLogger(), "Can't use dictionary join: {} {} is not supported", analyzed_join->kind(), analyzed_join->strictness()); return nullptr; } if (analyzed_join->getClauses().size() != 1 || analyzed_join->getClauses()[0].key_names_right.size() != 1) { - LOG_TRACE(&Poco::Logger::get("tryDictJoin"), "Can't use dictionary join: only one key is supported"); + LOG_TRACE(getLogger(), "Can't use dictionary join: only one key is supported"); return nullptr; } @@ -1136,7 +1137,7 @@ std::shared_ptr tryDictJoin(std::shared_ptr analy const auto & dictionary_name = analyzed_join->getRightStorageName(); if (dictionary_name.empty()) { - LOG_TRACE(&Poco::Logger::get("tryDictJoin"), "Can't use dictionary join: dictionary was not found"); + LOG_TRACE(getLogger(), "Can't use dictionary join: dictionary was not found"); return nullptr; } @@ -1145,14 +1146,14 @@ std::shared_ptr tryDictJoin(std::shared_ptr analy auto dictionary = dictionary_helper.getDictionary(dictionary_name); if (!dictionary) { - LOG_TRACE(&Poco::Logger::get("tryDictJoin"), "Can't use dictionary join: dictionary was not found"); + LOG_TRACE(getLogger(), "Can't use dictionary join: dictionary was not found"); return nullptr; } const auto & dict_keys = dictionary->getStructure().getKeysNames(); if (dict_keys.size() != 1 || dict_keys[0] != analyzed_join->getOriginalName(right_key)) { - LOG_TRACE(&Poco::Logger::get("tryDictJoin"), "Can't use dictionary join: join key '{}' doesn't natch to dictionary key ({})", + LOG_TRACE(getLogger(), "Can't use dictionary join: join key '{}' doesn't natch to dictionary key ({})", right_key, fmt::join(dict_keys, ", ")); return nullptr; } diff --git a/src/Interpreters/HashJoin.cpp b/src/Interpreters/HashJoin.cpp index a948769540a..1b9c6e72c07 100644 --- a/src/Interpreters/HashJoin.cpp +++ b/src/Interpreters/HashJoin.cpp @@ -266,7 +266,7 @@ HashJoin::HashJoin(std::shared_ptr table_join_, const Block & right_s const auto & key_names_right = clause.key_names_right; ColumnRawPtrs key_columns = JoinCommon::extractKeysForJoin(right_table_keys, key_names_right); - if (strictness == ASTTableJoin::Strictness::Asof) + if (strictness == JoinStrictness::Asof) { assert(disjuncts_num == 1); @@ -459,7 +459,7 @@ struct KeyGetterForType void HashJoin::dataMapInit(MapsVariant & map) { - if (kind == ASTTableJoin::Kind::Cross) + if (kind == JoinKind::Cross) return; joinDispatchInit(kind, strictness, map); joinDispatch(kind, strictness, map, [&](auto, auto, auto & map_) { map_.create(data->type); }); diff --git a/src/Interpreters/TableJoin.cpp b/src/Interpreters/TableJoin.cpp index fccd8430f1c..ce42af644b1 100644 --- a/src/Interpreters/TableJoin.cpp +++ b/src/Interpreters/TableJoin.cpp @@ -416,75 +416,6 @@ bool TableJoin::needStreamWithNonJoinedRows() const return isRightOrFull(kind()); } -static std::optional getDictKeyName(const String & dict_name , ContextPtr context) -{ - auto dictionary = context->getExternalDictionariesLoader().getDictionary(dict_name, context); - if (!dictionary) - return {}; - - if (const auto & structure = dictionary->getStructure(); structure.id) - return structure.id->name; - return {}; -} - -bool TableJoin::tryInitDictJoin(const Block & sample_block, ContextPtr context) -{ - bool allowed_inner = isInner(kind()) && strictness() == JoinStrictness::All; - bool allowed_left = isLeft(kind()) && (strictness() == JoinStrictness::Any || - strictness() == JoinStrictness::All || - strictness() == JoinStrictness::Semi || - strictness() == JoinStrictness::Anti); - - /// Support ALL INNER, [ANY | ALL | SEMI | ANTI] LEFT - if (!allowed_inner && !allowed_left) - return false; - - if (clauses.size() != 1 || clauses[0].key_names_right.size() != 1) - return false; - - const auto & right_key = getOnlyClause().key_names_right[0]; - - /// TODO: support 'JOIN ... ON expr(dict_key) = table_key' - auto it_key = original_names.find(right_key); - if (it_key == original_names.end()) - return false; - - if (!right_storage_dictionary) - return false; - - auto dict_name = right_storage_dictionary->getDictionaryName(); - - auto dict_key = getDictKeyName(dict_name, context); - if (!dict_key.has_value() || *dict_key != it_key->second) - return false; /// JOIN key != Dictionary key - - Names src_names; - NamesAndTypesList dst_columns; - for (const auto & col : sample_block) - { - if (col.name == right_key) - continue; /// do not extract key column - - auto it = original_names.find(col.name); - if (it != original_names.end()) - { - String original = it->second; - src_names.push_back(original); - dst_columns.push_back({col.name, col.type}); - } - else - { - /// Can't extract column from dictionary table - /// TODO: Sometimes it should be possible to recunstruct required column, - /// e.g. if it's an expression depending on dictionary attributes - return false; - } - } - dictionary_reader = std::make_shared(dict_name, src_names, dst_columns, context); - - return true; -} - static void renameIfNeeded(String & name, const NameToNameMap & renames) { if (const auto it = renames.find(name); it != renames.end()) From 91fe8f0b46a2b9460023709e45a70e0fe3d11c32 Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 4 Aug 2022 15:16:27 +0000 Subject: [PATCH 524/672] Force dict join in 01115_join_with_dictionary --- .../01115_join_with_dictionary.reference | 22 +++++++++--------- .../01115_join_with_dictionary.sql | 23 ++++++++++++++----- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/tests/queries/0_stateless/01115_join_with_dictionary.reference b/tests/queries/0_stateless/01115_join_with_dictionary.reference index 326f0c5e14b..612c3333160 100644 --- a/tests/queries/0_stateless/01115_join_with_dictionary.reference +++ b/tests/queries/0_stateless/01115_join_with_dictionary.reference @@ -16,12 +16,6 @@ flat: any left 2 2 2 2 3 3 3 3 4 0 0 -flat: any left + any_join_distinct_right_table_keys -0 0 0 0 -1 1 1 1 -2 2 2 2 -3 3 3 3 -4 0 0 flat: semi left 0 0 0 0 1 1 1 1 @@ -37,11 +31,6 @@ flat: inner on 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 -flat: inner or -0 0 0 0 0 -1000 1 1 1 1 -2 2 2 2 2 -3000 3 3 3 3 hashed: left on 0 0 0 0 0 1 1 1 1 1 @@ -75,6 +64,17 @@ hashed: inner on 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 +flat: inner or +0 0 0 0 0 +1000 1 1 1 1 +2 2 2 2 2 +3000 3 3 3 3 +flat: any left + any_join_distinct_right_table_keys +0 0 0 0 +1 1 1 1 +2 2 2 2 +3 3 3 3 +4 0 0 complex_cache (smoke) 0 \N \N \N \N 1 \N \N \N \N diff --git a/tests/queries/0_stateless/01115_join_with_dictionary.sql b/tests/queries/0_stateless/01115_join_with_dictionary.sql index 2b38abdec0e..cde1385eaae 100644 --- a/tests/queries/0_stateless/01115_join_with_dictionary.sql +++ b/tests/queries/0_stateless/01115_join_with_dictionary.sql @@ -1,5 +1,3 @@ -SET send_logs_level = 'fatal'; - DROP TABLE IF EXISTS t1; DROP DICTIONARY IF EXISTS dict_flat; @@ -29,14 +27,15 @@ LAYOUT(COMPLEX_KEY_CACHE(SIZE_IN_CELLS 1)); SET join_use_nulls = 0; +SET join_algorithm = 'direct'; + SELECT 'flat: left on'; SELECT * FROM (SELECT number AS key FROM numbers(5)) s1 LEFT JOIN dict_flat d ON s1.key = d.key ORDER BY s1.key; SELECT 'flat: left'; SELECT * FROM (SELECT number AS key FROM numbers(5)) s1 LEFT JOIN dict_flat d USING(key) ORDER BY key; SELECT 'flat: any left'; SELECT * FROM (SELECT number AS key FROM numbers(5)) s1 ANY LEFT JOIN dict_flat d USING(key) ORDER BY key; -SELECT 'flat: any left + any_join_distinct_right_table_keys'; -- falls back to regular join -SELECT * FROM (SELECT number AS key FROM numbers(5)) s1 ANY LEFT JOIN dict_flat d USING(key) ORDER BY key SETTINGS any_join_distinct_right_table_keys = '1'; + SELECT 'flat: semi left'; SELECT * FROM (SELECT number AS key FROM numbers(5)) s1 SEMI JOIN dict_flat d USING(key) ORDER BY key; SELECT 'flat: anti left'; @@ -45,8 +44,6 @@ SELECT 'flat: inner'; SELECT * FROM (SELECT number AS key FROM numbers(2)) s1 JOIN dict_flat d USING(key); SELECT 'flat: inner on'; SELECT * FROM (SELECT number AS k FROM numbers(100)) s1 JOIN dict_flat d ON k = key ORDER BY k; -SELECT 'flat: inner or'; -- it's not a join over dictionary, because it doen't suppoert multiple keys, but of falls back to regular join -SELECT * FROM (SELECT if(number % 2 = 0, number, number * 1000) AS k FROM numbers(100)) s1 JOIN dict_flat d ON k = key OR k == 1000 * key ORDER BY key; SET join_use_nulls = 1; @@ -65,6 +62,20 @@ SELECT * FROM (SELECT number AS key FROM numbers(2)) s1 JOIN dict_hashed d USING SELECT 'hashed: inner on'; SELECT * FROM (SELECT number AS k FROM numbers(100)) s1 JOIN dict_hashed d ON k = key ORDER BY k; +SET join_use_nulls = 0; + +-- unsupported cases for dictionary join, falls back to regular join + +SET join_algorithm = 'default'; + +SELECT 'flat: inner or'; +SELECT * FROM (SELECT if(number % 2 = 0, number, number * 1000) AS k FROM numbers(100)) s1 JOIN dict_flat d ON k = key OR k == 1000 * key ORDER BY key; + +SELECT 'flat: any left + any_join_distinct_right_table_keys'; +SELECT * FROM (SELECT number AS key FROM numbers(5)) s1 ANY LEFT JOIN dict_flat d USING(key) ORDER BY key SETTINGS any_join_distinct_right_table_keys = '1'; + +SET join_use_nulls = 1; + SELECT 'complex_cache (smoke)'; SELECT * FROM (SELECT number AS key FROM numbers(5)) s1 LEFT JOIN dict_complex_cache d ON s1.key = d.key ORDER BY s1.key; From f7b130b0cbe8f7915a54df849cd5b87f70a0c6e3 Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 4 Aug 2022 15:18:09 +0000 Subject: [PATCH 525/672] Rename IKVStorage.h -> IKeyValueStorage.h --- src/Interpreters/DictionaryJoinAdapter.h | 2 +- src/Interpreters/DirectJoin.h | 2 +- src/Interpreters/HashJoin.h | 2 +- src/Interpreters/TableJoin.h | 2 +- src/Storages/{IKVStorage.h => IKeyValueStorage.h} | 0 src/Storages/RocksDB/StorageEmbeddedRocksDB.h | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename src/Storages/{IKVStorage.h => IKeyValueStorage.h} (100%) diff --git a/src/Interpreters/DictionaryJoinAdapter.h b/src/Interpreters/DictionaryJoinAdapter.h index adfa75a996b..13695a378fa 100644 --- a/src/Interpreters/DictionaryJoinAdapter.h +++ b/src/Interpreters/DictionaryJoinAdapter.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace DB { diff --git a/src/Interpreters/DirectJoin.h b/src/Interpreters/DirectJoin.h index f7da06ef826..558cce124e9 100644 --- a/src/Interpreters/DirectJoin.h +++ b/src/Interpreters/DirectJoin.h @@ -9,7 +9,7 @@ #include -#include +#include #include namespace DB diff --git a/src/Interpreters/HashJoin.h b/src/Interpreters/HashJoin.h index f12997fd1c9..df448e015c1 100644 --- a/src/Interpreters/HashJoin.h +++ b/src/Interpreters/HashJoin.h @@ -27,7 +27,7 @@ #include #include -#include +#include namespace DB { diff --git a/src/Interpreters/TableJoin.h b/src/Interpreters/TableJoin.h index d7463f1f4b5..a05faae1609 100644 --- a/src/Interpreters/TableJoin.h +++ b/src/Interpreters/TableJoin.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/Storages/IKVStorage.h b/src/Storages/IKeyValueStorage.h similarity index 100% rename from src/Storages/IKVStorage.h rename to src/Storages/IKeyValueStorage.h diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.h b/src/Storages/RocksDB/StorageEmbeddedRocksDB.h index 62c9a0eeae7..038788f1710 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.h +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include From ad91c16ba0f4cb30b21f669b5fffa08de77ba3d9 Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 4 Aug 2022 15:20:19 +0000 Subject: [PATCH 526/672] Rename join_common -> JoinUtils --- src/Interpreters/DictionaryJoinAdapter.cpp | 2 +- src/Interpreters/DirectJoin.cpp | 2 +- src/Interpreters/ExpressionAnalyzer.cpp | 2 +- src/Interpreters/ExpressionAnalyzer.h | 2 +- src/Interpreters/HashJoin.cpp | 2 +- src/Interpreters/JoinSwitcher.cpp | 2 +- src/Interpreters/{join_common.cpp => JoinUtils.cpp} | 2 +- src/Interpreters/{join_common.h => JoinUtils.h} | 0 src/Interpreters/MergeJoin.cpp | 2 +- src/Interpreters/TableJoin.h | 2 +- src/Processors/Transforms/JoiningTransform.cpp | 2 +- src/Storages/StorageJoin.cpp | 2 +- src/Storages/StorageJoin.h | 2 +- 13 files changed, 12 insertions(+), 12 deletions(-) rename src/Interpreters/{join_common.cpp => JoinUtils.cpp} (99%) rename src/Interpreters/{join_common.h => JoinUtils.h} (100%) diff --git a/src/Interpreters/DictionaryJoinAdapter.cpp b/src/Interpreters/DictionaryJoinAdapter.cpp index e3c7deb3963..fdcb7f1b46e 100644 --- a/src/Interpreters/DictionaryJoinAdapter.cpp +++ b/src/Interpreters/DictionaryJoinAdapter.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include diff --git a/src/Interpreters/DirectJoin.cpp b/src/Interpreters/DirectJoin.cpp index d47a4f7a305..cf72aa9279b 100644 --- a/src/Interpreters/DirectJoin.cpp +++ b/src/Interpreters/DirectJoin.cpp @@ -30,7 +30,7 @@ static MutableColumns convertBlockStructure( MutableColumns result_columns; for (const auto & out_sample_col : result_sample_block) { - /// Some coulumns from result_sample_block may not be in source_sample_block, + /// Some columns from result_sample_block may not be in source_sample_block, /// e.g. if they will be calculated later based on joined columns if (!source_sample_block.has(out_sample_col.name)) continue; diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index 18fcef87d3c..80f8fda252c 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -64,7 +64,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/Interpreters/ExpressionAnalyzer.h b/src/Interpreters/ExpressionAnalyzer.h index da92bc10832..6eed349cda8 100644 --- a/src/Interpreters/ExpressionAnalyzer.h +++ b/src/Interpreters/ExpressionAnalyzer.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/Interpreters/HashJoin.cpp b/src/Interpreters/HashJoin.cpp index 1b9c6e72c07..e559977be49 100644 --- a/src/Interpreters/HashJoin.cpp +++ b/src/Interpreters/HashJoin.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/Interpreters/JoinSwitcher.cpp b/src/Interpreters/JoinSwitcher.cpp index 34c8bb4cfd5..5d5a9b27825 100644 --- a/src/Interpreters/JoinSwitcher.cpp +++ b/src/Interpreters/JoinSwitcher.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include namespace DB { diff --git a/src/Interpreters/join_common.cpp b/src/Interpreters/JoinUtils.cpp similarity index 99% rename from src/Interpreters/join_common.cpp rename to src/Interpreters/JoinUtils.cpp index ba71445df29..59e2475a9b2 100644 --- a/src/Interpreters/join_common.cpp +++ b/src/Interpreters/JoinUtils.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/Interpreters/join_common.h b/src/Interpreters/JoinUtils.h similarity index 100% rename from src/Interpreters/join_common.h rename to src/Interpreters/JoinUtils.h diff --git a/src/Interpreters/MergeJoin.cpp b/src/Interpreters/MergeJoin.cpp index 655d50355f9..f1dcff70c4c 100644 --- a/src/Interpreters/MergeJoin.cpp +++ b/src/Interpreters/MergeJoin.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/Interpreters/TableJoin.h b/src/Interpreters/TableJoin.h index a05faae1609..d473a42901a 100644 --- a/src/Interpreters/TableJoin.h +++ b/src/Interpreters/TableJoin.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/Processors/Transforms/JoiningTransform.cpp b/src/Processors/Transforms/JoiningTransform.cpp index 64343946ff4..fed28a11ad5 100644 --- a/src/Processors/Transforms/JoiningTransform.cpp +++ b/src/Processors/Transforms/JoiningTransform.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include diff --git a/src/Storages/StorageJoin.cpp b/src/Storages/StorageJoin.cpp index c94fd3b2256..2e3e1d443ae 100644 --- a/src/Storages/StorageJoin.cpp +++ b/src/Storages/StorageJoin.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/Storages/StorageJoin.h b/src/Storages/StorageJoin.h index a75fe944a34..2a28ff6d01b 100644 --- a/src/Storages/StorageJoin.h +++ b/src/Storages/StorageJoin.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include namespace DB From 44c688332a5125c1f11d2a347e68c71fb7acf19c Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 4 Aug 2022 15:39:28 +0000 Subject: [PATCH 527/672] IKeyValueEntity is not inheritor of IStorage --- src/Interpreters/DictionaryJoinAdapter.cpp | 17 +++++++++-------- src/Interpreters/DictionaryJoinAdapter.h | 14 ++++++-------- src/Interpreters/DirectJoin.cpp | 4 ++-- src/Interpreters/DirectJoin.h | 6 +++--- src/Interpreters/HashJoin.h | 2 +- .../IKeyValueEntity.h} | 12 +++++++----- src/Interpreters/JoinedTables.cpp | 2 +- src/Interpreters/TableJoin.cpp | 2 +- src/Interpreters/TableJoin.h | 10 +++++----- src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp | 9 ++++++++- src/Storages/RocksDB/StorageEmbeddedRocksDB.h | 6 ++++-- 11 files changed, 47 insertions(+), 37 deletions(-) rename src/{Storages/IKeyValueStorage.h => Interpreters/IKeyValueEntity.h} (80%) diff --git a/src/Interpreters/DictionaryJoinAdapter.cpp b/src/Interpreters/DictionaryJoinAdapter.cpp index fdcb7f1b46e..d55c2f3bad5 100644 --- a/src/Interpreters/DictionaryJoinAdapter.cpp +++ b/src/Interpreters/DictionaryJoinAdapter.cpp @@ -19,7 +19,7 @@ namespace ErrorCodes DictionaryJoinAdapter::DictionaryJoinAdapter( std::shared_ptr dictionary_, const Names & result_column_names) - : IKeyValueStorage(StorageID::createEmpty()) + : IKeyValueEntity() , dictionary(dictionary_) { if (!dictionary) @@ -30,24 +30,20 @@ DictionaryJoinAdapter::DictionaryJoinAdapter( if (key_types.size() != key_names.size()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Dictionary '{}' has invalid structure", dictionary->getFullName()); - StorageInMemoryMetadata storage_metadata; - for (size_t i = 0; i < key_types.size(); ++i) { - storage_metadata.columns.add(ColumnDescription(key_names[i], key_types[i])); + sample_block.insert(ColumnWithTypeAndName(nullptr, key_types[i], key_names[i])); } for (const auto & attr_name : result_column_names) { const auto & attr = dictionary->getStructure().getAttribute(attr_name); - storage_metadata.columns.add(ColumnDescription(attr_name, attr.type)); + + sample_block.insert(ColumnWithTypeAndName(nullptr, attr.type, attr_name)); attribute_names.emplace_back(attr_name); result_types.emplace_back(attr.type); } - - /// Fill in memory metadata to make getSampleBlock work. - setInMemoryMetadata(storage_metadata); } Names DictionaryJoinAdapter::getPrimaryKey() const @@ -55,6 +51,11 @@ Names DictionaryJoinAdapter::getPrimaryKey() const return dictionary->getStructure().getKeysNames(); } +Block DictionaryJoinAdapter::getSampleBlock() const +{ + return sample_block; +} + Chunk DictionaryJoinAdapter::getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map) const { if (keys.empty()) diff --git a/src/Interpreters/DictionaryJoinAdapter.h b/src/Interpreters/DictionaryJoinAdapter.h index 13695a378fa..888c1485831 100644 --- a/src/Interpreters/DictionaryJoinAdapter.h +++ b/src/Interpreters/DictionaryJoinAdapter.h @@ -3,30 +3,28 @@ #include #include #include -#include +#include namespace DB { /// Used in join with dictionary to provide sufficient interface to DirectJoin -class DictionaryJoinAdapter : public IKeyValueStorage +class DictionaryJoinAdapter : public IKeyValueEntity { public: - DictionaryJoinAdapter( - std::shared_ptr dictionary_, const Names & result_column_names); + DictionaryJoinAdapter(std::shared_ptr dictionary_, const Names & result_column_names); Names getPrimaryKey() const override; Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map) const override; - std::string getName() const override - { - return dictionary->getFullName(); - } + Block getSampleBlock() const override; private: std::shared_ptr dictionary; + Block sample_block; + Strings attribute_names; DataTypes result_types; }; diff --git a/src/Interpreters/DirectJoin.cpp b/src/Interpreters/DirectJoin.cpp index cf72aa9279b..2bae1afd7a5 100644 --- a/src/Interpreters/DirectJoin.cpp +++ b/src/Interpreters/DirectJoin.cpp @@ -63,7 +63,7 @@ static MutableColumns convertBlockStructure( DirectKeyValueJoin::DirectKeyValueJoin(std::shared_ptr table_join_, const Block & right_sample_block_, - std::shared_ptr storage_) + std::shared_ptr storage_) : table_join(table_join_) , storage(storage_) , right_sample_block(right_sample_block_) @@ -118,7 +118,7 @@ void DirectKeyValueJoin::joinBlock(Block & block, std::shared_ptr &) /// Expected right block may differ from structure in storage, because of `join_use_nulls` or we just select not all joined attributes Block original_right_block = originalRightBlock(right_sample_block, *table_join); - Block sample_storage_block = storage->getInMemoryMetadataPtr()->getSampleBlock(); + Block sample_storage_block = storage->getSampleBlock(); MutableColumns result_columns = convertBlockStructure(sample_storage_block, original_right_block, joined_chunk.mutateColumns(), null_map); for (size_t i = 0; i < result_columns.size(); ++i) diff --git a/src/Interpreters/DirectJoin.h b/src/Interpreters/DirectJoin.h index 558cce124e9..9aeca9b3a16 100644 --- a/src/Interpreters/DirectJoin.h +++ b/src/Interpreters/DirectJoin.h @@ -9,7 +9,7 @@ #include -#include +#include #include namespace DB @@ -23,7 +23,7 @@ public: DirectKeyValueJoin( std::shared_ptr table_join_, const Block & right_sample_block_, - std::shared_ptr storage_); + std::shared_ptr storage_); virtual const TableJoin & getTableJoin() const override { return *table_join; } @@ -50,7 +50,7 @@ public: private: std::shared_ptr table_join; - std::shared_ptr storage; + std::shared_ptr storage; Block right_sample_block; Block sample_block_with_columns_to_add; Poco::Logger * log; diff --git a/src/Interpreters/HashJoin.h b/src/Interpreters/HashJoin.h index df448e015c1..33955333aa2 100644 --- a/src/Interpreters/HashJoin.h +++ b/src/Interpreters/HashJoin.h @@ -27,7 +27,7 @@ #include #include -#include +#include namespace DB { diff --git a/src/Storages/IKeyValueStorage.h b/src/Interpreters/IKeyValueEntity.h similarity index 80% rename from src/Storages/IKeyValueStorage.h rename to src/Interpreters/IKeyValueEntity.h index 667ccda0c41..c1de1a2a9c5 100644 --- a/src/Storages/IKeyValueStorage.h +++ b/src/Interpreters/IKeyValueEntity.h @@ -1,18 +1,15 @@ #pragma once #include -#include #include namespace DB { -/// Storage that support key-value requests -class IKeyValueStorage : public IStorage +/// Interface for entities with key-value semantics. +class IKeyValueEntity { public: - using IStorage::IStorage; - /// Get primary key name that supports key-value requests. /// Primary key can constist of multiple columns. virtual Names getPrimaryKey() const = 0; @@ -28,6 +25,11 @@ public: * If the key was not found row would have a default value. */ virtual Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map) const = 0; + + /// Header for getByKeys result + virtual Block getSampleBlock() const = 0; + + virtual ~IKeyValueEntity() = default; }; } diff --git a/src/Interpreters/JoinedTables.cpp b/src/Interpreters/JoinedTables.cpp index 7a6f624ae47..c365239e6d8 100644 --- a/src/Interpreters/JoinedTables.cpp +++ b/src/Interpreters/JoinedTables.cpp @@ -325,7 +325,7 @@ std::shared_ptr JoinedTables::makeTableJoin(const ASTSelectQuery & se { table_join->setRightStorageName(storage_dict->getDictionaryName()); } - else if (auto storage_kv = std::dynamic_pointer_cast(storage); + else if (auto storage_kv = std::dynamic_pointer_cast(storage); storage_kv && join_algorithm.isSet(JoinAlgorithm::DIRECT)) { table_join->setStorageJoin(storage_kv); diff --git a/src/Interpreters/TableJoin.cpp b/src/Interpreters/TableJoin.cpp index ce42af644b1..67f66fb9694 100644 --- a/src/Interpreters/TableJoin.cpp +++ b/src/Interpreters/TableJoin.cpp @@ -645,7 +645,7 @@ ActionsDAGPtr TableJoin::applyKeyConvertToTable( return dag_stage1; } -void TableJoin::setStorageJoin(std::shared_ptr storage) +void TableJoin::setStorageJoin(std::shared_ptr storage) { right_kv_storage = storage; } diff --git a/src/Interpreters/TableJoin.h b/src/Interpreters/TableJoin.h index d473a42901a..1866ad2a5fd 100644 --- a/src/Interpreters/TableJoin.h +++ b/src/Interpreters/TableJoin.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include @@ -31,7 +31,7 @@ class Block; class DictionaryJoinAdapter; class StorageJoin; class StorageDictionary; -class IKeyValueStorage; +class IKeyValueEntity; struct ColumnWithTypeAndName; using ColumnsWithTypeAndName = std::vector; @@ -140,7 +140,7 @@ private: std::shared_ptr right_storage_join; - std::shared_ptr right_kv_storage; + std::shared_ptr right_kv_storage; std::string right_storage_name; @@ -304,14 +304,14 @@ public: void setRightStorageName(const std::string & storage_name); const std::string & getRightStorageName() const; - void setStorageJoin(std::shared_ptr storage); + void setStorageJoin(std::shared_ptr storage); void setStorageJoin(std::shared_ptr storage); std::shared_ptr getStorageJoin() { return right_storage_join; } bool isSpecialStorage() const { return !right_storage_name.empty() || right_storage_join || right_kv_storage; } - std::shared_ptr getStorageKeyValue() { return right_kv_storage; } + std::shared_ptr getStorageKeyValue() { return right_kv_storage; } }; } diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp index 2774c52fe7c..90cfbf910dd 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp @@ -349,7 +349,8 @@ StorageEmbeddedRocksDB::StorageEmbeddedRocksDB(const StorageID & table_id_, bool attach, ContextPtr context_, const String & primary_key_) - : IKeyValueStorage(table_id_) + : IStorage(table_id_) + , IKeyValueEntity() , WithContext(context_->getGlobalContext()) , primary_key{primary_key_} { @@ -571,6 +572,12 @@ Chunk StorageEmbeddedRocksDB::getByKeys( return getBySerializedKeys(raw_keys, &null_map); } +Block StorageEmbeddedRocksDB::getSampleBlock() const +{ + auto metadata = getInMemoryMetadataPtr(); + return metadata ? metadata->getSampleBlock() : Block(); +} + Chunk StorageEmbeddedRocksDB::getBySerializedKeys( const std::vector & keys, PaddedPODArray * null_map) const diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.h b/src/Storages/RocksDB/StorageEmbeddedRocksDB.h index 038788f1710..da98dca8262 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.h +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include @@ -23,7 +23,7 @@ class Context; /// Operates with rocksdb data structures via rocksdb API (holds pointer to rocksdb::DB inside for that). /// Storage have one primary key. /// Values are serialized into raw strings to store in rocksdb. -class StorageEmbeddedRocksDB final : public IKeyValueStorage, WithContext +class StorageEmbeddedRocksDB final : public IStorage, public IKeyValueEntity, WithContext { friend class EmbeddedRocksDBSink; public: @@ -65,6 +65,8 @@ public: Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & null_map) const override; + Block getSampleBlock() const override; + /// Return chunk with data for given serialized keys. /// If out_null_map is passed, fill it with 1/0 depending on key was/wasn't found. Result chunk may contain default values. /// If out_null_map is not passed. Not found rows excluded from result chunk. From 90fa2ed8c1b5d889f6a37b01cfa95ddf2c1637d3 Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 8 Aug 2022 10:58:28 +0000 Subject: [PATCH 528/672] better code for join with dict --- src/Dictionaries/IDictionary.h | 11 +- src/Dictionaries/IDictionaryKeyValue.cpp | 116 ++++++++++++++++++ src/Interpreters/DictionaryJoinAdapter.cpp | 95 +------------- src/Interpreters/DictionaryJoinAdapter.h | 25 ---- src/Interpreters/DirectJoin.cpp | 10 +- src/Interpreters/DirectJoin.h | 4 +- src/Interpreters/ExpressionAnalyzer.cpp | 88 ++----------- src/Interpreters/IKeyValueEntity.h | 12 +- src/Interpreters/JoinedTables.cpp | 24 +++- src/Interpreters/TableJoin.cpp | 2 +- src/Interpreters/TableJoin.h | 6 +- .../RocksDB/StorageEmbeddedRocksDB.cpp | 5 +- src/Storages/RocksDB/StorageEmbeddedRocksDB.h | 4 +- 13 files changed, 183 insertions(+), 219 deletions(-) create mode 100644 src/Dictionaries/IDictionaryKeyValue.cpp diff --git a/src/Dictionaries/IDictionary.h b/src/Dictionaries/IDictionary.h index 3f3c60206d6..61787aa38a5 100644 --- a/src/Dictionaries/IDictionary.h +++ b/src/Dictionaries/IDictionary.h @@ -5,16 +5,18 @@ #include #include +#include #include #include +#include #include #include #include #include - namespace DB { + namespace ErrorCodes { extern const int NOT_IMPLEMENTED; @@ -52,7 +54,7 @@ enum class DictionarySpecialKeyType /** * Base class for Dictionaries implementation. */ -class IDictionary : public IExternalLoadable +class IDictionary : public IExternalLoadable, public IKeyValueEntity { public: explicit IDictionary(const StorageID & dictionary_id_) @@ -290,6 +292,11 @@ public: return dictionary_comment; } + /// IKeyValueEntity implementation + Names getPrimaryKey() const override; + Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map, const Names & result_names) const override; + Block getSampleBlock(const Names & result_names) const override; + private: mutable std::mutex mutex; mutable StorageID dictionary_id TSA_GUARDED_BY(mutex); diff --git a/src/Dictionaries/IDictionaryKeyValue.cpp b/src/Dictionaries/IDictionaryKeyValue.cpp new file mode 100644 index 00000000000..d871ff55796 --- /dev/null +++ b/src/Dictionaries/IDictionaryKeyValue.cpp @@ -0,0 +1,116 @@ +#include +#include + + +namespace DB +{ + +static void splitNamesAndTypesFromStructure(const DictionaryStructure & structure, const Names & result_names, Names & attribute_names, DataTypes & result_types) +{ + if (!result_names.empty()) + { + for (const auto & attr_name : result_names) + { + if (!structure.hasAttribute(attr_name)) + continue; /// skip keys + const auto & attr = structure.getAttribute(attr_name); + attribute_names.emplace_back(attr.name); + result_types.emplace_back(attr.type); + } + } + else + { + /// If result_names is empty, then use all attributes from structure + for (const auto & attr : structure.attributes) + { + attribute_names.emplace_back(attr.name); + result_types.emplace_back(attr.type); + } + } +} + +Names IDictionary::getPrimaryKey() const +{ + return getStructure().getKeysNames(); +} + +Chunk IDictionary::getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map, const Names & result_names) const +{ + if (keys.empty()) + return Chunk(getSampleBlock(result_names).cloneEmpty().getColumns(), 0); + + const auto & dictionary_structure = getStructure(); + + /// Split column keys and types into separate vectors, to use in `IDictionary::getColumns` + Columns key_columns; + DataTypes key_types; + for (const auto & key : keys) + { + key_columns.emplace_back(key.column); + key_types.emplace_back(key.type); + } + + /// Fill null map + { + out_null_map.clear(); + + auto mask = hasKeys(key_columns, key_types); + const auto & mask_data = mask->getData(); + + out_null_map.resize(mask_data.size(), 0); + std::copy(mask_data.begin(), mask_data.end(), out_null_map.begin()); + } + + Names attribute_names; + DataTypes result_types; + splitNamesAndTypesFromStructure(dictionary_structure, result_names, attribute_names, result_types); + + Columns default_cols(result_types.size()); + for (size_t i = 0; i < result_types.size(); ++i) + /// Dictinonary may have non-standart default values specified + default_cols[i] = result_types[i]->createColumnConstWithDefaultValue(out_null_map.size()); + + Columns result_columns = getColumns(attribute_names, result_types, key_columns, key_types, default_cols); + + /// Result block should consist of key columns and then attributes + for (const auto & key_col : key_columns) + { + /// Insert default values for keys that were not found + ColumnPtr filtered_key_col = JoinCommon::filterWithBlanks(key_col, out_null_map); + result_columns.insert(result_columns.begin(), filtered_key_col); + } + + size_t num_rows = result_columns[0]->size(); + return Chunk(std::move(result_columns), num_rows); +} + +Block IDictionary::getSampleBlock(const Names & result_names) const +{ + const auto & dictionary_structure = getStructure(); + const auto & key_types = dictionary_structure.getKeyTypes(); + const auto & key_names = dictionary_structure.getKeysNames(); + + Block sample_block; + + for (size_t i = 0; i < key_types.size(); ++i) + sample_block.insert(ColumnWithTypeAndName(nullptr, key_types.at(i), key_names.at(i))); + + if (result_names.empty()) + { + for (const auto & attr : dictionary_structure.attributes) + sample_block.insert(ColumnWithTypeAndName(nullptr, attr.type, attr.name)); + } + else + { + for (const auto & attr_name : result_names) + { + if (!dictionary_structure.hasAttribute(attr_name)) + continue; /// skip keys + const auto & attr = dictionary_structure.getAttribute(attr_name); + sample_block.insert(ColumnWithTypeAndName(nullptr, attr.type, attr_name)); + } + } + return sample_block; +} + +} diff --git a/src/Interpreters/DictionaryJoinAdapter.cpp b/src/Interpreters/DictionaryJoinAdapter.cpp index d55c2f3bad5..bf0ad373204 100644 --- a/src/Interpreters/DictionaryJoinAdapter.cpp +++ b/src/Interpreters/DictionaryJoinAdapter.cpp @@ -1,101 +1,8 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include + namespace DB { -namespace ErrorCodes -{ - extern const int LOGICAL_ERROR; -} - -DictionaryJoinAdapter::DictionaryJoinAdapter( - std::shared_ptr dictionary_, const Names & result_column_names) - : IKeyValueEntity() - , dictionary(dictionary_) -{ - if (!dictionary) - throw Exception("Dictionary is not initialized", ErrorCodes::LOGICAL_ERROR); - - const auto & key_types = dictionary->getStructure().getKeyTypes(); - const auto & key_names = dictionary->getStructure().getKeysNames(); - if (key_types.size() != key_names.size()) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Dictionary '{}' has invalid structure", dictionary->getFullName()); - - for (size_t i = 0; i < key_types.size(); ++i) - { - sample_block.insert(ColumnWithTypeAndName(nullptr, key_types[i], key_names[i])); - } - - for (const auto & attr_name : result_column_names) - { - const auto & attr = dictionary->getStructure().getAttribute(attr_name); - - sample_block.insert(ColumnWithTypeAndName(nullptr, attr.type, attr_name)); - - attribute_names.emplace_back(attr_name); - result_types.emplace_back(attr.type); - } -} - -Names DictionaryJoinAdapter::getPrimaryKey() const -{ - return dictionary->getStructure().getKeysNames(); -} - -Block DictionaryJoinAdapter::getSampleBlock() const -{ - return sample_block; -} - -Chunk DictionaryJoinAdapter::getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map) const -{ - if (keys.empty()) - return {}; - - Columns key_columns; - DataTypes key_types; - for (const auto & key : keys) - { - key_columns.emplace_back(key.column); - key_types.emplace_back(key.type); - } - - { - out_null_map.clear(); - - auto mask = dictionary->hasKeys(key_columns, key_types); - const auto & mask_data = mask->getData(); - - out_null_map.resize(mask_data.size(), 0); - std::copy(mask_data.begin(), mask_data.end(), out_null_map.begin()); - } - - Columns default_cols(result_types.size()); - for (size_t i = 0; i < result_types.size(); ++i) - /// Dictinonary may have non-standart default values specified - default_cols[i] = result_types[i]->createColumnConstWithDefaultValue(out_null_map.size()); - - /// Result block consists of key columns and then attributes - Columns result_columns = dictionary->getColumns(attribute_names, result_types, key_columns, key_types, default_cols); - - for (const auto & key_col : key_columns) - { - /// Insert default values for keys that were not found - ColumnPtr filtered_key_col = JoinCommon::filterWithBlanks(key_col, out_null_map); - result_columns.insert(result_columns.begin(), filtered_key_col); - } - - size_t num_rows = result_columns[0]->size(); - return Chunk(std::move(result_columns), num_rows); -} } diff --git a/src/Interpreters/DictionaryJoinAdapter.h b/src/Interpreters/DictionaryJoinAdapter.h index 888c1485831..dade5da94e6 100644 --- a/src/Interpreters/DictionaryJoinAdapter.h +++ b/src/Interpreters/DictionaryJoinAdapter.h @@ -1,32 +1,7 @@ #pragma once -#include -#include -#include -#include namespace DB { -/// Used in join with dictionary to provide sufficient interface to DirectJoin -class DictionaryJoinAdapter : public IKeyValueEntity -{ -public: - DictionaryJoinAdapter(std::shared_ptr dictionary_, const Names & result_column_names); - - Names getPrimaryKey() const override; - - Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map) const override; - - Block getSampleBlock() const override; - -private: - std::shared_ptr dictionary; - - Block sample_block; - - Strings attribute_names; - DataTypes result_types; -}; - } diff --git a/src/Interpreters/DirectJoin.cpp b/src/Interpreters/DirectJoin.cpp index 2bae1afd7a5..02b3854a47b 100644 --- a/src/Interpreters/DirectJoin.cpp +++ b/src/Interpreters/DirectJoin.cpp @@ -63,7 +63,7 @@ static MutableColumns convertBlockStructure( DirectKeyValueJoin::DirectKeyValueJoin(std::shared_ptr table_join_, const Block & right_sample_block_, - std::shared_ptr storage_) + std::shared_ptr storage_) : table_join(table_join_) , storage(storage_) , right_sample_block(right_sample_block_) @@ -113,12 +113,14 @@ void DirectKeyValueJoin::joinBlock(Block & block, std::shared_ptr &) if (!key_col.column) return; + Block original_right_block = originalRightBlock(right_sample_block, *table_join); + const Names & attribute_names = original_right_block.getNames(); + NullMap null_map; - Chunk joined_chunk = storage->getByKeys({key_col}, null_map); + Chunk joined_chunk = storage->getByKeys({key_col}, null_map, attribute_names); /// Expected right block may differ from structure in storage, because of `join_use_nulls` or we just select not all joined attributes - Block original_right_block = originalRightBlock(right_sample_block, *table_join); - Block sample_storage_block = storage->getSampleBlock(); + Block sample_storage_block = storage->getSampleBlock(attribute_names); MutableColumns result_columns = convertBlockStructure(sample_storage_block, original_right_block, joined_chunk.mutateColumns(), null_map); for (size_t i = 0; i < result_columns.size(); ++i) diff --git a/src/Interpreters/DirectJoin.h b/src/Interpreters/DirectJoin.h index 9aeca9b3a16..8e82b59da02 100644 --- a/src/Interpreters/DirectJoin.h +++ b/src/Interpreters/DirectJoin.h @@ -23,7 +23,7 @@ public: DirectKeyValueJoin( std::shared_ptr table_join_, const Block & right_sample_block_, - std::shared_ptr storage_); + std::shared_ptr storage_); virtual const TableJoin & getTableJoin() const override { return *table_join; } @@ -50,7 +50,7 @@ public: private: std::shared_ptr table_join; - std::shared_ptr storage; + std::shared_ptr storage; Block right_sample_block; Block sample_block_with_columns_to_add; Poco::Logger * log; diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index 80f8fda252c..dffe96d0a7b 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -1008,7 +1008,6 @@ static ActionsDAGPtr createJoinedBlockActions(ContextPtr context, const TableJoi return ExpressionAnalyzer(expression_list, syntax_result, context).getActionsDAG(true, false); } -std::shared_ptr tryDictJoin(std::shared_ptr analyzed_join, const Block & right_sample_block, ContextPtr context); std::shared_ptr tryKeyValueJoin(std::shared_ptr analyzed_join, const Block & right_sample_block); static std::shared_ptr chooseJoinAlgorithm(std::shared_ptr analyzed_join, std::unique_ptr & joined_plan, ContextPtr context) @@ -1017,10 +1016,7 @@ static std::shared_ptr chooseJoinAlgorithm(std::shared_ptr ana if (analyzed_join->isEnabledAlgorithm(JoinAlgorithm::DIRECT)) { - JoinPtr direct_join = nullptr; - direct_join = direct_join ? direct_join : tryKeyValueJoin(analyzed_join, right_sample_block); - direct_join = direct_join ? direct_join : tryDictJoin(analyzed_join, right_sample_block, context); - + JoinPtr direct_join = tryKeyValueJoin(analyzed_join, right_sample_block); if (direct_join) { /// Do not need to execute plan for right part, it's ready. @@ -1112,67 +1108,6 @@ static std::unique_ptr buildJoinedPlan( return joined_plan; } -std::shared_ptr tryDictJoin(std::shared_ptr analyzed_join, const Block & right_sample_block, ContextPtr context) -{ - bool allowed_inner = isInner(analyzed_join->kind()) && analyzed_join->strictness() == JoinStrictness::All; - bool allowed_left = isLeft(analyzed_join->kind()) && (analyzed_join->strictness() == JoinStrictness::Any || - analyzed_join->strictness() == JoinStrictness::All || - analyzed_join->strictness() == JoinStrictness::Semi || - analyzed_join->strictness() == JoinStrictness::Anti); - if (!allowed_inner && !allowed_left) - { - LOG_TRACE(getLogger(), "Can't use dictionary join: {} {} is not supported", - analyzed_join->kind(), analyzed_join->strictness()); - return nullptr; - } - - if (analyzed_join->getClauses().size() != 1 || analyzed_join->getClauses()[0].key_names_right.size() != 1) - { - LOG_TRACE(getLogger(), "Can't use dictionary join: only one key is supported"); - return nullptr; - } - - const auto & right_key = analyzed_join->getOnlyClause().key_names_right[0]; - - const auto & dictionary_name = analyzed_join->getRightStorageName(); - if (dictionary_name.empty()) - { - LOG_TRACE(getLogger(), "Can't use dictionary join: dictionary was not found"); - return nullptr; - } - - FunctionDictHelper dictionary_helper(context); - - auto dictionary = dictionary_helper.getDictionary(dictionary_name); - if (!dictionary) - { - LOG_TRACE(getLogger(), "Can't use dictionary join: dictionary was not found"); - return nullptr; - } - - const auto & dict_keys = dictionary->getStructure().getKeysNames(); - if (dict_keys.size() != 1 || dict_keys[0] != analyzed_join->getOriginalName(right_key)) - { - LOG_TRACE(getLogger(), "Can't use dictionary join: join key '{}' doesn't natch to dictionary key ({})", - right_key, fmt::join(dict_keys, ", ")); - return nullptr; - } - - Names attr_names; - for (const auto & col : right_sample_block) - { - if (col.name == right_key) - continue; - - const auto & original_name = analyzed_join->getOriginalName(col.name); - if (dictionary->getStructure().hasAttribute(original_name)) - attr_names.push_back(original_name); - } - - auto dict_reader = std::make_shared(dictionary, attr_names); - return std::make_shared(analyzed_join, right_sample_block, dict_reader); -} - std::shared_ptr tryKeyValueJoin(std::shared_ptr analyzed_join, const Block & right_sample_block) { if (!analyzed_join->isEnabledAlgorithm(JoinAlgorithm::DIRECT)) @@ -1180,19 +1115,17 @@ std::shared_ptr tryKeyValueJoin(std::shared_ptr a auto storage = analyzed_join->getStorageKeyValue(); if (!storage) - { return nullptr; - } - if (!isInnerOrLeft(analyzed_join->kind())) - { - return nullptr; - } - - if (analyzed_join->strictness() != JoinStrictness::All && - analyzed_join->strictness() != JoinStrictness::Any && - analyzed_join->strictness() != JoinStrictness::RightAny) + bool allowed_inner = isInner(analyzed_join->kind()) && analyzed_join->strictness() == JoinStrictness::All; + bool allowed_left = isLeft(analyzed_join->kind()) && (analyzed_join->strictness() == JoinStrictness::Any || + analyzed_join->strictness() == JoinStrictness::All || + analyzed_join->strictness() == JoinStrictness::Semi || + analyzed_join->strictness() == JoinStrictness::Anti); + if (!allowed_inner && !allowed_left) { + LOG_TRACE(getLogger(), "Can't use direct join: {} {} is not supported", + analyzed_join->kind(), analyzed_join->strictness()); return nullptr; } @@ -1205,6 +1138,7 @@ std::shared_ptr tryKeyValueJoin(std::shared_ptr a if (!only_one_key) { + LOG_TRACE(getLogger(), "Can't use direct join: only one key is supported"); return nullptr; } @@ -1213,6 +1147,8 @@ std::shared_ptr tryKeyValueJoin(std::shared_ptr a const auto & storage_primary_key = storage->getPrimaryKey(); if (storage_primary_key.size() != 1 || storage_primary_key[0] != original_key_name) { + LOG_TRACE(getLogger(), "Can't use direct join: join key '{}' doesn't match to storage key ({})", + original_key_name, fmt::join(storage_primary_key, ", ")); return nullptr; } diff --git a/src/Interpreters/IKeyValueEntity.h b/src/Interpreters/IKeyValueEntity.h index c1de1a2a9c5..d1ceda57f0e 100644 --- a/src/Interpreters/IKeyValueEntity.h +++ b/src/Interpreters/IKeyValueEntity.h @@ -10,6 +10,9 @@ namespace DB class IKeyValueEntity { public: + IKeyValueEntity() = default; + virtual ~IKeyValueEntity() = default; + /// Get primary key name that supports key-value requests. /// Primary key can constist of multiple columns. virtual Names getPrimaryKey() const = 0; @@ -19,17 +22,20 @@ public: * * @param keys - keys to get data for. Key can be compound and represented by several columns. * @param out_null_map - output parameter indicating which keys were not found. + * @param required_columns - if we don't need all attributes, implementation can use it to benefit from reading a subset of them. * * @return - chunk of data corresponding for keys. * Number of rows in chunk is equal to size of columns in keys. * If the key was not found row would have a default value. */ - virtual Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map) const = 0; + virtual Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map, const Names & required_columns) const = 0; /// Header for getByKeys result - virtual Block getSampleBlock() const = 0; + virtual Block getSampleBlock(const Names & required_columns) const = 0; - virtual ~IKeyValueEntity() = default; +protected: + /// Names of result columns. If empty then all columns are required. + Names key_value_result_names; }; } diff --git a/src/Interpreters/JoinedTables.cpp b/src/Interpreters/JoinedTables.cpp index c365239e6d8..f4a98ada199 100644 --- a/src/Interpreters/JoinedTables.cpp +++ b/src/Interpreters/JoinedTables.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -320,13 +321,26 @@ std::shared_ptr JoinedTables::makeTableJoin(const ASTSelectQuery & se { table_join->setStorageJoin(storage_join); } - else if (auto storage_dict = std::dynamic_pointer_cast(storage); - storage_dict && join_algorithm.isSet(JoinAlgorithm::DIRECT)) + + if (auto storage_dict = std::dynamic_pointer_cast(storage); + storage_dict && join_algorithm.isSet(JoinAlgorithm::DIRECT)) { - table_join->setRightStorageName(storage_dict->getDictionaryName()); + FunctionDictHelper dictionary_helper(context); + + auto dictionary_name = storage_dict->getDictionaryName(); + auto dictionary = dictionary_helper.getDictionary(dictionary_name); + if (!dictionary) + { + LOG_TRACE(&Poco::Logger::get("JoinedTables"), "Can't use dictionary join: dictionary '{}' was not found", dictionary_name); + return nullptr; + } + + auto dictionary_kv = std::dynamic_pointer_cast(dictionary); + table_join->setStorageJoin(dictionary_kv); } - else if (auto storage_kv = std::dynamic_pointer_cast(storage); - storage_kv && join_algorithm.isSet(JoinAlgorithm::DIRECT)) + + if (auto storage_kv = std::dynamic_pointer_cast(storage); + storage_kv && join_algorithm.isSet(JoinAlgorithm::DIRECT)) { table_join->setStorageJoin(storage_kv); } diff --git a/src/Interpreters/TableJoin.cpp b/src/Interpreters/TableJoin.cpp index 67f66fb9694..5d065e564b2 100644 --- a/src/Interpreters/TableJoin.cpp +++ b/src/Interpreters/TableJoin.cpp @@ -645,7 +645,7 @@ ActionsDAGPtr TableJoin::applyKeyConvertToTable( return dag_stage1; } -void TableJoin::setStorageJoin(std::shared_ptr storage) +void TableJoin::setStorageJoin(std::shared_ptr storage) { right_kv_storage = storage; } diff --git a/src/Interpreters/TableJoin.h b/src/Interpreters/TableJoin.h index 1866ad2a5fd..d0bf64fdebe 100644 --- a/src/Interpreters/TableJoin.h +++ b/src/Interpreters/TableJoin.h @@ -140,7 +140,7 @@ private: std::shared_ptr right_storage_join; - std::shared_ptr right_kv_storage; + std::shared_ptr right_kv_storage; std::string right_storage_name; @@ -304,14 +304,14 @@ public: void setRightStorageName(const std::string & storage_name); const std::string & getRightStorageName() const; - void setStorageJoin(std::shared_ptr storage); + void setStorageJoin(std::shared_ptr storage); void setStorageJoin(std::shared_ptr storage); std::shared_ptr getStorageJoin() { return right_storage_join; } bool isSpecialStorage() const { return !right_storage_name.empty() || right_storage_join || right_kv_storage; } - std::shared_ptr getStorageKeyValue() { return right_kv_storage; } + std::shared_ptr getStorageKeyValue() { return right_kv_storage; } }; } diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp index 90cfbf910dd..2d82986eb25 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp @@ -559,7 +559,8 @@ std::vector StorageEmbeddedRocksDB::multiGet(const std::vector< Chunk StorageEmbeddedRocksDB::getByKeys( const ColumnsWithTypeAndName & keys, - PaddedPODArray & null_map) const + PaddedPODArray & null_map, + const Names &) const { if (keys.size() != 1) throw Exception(ErrorCodes::LOGICAL_ERROR, "StorageEmbeddedRocksDB supports only one key, got: {}", keys.size()); @@ -572,7 +573,7 @@ Chunk StorageEmbeddedRocksDB::getByKeys( return getBySerializedKeys(raw_keys, &null_map); } -Block StorageEmbeddedRocksDB::getSampleBlock() const +Block StorageEmbeddedRocksDB::getSampleBlock(const Names &) const { auto metadata = getInMemoryMetadataPtr(); return metadata ? metadata->getSampleBlock() : Block(); diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.h b/src/Storages/RocksDB/StorageEmbeddedRocksDB.h index da98dca8262..55770516b3f 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.h +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.h @@ -63,9 +63,9 @@ public: std::vector multiGet(const std::vector & slices_keys, std::vector & values) const; Names getPrimaryKey() const override { return {primary_key}; } - Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & null_map) const override; + Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & null_map, const Names &) const override; - Block getSampleBlock() const override; + Block getSampleBlock(const Names &) const override; /// Return chunk with data for given serialized keys. /// If out_null_map is passed, fill it with 1/0 depending on key was/wasn't found. Result chunk may contain default values. From e2929cdea5acdcdb16c962848076e6b4a67e2079 Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 8 Aug 2022 11:40:09 +0000 Subject: [PATCH 529/672] Add semi and anti to 02242_join_rocksdb.sql --- .../queries/0_stateless/02242_join_rocksdb.reference | 12 ++++++++++++ tests/queries/0_stateless/02242_join_rocksdb.sql | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/tests/queries/0_stateless/02242_join_rocksdb.reference b/tests/queries/0_stateless/02242_join_rocksdb.reference index b1f7307ff4f..5f2ac6c11b7 100644 --- a/tests/queries/0_stateless/02242_join_rocksdb.reference +++ b/tests/queries/0_stateless/02242_join_rocksdb.reference @@ -14,6 +14,18 @@ 7 [7,8] val27 8 [8,9] val28 9 [9,10] val29 +-- left semi +0 +2 +3 +6 +7 +8 +9 +-- left anti +1 +4 +5 -- join_use_nulls left 0 0 Nullable(String) val20 1 \N Nullable(String) \N diff --git a/tests/queries/0_stateless/02242_join_rocksdb.sql b/tests/queries/0_stateless/02242_join_rocksdb.sql index 34b3d120eae..9d4cdb7af78 100644 --- a/tests/queries/0_stateless/02242_join_rocksdb.sql +++ b/tests/queries/0_stateless/02242_join_rocksdb.sql @@ -26,6 +26,12 @@ SELECT * FROM (SELECT k as key FROM t2) as t2 INNER JOIN rdb ON rdb.key == t2.ke SELECT '-- using'; SELECT * FROM (SELECT k as key FROM t2) as t2 INNER JOIN rdb USING key ORDER BY key; +SELECT '-- left semi'; +SELECT k FROM t2 LEFT SEMI JOIN rdb ON rdb.key == t2.k ORDER BY k; + +SELECT '-- left anti'; +SELECT k FROM t2 LEFT ANTI JOIN rdb ON rdb.key == t2.k ORDER BY k; + SELECT '-- join_use_nulls left'; SELECT k, key, toTypeName(value2), value2 FROM t2 LEFT JOIN rdb ON rdb.key == t2.k ORDER BY k SETTINGS join_use_nulls = 1; @@ -59,6 +65,9 @@ SELECT * FROM t1 INNER JOIN rdb ON rdb.key + 1 == t1.k FORMAT Null SETTINGS join SELECT * FROM t1 INNER JOIN (SELECT * FROM rdb) AS rdb ON rdb.key == t1.k; -- { serverError NOT_IMPLEMENTED } SELECT * FROM t1 INNER JOIN (SELECT * FROM rdb) AS rdb ON rdb.key == t1.k FORMAT Null SETTINGS join_algorithm = 'direct,hash'; +SELECT * FROM t1 RIGHT SEMI JOIN (SELECT * FROM rdb) AS rdb ON rdb.key == t1.k; -- { serverError NOT_IMPLEMENTED } +SELECT * FROM t1 RIGHT ANTI JOIN (SELECT * FROM rdb) AS rdb ON rdb.key == t1.k; -- { serverError NOT_IMPLEMENTED } + DROP TABLE IF EXISTS rdb; DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; From 32c63f43a113a5c2b12c573faf91f3e59988cfd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Wed, 10 Aug 2022 17:54:56 +0200 Subject: [PATCH 530/672] Don't visit the AST for UDFs if none are registered --- src/Interpreters/TreeRewriter.cpp | 44 ++++++++++--------- .../UserDefinedSQLFunctionFactory.cpp | 5 +++ .../UserDefinedSQLFunctionFactory.h | 3 ++ 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/Interpreters/TreeRewriter.cpp b/src/Interpreters/TreeRewriter.cpp index 39f3453c7c4..9248e8eecb6 100644 --- a/src/Interpreters/TreeRewriter.cpp +++ b/src/Interpreters/TreeRewriter.cpp @@ -6,29 +6,30 @@ #include #include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include +#include +#include #include /// getSmallestColumn() -#include -#include -#include -#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -1405,8 +1406,11 @@ TreeRewriterResultPtr TreeRewriter::analyze( void TreeRewriter::normalize( ASTPtr & query, Aliases & aliases, const NameSet & source_columns_set, bool ignore_alias, const Settings & settings, bool allow_self_aliases, ContextPtr context_) { - UserDefinedSQLFunctionVisitor::Data data_user_defined_functions_visitor; - UserDefinedSQLFunctionVisitor(data_user_defined_functions_visitor).visit(query); + if (!UserDefinedSQLFunctionFactory::instance().empty()) + { + UserDefinedSQLFunctionVisitor::Data data_user_defined_functions_visitor; + UserDefinedSQLFunctionVisitor(data_user_defined_functions_visitor).visit(query); + } CustomizeCountDistinctVisitor::Data data_count_distinct{settings.count_distinct_implementation}; CustomizeCountDistinctVisitor(data_count_distinct).visit(query); diff --git a/src/Interpreters/UserDefinedSQLFunctionFactory.cpp b/src/Interpreters/UserDefinedSQLFunctionFactory.cpp index 698faac5fab..db11ee12b03 100644 --- a/src/Interpreters/UserDefinedSQLFunctionFactory.cpp +++ b/src/Interpreters/UserDefinedSQLFunctionFactory.cpp @@ -160,4 +160,9 @@ std::vector UserDefinedSQLFunctionFactory::getAllRegisteredNames() return registered_names; } +bool UserDefinedSQLFunctionFactory::empty() const +{ + std::lock_guard lock(mutex); + return function_name_to_create_query.size() == 0; +} } diff --git a/src/Interpreters/UserDefinedSQLFunctionFactory.h b/src/Interpreters/UserDefinedSQLFunctionFactory.h index 63bf5d73c15..db43bb7298e 100644 --- a/src/Interpreters/UserDefinedSQLFunctionFactory.h +++ b/src/Interpreters/UserDefinedSQLFunctionFactory.h @@ -43,6 +43,9 @@ public: /// Get all user defined functions registered names. std::vector getAllRegisteredNames() const override; + /// Check whether any UDFs have been registered + bool empty() const; + private: std::unordered_map function_name_to_create_query; mutable std::mutex mutex; From 5fea2091ac227044b828df7ce2608cd8e2b066ad Mon Sep 17 00:00:00 2001 From: vdimir Date: Wed, 10 Aug 2022 14:45:22 +0000 Subject: [PATCH 531/672] Embed IKeyValue impl into IDictionary.h --- src/Dictionaries/IDictionary.h | 103 +++++++++++++++- src/Dictionaries/IDictionaryKeyValue.cpp | 116 ------------------ .../RocksDB/StorageEmbeddedRocksDB.cpp | 1 - 3 files changed, 100 insertions(+), 120 deletions(-) delete mode 100644 src/Dictionaries/IDictionaryKeyValue.cpp diff --git a/src/Dictionaries/IDictionary.h b/src/Dictionaries/IDictionary.h index 61787aa38a5..d82f89b7473 100644 --- a/src/Dictionaries/IDictionary.h +++ b/src/Dictionaries/IDictionary.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -293,9 +294,105 @@ public: } /// IKeyValueEntity implementation - Names getPrimaryKey() const override; - Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map, const Names & result_names) const override; - Block getSampleBlock(const Names & result_names) const override; + Names getPrimaryKey() const override { return getStructure().getKeysNames(); } + + Chunk getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map, const Names & result_names) const override + { + if (keys.empty()) + return Chunk(getSampleBlock(result_names).cloneEmpty().getColumns(), 0); + + const auto & dictionary_structure = getStructure(); + + /// Split column keys and types into separate vectors, to use in `IDictionary::getColumns` + Columns key_columns; + DataTypes key_types; + for (const auto & key : keys) + { + key_columns.emplace_back(key.column); + key_types.emplace_back(key.type); + } + + /// Fill null map + { + out_null_map.clear(); + + auto mask = hasKeys(key_columns, key_types); + const auto & mask_data = mask->getData(); + + out_null_map.resize(mask_data.size(), 0); + std::copy(mask_data.begin(), mask_data.end(), out_null_map.begin()); + } + + Names attribute_names; + DataTypes result_types; + if (!result_names.empty()) + { + for (const auto & attr_name : result_names) + { + if (!dictionary_structure.hasAttribute(attr_name)) + continue; /// skip keys + const auto & attr = dictionary_structure.getAttribute(attr_name); + attribute_names.emplace_back(attr.name); + result_types.emplace_back(attr.type); + } + } + else + { + /// If result_names is empty, then use all attributes from dictionary_structure + for (const auto & attr : dictionary_structure.attributes) + { + attribute_names.emplace_back(attr.name); + result_types.emplace_back(attr.type); + } + } + + Columns default_cols(result_types.size()); + for (size_t i = 0; i < result_types.size(); ++i) + /// Dictinonary may have non-standart default values specified + default_cols[i] = result_types[i]->createColumnConstWithDefaultValue(out_null_map.size()); + + Columns result_columns = getColumns(attribute_names, result_types, key_columns, key_types, default_cols); + + /// Result block should consist of key columns and then attributes + for (const auto & key_col : key_columns) + { + /// Insert default values for keys that were not found + ColumnPtr filtered_key_col = JoinCommon::filterWithBlanks(key_col, out_null_map); + result_columns.insert(result_columns.begin(), filtered_key_col); + } + + size_t num_rows = result_columns[0]->size(); + return Chunk(std::move(result_columns), num_rows); + } + + Block getSampleBlock(const Names & result_names) const override + { + const auto & dictionary_structure = getStructure(); + const auto & key_types = dictionary_structure.getKeyTypes(); + const auto & key_names = dictionary_structure.getKeysNames(); + + Block sample_block; + + for (size_t i = 0; i < key_types.size(); ++i) + sample_block.insert(ColumnWithTypeAndName(nullptr, key_types.at(i), key_names.at(i))); + + if (result_names.empty()) + { + for (const auto & attr : dictionary_structure.attributes) + sample_block.insert(ColumnWithTypeAndName(nullptr, attr.type, attr.name)); + } + else + { + for (const auto & attr_name : result_names) + { + if (!dictionary_structure.hasAttribute(attr_name)) + continue; /// skip keys + const auto & attr = dictionary_structure.getAttribute(attr_name); + sample_block.insert(ColumnWithTypeAndName(nullptr, attr.type, attr_name)); + } + } + return sample_block; + } private: mutable std::mutex mutex; diff --git a/src/Dictionaries/IDictionaryKeyValue.cpp b/src/Dictionaries/IDictionaryKeyValue.cpp deleted file mode 100644 index d871ff55796..00000000000 --- a/src/Dictionaries/IDictionaryKeyValue.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include -#include - - -namespace DB -{ - -static void splitNamesAndTypesFromStructure(const DictionaryStructure & structure, const Names & result_names, Names & attribute_names, DataTypes & result_types) -{ - if (!result_names.empty()) - { - for (const auto & attr_name : result_names) - { - if (!structure.hasAttribute(attr_name)) - continue; /// skip keys - const auto & attr = structure.getAttribute(attr_name); - attribute_names.emplace_back(attr.name); - result_types.emplace_back(attr.type); - } - } - else - { - /// If result_names is empty, then use all attributes from structure - for (const auto & attr : structure.attributes) - { - attribute_names.emplace_back(attr.name); - result_types.emplace_back(attr.type); - } - } -} - -Names IDictionary::getPrimaryKey() const -{ - return getStructure().getKeysNames(); -} - -Chunk IDictionary::getByKeys(const ColumnsWithTypeAndName & keys, PaddedPODArray & out_null_map, const Names & result_names) const -{ - if (keys.empty()) - return Chunk(getSampleBlock(result_names).cloneEmpty().getColumns(), 0); - - const auto & dictionary_structure = getStructure(); - - /// Split column keys and types into separate vectors, to use in `IDictionary::getColumns` - Columns key_columns; - DataTypes key_types; - for (const auto & key : keys) - { - key_columns.emplace_back(key.column); - key_types.emplace_back(key.type); - } - - /// Fill null map - { - out_null_map.clear(); - - auto mask = hasKeys(key_columns, key_types); - const auto & mask_data = mask->getData(); - - out_null_map.resize(mask_data.size(), 0); - std::copy(mask_data.begin(), mask_data.end(), out_null_map.begin()); - } - - Names attribute_names; - DataTypes result_types; - splitNamesAndTypesFromStructure(dictionary_structure, result_names, attribute_names, result_types); - - Columns default_cols(result_types.size()); - for (size_t i = 0; i < result_types.size(); ++i) - /// Dictinonary may have non-standart default values specified - default_cols[i] = result_types[i]->createColumnConstWithDefaultValue(out_null_map.size()); - - Columns result_columns = getColumns(attribute_names, result_types, key_columns, key_types, default_cols); - - /// Result block should consist of key columns and then attributes - for (const auto & key_col : key_columns) - { - /// Insert default values for keys that were not found - ColumnPtr filtered_key_col = JoinCommon::filterWithBlanks(key_col, out_null_map); - result_columns.insert(result_columns.begin(), filtered_key_col); - } - - size_t num_rows = result_columns[0]->size(); - return Chunk(std::move(result_columns), num_rows); -} - -Block IDictionary::getSampleBlock(const Names & result_names) const -{ - const auto & dictionary_structure = getStructure(); - const auto & key_types = dictionary_structure.getKeyTypes(); - const auto & key_names = dictionary_structure.getKeysNames(); - - Block sample_block; - - for (size_t i = 0; i < key_types.size(); ++i) - sample_block.insert(ColumnWithTypeAndName(nullptr, key_types.at(i), key_names.at(i))); - - if (result_names.empty()) - { - for (const auto & attr : dictionary_structure.attributes) - sample_block.insert(ColumnWithTypeAndName(nullptr, attr.type, attr.name)); - } - else - { - for (const auto & attr_name : result_names) - { - if (!dictionary_structure.hasAttribute(attr_name)) - continue; /// skip keys - const auto & attr = dictionary_structure.getAttribute(attr_name); - sample_block.insert(ColumnWithTypeAndName(nullptr, attr.type, attr_name)); - } - } - return sample_block; -} - -} diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp index 2d82986eb25..3c277abb693 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp @@ -350,7 +350,6 @@ StorageEmbeddedRocksDB::StorageEmbeddedRocksDB(const StorageID & table_id_, ContextPtr context_, const String & primary_key_) : IStorage(table_id_) - , IKeyValueEntity() , WithContext(context_->getGlobalContext()) , primary_key{primary_key_} { From 7f54fa726b8d6f2207bad8bdde4effed980e8eaa Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 14 Jun 2022 19:08:27 +0800 Subject: [PATCH 532/672] Decoupling cache functions and algorithms --- src/Common/FileSegment.h | 2 +- src/Common/IFileCachePriority.h | 92 ++++++++++++ src/Common/LRUFileCache.cpp | 169 ++++++++++++---------- src/Common/tests/gtest_lru_file_cache.cpp | 8 +- 4 files changed, 186 insertions(+), 85 deletions(-) create mode 100644 src/Common/IFileCachePriority.h diff --git a/src/Common/FileSegment.h b/src/Common/FileSegment.h index 93cbf269a8e..4404d0e14be 100644 --- a/src/Common/FileSegment.h +++ b/src/Common/FileSegment.h @@ -27,7 +27,7 @@ using FileSegments = std::list; class FileSegment : boost::noncopyable { -friend class LRUFileCache; +friend class FileCache; friend struct FileSegmentsHolder; friend class FileSegmentRangeWriter; diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h new file mode 100644 index 00000000000..35b82d61228 --- /dev/null +++ b/src/Common/IFileCachePriority.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include + +namespace DB +{ + +class IFileCachePriority; +using FileCachePriorityPtr = std::shared_ptr; + +/// IFileCachePriority is used to maintain the priority of cached data. +class IFileCachePriority +{ +public: + using Key = UInt128; + + class IIterator; + friend class IIterator; + using Iterator = std::shared_ptr; + + struct FileCacheRecord + { + Key key; + size_t offset; + size_t size; + size_t hits = 0; + + FileCacheRecord(const Key & key_, size_t offset_, size_t size_) : key(key_), offset(offset_), size(size_) { } + }; + + /// It provides an iterator to traverse the cache priority. Under normal circumstances, + /// the iterator can only return the records that have been directly swapped out. + /// For example, in the LRU algorithm, it can traverse all records, but in the LRU-K, it + /// can only traverse the records in the low priority queue. + class IIterator + { + public: + virtual ~IIterator() = default; + + virtual void next() = 0; + + virtual bool vaild() const = 0; + + /// Mark a cache record as recently used, it will update the priority + /// of the cache record according to different cache algorithms. + virtual void use(std::lock_guard & cache_lock) = 0; + + /// Deletes an existing cached record. + virtual void remove(std::lock_guard & cache_lock) = 0; + + virtual Key & key() const = 0; + + virtual size_t offset() const = 0; + + virtual size_t size() const = 0; + + virtual size_t hits() const = 0; + + virtual Iterator getSnapshot() = 0; + + virtual void incrementSize(size_t size_increment, std::lock_guard & cache_lock) = 0; + }; + +public: + virtual ~IFileCachePriority() = default; + + /// Add a cache record that did not exist before, and throw a + /// logical exception if the cache block already exists. + virtual Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) = 0; + + /// Query whether a cache record exists. If it exists, return true. If not, return false. + virtual bool contains(const Key & key, size_t offset, std::lock_guard & cache_lock) = 0; + + virtual void removeAll(std::lock_guard & cache_lock) = 0; + + /// Returns an iterator pointing to the lowest priority cached record. + /// We can traverse all cached records through the iterator's next(). + virtual Iterator getNewIterator(std::lock_guard & cache_lock) = 0; + + virtual size_t getElementsNum(std::lock_guard & cache_lock) const = 0; + + size_t getCacheSize(std::lock_guard &) const { return cache_size; } + + virtual std::string toString(std::lock_guard & cache_lock) const = 0; + +protected: + size_t max_cache_size = 0; + size_t cache_size = 0; +}; +}; diff --git a/src/Common/LRUFileCache.cpp b/src/Common/LRUFileCache.cpp index 6306b6de059..817208c6c30 100644 --- a/src/Common/LRUFileCache.cpp +++ b/src/Common/LRUFileCache.cpp @@ -24,6 +24,8 @@ namespace ErrorCodes LRUFileCache::LRUFileCache(const String & cache_base_path_, const FileCacheSettings & cache_settings_) : IFileCache(cache_base_path_, cache_settings_) + , main_priority(std::make_shared()) + , stash_priority(std::make_shared()) , max_stash_element_size(cache_settings_.max_elements) , enable_cache_hits_threshold(cache_settings_.enable_cache_hits_threshold) , log(&Poco::Logger::get("LRUFileCache")) @@ -31,7 +33,7 @@ LRUFileCache::LRUFileCache(const String & cache_base_path_, const FileCacheSetti { } -void LRUFileCache::initialize() +void FileCache::initialize() { std::lock_guard cache_lock(mutex); if (!is_initialized) @@ -55,7 +57,7 @@ void LRUFileCache::initialize() } } -void LRUFileCache::useCell( +void FileCache::useCell( const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock) { auto file_segment = cell.file_segment; @@ -75,11 +77,11 @@ void LRUFileCache::useCell( if (cell.queue_iterator) { /// Move to the end of the queue. The iterator remains valid. - queue.moveToEnd(*cell.queue_iterator, cache_lock); + cell.queue_iterator->use(cache_lock); } } -LRUFileCache::FileSegmentCell * LRUFileCache::getCell( +FileCache::FileSegmentCell * FileCache::getCell( const Key & key, size_t offset, std::lock_guard & /* cache_lock */) { auto it = files.find(key); @@ -94,7 +96,7 @@ LRUFileCache::FileSegmentCell * LRUFileCache::getCell( return &cell_it->second; } -FileSegments LRUFileCache::getImpl( +FileSegments FileCache::getImpl( const Key & key, const FileSegment::Range & range, std::lock_guard & cache_lock) { /// Given range = [left, right] and non-overlapping ordered set of file segments, @@ -145,12 +147,8 @@ FileSegments LRUFileCache::getImpl( if (range.left <= prev_cell_range.right) { - /// segment{k-1} segment{k} /// [________] [_____ /// [___________ - /// ^ - /// range.left - useCell(prev_cell, result, cache_lock); } } @@ -204,7 +202,7 @@ FileSegments LRUFileCache::splitRangeIntoCells( return file_segments; } -void LRUFileCache::fillHolesWithEmptyFileSegments( +void FileCache::fillHolesWithEmptyFileSegments( FileSegments & file_segments, const Key & key, const FileSegment::Range & range, @@ -326,7 +324,7 @@ FileSegmentsHolder LRUFileCache::getOrSet(const Key & key, size_t offset, size_t return FileSegmentsHolder(std::move(file_segments)); } -FileSegmentsHolder LRUFileCache::get(const Key & key, size_t offset, size_t size) +FileSegmentsHolder FileCache::get(const Key & key, size_t offset, size_t size) { assertInitialized(); @@ -379,20 +377,19 @@ LRUFileCache::FileSegmentCell * LRUFileCache::addCell( FileSegment::State result_state = state; if (state == FileSegment::State::EMPTY && enable_cache_hits_threshold) { - auto record = records.find({key, offset}); + auto record = stash_records.find({key, offset}); - if (record == records.end()) + if (record == stash_records.end()) { - auto queue_iter = stash_queue.add(key, offset, 0, cache_lock); - records.insert({{key, offset}, queue_iter}); + auto priority_iter = stash_priority->add(key, offset, 0, cache_lock); + stash_records.insert({{key, offset}, priority_iter}); - if (stash_queue.getElementsNum(cache_lock) > max_stash_element_size) + if (stash_priority->getElementsNum(cache_lock) > max_stash_element_size) { - auto remove_queue_iter = stash_queue.begin(); - records.erase({remove_queue_iter->key, remove_queue_iter->offset}); - stash_queue.remove(remove_queue_iter, cache_lock); + auto remove_priority_iter = stash_priority->getNewIterator(cache_lock); + stash_records.erase({remove_priority_iter->key(), remove_priority_iter->offset()}); + remove_priority_iter->remove(cache_lock); } - /// For segments that do not reach the download threshold, we do not download them, but directly read them result_state = FileSegment::State::SKIP_CACHE; } @@ -452,7 +449,7 @@ FileSegmentsHolder LRUFileCache::setDownloading( return FileSegmentsHolder(std::move(file_segments)); } -bool LRUFileCache::tryReserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) +bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) { auto query_context = enable_filesystem_query_cache_limit ? getCurrentQueryContext(cache_lock) : nullptr; if (!query_context) @@ -473,40 +470,40 @@ bool LRUFileCache::tryReserve(const Key & key, size_t offset, size_t size, std:: else { size_t removed_size = 0; - size_t queue_size = queue.getElementsNum(cache_lock); + size_t queue_size = main_priority->getElementsNum(cache_lock); auto * cell_for_reserve = getCell(key, offset, cache_lock); - std::vector ghost; + std::vector ghost; std::vector trash; std::vector to_evict; auto is_overflow = [&] { - return (max_size != 0 && queue.getTotalCacheSize(cache_lock) + size - removed_size > max_size) + return (max_size != 0 && main_priority->getCacheSize(cache_lock) + size - removed_size > max_size) || (max_element_size != 0 && queue_size > max_element_size) || (query_context->getCacheSize() + size - removed_size > query_context->getMaxCacheSize()); }; /// Select the cache from the LRU queue held by query for expulsion. - for (auto iter = query_context->queue().begin(); iter != query_context->queue().end(); iter++) + for (auto iter = query_context->getPriority()->getNewIterator(cache_lock); iter->vaild(); iter->next()) { if (!is_overflow()) break; - auto * cell = getCell(iter->key, iter->offset, cache_lock); + auto * cell = getCell(iter->key(), iter->offset(), cache_lock); if (!cell) { /// The cache corresponding to this record may be swapped out by /// other queries, so it has become invalid. - ghost.push_back(iter); - removed_size += iter->size; + ghost.push_back(iter->getSnapshot()); + removed_size += iter->size(); } else { size_t cell_size = cell->size(); - assert(iter->size == cell_size); + assert(iter->size() == cell_size); if (cell->releasable()) { @@ -548,7 +545,7 @@ bool LRUFileCache::tryReserve(const Key & key, size_t offset, size_t size, std:: } for (auto & iter : ghost) - query_context->remove(iter->key, iter->offset, iter->size, cache_lock); + query_context->remove(iter->key(), iter->offset(), iter->size(), cache_lock); if (is_overflow()) return false; @@ -557,9 +554,9 @@ bool LRUFileCache::tryReserve(const Key & key, size_t offset, size_t size, std:: { auto queue_iterator = cell_for_reserve->queue_iterator; if (queue_iterator) - queue.incrementSize(*queue_iterator, size, cache_lock); + queue_iterator->incrementSize(size, cache_lock); else - cell_for_reserve->queue_iterator = queue.add(key, offset, size, cache_lock); + cell_for_reserve->queue_iterator = main_priority->add(key, offset, size, cache_lock); } for (auto & cell : to_evict) @@ -573,11 +570,11 @@ bool LRUFileCache::tryReserve(const Key & key, size_t offset, size_t size, std:: } } -bool LRUFileCache::tryReserveForMainList( +bool FileCache::tryReserveForMainList( const Key & key, size_t offset, size_t size, QueryContextPtr query_context, std::lock_guard & cache_lock) { auto removed_size = 0; - size_t queue_size = queue.getElementsNum(cache_lock); + size_t queue_size = main_priority->getElementsNum(cache_lock); assert(queue_size <= max_element_size); /// Since space reservation is incremental, cache cell already exists if it's state is EMPTY. @@ -592,15 +589,18 @@ bool LRUFileCache::tryReserveForMainList( auto is_overflow = [&] { /// max_size == 0 means unlimited cache size, max_element_size means unlimited number of cache elements. - return (max_size != 0 && queue.getTotalCacheSize(cache_lock) + size - removed_size > max_size) + return (max_size != 0 && main_priority->getCacheSize(cache_lock) + size - removed_size > max_size) || (max_element_size != 0 && queue_size > max_element_size); }; std::vector to_evict; std::vector trash; - for (const auto & [entry_key, entry_offset, entry_size, _] : queue) + for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) { + auto entry_key = it->key(); + auto entry_offset = it->offset(); + if (!is_overflow()) break; @@ -612,7 +612,7 @@ bool LRUFileCache::tryReserveForMainList( key.toString(), offset); size_t cell_size = cell->size(); - assert(entry_size == cell_size); + assert(it->size() == cell_size); /// It is guaranteed that cell is not removed from cache as long as /// pointer to corresponding file segment is hold by any other thread. @@ -671,9 +671,9 @@ bool LRUFileCache::tryReserveForMainList( /// If queue iterator already exists, we need to update the size after each space reservation. auto queue_iterator = cell_for_reserve->queue_iterator; if (queue_iterator) - queue.incrementSize(*queue_iterator, size, cache_lock); + queue_iterator->incrementSize(size, cache_lock); else - cell_for_reserve->queue_iterator = queue.add(key, offset, size, cache_lock); + cell_for_reserve->queue_iterator = main_priority->add(key, offset, size, cache_lock); } for (auto & cell : to_evict) @@ -682,7 +682,7 @@ bool LRUFileCache::tryReserveForMainList( remove_file_segment(file_segment); } - if (queue.getTotalCacheSize(cache_lock) > (1ull << 63)) + if (main_priority->getCacheSize(cache_lock) > (1ull << 63)) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cache became inconsistent. There must be a bug"); if (query_context) @@ -751,10 +751,12 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) std::lock_guard cache_lock(mutex); - std::vector to_remove; - for (auto it = queue.begin(); it != queue.end();) + std::vector to_remove; + for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) { - const auto & [key, offset, size, _] = *it++; + auto key = it->key(); + auto offset = it->offset(); + auto * cell = getCell(key, offset, cache_lock); if (!cell) throw Exception( @@ -776,6 +778,13 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) } } + for (auto & file_segment : to_remove) + { + std::lock_guard segment_lock(file_segment->mutex); + file_segment->detach(cache_lock, segment_lock); + remove(file_segment->key(), file_segment->offset(), cache_lock, segment_lock); + } + /// Remove all access information. records.clear(); stash_queue.removeAll(cache_lock); @@ -785,7 +794,7 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) #endif } -void LRUFileCache::remove( +void FileCache::remove( Key key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) { @@ -799,7 +808,7 @@ void LRUFileCache::remove( if (cell->queue_iterator) { - queue.remove(*cell->queue_iterator, cache_lock); + cell->queue_iterator->remove(cache_lock); } auto & offsets = files[key]; @@ -831,12 +840,12 @@ void LRUFileCache::remove( } } -void LRUFileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_lock) +void FileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_lock) { Key key; UInt64 offset = 0; size_t size = 0; - std::vector>> queue_entries; + std::vector>> queue_entries; /// cache_base_path / key_prefix / key / offset @@ -888,7 +897,7 @@ void LRUFileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_l { auto * cell = addCell(key, offset, size, FileSegment::State::DOWNLOADED, is_persistent, cache_lock); if (cell) - queue_entries.emplace_back(*cell->queue_iterator, cell->file_segment); + queue_entries.emplace_back(cell->queue_iterator, cell->file_segment); } else { @@ -912,14 +921,14 @@ void LRUFileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_l if (file_segment.expired()) continue; - queue.moveToEnd(it, cache_lock); + it->use(cache_lock); } #ifndef NDEBUG assertCacheCorrectness(cache_lock); #endif } -void LRUFileCache::reduceSizeToDownloaded( +void FileCache::reduceSizeToDownloaded( const Key & key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) { @@ -952,7 +961,7 @@ void LRUFileCache::reduceSizeToDownloaded( cell->file_segment = std::make_shared(offset, downloaded_size, key, this, FileSegment::State::DOWNLOADED); } -bool LRUFileCache::isLastFileSegmentHolder( +bool FileCache::isLastFileSegmentHolder( const Key & key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) { @@ -965,7 +974,7 @@ bool LRUFileCache::isLastFileSegmentHolder( return cell->file_segment.use_count() == 2; } -FileSegments LRUFileCache::getSnapshot() const +FileSegments FileCache::getSnapshot() const { std::lock_guard cache_lock(mutex); @@ -979,7 +988,7 @@ FileSegments LRUFileCache::getSnapshot() const return file_segments; } -std::vector LRUFileCache::tryGetCachePaths(const Key & key) +std::vector FileCache::tryGetCachePaths(const Key & key) { std::lock_guard cache_lock(mutex); @@ -996,42 +1005,42 @@ std::vector LRUFileCache::tryGetCachePaths(const Key & key) return cache_paths; } -size_t LRUFileCache::getUsedCacheSize() const +size_t FileCache::getUsedCacheSize() const { std::lock_guard cache_lock(mutex); return getUsedCacheSizeUnlocked(cache_lock); } -size_t LRUFileCache::getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const +size_t FileCache::getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const { - return queue.getTotalCacheSize(cache_lock); + return main_priority->getCacheSize(cache_lock); } -size_t LRUFileCache::getAvailableCacheSize() const +size_t FileCache::getAvailableCacheSize() const { std::lock_guard cache_lock(mutex); return getAvailableCacheSizeUnlocked(cache_lock); } -size_t LRUFileCache::getAvailableCacheSizeUnlocked(std::lock_guard & cache_lock) const +size_t FileCache::getAvailableCacheSizeUnlocked(std::lock_guard & cache_lock) const { return max_size - getUsedCacheSizeUnlocked(cache_lock); } -size_t LRUFileCache::getFileSegmentsNum() const +size_t FileCache::getFileSegmentsNum() const { std::lock_guard cache_lock(mutex); return getFileSegmentsNumUnlocked(cache_lock); } -size_t LRUFileCache::getFileSegmentsNumUnlocked(std::lock_guard & cache_lock) const +size_t FileCache::getFileSegmentsNumUnlocked(std::lock_guard & cache_lock) const { - return queue.getElementsNum(cache_lock); + return main_priority->getElementsNum(cache_lock); } -LRUFileCache::FileSegmentCell::FileSegmentCell( +FileCache::FileSegmentCell::FileSegmentCell( FileSegmentPtr file_segment_, - LRUFileCache * cache, + FileCache * cache, std::lock_guard & cache_lock) : file_segment(file_segment_) { @@ -1045,7 +1054,7 @@ LRUFileCache::FileSegmentCell::FileSegmentCell( { case FileSegment::State::DOWNLOADED: { - queue_iterator = cache->queue.add(file_segment->key(), file_segment->offset(), file_segment->range().size(), cache_lock); + queue_iterator = cache->main_priority->add(file_segment->key(), file_segment->offset(), file_segment->range().size(), cache_lock); break; } case FileSegment::State::SKIP_CACHE: @@ -1133,7 +1142,7 @@ String LRUFileCache::dumpStructure(const Key & key) return dumpStructureUnlocked(key, cache_lock); } -String LRUFileCache::dumpStructureUnlocked(const Key & key, std::lock_guard & cache_lock) +String FileCache::dumpStructureUnlocked(const Key & key, std::lock_guard & cache_lock) { WriteBufferFromOwnString result; const auto & cells_by_offset = files[key]; @@ -1141,11 +1150,11 @@ String LRUFileCache::dumpStructureUnlocked(const Key & key, std::lock_guardgetInfoForLog() << "\n"; - result << "\n\nQueue: " << queue.toString(cache_lock); + result << "\n\nPriority: " << main_priority->toString(cache_lock); return result.str(); } -void LRUFileCache::assertCacheCellsCorrectness( +void FileCache::assertCacheCellsCorrectness( const FileSegmentsByOffset & cells_by_offset, [[maybe_unused]] std::lock_guard & cache_lock) { for (const auto & [_, cell] : cells_by_offset) @@ -1156,30 +1165,32 @@ void LRUFileCache::assertCacheCellsCorrectness( if (file_segment->reserved_size != 0) { assert(cell.queue_iterator); - assert(queue.contains(file_segment->key(), file_segment->offset(), cache_lock)); + assert(priority.contains(file_segment->key(), file_segment->offset(), cache_lock)); } } } -void LRUFileCache::assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock) +void FileCache::assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock) { assertCacheCellsCorrectness(files[key], cache_lock); - assertQueueCorrectness(cache_lock); + assertPriorityCorrectness(cache_lock); } -void LRUFileCache::assertCacheCorrectness(std::lock_guard & cache_lock) +void FileCache::assertCacheCorrectness(std::lock_guard & cache_lock) { for (const auto & [key, cells_by_offset] : files) assertCacheCellsCorrectness(files[key], cache_lock); - assertQueueCorrectness(cache_lock); + assertPriorityCorrectness(cache_lock); } -void LRUFileCache::assertQueueCorrectness(std::lock_guard & cache_lock) +void FileCache::assertPriorityCorrectness(std::lock_guard & cache_lock) { [[maybe_unused]] size_t total_size = 0; - for (auto it = queue.begin(); it != queue.end();) + for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) { - auto & [key, offset, size, _] = *it++; + auto key = it->key(); + auto offset = it->offset(); + auto size = it->size(); auto * cell = getCell(key, offset, cache_lock); if (!cell) @@ -1188,14 +1199,12 @@ void LRUFileCache::assertQueueCorrectness(std::lock_guard & cache_lo ErrorCodes::LOGICAL_ERROR, "Cache is in inconsistent state: LRU queue contains entries with no cache cell (assertCorrectness())"); } - assert(cell->size() == size); total_size += size; } - - assert(total_size == queue.getTotalCacheSize(cache_lock)); - assert(queue.getTotalCacheSize(cache_lock) <= max_size); - assert(queue.getElementsNum(cache_lock) <= max_element_size); + assert(total_size == main_priority->getCacheSize(cache_lock)); + assert(main_priority->getCacheSize(cache_lock) <= max_size); + assert(main_priority->getElementsNum(cache_lock) <= max_element_size); } } diff --git a/src/Common/tests/gtest_lru_file_cache.cpp b/src/Common/tests/gtest_lru_file_cache.cpp index 2f268e217df..8e7554f0418 100644 --- a/src/Common/tests/gtest_lru_file_cache.cpp +++ b/src/Common/tests/gtest_lru_file_cache.cpp @@ -85,7 +85,7 @@ void complete(const DB::FileSegmentsHolder & holder) } -TEST(LRUFileCache, get) +TEST(FileCache, get) { if (fs::exists(cache_base_path)) fs::remove_all(cache_base_path); @@ -103,7 +103,7 @@ TEST(LRUFileCache, get) DB::FileCacheSettings settings; settings.max_size = 30; settings.max_elements = 5; - auto cache = DB::LRUFileCache(cache_base_path, settings); + auto cache = DB::FileCache(cache_base_path, settings); cache.initialize(); auto key = cache.hash("key1"); @@ -479,7 +479,7 @@ TEST(LRUFileCache, get) { /// Test LRUCache::restore(). - auto cache2 = DB::LRUFileCache(cache_base_path, settings); + auto cache2 = DB::FileCache(cache_base_path, settings); cache2.initialize(); auto holder1 = cache2.getOrSet(key, 2, 28, false); /// Get [2, 29] @@ -499,7 +499,7 @@ TEST(LRUFileCache, get) auto settings2 = settings; settings2.max_file_segment_size = 10; - auto cache2 = DB::LRUFileCache(caches_dir / "cache2", settings2); + auto cache2 = DB::FileCache(caches_dir / "cache2", settings2); cache2.initialize(); auto holder1 = cache2.getOrSet(key, 0, 25, false); /// Get [0, 24] From ffaf44c1c1838fda438c4da374d2427e6576b0ff Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 14 Jun 2022 20:32:30 +0800 Subject: [PATCH 533/672] fix style --- src/Common/FileCache.h | 390 ++++++++++++++++++++++++++++++++ src/Common/IFileCachePriority.h | 2 +- src/Common/LRUFileCache.cpp | 8 +- 3 files changed, 395 insertions(+), 5 deletions(-) create mode 100644 src/Common/FileCache.h diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h new file mode 100644 index 00000000000..13bca0e2dae --- /dev/null +++ b/src/Common/FileCache.h @@ -0,0 +1,390 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FileCache_fwd.h" +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +class IFileCache; +using FileCachePtr = std::shared_ptr; + +/** + * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. + */ +class IFileCache : private boost::noncopyable +{ +friend class FileSegment; +friend struct FileSegmentsHolder; +friend class FileSegmentRangeWriter; + +public: + using Key = UInt128; + using Downloader = std::unique_ptr; + + IFileCache( + const String & cache_base_path_, + const FileCacheSettings & cache_settings_); + + virtual ~IFileCache() = default; + + /// Restore cache from local filesystem. + virtual void initialize() = 0; + + virtual void remove(const Key & key) = 0; + + virtual void remove() = 0; + + static bool isReadOnly(); + + /// Cache capacity in bytes. + size_t capacity() const { return max_size; } + + static Key hash(const String & path); + + String getPathInLocalCache(const Key & key, size_t offset); + + String getPathInLocalCache(const Key & key); + + const String & getBasePath() const { return cache_base_path; } + + virtual std::vector tryGetCachePaths(const Key & key) = 0; + + /** + * Given an `offset` and `size` representing [offset, offset + size) bytes interval, + * return list of cached non-overlapping non-empty + * file segments `[segment1, ..., segmentN]` which intersect with given interval. + * + * Segments in returned list are ordered in ascending order and represent a full contiguous + * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. + * + * As long as pointers to returned file segments are hold + * it is guaranteed that these file segments are not removed from cache. + */ + virtual FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size) = 0; + + /** + * Segments in returned list are ordered in ascending order and represent a full contiguous + * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. + * + * If file segment has state EMPTY, then it is also marked as "detached". E.g. it is "detached" + * from cache (not owned by cache), and as a result will never change it's state and will be destructed + * with the destruction of the holder, while in getOrSet() EMPTY file segments can eventually change + * it's state (and become DOWNLOADED). + */ + virtual FileSegmentsHolder get(const Key & key, size_t offset, size_t size) = 0; + + virtual FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size) = 0; + + virtual FileSegments getSnapshot() const = 0; + + /// For debug. + virtual String dumpStructure(const Key & key) = 0; + + virtual size_t getUsedCacheSize() const = 0; + + virtual size_t getFileSegmentsNum() const = 0; + +protected: + String cache_base_path; + size_t max_size; + size_t max_element_size; + size_t max_file_segment_size; + + bool is_initialized = false; + + mutable std::mutex mutex; + + virtual bool tryReserve( + const Key & key, size_t offset, size_t size, + std::lock_guard & cache_lock) = 0; + + virtual void remove( + Key key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) = 0; + + virtual bool isLastFileSegmentHolder( + const Key & key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) = 0; + + /// If file segment was partially downloaded and then space reservation fails (because of no + /// space left), then update corresponding cache cell metadata (file segment size). + virtual void reduceSizeToDownloaded( + const Key & key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) = 0; + + void assertInitialized() const; + +protected: + using KeyAndOffset = std::pair; + + struct KeyAndOffsetHash + { + std::size_t operator()(const KeyAndOffset & key) const + { + return std::hash()(key.first) ^ std::hash()(key.second); + } + }; + + using FileCacheRecords = std::unordered_map; + + /// Used to track and control the cache access of each query. + /// Through it, we can realize the processing of different queries by the cache layer. + struct QueryContext + { + FileCacheRecords records; + FileCachePriorityPtr priority; + + size_t cache_size = 0; + size_t max_cache_size; + + bool skip_download_if_exceeds_query_cache; + + QueryContext(size_t max_cache_size_, bool skip_download_if_exceeds_query_cache_) + : priority(std::make_shared()) + , max_cache_size(max_cache_size_) + , skip_download_if_exceeds_query_cache(skip_download_if_exceeds_query_cache_) {} + + void remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) + { + if (cache_size < size) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Deleted cache size exceeds existing cache size"); + + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record != records.end()) + { + record->second->remove(cache_lock); + records.erase({key, offset}); + } + } + cache_size -= size; + } + + void reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) + { + if (cache_size + size > max_cache_size) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Reserved cache size exceeds the remaining cache size"); + + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record == records.end()) + { + auto queue_iter = priority->add(key, offset, 0, cache_lock); + record = records.insert({{key, offset}, queue_iter}).first; + } + record->second->incrementSize(size, cache_lock); + } + cache_size += size; + } + + void use(const Key & key, size_t offset, std::lock_guard & cache_lock) + { + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record != records.end()) + record->second->use(cache_lock); + } + } + + size_t getMaxCacheSize() { return max_cache_size; } + + size_t getCacheSize() { return cache_size; } + + FileCachePriorityPtr getPriority() { return priority; } + + bool isSkipDownloadIfExceed() { return skip_download_if_exceeds_query_cache; } + }; + + using QueryContextPtr = std::shared_ptr; + using QueryContextMap = std::unordered_map; + + QueryContextMap query_map; + + bool enable_filesystem_query_cache_limit; + + QueryContextPtr getCurrentQueryContext(std::lock_guard & cache_lock); + + QueryContextPtr getQueryContext(const String & query_id, std::lock_guard & cache_lock); + + void removeQueryContext(const String & query_id); + + QueryContextPtr getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard &); + +public: + /// Save a query context information, and adopt different cache policies + /// for different queries through the context cache layer. + struct QueryContextHolder : private boost::noncopyable + { + explicit QueryContextHolder(const String & query_id_, IFileCache * cache_, QueryContextPtr context_); + + QueryContextHolder() = default; + + ~QueryContextHolder(); + + String query_id {}; + IFileCache * cache = nullptr; + QueryContextPtr context = nullptr; + }; + + QueryContextHolder getQueryContextHolder(const String & query_id, const ReadSettings & settings); +}; + +class FileCache final : public IFileCache +{ +public: + FileCache( + const String & cache_base_path_, + const FileCacheSettings & cache_settings_); + + FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size) override; + + FileSegmentsHolder get(const Key & key, size_t offset, size_t size) override; + + FileSegments getSnapshot() const override; + + void initialize() override; + + void remove(const Key & key) override; + + void remove() override; + + std::vector tryGetCachePaths(const Key & key) override; + + size_t getUsedCacheSize() const override; + + size_t getFileSegmentsNum() const override; + +private: + struct FileSegmentCell : private boost::noncopyable + { + FileSegmentPtr file_segment; + + /// Iterator is put here on first reservation attempt, if successful. + IFileCachePriority::Iterator queue_iterator; + + /// Pointer to file segment is always hold by the cache itself. + /// Apart from pointer in cache, it can be hold by cache users, when they call + /// getorSet(), but cache users always hold it via FileSegmentsHolder. + bool releasable() const { return file_segment.unique(); } + + size_t size() const { return file_segment->reserved_size; } + + FileSegmentCell(FileSegmentPtr file_segment_, FileCache * cache, std::lock_guard & cache_lock); + + FileSegmentCell(FileSegmentCell && other) noexcept + : file_segment(std::move(other.file_segment)) + , queue_iterator(other.queue_iterator) {} + }; + + using FileSegmentsByOffset = std::map; + using CachedFiles = std::unordered_map; + + CachedFiles files; + FileCachePriorityPtr main_priority; + + FileCacheRecords stash_records; + FileCachePriorityPtr stash_priority; + + size_t max_stash_element_size; + size_t enable_cache_hits_threshold; + + Poco::Logger * log; + + FileSegments getImpl( + const Key & key, const FileSegment::Range & range, + std::lock_guard & cache_lock); + + FileSegmentCell * getCell( + const Key & key, size_t offset, std::lock_guard & cache_lock); + + FileSegmentCell * addCell( + const Key & key, size_t offset, size_t size, + FileSegment::State state, std::lock_guard & cache_lock); + + void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); + + bool tryReserve( + const Key & key, size_t offset, size_t size, + std::lock_guard & cache_lock) override; + + bool tryReserveForMainList( + const Key & key, size_t offset, size_t size, + QueryContextPtr query_context, + std::lock_guard & cache_lock); + + void remove( + Key key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) override; + + bool isLastFileSegmentHolder( + const Key & key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) override; + + void reduceSizeToDownloaded( + const Key & key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) override; + + size_t getAvailableCacheSize() const; + + void loadCacheInfoIntoMemory(std::lock_guard & cache_lock); + + FileSegments splitRangeIntoCells( + const Key & key, size_t offset, size_t size, FileSegment::State state, std::lock_guard & cache_lock); + + String dumpStructureUnlocked(const Key & key_, std::lock_guard & cache_lock); + + void fillHolesWithEmptyFileSegments( + FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, std::lock_guard & cache_lock); + + FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size) override; + + size_t getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const; + + size_t getAvailableCacheSizeUnlocked(std::lock_guard & cache_lock) const; + + size_t getFileSegmentsNumUnlocked(std::lock_guard & cache_lock) const; + + void assertCacheCellsCorrectness(const FileSegmentsByOffset & cells_by_offset, std::lock_guard & cache_lock); + +public: + String dumpStructure(const Key & key_) override; + + void assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock); + + void assertCacheCorrectness(std::lock_guard & cache_lock); + + void assertPriorityCorrectness(std::lock_guard & cache_lock); +}; + +} diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 35b82d61228..a5186bbeea8 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -41,7 +41,7 @@ public: virtual void next() = 0; - virtual bool vaild() const = 0; + virtual bool valid() const = 0; /// Mark a cache record as recently used, it will update the priority /// of the cache record according to different cache algorithms. diff --git a/src/Common/LRUFileCache.cpp b/src/Common/LRUFileCache.cpp index 817208c6c30..54b07a81afe 100644 --- a/src/Common/LRUFileCache.cpp +++ b/src/Common/LRUFileCache.cpp @@ -486,7 +486,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc }; /// Select the cache from the LRU queue held by query for expulsion. - for (auto iter = query_context->getPriority()->getNewIterator(cache_lock); iter->vaild(); iter->next()) + for (auto iter = query_context->getPriority()->getNewIterator(cache_lock); iter->valid(); iter->next()) { if (!is_overflow()) break; @@ -596,7 +596,7 @@ bool FileCache::tryReserveForMainList( std::vector to_evict; std::vector trash; - for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) + for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) { auto entry_key = it->key(); auto entry_offset = it->offset(); @@ -752,7 +752,7 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) std::lock_guard cache_lock(mutex); std::vector to_remove; - for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) + for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) { auto key = it->key(); auto offset = it->offset(); @@ -1186,7 +1186,7 @@ void FileCache::assertCacheCorrectness(std::lock_guard & cache_lock) void FileCache::assertPriorityCorrectness(std::lock_guard & cache_lock) { [[maybe_unused]] size_t total_size = 0; - for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) + for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) { auto key = it->key(); auto offset = it->offset(); From 43cf7716574c5d4c99a16433d18143928a480d25 Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 14 Jun 2022 21:17:52 +0800 Subject: [PATCH 534/672] better --- src/Common/IFileCachePriority.h | 30 ++++++++++++++++++++++-------- src/Common/LRUFileCache.cpp | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index a5186bbeea8..677ccd76934 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -7,6 +7,11 @@ namespace DB { +namespace ErrorCodes +{ + extern const int NOT_IMPLEMENTED; +} + class IFileCachePriority; using FileCachePriorityPtr = std::shared_ptr; @@ -39,16 +44,29 @@ public: public: virtual ~IIterator() = default; - virtual void next() = 0; + virtual void next() { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support next() for IIterator."); } - virtual bool valid() const = 0; + virtual bool valid() const { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support valid() for IIterator."); } /// Mark a cache record as recently used, it will update the priority /// of the cache record according to different cache algorithms. - virtual void use(std::lock_guard & cache_lock) = 0; + virtual void use(std::lock_guard &) + { + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support use() for IIterator."); + } /// Deletes an existing cached record. - virtual void remove(std::lock_guard & cache_lock) = 0; + virtual void remove(std::lock_guard &) + { + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support remove() for IIterator."); + } + + virtual Iterator getSnapshot() { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support getSnapshot() for IIterator."); } + + virtual void incrementSize(size_t, std::lock_guard &) + { + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support incrementSize() for IIterator."); + } virtual Key & key() const = 0; @@ -57,10 +75,6 @@ public: virtual size_t size() const = 0; virtual size_t hits() const = 0; - - virtual Iterator getSnapshot() = 0; - - virtual void incrementSize(size_t size_increment, std::lock_guard & cache_lock) = 0; }; public: diff --git a/src/Common/LRUFileCache.cpp b/src/Common/LRUFileCache.cpp index 54b07a81afe..1a9924ba332 100644 --- a/src/Common/LRUFileCache.cpp +++ b/src/Common/LRUFileCache.cpp @@ -1165,7 +1165,7 @@ void FileCache::assertCacheCellsCorrectness( if (file_segment->reserved_size != 0) { assert(cell.queue_iterator); - assert(priority.contains(file_segment->key(), file_segment->offset(), cache_lock)); + assert(main_priority->contains(file_segment->key(), file_segment->offset(), cache_lock)); } } } From c5f90225103b521b8c2dafb68c50813657d6c65f Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Sun, 26 Jun 2022 03:05:54 +0800 Subject: [PATCH 535/672] fix --- .../{LRUFileCache.cpp => FileCache.cpp} | 99 ++----- src/Common/FileCache.h | 267 ++---------------- src/Common/FileCacheFactory.cpp | 4 +- src/Common/IFileCache.cpp | 8 +- src/Common/IFileCache.h | 63 +---- src/Common/IFileCachePriority.h | 14 +- src/Common/LRUFileCache.h | 202 +++++-------- src/Common/tests/gtest_lru_file_cache.cpp | 2 +- 8 files changed, 131 insertions(+), 528 deletions(-) rename src/Common/{LRUFileCache.cpp => FileCache.cpp} (92%) diff --git a/src/Common/LRUFileCache.cpp b/src/Common/FileCache.cpp similarity index 92% rename from src/Common/LRUFileCache.cpp rename to src/Common/FileCache.cpp index 1a9924ba332..56eb4c9e081 100644 --- a/src/Common/LRUFileCache.cpp +++ b/src/Common/FileCache.cpp @@ -1,4 +1,4 @@ -#include "LRUFileCache.h" +#include "FileCache.h" #include #include @@ -11,6 +11,7 @@ #include #include #include +#include namespace fs = std::filesystem; @@ -22,13 +23,13 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -LRUFileCache::LRUFileCache(const String & cache_base_path_, const FileCacheSettings & cache_settings_) +FileCache::FileCache(const String & cache_base_path_, const FileCacheSettings & cache_settings_) : IFileCache(cache_base_path_, cache_settings_) , main_priority(std::make_shared()) , stash_priority(std::make_shared()) , max_stash_element_size(cache_settings_.max_elements) , enable_cache_hits_threshold(cache_settings_.enable_cache_hits_threshold) - , log(&Poco::Logger::get("LRUFileCache")) + , log(&Poco::Logger::get("FileCache")) , allow_to_remove_persistent_segments_from_cache_by_default(cache_settings_.allow_to_remove_persistent_segments_from_cache_by_default) { } @@ -173,7 +174,7 @@ FileSegments FileCache::getImpl( return result; } -FileSegments LRUFileCache::splitRangeIntoCells( +FileSegments FileCache::splitRangeIntoCells( const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock) { assert(size > 0); @@ -296,7 +297,7 @@ void FileCache::fillHolesWithEmptyFileSegments( } } -FileSegmentsHolder LRUFileCache::getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) +FileSegmentsHolder FileCache::getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) { assertInitialized(); @@ -356,7 +357,7 @@ FileSegmentsHolder FileCache::get(const Key & key, size_t offset, size_t size) return FileSegmentsHolder(std::move(file_segments)); } -LRUFileCache::FileSegmentCell * LRUFileCache::addCell( +FileCache::FileSegmentCell * FileCache::addCell( const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock) @@ -395,11 +396,9 @@ LRUFileCache::FileSegmentCell * LRUFileCache::addCell( } else { - auto queue_iter = record->second; - queue_iter->hits++; - stash_queue.moveToEnd(queue_iter, cache_lock); - - result_state = queue_iter->hits >= enable_cache_hits_threshold ? FileSegment::State::EMPTY : FileSegment::State::SKIP_CACHE; + auto priority_iter = record->second; + priority_iter->use(cache_lock); + result_state = priority_iter->hits() >= enable_cache_hits_threshold ? FileSegment::State::EMPTY : FileSegment::State::SKIP_CACHE; } } @@ -426,7 +425,7 @@ LRUFileCache::FileSegmentCell * LRUFileCache::addCell( return &(it->second); } -FileSegmentsHolder LRUFileCache::setDownloading( +FileSegmentsHolder FileCache::setDownloading( const Key & key, size_t offset, size_t size, @@ -691,7 +690,7 @@ bool FileCache::tryReserveForMainList( return true; } -void LRUFileCache::removeIfExists(const Key & key) +void FileCache::removeIfExists(const Key & key) { assertInitialized(); @@ -742,7 +741,7 @@ void LRUFileCache::removeIfExists(const Key & key) } } -void LRUFileCache::removeIfReleasable(bool remove_persistent_files) +void FileCache::removeIfReleasable(bool remove_persistent_files) { /// Try remove all cached files by cache_base_path. /// Only releasable file segments are evicted. @@ -786,8 +785,8 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) } /// Remove all access information. - records.clear(); - stash_queue.removeAll(cache_lock); + stash_records.clear(); + stash_priority->removeAll(cache_lock); #ifndef NDEBUG assertCacheCorrectness(cache_lock); @@ -1070,73 +1069,7 @@ FileCache::FileSegmentCell::FileSegmentCell( } } -IFileCache::LRUQueue::Iterator IFileCache::LRUQueue::add( - const IFileCache::Key & key, size_t offset, size_t size, std::lock_guard & /* cache_lock */) -{ -#ifndef NDEBUG - for (const auto & [entry_key, entry_offset, entry_size, entry_hits] : queue) - { - if (entry_key == key && entry_offset == offset) - throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Attempt to add duplicate queue entry to queue. (Key: {}, offset: {}, size: {})", - key.toString(), offset, size); - } -#endif - - cache_size += size; - return queue.insert(queue.end(), FileKeyAndOffset(key, offset, size)); -} - -void IFileCache::LRUQueue::remove(Iterator queue_it, std::lock_guard & /* cache_lock */) -{ - cache_size -= queue_it->size; - queue.erase(queue_it); -} - -void IFileCache::LRUQueue::removeAll(std::lock_guard & /* cache_lock */) -{ - queue.clear(); - cache_size = 0; -} - -void IFileCache::LRUQueue::moveToEnd(Iterator queue_it, std::lock_guard & /* cache_lock */) -{ - queue.splice(queue.end(), queue, queue_it); -} - -void IFileCache::LRUQueue::incrementSize(Iterator queue_it, size_t size_increment, std::lock_guard & /* cache_lock */) -{ - cache_size += size_increment; - queue_it->size += size_increment; -} - -bool IFileCache::LRUQueue::contains( - const IFileCache::Key & key, size_t offset, std::lock_guard & /* cache_lock */) const -{ - /// This method is used for assertions in debug mode. - /// So we do not care about complexity here. - for (const auto & [entry_key, entry_offset, size, _] : queue) - { - if (key == entry_key && offset == entry_offset) - return true; - } - return false; -} - -String IFileCache::LRUQueue::toString(std::lock_guard & /* cache_lock */) const -{ - String result; - for (const auto & [key, offset, size, _] : queue) - { - if (!result.empty()) - result += ", "; - result += fmt::format("{}: [{}, {}]", key.toString(), offset, offset + size - 1); - } - return result; -} - -String LRUFileCache::dumpStructure(const Key & key) +String FileCache::dumpStructure(const Key & key) { std::lock_guard cache_lock(mutex); return dumpStructureUnlocked(key, cache_lock); diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index 13bca0e2dae..5aa32ac94ca 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -11,252 +11,18 @@ #include #include -#include "FileCache_fwd.h" -#include #include #include -#include -#include -#include +#include + namespace DB { -namespace ErrorCodes -{ - extern const int LOGICAL_ERROR; -} - -class IFileCache; -using FileCachePtr = std::shared_ptr; - /** * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. + * Implements LRU eviction policy. */ -class IFileCache : private boost::noncopyable -{ -friend class FileSegment; -friend struct FileSegmentsHolder; -friend class FileSegmentRangeWriter; - -public: - using Key = UInt128; - using Downloader = std::unique_ptr; - - IFileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_); - - virtual ~IFileCache() = default; - - /// Restore cache from local filesystem. - virtual void initialize() = 0; - - virtual void remove(const Key & key) = 0; - - virtual void remove() = 0; - - static bool isReadOnly(); - - /// Cache capacity in bytes. - size_t capacity() const { return max_size; } - - static Key hash(const String & path); - - String getPathInLocalCache(const Key & key, size_t offset); - - String getPathInLocalCache(const Key & key); - - const String & getBasePath() const { return cache_base_path; } - - virtual std::vector tryGetCachePaths(const Key & key) = 0; - - /** - * Given an `offset` and `size` representing [offset, offset + size) bytes interval, - * return list of cached non-overlapping non-empty - * file segments `[segment1, ..., segmentN]` which intersect with given interval. - * - * Segments in returned list are ordered in ascending order and represent a full contiguous - * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. - * - * As long as pointers to returned file segments are hold - * it is guaranteed that these file segments are not removed from cache. - */ - virtual FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size) = 0; - - /** - * Segments in returned list are ordered in ascending order and represent a full contiguous - * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. - * - * If file segment has state EMPTY, then it is also marked as "detached". E.g. it is "detached" - * from cache (not owned by cache), and as a result will never change it's state and will be destructed - * with the destruction of the holder, while in getOrSet() EMPTY file segments can eventually change - * it's state (and become DOWNLOADED). - */ - virtual FileSegmentsHolder get(const Key & key, size_t offset, size_t size) = 0; - - virtual FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size) = 0; - - virtual FileSegments getSnapshot() const = 0; - - /// For debug. - virtual String dumpStructure(const Key & key) = 0; - - virtual size_t getUsedCacheSize() const = 0; - - virtual size_t getFileSegmentsNum() const = 0; - -protected: - String cache_base_path; - size_t max_size; - size_t max_element_size; - size_t max_file_segment_size; - - bool is_initialized = false; - - mutable std::mutex mutex; - - virtual bool tryReserve( - const Key & key, size_t offset, size_t size, - std::lock_guard & cache_lock) = 0; - - virtual void remove( - Key key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - virtual bool isLastFileSegmentHolder( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - /// If file segment was partially downloaded and then space reservation fails (because of no - /// space left), then update corresponding cache cell metadata (file segment size). - virtual void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - void assertInitialized() const; - -protected: - using KeyAndOffset = std::pair; - - struct KeyAndOffsetHash - { - std::size_t operator()(const KeyAndOffset & key) const - { - return std::hash()(key.first) ^ std::hash()(key.second); - } - }; - - using FileCacheRecords = std::unordered_map; - - /// Used to track and control the cache access of each query. - /// Through it, we can realize the processing of different queries by the cache layer. - struct QueryContext - { - FileCacheRecords records; - FileCachePriorityPtr priority; - - size_t cache_size = 0; - size_t max_cache_size; - - bool skip_download_if_exceeds_query_cache; - - QueryContext(size_t max_cache_size_, bool skip_download_if_exceeds_query_cache_) - : priority(std::make_shared()) - , max_cache_size(max_cache_size_) - , skip_download_if_exceeds_query_cache(skip_download_if_exceeds_query_cache_) {} - - void remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) - { - if (cache_size < size) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Deleted cache size exceeds existing cache size"); - - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record != records.end()) - { - record->second->remove(cache_lock); - records.erase({key, offset}); - } - } - cache_size -= size; - } - - void reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) - { - if (cache_size + size > max_cache_size) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Reserved cache size exceeds the remaining cache size"); - - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record == records.end()) - { - auto queue_iter = priority->add(key, offset, 0, cache_lock); - record = records.insert({{key, offset}, queue_iter}).first; - } - record->second->incrementSize(size, cache_lock); - } - cache_size += size; - } - - void use(const Key & key, size_t offset, std::lock_guard & cache_lock) - { - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record != records.end()) - record->second->use(cache_lock); - } - } - - size_t getMaxCacheSize() { return max_cache_size; } - - size_t getCacheSize() { return cache_size; } - - FileCachePriorityPtr getPriority() { return priority; } - - bool isSkipDownloadIfExceed() { return skip_download_if_exceeds_query_cache; } - }; - - using QueryContextPtr = std::shared_ptr; - using QueryContextMap = std::unordered_map; - - QueryContextMap query_map; - - bool enable_filesystem_query_cache_limit; - - QueryContextPtr getCurrentQueryContext(std::lock_guard & cache_lock); - - QueryContextPtr getQueryContext(const String & query_id, std::lock_guard & cache_lock); - - void removeQueryContext(const String & query_id); - - QueryContextPtr getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard &); - -public: - /// Save a query context information, and adopt different cache policies - /// for different queries through the context cache layer. - struct QueryContextHolder : private boost::noncopyable - { - explicit QueryContextHolder(const String & query_id_, IFileCache * cache_, QueryContextPtr context_); - - QueryContextHolder() = default; - - ~QueryContextHolder(); - - String query_id {}; - IFileCache * cache = nullptr; - QueryContextPtr context = nullptr; - }; - - QueryContextHolder getQueryContextHolder(const String & query_id, const ReadSettings & settings); -}; - class FileCache final : public IFileCache { public: @@ -264,7 +30,7 @@ public: const String & cache_base_path_, const FileCacheSettings & cache_settings_); - FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size) override; + FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) override; FileSegmentsHolder get(const Key & key, size_t offset, size_t size) override; @@ -272,9 +38,9 @@ public: void initialize() override; - void remove(const Key & key) override; + void removeIfExists(const Key & key) override; - void remove() override; + void removeIfReleasable(bool remove_persistent_files) override; std::vector tryGetCachePaths(const Key & key) override; @@ -293,7 +59,7 @@ private: /// Pointer to file segment is always hold by the cache itself. /// Apart from pointer in cache, it can be hold by cache users, when they call /// getorSet(), but cache users always hold it via FileSegmentsHolder. - bool releasable() const { return file_segment.unique(); } + bool releasable() const {return file_segment.unique(); } size_t size() const { return file_segment->reserved_size; } @@ -317,6 +83,7 @@ private: size_t enable_cache_hits_threshold; Poco::Logger * log; + bool allow_to_remove_persistent_segments_from_cache_by_default; FileSegments getImpl( const Key & key, const FileSegment::Range & range, @@ -327,7 +94,8 @@ private: FileSegmentCell * addCell( const Key & key, size_t offset, size_t size, - FileSegment::State state, std::lock_guard & cache_lock); + FileSegment::State state, bool is_persistent, + std::lock_guard & cache_lock); void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); @@ -350,24 +118,19 @@ private: std::lock_guard & cache_lock, std::lock_guard & segment_lock) override; - void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; - size_t getAvailableCacheSize() const; void loadCacheInfoIntoMemory(std::lock_guard & cache_lock); FileSegments splitRangeIntoCells( - const Key & key, size_t offset, size_t size, FileSegment::State state, std::lock_guard & cache_lock); + const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock); String dumpStructureUnlocked(const Key & key_, std::lock_guard & cache_lock); void fillHolesWithEmptyFileSegments( - FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, std::lock_guard & cache_lock); + FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, bool is_persistent, std::lock_guard & cache_lock); - FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size) override; + FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent) override; size_t getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const; @@ -377,6 +140,10 @@ private: void assertCacheCellsCorrectness(const FileSegmentsByOffset & cells_by_offset, std::lock_guard & cache_lock); + void reduceSizeToDownloaded( + const Key & key, size_t offset, + std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) override; + public: String dumpStructure(const Key & key_) override; diff --git a/src/Common/FileCacheFactory.cpp b/src/Common/FileCacheFactory.cpp index 259c1d3f48e..b276760c0dd 100644 --- a/src/Common/FileCacheFactory.cpp +++ b/src/Common/FileCacheFactory.cpp @@ -1,5 +1,5 @@ #include "FileCacheFactory.h" -#include "LRUFileCache.h" +#include "FileCache.h" namespace DB { @@ -53,7 +53,7 @@ FileCachePtr FileCacheFactory::getOrCreate( return it->second->cache; } - auto cache = std::make_shared(cache_base_path, file_cache_settings); + auto cache = std::make_shared(cache_base_path, file_cache_settings); FileCacheData result{cache, file_cache_settings}; auto cache_it = caches.insert(caches.end(), std::move(result)); diff --git a/src/Common/IFileCache.cpp b/src/Common/IFileCache.cpp index 8fe434dd740..e3ed82d7b62 100644 --- a/src/Common/IFileCache.cpp +++ b/src/Common/IFileCache.cpp @@ -140,7 +140,7 @@ void IFileCache::QueryContext::remove(const Key & key, size_t offset, size_t siz auto record = records.find({key, offset}); if (record != records.end()) { - lru_queue.remove(record->second, cache_lock); + record->second->remove(cache_lock); records.erase({key, offset}); } } @@ -162,10 +162,10 @@ void IFileCache::QueryContext::reserve(const Key & key, size_t offset, size_t si auto record = records.find({key, offset}); if (record == records.end()) { - auto queue_iter = lru_queue.add(key, offset, 0, cache_lock); + auto queue_iter = priority->add(key, offset, 0, cache_lock); record = records.insert({{key, offset}, queue_iter}).first; } - record->second->size += size; + record->second->incrementSize(size, cache_lock); } cache_size += size; } @@ -177,7 +177,7 @@ void IFileCache::QueryContext::use(const Key & key, size_t offset, std::lock_gua auto record = records.find({key, offset}); if (record != records.end()) - lru_queue.moveToEnd(record->second, cache_lock); + record->second->use(cache_lock); } IFileCache::QueryContextHolder::QueryContextHolder( diff --git a/src/Common/IFileCache.h b/src/Common/IFileCache.h index c820d18cb95..f46a83d52cf 100644 --- a/src/Common/IFileCache.h +++ b/src/Common/IFileCache.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -28,16 +29,7 @@ friend struct FileSegmentsHolder; friend class FileSegmentRangeWriter; public: - struct Key - { - UInt128 key; - String toString() const; - - Key() = default; - explicit Key(const UInt128 & key_) : key(key_) {} - - bool operator==(const Key & other) const { return key == other.key; } - }; + using Key = IFileCachePriority::Key; IFileCache( const String & cache_base_path_, @@ -133,49 +125,6 @@ protected: void assertInitialized() const; - class LRUQueue - { - public: - struct FileKeyAndOffset - { - Key key; - size_t offset; - size_t size; - size_t hits = 0; - - FileKeyAndOffset(const Key & key_, size_t offset_, size_t size_) : key(key_), offset(offset_), size(size_) {} - }; - - using Iterator = typename std::list::iterator; - - size_t getTotalCacheSize(std::lock_guard & /* cache_lock */) const { return cache_size; } - - size_t getElementsNum(std::lock_guard & /* cache_lock */) const { return queue.size(); } - - Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); - - void remove(Iterator queue_it, std::lock_guard & cache_lock); - - void moveToEnd(Iterator queue_it, std::lock_guard & cache_lock); - - /// Space reservation for a file segment is incremental, so we need to be able to increment size of the queue entry. - void incrementSize(Iterator queue_it, size_t size_increment, std::lock_guard & cache_lock); - - String toString(std::lock_guard & cache_lock) const; - - bool contains(const Key & key, size_t offset, std::lock_guard & cache_lock) const; - - Iterator begin() { return queue.begin(); } - - Iterator end() { return queue.end(); } - - void removeAll(std::lock_guard & cache_lock); - - private: - std::list queue; - size_t cache_size = 0; - }; - using AccessKeyAndOffset = std::pair; struct KeyAndOffsetHash { @@ -185,14 +134,14 @@ protected: } }; - using AccessRecord = std::unordered_map; + using FileCacheRecords = std::unordered_map; /// Used to track and control the cache access of each query. /// Through it, we can realize the processing of different queries by the cache layer. struct QueryContext { - LRUQueue lru_queue; - AccessRecord records; + FileCacheRecords records; + FileCachePriorityPtr priority; size_t cache_size = 0; size_t max_cache_size; @@ -213,7 +162,7 @@ protected: size_t getCacheSize() const { return cache_size; } - LRUQueue & queue() { return lru_queue; } + FileCachePriorityPtr getPriority() { return priority; } bool isSkipDownloadIfExceed() const { return skip_download_if_exceeds_query_cache; } }; diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 677ccd76934..a29d66c70be 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include namespace DB { @@ -19,7 +22,16 @@ using FileCachePriorityPtr = std::shared_ptr; class IFileCachePriority { public: - using Key = UInt128; + struct Key + { + UInt128 key; + String toString() const; + + Key() = default; + explicit Key(const UInt128 & key_) : key(key_) {} + + bool operator==(const Key & other) const { return key == other.key; } + }; class IIterator; friend class IIterator; diff --git a/src/Common/LRUFileCache.h b/src/Common/LRUFileCache.h index 059fc0c22c9..0bd87b2b38c 100644 --- a/src/Common/LRUFileCache.h +++ b/src/Common/LRUFileCache.h @@ -1,157 +1,99 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - +#include namespace DB { -/** - * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. - * Implements LRU eviction policy. - */ -class LRUFileCache final : public IFileCache +class LRUFileCache : public IFileCachePriority { public: - LRUFileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_); + using LRUQueue = std::list; + using LRUQueueIterator = typename LRUQueue::iterator; + class WriteableIterator; - FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) override; - - FileSegmentsHolder get(const Key & key, size_t offset, size_t size) override; - - FileSegments getSnapshot() const override; - - void initialize() override; - - void removeIfExists(const Key & key) override; - - void removeIfReleasable(bool remove_persistent_files) override; - - std::vector tryGetCachePaths(const Key & key) override; - - size_t getUsedCacheSize() const override; - - size_t getFileSegmentsNum() const override; - -private: - struct FileSegmentCell : private boost::noncopyable + class ReadableIterator : public IIterator { - FileSegmentPtr file_segment; + public: + ReadableIterator(LRUFileCache * file_cache_, LRUQueueIterator queue_iter_) : file_cache(file_cache_), queue_iter(queue_iter_) { } - /// Iterator is put here on first reservation attempt, if successful. - std::optional queue_iterator; + void next() override { queue_iter++; } - /// Pointer to file segment is always hold by the cache itself. - /// Apart from pointer in cache, it can be hold by cache users, when they call - /// getorSet(), but cache users always hold it via FileSegmentsHolder. - bool releasable() const {return file_segment.unique(); } + bool valid() const override { return queue_iter != file_cache->queue.end(); } - size_t size() const { return file_segment->reserved_size; } + Key & key() const override { return queue_iter->key; } - FileSegmentCell(FileSegmentPtr file_segment_, LRUFileCache * cache, std::lock_guard & cache_lock); + size_t offset() const override { return queue_iter->offset; } - FileSegmentCell(FileSegmentCell && other) noexcept - : file_segment(std::move(other.file_segment)) - , queue_iterator(other.queue_iterator) {} + size_t size() const override { return queue_iter->size; } + + size_t hits() const override { return queue_iter->hits; } + + Iterator getSnapshot() override { return std::make_shared(file_cache, queue_iter); } + + protected: + LRUFileCache * file_cache; + LRUQueueIterator queue_iter; }; - using FileSegmentsByOffset = std::map; - using CachedFiles = std::unordered_map; + class WriteableIterator : public ReadableIterator + { + public: + WriteableIterator(LRUFileCache * file_cache_, LRUQueueIterator queue_iter_) : ReadableIterator(file_cache_, queue_iter_) { } - CachedFiles files; - LRUQueue queue; + void remove(std::lock_guard &) override + { + file_cache->cache_size -= queue_iter->size; + file_cache->queue.erase(queue_iter); + } - LRUQueue stash_queue; - AccessRecord records; + void incrementSize(size_t size_increment, std::lock_guard &) override + { + file_cache->cache_size += size_increment; + queue_iter->size += size_increment; + } - size_t max_stash_element_size; - size_t enable_cache_hits_threshold; - - Poco::Logger * log; - bool allow_to_remove_persistent_segments_from_cache_by_default; - - FileSegments getImpl( - const Key & key, const FileSegment::Range & range, - std::lock_guard & cache_lock); - - FileSegmentCell * getCell( - const Key & key, size_t offset, std::lock_guard & cache_lock); - - FileSegmentCell * addCell( - const Key & key, size_t offset, size_t size, - FileSegment::State state, bool is_persistent, - std::lock_guard & cache_lock); - - void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); - - bool tryReserve( - const Key & key, size_t offset, size_t size, - std::lock_guard & cache_lock) override; - - bool tryReserveForMainList( - const Key & key, size_t offset, size_t size, - QueryContextPtr query_context, - std::lock_guard & cache_lock); - - void remove( - Key key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; - - bool isLastFileSegmentHolder( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; - - size_t getAvailableCacheSize() const; - - void loadCacheInfoIntoMemory(std::lock_guard & cache_lock); - - FileSegments splitRangeIntoCells( - const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock); - - String dumpStructureUnlocked(const Key & key_, std::lock_guard & cache_lock); - - void fillHolesWithEmptyFileSegments( - FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, bool is_persistent, std::lock_guard & cache_lock); - - FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent) override; - - size_t getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const; - - size_t getAvailableCacheSizeUnlocked(std::lock_guard & cache_lock) const; - - size_t getFileSegmentsNumUnlocked(std::lock_guard & cache_lock) const; - - void assertCacheCellsCorrectness(const FileSegmentsByOffset & cells_by_offset, std::lock_guard & cache_lock); - - void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) override; + void use(std::lock_guard &) override + { + queue_iter->hits++; + file_cache->queue.splice(file_cache->queue.end(), file_cache->queue, queue_iter); + } + }; public: - String dumpStructure(const Key & key_) override; + LRUFileCache() = default; - void assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock); + Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override + { + auto iter = queue.insert(queue.end(), FileCacheRecord(key, offset, size)); + cache_size += size; + return std::make_shared(this, iter); + } - void assertCacheCorrectness(std::lock_guard & cache_lock); + bool contains(const Key & key, size_t offset, std::lock_guard &) override + { + for (const auto & record : queue) + { + if (key == record.key && offset == record.offset) + return true; + } + return false; + } - void assertQueueCorrectness(std::lock_guard & cache_lock); + void removeAll(std::lock_guard &) override + { + queue.clear(); + cache_size = 0; + } + + Iterator getNewIterator(std::lock_guard &) override { return std::make_shared(this, queue.begin()); } + + size_t getElementsNum(std::lock_guard &) const override { return queue.size(); } + + std::string toString(std::lock_guard &) const override { return {}; } + +private: + LRUQueue queue; }; -} +}; diff --git a/src/Common/tests/gtest_lru_file_cache.cpp b/src/Common/tests/gtest_lru_file_cache.cpp index 8e7554f0418..ac942d97a32 100644 --- a/src/Common/tests/gtest_lru_file_cache.cpp +++ b/src/Common/tests/gtest_lru_file_cache.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include From 50fd740ec34825962e1f4e17c7bba2e84742692f Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Sun, 26 Jun 2022 20:35:02 +0800 Subject: [PATCH 536/672] fix --- src/Common/FileCache.cpp | 2 +- src/Common/IFileCachePriority.h | 6 +++--- src/Common/LRUFileCache.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 56eb4c9e081..bf981cc466e 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -772,7 +772,7 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) { std::lock_guard segment_lock(file_segment->mutex); file_segment->detach(cache_lock, segment_lock); - remove(file_segment->key(), file_segment->offset(), cache_lock, segment_lock); + to_remove.emplace_back(file_segment); } } } diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index a29d66c70be..2e73bb92841 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -3,9 +3,9 @@ #include #include #include +#include #include #include -#include namespace DB { @@ -28,7 +28,7 @@ public: String toString() const; Key() = default; - explicit Key(const UInt128 & key_) : key(key_) {} + explicit Key(const UInt128 & key_) : key(key_) { } bool operator==(const Key & other) const { return key == other.key; } }; @@ -80,7 +80,7 @@ public: throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support incrementSize() for IIterator."); } - virtual Key & key() const = 0; + virtual Key key() const = 0; virtual size_t offset() const = 0; diff --git a/src/Common/LRUFileCache.h b/src/Common/LRUFileCache.h index 0bd87b2b38c..6e42a7732d4 100644 --- a/src/Common/LRUFileCache.h +++ b/src/Common/LRUFileCache.h @@ -19,9 +19,9 @@ public: void next() override { queue_iter++; } - bool valid() const override { return queue_iter != file_cache->queue.end(); } + bool valid() const override { return (file_cache->queue.size() && (queue_iter != file_cache->queue.end())); } - Key & key() const override { return queue_iter->key; } + Key key() const override { return queue_iter->key; } size_t offset() const override { return queue_iter->offset; } From 9d83b93e88025f4052e5a32562377b26ab87a0cd Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Wed, 10 Aug 2022 13:50:30 +0800 Subject: [PATCH 537/672] fix rebase --- src/Common/FileCache.cpp | 196 +++++++++++++- src/Common/FileCache.h | 247 ++++++++++++++---- src/Common/FileCacheType.h | 28 ++ src/Common/FileCache_fwd.h | 4 +- src/Common/FileSegment.cpp | 6 +- src/Common/FileSegment.h | 10 +- src/Common/IFileCache.cpp | 201 -------------- src/Common/IFileCache.h | 216 --------------- src/Common/IFileCachePriority.h | 68 ++--- ...{LRUFileCache.h => LRUFileCachePriority.h} | 41 +-- src/Common/tests/gtest_lru_file_cache.cpp | 2 +- src/Disks/IO/CachedReadBufferFromRemoteFS.cpp | 2 +- src/Disks/IO/CachedReadBufferFromRemoteFS.h | 6 +- .../ObjectStorages/DiskObjectStorage.cpp | 3 +- .../DiskObjectStorageCommon.cpp | 2 +- src/Disks/ObjectStorages/IObjectStorage.h | 1 + .../ObjectStorages/S3/S3ObjectStorage.cpp | 2 +- src/IO/WriteBufferFromS3.cpp | 2 +- src/Interpreters/AsynchronousMetrics.cpp | 2 +- .../InterpreterDescribeCacheQuery.cpp | 2 +- src/Interpreters/InterpreterSystemQuery.cpp | 2 +- .../System/StorageSystemFilesystemCache.cpp | 2 +- .../System/StorageSystemRemoteDataPaths.cpp | 2 +- 23 files changed, 480 insertions(+), 567 deletions(-) create mode 100644 src/Common/FileCacheType.h delete mode 100644 src/Common/IFileCache.cpp delete mode 100644 src/Common/IFileCache.h rename src/Common/{LRUFileCache.h => LRUFileCachePriority.h} (61%) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index bf981cc466e..2a2fc68e768 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include namespace fs = std::filesystem; @@ -23,10 +23,16 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -FileCache::FileCache(const String & cache_base_path_, const FileCacheSettings & cache_settings_) - : IFileCache(cache_base_path_, cache_settings_) - , main_priority(std::make_shared()) - , stash_priority(std::make_shared()) +FileCache::FileCache( + const String & cache_base_path_, + const FileCacheSettings & cache_settings_) + : cache_base_path(cache_base_path_) + , max_size(cache_settings_.max_size) + , max_element_size(cache_settings_.max_elements) + , max_file_segment_size(cache_settings_.max_file_segment_size) + , enable_filesystem_query_cache_limit(cache_settings_.enable_filesystem_query_cache_limit) + , main_priority(std::make_shared()) + , stash_priority(std::make_shared()) , max_stash_element_size(cache_settings_.max_elements) , enable_cache_hits_threshold(cache_settings_.enable_cache_hits_threshold) , log(&Poco::Logger::get("FileCache")) @@ -34,6 +40,175 @@ FileCache::FileCache(const String & cache_base_path_, const FileCacheSettings & { } +String FileCache::Key::toString() const +{ + return getHexUIntLowercase(key); +} + +FileCache::Key FileCache::hash(const String & path) +{ + return Key(sipHash128(path.data(), path.size())); +} + +String FileCache::getPathInLocalCache(const Key & key, size_t offset, bool is_persistent) const +{ + auto key_str = key.toString(); + return fs::path(cache_base_path) + / key_str.substr(0, 3) + / key_str + / (std::to_string(offset) + (is_persistent ? "_persistent" : "")); +} + +String FileCache::getPathInLocalCache(const Key & key) const +{ + auto key_str = key.toString(); + return fs::path(cache_base_path) / key_str.substr(0, 3) / key_str; +} + +static bool isQueryInitialized() +{ + return CurrentThread::isInitialized() + && CurrentThread::get().getQueryContext() + && CurrentThread::getQueryId().size != 0; +} + +bool FileCache::isReadOnly() +{ + return !isQueryInitialized(); +} + +void FileCache::assertInitialized() const +{ + if (!is_initialized) + throw Exception(ErrorCodes::REMOTE_FS_OBJECT_CACHE_ERROR, "Cache not initialized"); +} + +FileCache::QueryContextPtr FileCache::getCurrentQueryContext(std::lock_guard & cache_lock) +{ + if (!isQueryInitialized()) + return nullptr; + + return getQueryContext(CurrentThread::getQueryId().toString(), cache_lock); +} + +FileCache::QueryContextPtr FileCache::getQueryContext(const String & query_id, std::lock_guard & /* cache_lock */) +{ + auto query_iter = query_map.find(query_id); + return (query_iter == query_map.end()) ? nullptr : query_iter->second; +} + +void FileCache::removeQueryContext(const String & query_id) +{ + std::lock_guard cache_lock(mutex); + auto query_iter = query_map.find(query_id); + + if (query_iter == query_map.end()) + { + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Attempt to release query context that does not exist (query_id: {})", + query_id); + } + + query_map.erase(query_iter); +} + +FileCache::QueryContextPtr FileCache::getOrSetQueryContext( + const String & query_id, const ReadSettings & settings, std::lock_guard & cache_lock) +{ + if (query_id.empty()) + return nullptr; + + auto context = getQueryContext(query_id, cache_lock); + if (context) + return context; + + auto query_context = std::make_shared(settings.max_query_cache_size, settings.skip_download_if_exceeds_query_cache); + auto query_iter = query_map.emplace(query_id, query_context).first; + return query_iter->second; +} + +FileCache::QueryContextHolder FileCache::getQueryContextHolder(const String & query_id, const ReadSettings & settings) +{ + std::lock_guard cache_lock(mutex); + + if (!enable_filesystem_query_cache_limit || settings.max_query_cache_size == 0) + return {}; + + /// if enable_filesystem_query_cache_limit is true, and max_query_cache_size large than zero, + /// we create context query for current query. + auto context = getOrSetQueryContext(query_id, settings, cache_lock); + return QueryContextHolder(query_id, this, context); +} + +void FileCache::QueryContext::remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) +{ + if (cache_size < size) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Deleted cache size exceeds existing cache size"); + + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record != records.end()) + { + record->second->remove(cache_lock); + records.erase({key, offset}); + } + } + cache_size -= size; +} + +void FileCache::QueryContext::reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) +{ + if (cache_size + size > max_cache_size) + { + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Reserved cache size exceeds the remaining cache size (key: {}, offset: {})", + key.toString(), offset); + } + + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record == records.end()) + { + auto queue_iter = priority->add(key, offset, 0, cache_lock); + record = records.insert({{key, offset}, queue_iter}).first; + } + record->second->incrementSize(size, cache_lock); + } + cache_size += size; +} + +void FileCache::QueryContext::use(const Key & key, size_t offset, std::lock_guard & cache_lock) +{ + if (skip_download_if_exceeds_query_cache) + return; + + auto record = records.find({key, offset}); + if (record != records.end()) + record->second->use(cache_lock); +} + +FileCache::QueryContextHolder::QueryContextHolder( + const String & query_id_, + FileCache * cache_, + FileCache::QueryContextPtr context_) + : query_id(query_id_) + , cache(cache_) + , context(context_) +{ +} + +FileCache::QueryContextHolder::~QueryContextHolder() +{ + /// If only the query_map and the current holder hold the context_query, + /// the query has been completed and the query_context is released. + if (context && context.use_count() == 2) + cache->removeQueryContext(query_id); +} + void FileCache::initialize() { std::lock_guard cache_lock(mutex); @@ -115,7 +290,7 @@ FileSegments FileCache::getImpl( files.erase(key); /// Note: it is guaranteed that there is no concurrency with files deletion, - /// because cache files are deleted only inside IFileCache and under cache lock. + /// because cache files are deleted only inside FileCache and under cache lock. if (fs::exists(key_path)) fs::remove_all(key_path); @@ -387,7 +562,7 @@ FileCache::FileSegmentCell * FileCache::addCell( if (stash_priority->getElementsNum(cache_lock) > max_stash_element_size) { - auto remove_priority_iter = stash_priority->getNewIterator(cache_lock); + auto remove_priority_iter = stash_priority->getNewIterator(cache_lock)->getWriteIterator(); stash_records.erase({remove_priority_iter->key(), remove_priority_iter->offset()}); remove_priority_iter->remove(cache_lock); } @@ -473,7 +648,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc auto * cell_for_reserve = getCell(key, offset, cache_lock); - std::vector ghost; + std::vector ghost; std::vector trash; std::vector to_evict; @@ -496,7 +671,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc { /// The cache corresponding to this record may be swapped out by /// other queries, so it has become invalid. - ghost.push_back(iter->getSnapshot()); + ghost.push_back(iter->getWriteIterator()); removed_size += iter->size(); } else @@ -844,10 +1019,9 @@ void FileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_lock Key key; UInt64 offset = 0; size_t size = 0; - std::vector>> queue_entries; + std::vector>> queue_entries; /// cache_base_path / key_prefix / key / offset - if (!files.empty()) throw Exception( ErrorCodes::REMOTE_FS_OBJECT_CACHE_ERROR, diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index 5aa32ac94ca..bccd2dbd458 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -3,50 +3,196 @@ #include #include #include +#include #include #include #include #include #include #include -#include -#include +#include +#include +#include #include -#include - +#include +#include +#include namespace DB { /** * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. - * Implements LRU eviction policy. - */ -class FileCache final : public IFileCache + */ +class FileCache : private boost::noncopyable { + friend class FileSegment; + friend class IFileCachePriority; + friend struct FileSegmentsHolder; + friend class FileSegmentRangeWriter; + public: - FileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_); + using Key = DB::FileCacheKey; - FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) override; + FileCache(const String & cache_base_path_, const FileCacheSettings & cache_settings_); - FileSegmentsHolder get(const Key & key, size_t offset, size_t size) override; + ~FileCache() = default; - FileSegments getSnapshot() const override; + /// Restore cache from local filesystem. + void initialize(); - void initialize() override; + void removeIfExists(const Key & key); - void removeIfExists(const Key & key) override; + void removeIfReleasable(bool remove_persistent_files); - void removeIfReleasable(bool remove_persistent_files) override; + static bool isReadOnly(); - std::vector tryGetCachePaths(const Key & key) override; + /// Cache capacity in bytes. + size_t capacity() const { return max_size; } - size_t getUsedCacheSize() const override; + static Key hash(const String & path); - size_t getFileSegmentsNum() const override; + String getPathInLocalCache(const Key & key, size_t offset, bool is_persistent) const; + + String getPathInLocalCache(const Key & key) const; + + const String & getBasePath() const { return cache_base_path; } + + std::vector tryGetCachePaths(const Key & key); + + /** + * Given an `offset` and `size` representing [offset, offset + size) bytes interval, + * return list of cached non-overlapping non-empty + * file segments `[segment1, ..., segmentN]` which intersect with given interval. + * + * Segments in returned list are ordered in ascending order and represent a full contiguous + * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. + * + * As long as pointers to returned file segments are hold + * it is guaranteed that these file segments are not removed from cache. + */ + FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent); + + /** + * Segments in returned list are ordered in ascending order and represent a full contiguous + * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. + * + * If file segment has state EMPTY, then it is also marked as "detached". E.g. it is "detached" + * from cache (not owned by cache), and as a result will never change it's state and will be destructed + * with the destruction of the holder, while in getOrSet() EMPTY file segments can eventually change + * it's state (and become DOWNLOADED). + */ + FileSegmentsHolder get(const Key & key, size_t offset, size_t size); + + FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent); + + FileSegments getSnapshot() const; + + /// For debug. + String dumpStructure(const Key & key); + + size_t getUsedCacheSize() const; + + size_t getFileSegmentsNum() const; + +private: + String cache_base_path; + size_t max_size; + size_t max_element_size; + size_t max_file_segment_size; + + bool is_initialized = false; + + mutable std::mutex mutex; + + bool tryReserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); + + void remove(Key key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & segment_lock); + + bool isLastFileSegmentHolder( + const Key & key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & segment_lock); + + void reduceSizeToDownloaded( + const Key & key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */); + + void assertInitialized() const; + + using AccessKeyAndOffset = std::pair; + struct KeyAndOffsetHash + { + std::size_t operator()(const AccessKeyAndOffset & key) const + { + return std::hash()(key.first.key) ^ std::hash()(key.second); + } + }; + + using FileCacheRecords = std::unordered_map; + + /// Used to track and control the cache access of each query. + /// Through it, we can realize the processing of different queries by the cache layer. + struct QueryContext + { + FileCacheRecords records; + FileCachePriorityPtr priority; + + size_t cache_size = 0; + size_t max_cache_size; + + bool skip_download_if_exceeds_query_cache; + + QueryContext(size_t max_cache_size_, bool skip_download_if_exceeds_query_cache_) + : max_cache_size(max_cache_size_), skip_download_if_exceeds_query_cache(skip_download_if_exceeds_query_cache_) + { + } + + void remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); + + void reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); + + void use(const Key & key, size_t offset, std::lock_guard & cache_lock); + + size_t getMaxCacheSize() const { return max_cache_size; } + + size_t getCacheSize() const { return cache_size; } + + FileCachePriorityPtr getPriority() { return priority; } + + bool isSkipDownloadIfExceed() const { return skip_download_if_exceeds_query_cache; } + }; + + using QueryContextPtr = std::shared_ptr; + using QueryContextMap = std::unordered_map; + + QueryContextMap query_map; + + bool enable_filesystem_query_cache_limit; + + QueryContextPtr getCurrentQueryContext(std::lock_guard & cache_lock); + + QueryContextPtr getQueryContext(const String & query_id, std::lock_guard & cache_lock); + + void removeQueryContext(const String & query_id); + + QueryContextPtr getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard &); + +public: + /// Save a query context information, and adopt different cache policies + /// for different queries through the context cache layer. + struct QueryContextHolder : private boost::noncopyable + { + QueryContextHolder(const String & query_id_, FileCache * cache_, QueryContextPtr context_); + + QueryContextHolder() = default; + + ~QueryContextHolder(); + + String query_id; + FileCache * cache = nullptr; + QueryContextPtr context; + }; + + QueryContextHolder getQueryContextHolder(const String & query_id, const ReadSettings & settings); private: struct FileSegmentCell : private boost::noncopyable @@ -54,20 +200,21 @@ private: FileSegmentPtr file_segment; /// Iterator is put here on first reservation attempt, if successful. - IFileCachePriority::Iterator queue_iterator; + IFileCachePriority::WriteIterator queue_iterator; /// Pointer to file segment is always hold by the cache itself. /// Apart from pointer in cache, it can be hold by cache users, when they call /// getorSet(), but cache users always hold it via FileSegmentsHolder. - bool releasable() const {return file_segment.unique(); } + bool releasable() const { return file_segment.unique(); } size_t size() const { return file_segment->reserved_size; } FileSegmentCell(FileSegmentPtr file_segment_, FileCache * cache, std::lock_guard & cache_lock); FileSegmentCell(FileSegmentCell && other) noexcept - : file_segment(std::move(other.file_segment)) - , queue_iterator(other.queue_iterator) {} + : file_segment(std::move(other.file_segment)), queue_iterator(other.queue_iterator) + { + } }; using FileSegmentsByOffset = std::map; @@ -85,52 +232,44 @@ private: Poco::Logger * log; bool allow_to_remove_persistent_segments_from_cache_by_default; - FileSegments getImpl( - const Key & key, const FileSegment::Range & range, - std::lock_guard & cache_lock); + FileSegments getImpl(const Key & key, const FileSegment::Range & range, std::lock_guard & cache_lock); - FileSegmentCell * getCell( - const Key & key, size_t offset, std::lock_guard & cache_lock); + FileSegmentCell * getCell(const Key & key, size_t offset, std::lock_guard & cache_lock); FileSegmentCell * addCell( - const Key & key, size_t offset, size_t size, - FileSegment::State state, bool is_persistent, + const Key & key, + size_t offset, + size_t size, + FileSegment::State state, + bool is_persistent, std::lock_guard & cache_lock); void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); - bool tryReserve( - const Key & key, size_t offset, size_t size, - std::lock_guard & cache_lock) override; - bool tryReserveForMainList( - const Key & key, size_t offset, size_t size, - QueryContextPtr query_context, - std::lock_guard & cache_lock); - - void remove( - Key key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; - - bool isLastFileSegmentHolder( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; + const Key & key, size_t offset, size_t size, QueryContextPtr query_context, std::lock_guard & cache_lock); size_t getAvailableCacheSize() const; void loadCacheInfoIntoMemory(std::lock_guard & cache_lock); FileSegments splitRangeIntoCells( - const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock); + const Key & key, + size_t offset, + size_t size, + FileSegment::State state, + bool is_persistent, + std::lock_guard & cache_lock); String dumpStructureUnlocked(const Key & key_, std::lock_guard & cache_lock); void fillHolesWithEmptyFileSegments( - FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, bool is_persistent, std::lock_guard & cache_lock); - - FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent) override; + FileSegments & file_segments, + const Key & key, + const FileSegment::Range & range, + bool fill_with_detached_file_segments, + bool is_persistent, + std::lock_guard & cache_lock); size_t getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const; @@ -140,13 +279,7 @@ private: void assertCacheCellsCorrectness(const FileSegmentsByOffset & cells_by_offset, std::lock_guard & cache_lock); - void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) override; - public: - String dumpStructure(const Key & key_) override; - void assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock); void assertCacheCorrectness(std::lock_guard & cache_lock); diff --git a/src/Common/FileCacheType.h b/src/Common/FileCacheType.h new file mode 100644 index 00000000000..9b3ec5a6af0 --- /dev/null +++ b/src/Common/FileCacheType.h @@ -0,0 +1,28 @@ +#pragma once +#include + +namespace DB +{ + +struct FileCacheKey +{ + UInt128 key; + String toString() const; + + FileCacheKey() = default; + explicit FileCacheKey(const UInt128 & key_) : key(key_) { } + + bool operator==(const FileCacheKey & other) const { return key == other.key; } +}; + +} + +namespace std +{ +template <> +struct hash +{ + std::size_t operator()(const DB::FileCacheKey & k) const { return hash()(k.key); } +}; + +} diff --git a/src/Common/FileCache_fwd.h b/src/Common/FileCache_fwd.h index 8a7c2eeb458..9f6b2a740fc 100644 --- a/src/Common/FileCache_fwd.h +++ b/src/Common/FileCache_fwd.h @@ -9,8 +9,8 @@ static constexpr int REMOTE_FS_OBJECTS_CACHE_DEFAULT_MAX_FILE_SEGMENT_SIZE = 100 static constexpr int REMOTE_FS_OBJECTS_CACHE_DEFAULT_MAX_ELEMENTS = 1024 * 1024; static constexpr int REMOTE_FS_OBJECTS_CACHE_ENABLE_HITS_THRESHOLD = 0; -class IFileCache; -using FileCachePtr = std::shared_ptr; +class FileCache; +using FileCachePtr = std::shared_ptr; struct FileCacheSettings; diff --git a/src/Common/FileSegment.cpp b/src/Common/FileSegment.cpp index c16d4658ae5..2aba93bbdb0 100644 --- a/src/Common/FileSegment.cpp +++ b/src/Common/FileSegment.cpp @@ -5,7 +5,7 @@ #include #include #include - +#include namespace CurrentMetrics { @@ -25,7 +25,7 @@ FileSegment::FileSegment( size_t offset_, size_t size_, const Key & key_, - IFileCache * cache_, + FileCache * cache_, State download_state_, bool is_persistent_) : segment_range(offset_, offset_ + size_ - 1) @@ -787,7 +787,7 @@ FileSegmentsHolder::~FileSegmentsHolder() /// FileSegmentsHolder right after calling file_segment->complete(), so on destruction here /// remain only uncompleted file segments. - IFileCache * cache = nullptr; + FileCache * cache = nullptr; for (auto file_segment_it = file_segments.begin(); file_segment_it != file_segments.end();) { diff --git a/src/Common/FileSegment.h b/src/Common/FileSegment.h index 4404d0e14be..b129b851d7c 100644 --- a/src/Common/FileSegment.h +++ b/src/Common/FileSegment.h @@ -1,11 +1,11 @@ #pragma once #include -#include #include #include #include #include +#include namespace Poco { class Logger; } @@ -17,7 +17,7 @@ extern const Metric CacheFileSegments; namespace DB { -class IFileCache; +class FileCache; class FileSegment; using FileSegmentPtr = std::shared_ptr; @@ -32,7 +32,7 @@ friend struct FileSegmentsHolder; friend class FileSegmentRangeWriter; public: - using Key = IFileCache::Key; + using Key = FileCacheKey; using RemoteFileReaderPtr = std::shared_ptr; using LocalCacheWriterPtr = std::unique_ptr; @@ -74,7 +74,7 @@ public: size_t offset_, size_t size_, const Key & key_, - IFileCache * cache_, + FileCache * cache_, State download_state_, bool is_persistent_ = false); @@ -234,7 +234,7 @@ private: mutable std::mutex download_mutex; Key file_key; - IFileCache * cache; + FileCache * cache; Poco::Logger * log; diff --git a/src/Common/IFileCache.cpp b/src/Common/IFileCache.cpp deleted file mode 100644 index e3ed82d7b62..00000000000 --- a/src/Common/IFileCache.cpp +++ /dev/null @@ -1,201 +0,0 @@ -#include "IFileCache.h" - -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace DB -{ - -namespace ErrorCodes -{ - extern const int REMOTE_FS_OBJECT_CACHE_ERROR; - extern const int LOGICAL_ERROR; -} - -IFileCache::IFileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_) - : cache_base_path(cache_base_path_) - , max_size(cache_settings_.max_size) - , max_element_size(cache_settings_.max_elements) - , max_file_segment_size(cache_settings_.max_file_segment_size) - , enable_filesystem_query_cache_limit(cache_settings_.enable_filesystem_query_cache_limit) -{ -} - -String IFileCache::Key::toString() const -{ - return getHexUIntLowercase(key); -} - -IFileCache::Key IFileCache::hash(const String & path) -{ - return Key(sipHash128(path.data(), path.size())); -} - -String IFileCache::getPathInLocalCache(const Key & key, size_t offset, bool is_persistent) const -{ - auto key_str = key.toString(); - return fs::path(cache_base_path) - / key_str.substr(0, 3) - / key_str - / (std::to_string(offset) + (is_persistent ? "_persistent" : "")); -} - -String IFileCache::getPathInLocalCache(const Key & key) const -{ - auto key_str = key.toString(); - return fs::path(cache_base_path) / key_str.substr(0, 3) / key_str; -} - -static bool isQueryInitialized() -{ - return CurrentThread::isInitialized() - && CurrentThread::get().getQueryContext() - && !CurrentThread::getQueryId().empty(); -} - -bool IFileCache::isReadOnly() -{ - return !isQueryInitialized(); -} - -void IFileCache::assertInitialized() const -{ - if (!is_initialized) - throw Exception(ErrorCodes::REMOTE_FS_OBJECT_CACHE_ERROR, "Cache not initialized"); -} - -IFileCache::QueryContextPtr IFileCache::getCurrentQueryContext(std::lock_guard & cache_lock) -{ - if (!isQueryInitialized()) - return nullptr; - - return getQueryContext(std::string(CurrentThread::getQueryId()), cache_lock); -} - -IFileCache::QueryContextPtr IFileCache::getQueryContext(const String & query_id, std::lock_guard & /* cache_lock */) -{ - auto query_iter = query_map.find(query_id); - return (query_iter == query_map.end()) ? nullptr : query_iter->second; -} - -void IFileCache::removeQueryContext(const String & query_id) -{ - std::lock_guard cache_lock(mutex); - auto query_iter = query_map.find(query_id); - - if (query_iter == query_map.end()) - { - throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Attempt to release query context that does not exist (query_id: {})", - query_id); - } - - query_map.erase(query_iter); -} - -IFileCache::QueryContextPtr IFileCache::getOrSetQueryContext( - const String & query_id, const ReadSettings & settings, std::lock_guard & cache_lock) -{ - if (query_id.empty()) - return nullptr; - - auto context = getQueryContext(query_id, cache_lock); - if (context) - return context; - - auto query_context = std::make_shared(settings.max_query_cache_size, settings.skip_download_if_exceeds_query_cache); - auto query_iter = query_map.emplace(query_id, query_context).first; - return query_iter->second; -} - -IFileCache::QueryContextHolder IFileCache::getQueryContextHolder(const String & query_id, const ReadSettings & settings) -{ - std::lock_guard cache_lock(mutex); - - if (!enable_filesystem_query_cache_limit || settings.max_query_cache_size == 0) - return {}; - - /// if enable_filesystem_query_cache_limit is true, and max_query_cache_size large than zero, - /// we create context query for current query. - auto context = getOrSetQueryContext(query_id, settings, cache_lock); - return QueryContextHolder(query_id, this, context); -} - -void IFileCache::QueryContext::remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) -{ - if (cache_size < size) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Deleted cache size exceeds existing cache size"); - - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record != records.end()) - { - record->second->remove(cache_lock); - records.erase({key, offset}); - } - } - cache_size -= size; -} - -void IFileCache::QueryContext::reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) -{ - if (cache_size + size > max_cache_size) - { - throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Reserved cache size exceeds the remaining cache size (key: {}, offset: {})", - key.toString(), offset); - } - - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record == records.end()) - { - auto queue_iter = priority->add(key, offset, 0, cache_lock); - record = records.insert({{key, offset}, queue_iter}).first; - } - record->second->incrementSize(size, cache_lock); - } - cache_size += size; -} - -void IFileCache::QueryContext::use(const Key & key, size_t offset, std::lock_guard & cache_lock) -{ - if (skip_download_if_exceeds_query_cache) - return; - - auto record = records.find({key, offset}); - if (record != records.end()) - record->second->use(cache_lock); -} - -IFileCache::QueryContextHolder::QueryContextHolder( - const String & query_id_, - IFileCache * cache_, - IFileCache::QueryContextPtr context_) - : query_id(query_id_) - , cache(cache_) - , context(context_) -{ -} - -IFileCache::QueryContextHolder::~QueryContextHolder() -{ - /// If only the query_map and the current holder hold the context_query, - /// the query has been completed and the query_context is released. - if (context && context.use_count() == 2) - cache->removeQueryContext(query_id); -} - -} diff --git a/src/Common/IFileCache.h b/src/Common/IFileCache.h deleted file mode 100644 index f46a83d52cf..00000000000 --- a/src/Common/IFileCache.h +++ /dev/null @@ -1,216 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include -#include -#include - - -namespace DB -{ - -class FileSegment; -using FileSegmentPtr = std::shared_ptr; -using FileSegments = std::list; -struct FileSegmentsHolder; -struct ReadSettings; - -/** - * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. - */ -class IFileCache : private boost::noncopyable -{ -friend class FileSegment; -friend struct FileSegmentsHolder; -friend class FileSegmentRangeWriter; - -public: - using Key = IFileCachePriority::Key; - - IFileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_); - - virtual ~IFileCache() = default; - - /// Restore cache from local filesystem. - virtual void initialize() = 0; - - virtual void removeIfExists(const Key & key) = 0; - - virtual void removeIfReleasable(bool remove_persistent_files) = 0; - - static bool isReadOnly(); - - /// Cache capacity in bytes. - size_t capacity() const { return max_size; } - - static Key hash(const String & path); - - String getPathInLocalCache(const Key & key, size_t offset, bool is_persistent) const; - - String getPathInLocalCache(const Key & key) const; - - const String & getBasePath() const { return cache_base_path; } - - virtual std::vector tryGetCachePaths(const Key & key) = 0; - - /** - * Given an `offset` and `size` representing [offset, offset + size) bytes interval, - * return list of cached non-overlapping non-empty - * file segments `[segment1, ..., segmentN]` which intersect with given interval. - * - * Segments in returned list are ordered in ascending order and represent a full contiguous - * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. - * - * As long as pointers to returned file segments are hold - * it is guaranteed that these file segments are not removed from cache. - */ - virtual FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) = 0; - - /** - * Segments in returned list are ordered in ascending order and represent a full contiguous - * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. - * - * If file segment has state EMPTY, then it is also marked as "detached". E.g. it is "detached" - * from cache (not owned by cache), and as a result will never change it's state and will be destructed - * with the destruction of the holder, while in getOrSet() EMPTY file segments can eventually change - * it's state (and become DOWNLOADED). - */ - virtual FileSegmentsHolder get(const Key & key, size_t offset, size_t size) = 0; - - virtual FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent) = 0; - - virtual FileSegments getSnapshot() const = 0; - - /// For debug. - virtual String dumpStructure(const Key & key) = 0; - - virtual size_t getUsedCacheSize() const = 0; - - virtual size_t getFileSegmentsNum() const = 0; - -protected: - String cache_base_path; - size_t max_size; - size_t max_element_size; - size_t max_file_segment_size; - - bool is_initialized = false; - - mutable std::mutex mutex; - - virtual bool tryReserve( - const Key & key, size_t offset, size_t size, - std::lock_guard & cache_lock) = 0; - - virtual void remove( - Key key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - virtual bool isLastFileSegmentHolder( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - virtual void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & /* segment_lock */) = 0; - - void assertInitialized() const; - - using AccessKeyAndOffset = std::pair; - struct KeyAndOffsetHash - { - std::size_t operator()(const AccessKeyAndOffset & key) const - { - return std::hash()(key.first.key) ^ std::hash()(key.second); - } - }; - - using FileCacheRecords = std::unordered_map; - - /// Used to track and control the cache access of each query. - /// Through it, we can realize the processing of different queries by the cache layer. - struct QueryContext - { - FileCacheRecords records; - FileCachePriorityPtr priority; - - size_t cache_size = 0; - size_t max_cache_size; - - bool skip_download_if_exceeds_query_cache; - - QueryContext(size_t max_cache_size_, bool skip_download_if_exceeds_query_cache_) - : max_cache_size(max_cache_size_) - , skip_download_if_exceeds_query_cache(skip_download_if_exceeds_query_cache_) {} - - void remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); - - void reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); - - void use(const Key & key, size_t offset, std::lock_guard & cache_lock); - - size_t getMaxCacheSize() const { return max_cache_size; } - - size_t getCacheSize() const { return cache_size; } - - FileCachePriorityPtr getPriority() { return priority; } - - bool isSkipDownloadIfExceed() const { return skip_download_if_exceeds_query_cache; } - }; - - using QueryContextPtr = std::shared_ptr; - using QueryContextMap = std::unordered_map; - - QueryContextMap query_map; - - bool enable_filesystem_query_cache_limit; - - QueryContextPtr getCurrentQueryContext(std::lock_guard & cache_lock); - - QueryContextPtr getQueryContext(const String & query_id, std::lock_guard & cache_lock); - - void removeQueryContext(const String & query_id); - - QueryContextPtr getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard &); - -public: - /// Save a query context information, and adopt different cache policies - /// for different queries through the context cache layer. - struct QueryContextHolder : private boost::noncopyable - { - QueryContextHolder(const String & query_id_, IFileCache * cache_, QueryContextPtr context_); - - QueryContextHolder() = default; - - ~QueryContextHolder(); - - String query_id; - IFileCache * cache = nullptr; - QueryContextPtr context; - }; - - QueryContextHolder getQueryContextHolder(const String & query_id, const ReadSettings & settings); - -}; - -using FileCachePtr = std::shared_ptr; - -} - -namespace std -{ -template <> struct hash -{ - std::size_t operator()(const DB::IFileCache::Key & k) const { return hash()(k.key); } -}; - -} diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 2e73bb92841..84e1a386d24 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -5,7 +5,8 @@ #include #include #include -#include +#include +#include namespace DB { @@ -15,6 +16,8 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; } +class FileCache; + class IFileCachePriority; using FileCachePriorityPtr = std::shared_ptr; @@ -22,20 +25,14 @@ using FileCachePriorityPtr = std::shared_ptr; class IFileCachePriority { public: - struct Key - { - UInt128 key; - String toString() const; - - Key() = default; - explicit Key(const UInt128 & key_) : key(key_) { } - - bool operator==(const Key & other) const { return key == other.key; } - }; - class IIterator; friend class IIterator; - using Iterator = std::shared_ptr; + + using ReadIterator = std::shared_ptr; + using WriteIterator = std::shared_ptr; + + friend class FileCache; + using Key = FileCacheKey; struct FileCacheRecord { @@ -56,30 +53,6 @@ public: public: virtual ~IIterator() = default; - virtual void next() { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support next() for IIterator."); } - - virtual bool valid() const { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support valid() for IIterator."); } - - /// Mark a cache record as recently used, it will update the priority - /// of the cache record according to different cache algorithms. - virtual void use(std::lock_guard &) - { - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support use() for IIterator."); - } - - /// Deletes an existing cached record. - virtual void remove(std::lock_guard &) - { - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support remove() for IIterator."); - } - - virtual Iterator getSnapshot() { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support getSnapshot() for IIterator."); } - - virtual void incrementSize(size_t, std::lock_guard &) - { - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support incrementSize() for IIterator."); - } - virtual Key key() const = 0; virtual size_t offset() const = 0; @@ -87,6 +60,23 @@ public: virtual size_t size() const = 0; virtual size_t hits() const = 0; + + virtual void next() const = 0; + + virtual bool valid() const = 0; + + /// Mark a cache record as recently used, it will update the priority + /// of the cache record according to different cache algorithms. + virtual void use(std::lock_guard &) = 0; + + /// Deletes an existing cached record. + virtual void remove(std::lock_guard &) = 0; + + virtual WriteIterator getWriteIterator() const = 0; + + virtual void incrementSize(size_t, std::lock_guard &) = 0; + + virtual void seekToLowestPriority() const = 0; }; public: @@ -94,7 +84,7 @@ public: /// Add a cache record that did not exist before, and throw a /// logical exception if the cache block already exists. - virtual Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) = 0; + virtual WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) = 0; /// Query whether a cache record exists. If it exists, return true. If not, return false. virtual bool contains(const Key & key, size_t offset, std::lock_guard & cache_lock) = 0; @@ -103,7 +93,7 @@ public: /// Returns an iterator pointing to the lowest priority cached record. /// We can traverse all cached records through the iterator's next(). - virtual Iterator getNewIterator(std::lock_guard & cache_lock) = 0; + virtual ReadIterator getNewIterator(std::lock_guard & cache_lock) = 0; virtual size_t getElementsNum(std::lock_guard & cache_lock) const = 0; diff --git a/src/Common/LRUFileCache.h b/src/Common/LRUFileCachePriority.h similarity index 61% rename from src/Common/LRUFileCache.h rename to src/Common/LRUFileCachePriority.h index 6e42a7732d4..bc9badc0af6 100644 --- a/src/Common/LRUFileCache.h +++ b/src/Common/LRUFileCachePriority.h @@ -5,19 +5,23 @@ namespace DB { -class LRUFileCache : public IFileCachePriority +/// Based on the LRU algorithm implementation, the data with the lowest priority is stored at +/// the head of the queue, and the data with the highest priority is stored at the tail. +class LRUFileCachePriority : public IFileCachePriority { public: using LRUQueue = std::list; using LRUQueueIterator = typename LRUQueue::iterator; - class WriteableIterator; - class ReadableIterator : public IIterator + class LRUFileCacheIterator : public IIterator { public: - ReadableIterator(LRUFileCache * file_cache_, LRUQueueIterator queue_iter_) : file_cache(file_cache_), queue_iter(queue_iter_) { } + LRUFileCacheIterator(LRUFileCachePriority * file_cache_, LRUQueueIterator queue_iter_) + : file_cache(file_cache_), queue_iter(queue_iter_) + { + } - void next() override { queue_iter++; } + void next() const override { queue_iter++; } bool valid() const override { return (file_cache->queue.size() && (queue_iter != file_cache->queue.end())); } @@ -29,17 +33,9 @@ public: size_t hits() const override { return queue_iter->hits; } - Iterator getSnapshot() override { return std::make_shared(file_cache, queue_iter); } + WriteIterator getWriteIterator() const override { return std::make_shared(file_cache, queue_iter); } - protected: - LRUFileCache * file_cache; - LRUQueueIterator queue_iter; - }; - - class WriteableIterator : public ReadableIterator - { - public: - WriteableIterator(LRUFileCache * file_cache_, LRUQueueIterator queue_iter_) : ReadableIterator(file_cache_, queue_iter_) { } + void seekToLowestPriority() const override { queue_iter = file_cache->queue.begin(); } void remove(std::lock_guard &) override { @@ -58,16 +54,20 @@ public: queue_iter->hits++; file_cache->queue.splice(file_cache->queue.end(), file_cache->queue, queue_iter); } + + private: + mutable LRUFileCachePriority * file_cache; + mutable LRUQueueIterator queue_iter; }; public: - LRUFileCache() = default; + LRUFileCachePriority() = default; - Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override + WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override { auto iter = queue.insert(queue.end(), FileCacheRecord(key, offset, size)); cache_size += size; - return std::make_shared(this, iter); + return std::make_shared(this, iter); } bool contains(const Key & key, size_t offset, std::lock_guard &) override @@ -86,7 +86,10 @@ public: cache_size = 0; } - Iterator getNewIterator(std::lock_guard &) override { return std::make_shared(this, queue.begin()); } + ReadIterator getNewIterator(std::lock_guard &) override + { + return std::make_shared(this, queue.begin()); + } size_t getElementsNum(std::lock_guard &) const override { return queue.size(); } diff --git a/src/Common/tests/gtest_lru_file_cache.cpp b/src/Common/tests/gtest_lru_file_cache.cpp index ac942d97a32..3f481ee25ca 100644 --- a/src/Common/tests/gtest_lru_file_cache.cpp +++ b/src/Common/tests/gtest_lru_file_cache.cpp @@ -47,7 +47,7 @@ std::vector fromHolder(const DB::FileSegmentsHolder & holder return std::vector(holder.file_segments.begin(), holder.file_segments.end()); } -String getFileSegmentPath(const String & base_path, const DB::IFileCache::Key & key, size_t offset) +String getFileSegmentPath(const String & base_path, const DB::FileCache::Key & key, size_t offset) { auto key_str = key.toString(); return fs::path(base_path) / key_str.substr(0, 3) / key_str / DB::toString(offset); diff --git a/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp b/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp index a3d5cfc408d..a10e136334e 100644 --- a/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp +++ b/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp @@ -1024,7 +1024,7 @@ std::optional CachedReadBufferFromRemoteFS::getLastNonDownloadedOffset() void CachedReadBufferFromRemoteFS::assertCorrectness() const { - if (IFileCache::isReadOnly() && !settings.read_from_filesystem_cache_if_exists_otherwise_bypass_cache) + if (FileCache::isReadOnly() && !settings.read_from_filesystem_cache_if_exists_otherwise_bypass_cache) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cache usage is not allowed"); } diff --git a/src/Disks/IO/CachedReadBufferFromRemoteFS.h b/src/Disks/IO/CachedReadBufferFromRemoteFS.h index aff29dd200c..7fe3af29ef7 100644 --- a/src/Disks/IO/CachedReadBufferFromRemoteFS.h +++ b/src/Disks/IO/CachedReadBufferFromRemoteFS.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include @@ -81,7 +81,7 @@ private: bool writeCache(char * data, size_t size, size_t offset, FileSegment & file_segment); Poco::Logger * log; - IFileCache::Key cache_key; + FileCache::Key cache_key; String remote_fs_object_path; FileCachePtr cache; ReadSettings settings; @@ -128,7 +128,7 @@ private: CurrentMetrics::Increment metric_increment{CurrentMetrics::FilesystemCacheReadBuffers}; ProfileEvents::Counters current_file_segment_counters; - IFileCache::QueryContextHolder query_context_holder; + FileCache::QueryContextHolder query_context_holder; bool is_persistent; }; diff --git a/src/Disks/ObjectStorages/DiskObjectStorage.cpp b/src/Disks/ObjectStorages/DiskObjectStorage.cpp index 970a971d5dc..0849a3f09e3 100644 --- a/src/Disks/ObjectStorages/DiskObjectStorage.cpp +++ b/src/Disks/ObjectStorages/DiskObjectStorage.cpp @@ -10,7 +10,8 @@ #include #include #include -#include +#include +#include #include #include #include diff --git a/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp b/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp index b8ab2f49202..499791caf94 100644 --- a/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp +++ b/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/Disks/ObjectStorages/IObjectStorage.h b/src/Disks/ObjectStorages/IObjectStorage.h index 1ab2d75ff86..69c1c31403d 100644 --- a/src/Disks/ObjectStorages/IObjectStorage.h +++ b/src/Disks/ObjectStorages/IObjectStorage.h @@ -16,6 +16,7 @@ #include #include #include +#include #include diff --git a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp index 25dafac4120..901deeebefc 100644 --- a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp +++ b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/IO/WriteBufferFromS3.cpp b/src/IO/WriteBufferFromS3.cpp index 51f0c0d0743..79da7832a34 100644 --- a/src/IO/WriteBufferFromS3.cpp +++ b/src/IO/WriteBufferFromS3.cpp @@ -3,8 +3,8 @@ #if USE_AWS_S3 #include -#include #include +#include #include #include diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index 9fd27fc28b6..f9bc22dd110 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -12,9 +12,9 @@ #include #include #include -#include #include #include +#include #include #include #include diff --git a/src/Interpreters/InterpreterDescribeCacheQuery.cpp b/src/Interpreters/InterpreterDescribeCacheQuery.cpp index dd6df26c6af..d7c13dbb077 100644 --- a/src/Interpreters/InterpreterDescribeCacheQuery.cpp +++ b/src/Interpreters/InterpreterDescribeCacheQuery.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/Interpreters/InterpreterSystemQuery.cpp b/src/Interpreters/InterpreterSystemQuery.cpp index 695ea53e65e..b37274a3152 100644 --- a/src/Interpreters/InterpreterSystemQuery.cpp +++ b/src/Interpreters/InterpreterSystemQuery.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/Storages/System/StorageSystemFilesystemCache.cpp b/src/Storages/System/StorageSystemFilesystemCache.cpp index 2baddadec90..6d711498091 100644 --- a/src/Storages/System/StorageSystemFilesystemCache.cpp +++ b/src/Storages/System/StorageSystemFilesystemCache.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/Storages/System/StorageSystemRemoteDataPaths.cpp b/src/Storages/System/StorageSystemRemoteDataPaths.cpp index a482f5d87ca..b224a72e787 100644 --- a/src/Storages/System/StorageSystemRemoteDataPaths.cpp +++ b/src/Storages/System/StorageSystemRemoteDataPaths.cpp @@ -1,7 +1,7 @@ #include "StorageSystemRemoteDataPaths.h" #include #include -#include +#include #include #include #include From 081cd4938ad2f3f8a1cddcbe42d9dc2698e37abf Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 28 Jun 2022 03:35:37 +0800 Subject: [PATCH 538/672] fix style --- src/Common/FileCache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index bccd2dbd458..4d2654d1491 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -24,7 +24,7 @@ namespace DB /** * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. - */ + */ class FileCache : private boost::noncopyable { friend class FileSegment; From 61b580aba449a7d7bb756659c287c3f8fbfc2564 Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 28 Jun 2022 03:50:44 +0800 Subject: [PATCH 539/672] add note --- src/Common/FileCache.h | 5 ++--- src/Common/IFileCachePriority.h | 3 +++ src/Common/LRUFileCachePriority.h | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index 4d2654d1491..f809f57f389 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -22,9 +22,8 @@ namespace DB { -/** - * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. - */ +/// Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. +/// Different caching algorithms are implemented based on IFileCachePriority. class FileCache : private boost::noncopyable { friend class FileSegment; diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 84e1a386d24..568b778d296 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -61,6 +61,7 @@ public: virtual size_t hits() const = 0; + /// Point the iterator to the next higher priority cache record. virtual void next() const = 0; virtual bool valid() const = 0; @@ -72,6 +73,8 @@ public: /// Deletes an existing cached record. virtual void remove(std::lock_guard &) = 0; + /// Get an iterator to handle write operations. Write iterators should only + /// be allowed to call remove, use and incrementSize methods. virtual WriteIterator getWriteIterator() const = 0; virtual void incrementSize(size_t, std::lock_guard &) = 0; diff --git a/src/Common/LRUFileCachePriority.h b/src/Common/LRUFileCachePriority.h index bc9badc0af6..10ad21672dd 100644 --- a/src/Common/LRUFileCachePriority.h +++ b/src/Common/LRUFileCachePriority.h @@ -5,8 +5,8 @@ namespace DB { -/// Based on the LRU algorithm implementation, the data with the lowest priority is stored at -/// the head of the queue, and the data with the highest priority is stored at the tail. +/// Based on the LRU algorithm implementation, the record with the lowest priority is stored at +/// the head of the queue, and the record with the highest priority is stored at the tail. class LRUFileCachePriority : public IFileCachePriority { public: From 164fa1ab0e5a76c5e124df11d5fb12345c0c131a Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 28 Jun 2022 14:36:41 +0800 Subject: [PATCH 540/672] fix build and style --- src/Common/FileCache.cpp | 4 +--- src/Common/FileCache.h | 2 +- src/Common/IFileCachePriority.h | 9 ++------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 2a2fc68e768..8358504c70e 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -234,7 +234,7 @@ void FileCache::initialize() } void FileCache::useCell( - const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock) + const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock) const { auto file_segment = cell.file_segment; @@ -945,8 +945,6 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) || remove_persistent_files || allow_to_remove_persistent_segments_from_cache_by_default)) { - std::lock_guard segment_lock(file_segment->mutex); - file_segment->detach(cache_lock, segment_lock); to_remove.emplace_back(file_segment); } } diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index f809f57f389..8c6a9396b43 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -243,7 +243,7 @@ private: bool is_persistent, std::lock_guard & cache_lock); - void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); + void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock) const; bool tryReserveForMainList( const Key & key, size_t offset, size_t size, QueryContextPtr query_context, std::lock_guard & cache_lock); diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 568b778d296..df3ffd9fd9c 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -11,13 +11,7 @@ namespace DB { -namespace ErrorCodes -{ - extern const int NOT_IMPLEMENTED; -} - class FileCache; - class IFileCachePriority; using FileCachePriorityPtr = std::shared_ptr; @@ -66,7 +60,7 @@ public: virtual bool valid() const = 0; - /// Mark a cache record as recently used, it will update the priority + /// Mark a cache record as recently used, it will update the priority /// of the cache record according to different cache algorithms. virtual void use(std::lock_guard &) = 0; @@ -79,6 +73,7 @@ public: virtual void incrementSize(size_t, std::lock_guard &) = 0; + /// Repoint the iterator to the record with the lowest priority. virtual void seekToLowestPriority() const = 0; }; From 1b01cc8ed943c0053cebe98a7464636f2b643c33 Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Wed, 29 Jun 2022 17:44:38 +0800 Subject: [PATCH 541/672] fix --- src/Common/FileCache.cpp | 38 +++++++++++++------------- src/Common/FileCacheType.h | 5 +++- src/Common/IFileCachePriority.h | 26 ++++++++---------- src/Common/LRUFileCachePriority.h | 45 ++++++++++++++++++++++--------- 4 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 8358504c70e..818cc0c1b76 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -40,11 +39,6 @@ FileCache::FileCache( { } -String FileCache::Key::toString() const -{ - return getHexUIntLowercase(key); -} - FileCache::Key FileCache::hash(const String & path) { return Key(sipHash128(path.data(), path.size())); @@ -323,8 +317,11 @@ FileSegments FileCache::getImpl( if (range.left <= prev_cell_range.right) { + /// segment{k-1} segment{k} /// [________] [_____ /// [___________ + /// ^ + /// range.left useCell(prev_cell, result, cache_lock); } } @@ -562,7 +559,7 @@ FileCache::FileSegmentCell * FileCache::addCell( if (stash_priority->getElementsNum(cache_lock) > max_stash_element_size) { - auto remove_priority_iter = stash_priority->getNewIterator(cache_lock)->getWriteIterator(); + auto remove_priority_iter = stash_priority->getLowestPriorityWriteIterator(cache_lock); stash_records.erase({remove_priority_iter->key(), remove_priority_iter->offset()}); remove_priority_iter->remove(cache_lock); } @@ -648,7 +645,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc auto * cell_for_reserve = getCell(key, offset, cache_lock); - std::vector ghost; + std::vector> ghost; std::vector trash; std::vector to_evict; @@ -660,7 +657,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc }; /// Select the cache from the LRU queue held by query for expulsion. - for (auto iter = query_context->getPriority()->getNewIterator(cache_lock); iter->valid(); iter->next()) + for (auto iter = query_context->getPriority()->getLowestPriorityWriteIterator(cache_lock); iter->valid();) { if (!is_overflow()) break; @@ -671,8 +668,10 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc { /// The cache corresponding to this record may be swapped out by /// other queries, so it has become invalid. - ghost.push_back(iter->getWriteIterator()); removed_size += iter->size(); + ghost.push_back({iter->key(), iter->offset(), iter->size()}); + /// next() + iter->remove(cache_lock); } else { @@ -700,6 +699,8 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc removed_size += cell_size; --queue_size; } + + iter->next(); } } @@ -718,8 +719,8 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc remove_file_segment(file_segment, cell->size()); } - for (auto & iter : ghost) - query_context->remove(iter->key(), iter->offset(), iter->size(), cache_lock); + for (auto & entry : ghost) + query_context->remove(std::get<0>(entry), std::get<1>(entry), std::get<2>(entry), cache_lock); if (is_overflow()) return false; @@ -770,7 +771,7 @@ bool FileCache::tryReserveForMainList( std::vector to_evict; std::vector trash; - for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) + for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { auto entry_key = it->key(); auto entry_offset = it->offset(); @@ -926,9 +927,9 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) std::lock_guard cache_lock(mutex); std::vector to_remove; - for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) + for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { - auto key = it->key(); + const auto & key = it->key(); auto offset = it->offset(); auto * cell = getCell(key, offset, cache_lock); @@ -1247,7 +1248,7 @@ String FileCache::dumpStructure(const Key & key) return dumpStructureUnlocked(key, cache_lock); } -String FileCache::dumpStructureUnlocked(const Key & key, std::lock_guard & cache_lock) +String FileCache::dumpStructureUnlocked(const Key & key, std::lock_guard &) { WriteBufferFromOwnString result; const auto & cells_by_offset = files[key]; @@ -1255,7 +1256,6 @@ String FileCache::dumpStructureUnlocked(const Key & key, std::lock_guardgetInfoForLog() << "\n"; - result << "\n\nPriority: " << main_priority->toString(cache_lock); return result.str(); } @@ -1291,9 +1291,9 @@ void FileCache::assertCacheCorrectness(std::lock_guard & cache_lock) void FileCache::assertPriorityCorrectness(std::lock_guard & cache_lock) { [[maybe_unused]] size_t total_size = 0; - for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) + for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { - auto key = it->key(); + const auto & key = it->key(); auto offset = it->offset(); auto size = it->size(); diff --git a/src/Common/FileCacheType.h b/src/Common/FileCacheType.h index 9b3ec5a6af0..cf4ab5d20c5 100644 --- a/src/Common/FileCacheType.h +++ b/src/Common/FileCacheType.h @@ -1,5 +1,6 @@ #pragma once #include +#include namespace DB { @@ -7,9 +8,11 @@ namespace DB struct FileCacheKey { UInt128 key; - String toString() const; + + String toString() const { return getHexUIntLowercase(key); } FileCacheKey() = default; + explicit FileCacheKey(const UInt128 & key_) : key(key_) { } bool operator==(const FileCacheKey & other) const { return key == other.key; } diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index df3ffd9fd9c..8b448a5ef9d 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -21,13 +21,13 @@ class IFileCachePriority public: class IIterator; friend class IIterator; + friend class FileCache; + + using Key = FileCacheKey; using ReadIterator = std::shared_ptr; using WriteIterator = std::shared_ptr; - friend class FileCache; - using Key = FileCacheKey; - struct FileCacheRecord { Key key; @@ -47,7 +47,7 @@ public: public: virtual ~IIterator() = default; - virtual Key key() const = 0; + virtual const Key & key() const = 0; virtual size_t offset() const = 0; @@ -64,17 +64,11 @@ public: /// of the cache record according to different cache algorithms. virtual void use(std::lock_guard &) = 0; - /// Deletes an existing cached record. + /// Deletes an existing cached record. And to avoid pointer suspension + /// the iterator should automatically point to the next record. virtual void remove(std::lock_guard &) = 0; - /// Get an iterator to handle write operations. Write iterators should only - /// be allowed to call remove, use and incrementSize methods. - virtual WriteIterator getWriteIterator() const = 0; - virtual void incrementSize(size_t, std::lock_guard &) = 0; - - /// Repoint the iterator to the record with the lowest priority. - virtual void seekToLowestPriority() const = 0; }; public: @@ -84,6 +78,7 @@ public: /// logical exception if the cache block already exists. virtual WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) = 0; + /// This method is used for assertions in debug mode. So we do not care about complexity here. /// Query whether a cache record exists. If it exists, return true. If not, return false. virtual bool contains(const Key & key, size_t offset, std::lock_guard & cache_lock) = 0; @@ -91,14 +86,15 @@ public: /// Returns an iterator pointing to the lowest priority cached record. /// We can traverse all cached records through the iterator's next(). - virtual ReadIterator getNewIterator(std::lock_guard & cache_lock) = 0; + virtual ReadIterator getLowestPriorityReadIterator(std::lock_guard & cache_lock) = 0; + + /// The same as getLowestPriorityReadIterator(), but it is writeable. + virtual WriteIterator getLowestPriorityWriteIterator(std::lock_guard & cache_lock) = 0; virtual size_t getElementsNum(std::lock_guard & cache_lock) const = 0; size_t getCacheSize(std::lock_guard &) const { return cache_size; } - virtual std::string toString(std::lock_guard & cache_lock) const = 0; - protected: size_t max_cache_size = 0; size_t cache_size = 0; diff --git a/src/Common/LRUFileCachePriority.h b/src/Common/LRUFileCachePriority.h index 10ad21672dd..ecbe2b47bd8 100644 --- a/src/Common/LRUFileCachePriority.h +++ b/src/Common/LRUFileCachePriority.h @@ -5,11 +5,16 @@ namespace DB { +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + /// Based on the LRU algorithm implementation, the record with the lowest priority is stored at /// the head of the queue, and the record with the highest priority is stored at the tail. class LRUFileCachePriority : public IFileCachePriority { -public: +private: using LRUQueue = std::list; using LRUQueueIterator = typename LRUQueue::iterator; @@ -23,9 +28,9 @@ public: void next() const override { queue_iter++; } - bool valid() const override { return (file_cache->queue.size() && (queue_iter != file_cache->queue.end())); } + bool valid() const override { return queue_iter != file_cache->queue.end(); } - Key key() const override { return queue_iter->key; } + const Key & key() const override { return queue_iter->key; } size_t offset() const override { return queue_iter->offset; } @@ -33,14 +38,12 @@ public: size_t hits() const override { return queue_iter->hits; } - WriteIterator getWriteIterator() const override { return std::make_shared(file_cache, queue_iter); } - - void seekToLowestPriority() const override { queue_iter = file_cache->queue.begin(); } - void remove(std::lock_guard &) override { - file_cache->cache_size -= queue_iter->size; - file_cache->queue.erase(queue_iter); + auto remove_iter = queue_iter; + queue_iter++; + file_cache->cache_size -= remove_iter->size; + file_cache->queue.erase(remove_iter); } void incrementSize(size_t size_increment, std::lock_guard &) override @@ -65,6 +68,18 @@ public: WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override { +#ifndef NDEBUG + for (const auto & entry : queue) + { + if (entry.key() == key && entry.offset() == offset) + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Attempt to add duplicate queue entry to queue. (Key: {}, offset: {}, size: {})", + entry.key().toString(), + entry.offset(), + entry.size()); + } +#endif auto iter = queue.insert(queue.end(), FileCacheRecord(key, offset, size)); cache_size += size; return std::make_shared(this, iter); @@ -86,14 +101,20 @@ public: cache_size = 0; } - ReadIterator getNewIterator(std::lock_guard &) override + ReadIterator getLowestPriorityReadIterator(std::lock_guard &) override { return std::make_shared(this, queue.begin()); } - size_t getElementsNum(std::lock_guard &) const override { return queue.size(); } + WriteIterator getLowestPriorityWriteIterator(std::lock_guard &) override + { + return std::make_shared(this, queue.begin()); + } - std::string toString(std::lock_guard &) const override { return {}; } + size_t getElementsNum(std::lock_guard &) const override + { + return queue.size(); + } private: LRUQueue queue; From d2b5581632e8025a605d7832212d689a4fd85266 Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Wed, 29 Jun 2022 21:28:19 +0800 Subject: [PATCH 542/672] fix --- src/Common/FileCache.cpp | 2 +- src/Common/IFileCachePriority.h | 1 - src/Common/LRUFileCachePriority.cpp | 62 ++++++++++++ src/Common/LRUFileCachePriority.h | 146 ++++++++++------------------ 4 files changed, 115 insertions(+), 96 deletions(-) create mode 100644 src/Common/LRUFileCachePriority.cpp diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 818cc0c1b76..f49f441969c 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -773,7 +773,7 @@ bool FileCache::tryReserveForMainList( for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { - auto entry_key = it->key(); + const auto & entry_key = it->key(); auto entry_offset = it->offset(); if (!is_overflow()) diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 8b448a5ef9d..691f23a1b54 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -24,7 +24,6 @@ public: friend class FileCache; using Key = FileCacheKey; - using ReadIterator = std::shared_ptr; using WriteIterator = std::shared_ptr; diff --git a/src/Common/LRUFileCachePriority.cpp b/src/Common/LRUFileCachePriority.cpp new file mode 100644 index 00000000000..ed13e58e5fd --- /dev/null +++ b/src/Common/LRUFileCachePriority.cpp @@ -0,0 +1,62 @@ +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +IFileCachePriority::WriteIterator +LRUFileCachePriority::add(const Key & key, size_t offset, size_t size, std::lock_guard &) override +{ +#ifndef NDEBUG + for (const auto & entry : queue) + { + if (entry.key == key && entry.offset == offset) + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Attempt to add duplicate queue entry to queue. (Key: {}, offset: {}, size: {})", + entry.key.toString(), + entry.offset, + entry.size); + } +#endif + auto iter = queue.insert(queue.end(), FileCacheRecord(key, offset, size)); + cache_size += size; + return std::make_shared(this, iter); +} + +bool LRUFileCachePriority::contains(const Key & key, size_t offset, std::lock_guard &) override +{ + for (const auto & record : queue) + { + if (key == record.key && offset == record.offset) + return true; + } + return false; +} + +void LRUFileCachePriority::removeAll(std::lock_guard &) override +{ + queue.clear(); + cache_size = 0; +} + +IFileCachePriority::ReadIterator LRUFileCachePriority::getLowestPriorityReadIterator(std::lock_guard &) override +{ + return std::make_shared(this, queue.begin()); +} + +IFileCachePriority::WriteIterator LRUFileCachePriority::getLowestPriorityWriteIterator(std::lock_guard &) override +{ + return std::make_shared(this, queue.begin()); +} + +size_t LRUFileCachePriority::getElementsNum(std::lock_guard &) const override +{ + return queue.size(); +} + +}; diff --git a/src/Common/LRUFileCachePriority.h b/src/Common/LRUFileCachePriority.h index ecbe2b47bd8..20d1e05b9f0 100644 --- a/src/Common/LRUFileCachePriority.h +++ b/src/Common/LRUFileCachePriority.h @@ -5,11 +5,6 @@ namespace DB { -namespace ErrorCodes -{ - extern const int LOGICAL_ERROR; -} - /// Based on the LRU algorithm implementation, the record with the lowest priority is stored at /// the head of the queue, and the record with the highest priority is stored at the tail. class LRUFileCachePriority : public IFileCachePriority @@ -17,107 +12,70 @@ class LRUFileCachePriority : public IFileCachePriority private: using LRUQueue = std::list; using LRUQueueIterator = typename LRUQueue::iterator; - - class LRUFileCacheIterator : public IIterator - { - public: - LRUFileCacheIterator(LRUFileCachePriority * file_cache_, LRUQueueIterator queue_iter_) - : file_cache(file_cache_), queue_iter(queue_iter_) - { - } - - void next() const override { queue_iter++; } - - bool valid() const override { return queue_iter != file_cache->queue.end(); } - - const Key & key() const override { return queue_iter->key; } - - size_t offset() const override { return queue_iter->offset; } - - size_t size() const override { return queue_iter->size; } - - size_t hits() const override { return queue_iter->hits; } - - void remove(std::lock_guard &) override - { - auto remove_iter = queue_iter; - queue_iter++; - file_cache->cache_size -= remove_iter->size; - file_cache->queue.erase(remove_iter); - } - - void incrementSize(size_t size_increment, std::lock_guard &) override - { - file_cache->cache_size += size_increment; - queue_iter->size += size_increment; - } - - void use(std::lock_guard &) override - { - queue_iter->hits++; - file_cache->queue.splice(file_cache->queue.end(), file_cache->queue, queue_iter); - } - - private: - mutable LRUFileCachePriority * file_cache; - mutable LRUQueueIterator queue_iter; - }; + class LRUFileCacheIterator; public: LRUFileCachePriority() = default; - WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override - { -#ifndef NDEBUG - for (const auto & entry : queue) - { - if (entry.key() == key && entry.offset() == offset) - throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Attempt to add duplicate queue entry to queue. (Key: {}, offset: {}, size: {})", - entry.key().toString(), - entry.offset(), - entry.size()); - } -#endif - auto iter = queue.insert(queue.end(), FileCacheRecord(key, offset, size)); - cache_size += size; - return std::make_shared(this, iter); - } + WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override; - bool contains(const Key & key, size_t offset, std::lock_guard &) override - { - for (const auto & record : queue) - { - if (key == record.key && offset == record.offset) - return true; - } - return false; - } + bool contains(const Key & key, size_t offset, std::lock_guard &) override; - void removeAll(std::lock_guard &) override - { - queue.clear(); - cache_size = 0; - } + void removeAll(std::lock_guard &) override; - ReadIterator getLowestPriorityReadIterator(std::lock_guard &) override - { - return std::make_shared(this, queue.begin()); - } + ReadIterator getLowestPriorityReadIterator(std::lock_guard &) override; - WriteIterator getLowestPriorityWriteIterator(std::lock_guard &) override - { - return std::make_shared(this, queue.begin()); - } + WriteIterator getLowestPriorityWriteIterator(std::lock_guard &) override; - size_t getElementsNum(std::lock_guard &) const override - { - return queue.size(); - } + size_t getElementsNum(std::lock_guard &) const override; private: LRUQueue queue; }; +class LRUFileCachePriority::LRUFileCacheIterator : public IFileCachePriority::IIterator +{ +public: + LRUFileCacheIterator(LRUFileCachePriority * file_cache_, LRUFileCachePriority::LRUQueueIterator queue_iter_) + : file_cache(file_cache_), queue_iter(queue_iter_) + { + } + + void next() const override { queue_iter++; } + + bool valid() const override { return queue_iter != file_cache->queue.end(); } + + const Key & key() const override { return queue_iter->key; } + + size_t offset() const override { return queue_iter->offset; } + + size_t size() const override { return queue_iter->size; } + + size_t hits() const override { return queue_iter->hits; } + + void remove(std::lock_guard &) override + { + auto remove_iter = queue_iter; + queue_iter++; + file_cache->cache_size -= remove_iter->size; + file_cache->queue.erase(remove_iter); + } + + void incrementSize(size_t size_increment, std::lock_guard &) override + { + file_cache->cache_size += size_increment; + queue_iter->size += size_increment; + } + + void use(std::lock_guard &) override + { + queue_iter->hits++; + file_cache->queue.splice(file_cache->queue.end(), file_cache->queue, queue_iter); + } + +private: + mutable LRUFileCachePriority * file_cache; + mutable LRUFileCachePriority::LRUQueueIterator queue_iter; +}; + }; From f6a58bff4ca1ef3ada0b0b942f675a812dec6e47 Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Wed, 29 Jun 2022 22:45:34 +0800 Subject: [PATCH 543/672] fix build --- src/Common/IFileCachePriority.h | 6 ------ src/Common/LRUFileCachePriority.cpp | 13 ++++++------- src/Common/LRUFileCachePriority.h | 3 ++- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 691f23a1b54..fe925fd275d 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -1,17 +1,14 @@ #pragma once -#include #include #include #include #include -#include #include namespace DB { -class FileCache; class IFileCachePriority; using FileCachePriorityPtr = std::shared_ptr; @@ -20,9 +17,6 @@ class IFileCachePriority { public: class IIterator; - friend class IIterator; - friend class FileCache; - using Key = FileCacheKey; using ReadIterator = std::shared_ptr; using WriteIterator = std::shared_ptr; diff --git a/src/Common/LRUFileCachePriority.cpp b/src/Common/LRUFileCachePriority.cpp index ed13e58e5fd..c54b65f6ee0 100644 --- a/src/Common/LRUFileCachePriority.cpp +++ b/src/Common/LRUFileCachePriority.cpp @@ -8,8 +8,7 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -IFileCachePriority::WriteIterator -LRUFileCachePriority::add(const Key & key, size_t offset, size_t size, std::lock_guard &) override +IFileCachePriority::WriteIterator LRUFileCachePriority::add(const Key & key, size_t offset, size_t size, std::lock_guard &) { #ifndef NDEBUG for (const auto & entry : queue) @@ -28,7 +27,7 @@ LRUFileCachePriority::add(const Key & key, size_t offset, size_t size, std::lock return std::make_shared(this, iter); } -bool LRUFileCachePriority::contains(const Key & key, size_t offset, std::lock_guard &) override +bool LRUFileCachePriority::contains(const Key & key, size_t offset, std::lock_guard &) { for (const auto & record : queue) { @@ -38,23 +37,23 @@ bool LRUFileCachePriority::contains(const Key & key, size_t offset, std::lock_gu return false; } -void LRUFileCachePriority::removeAll(std::lock_guard &) override +void LRUFileCachePriority::removeAll(std::lock_guard &) { queue.clear(); cache_size = 0; } -IFileCachePriority::ReadIterator LRUFileCachePriority::getLowestPriorityReadIterator(std::lock_guard &) override +IFileCachePriority::ReadIterator LRUFileCachePriority::getLowestPriorityReadIterator(std::lock_guard &) { return std::make_shared(this, queue.begin()); } -IFileCachePriority::WriteIterator LRUFileCachePriority::getLowestPriorityWriteIterator(std::lock_guard &) override +IFileCachePriority::WriteIterator LRUFileCachePriority::getLowestPriorityWriteIterator(std::lock_guard &) { return std::make_shared(this, queue.begin()); } -size_t LRUFileCachePriority::getElementsNum(std::lock_guard &) const override +size_t LRUFileCachePriority::getElementsNum(std::lock_guard &) const { return queue.size(); } diff --git a/src/Common/LRUFileCachePriority.h b/src/Common/LRUFileCachePriority.h index 20d1e05b9f0..250a55480f9 100644 --- a/src/Common/LRUFileCachePriority.h +++ b/src/Common/LRUFileCachePriority.h @@ -1,5 +1,6 @@ #pragma once +#include #include namespace DB @@ -10,9 +11,9 @@ namespace DB class LRUFileCachePriority : public IFileCachePriority { private: + class LRUFileCacheIterator; using LRUQueue = std::list; using LRUQueueIterator = typename LRUQueue::iterator; - class LRUFileCacheIterator; public: LRUFileCachePriority() = default; From fbaa70b3130e1ae66ec0fdefed5f2921dc55c9cf Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Thu, 28 Jul 2022 13:23:57 +0800 Subject: [PATCH 544/672] fix --- src/Common/FileCache.cpp | 4 ++-- src/Disks/ObjectStorages/LocalObjectStorage.cpp | 2 +- src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index f49f441969c..2d13c23e837 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -63,7 +63,7 @@ static bool isQueryInitialized() { return CurrentThread::isInitialized() && CurrentThread::get().getQueryContext() - && CurrentThread::getQueryId().size != 0; + && CurrentThread::getQueryId().size() != 0; } bool FileCache::isReadOnly() @@ -82,7 +82,7 @@ FileCache::QueryContextPtr FileCache::getCurrentQueryContext(std::lock_guard & /* cache_lock */) diff --git a/src/Disks/ObjectStorages/LocalObjectStorage.cpp b/src/Disks/ObjectStorages/LocalObjectStorage.cpp index c052f2f0d77..af6dab0b8a6 100644 --- a/src/Disks/ObjectStorages/LocalObjectStorage.cpp +++ b/src/Disks/ObjectStorages/LocalObjectStorage.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp index 901deeebefc..e017e19c06c 100644 --- a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp +++ b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp @@ -128,7 +128,7 @@ void S3ObjectStorage::removeCacheIfExists(const std::string & path_key) if (!cache || path_key.empty()) return; - IFileCache::Key key = cache->hash(path_key); + FileCache::Key key = cache->hash(path_key); cache->removeIfExists(key); } @@ -500,7 +500,7 @@ ReadSettings S3ObjectStorage::patchSettings(const ReadSettings & read_settings) ReadSettings settings{read_settings}; if (cache) { - if (IFileCache::isReadOnly()) + if (FileCache::isReadOnly()) settings.read_from_filesystem_cache_if_exists_otherwise_bypass_cache = true; settings.remote_fs_cache = cache; From 76e0aad69e361e9a25823b7c9287b785a5111517 Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Tue, 9 Aug 2022 20:21:57 +0800 Subject: [PATCH 545/672] fix --- src/Common/FileCache.cpp | 32 ++++++++++++------- src/Common/FileCache.h | 4 +-- src/Common/IFileCachePriority.h | 2 +- src/Common/LRUFileCachePriority.h | 10 +++--- src/Disks/IO/ReadBufferFromRemoteFSGather.cpp | 2 +- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 2d13c23e837..d97d20310c8 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -30,8 +30,8 @@ FileCache::FileCache( , max_element_size(cache_settings_.max_elements) , max_file_segment_size(cache_settings_.max_file_segment_size) , enable_filesystem_query_cache_limit(cache_settings_.enable_filesystem_query_cache_limit) - , main_priority(std::make_shared()) - , stash_priority(std::make_shared()) + , main_priority(std::make_unique()) + , stash_priority(std::make_unique()) , max_stash_element_size(cache_settings_.max_elements) , enable_cache_hits_threshold(cache_settings_.enable_cache_hits_threshold) , log(&Poco::Logger::get("FileCache")) @@ -145,7 +145,7 @@ void FileCache::QueryContext::remove(const Key & key, size_t offset, size_t size auto record = records.find({key, offset}); if (record != records.end()) { - record->second->remove(cache_lock); + record->second->removeAndGetNext(cache_lock); records.erase({key, offset}); } } @@ -561,7 +561,7 @@ FileCache::FileSegmentCell * FileCache::addCell( { auto remove_priority_iter = stash_priority->getLowestPriorityWriteIterator(cache_lock); stash_records.erase({remove_priority_iter->key(), remove_priority_iter->offset()}); - remove_priority_iter->remove(cache_lock); + remove_priority_iter->removeAndGetNext(cache_lock); } /// For segments that do not reach the download threshold, we do not download them, but directly read them result_state = FileSegment::State::SKIP_CACHE; @@ -645,7 +645,17 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc auto * cell_for_reserve = getCell(key, offset, cache_lock); - std::vector> ghost; + struct Segment + { + Key key; + size_t offset; + size_t size; + + Segment(Key key_, size_t offset_, size_t size_) + : key(key_), offset(offset_), size(size_) {} + }; + + std::vector ghost; std::vector trash; std::vector to_evict; @@ -669,9 +679,9 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc /// The cache corresponding to this record may be swapped out by /// other queries, so it has become invalid. removed_size += iter->size(); - ghost.push_back({iter->key(), iter->offset(), iter->size()}); + ghost.push_back(Segment(iter->key(), iter->offset(), iter->size())); /// next() - iter->remove(cache_lock); + iter->removeAndGetNext(cache_lock); } else { @@ -720,7 +730,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc } for (auto & entry : ghost) - query_context->remove(std::get<0>(entry), std::get<1>(entry), std::get<2>(entry), cache_lock); + query_context->remove(entry.key, entry.offset, entry.size, cache_lock); if (is_overflow()) return false; @@ -926,7 +936,7 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) std::lock_guard cache_lock(mutex); - std::vector to_remove; + std::vector to_remove; for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { const auto & key = it->key(); @@ -946,7 +956,7 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) || remove_persistent_files || allow_to_remove_persistent_segments_from_cache_by_default)) { - to_remove.emplace_back(file_segment); + to_remove.emplace_back(file_segment.get()); } } } @@ -981,7 +991,7 @@ void FileCache::remove( if (cell->queue_iterator) { - cell->queue_iterator->remove(cache_lock); + cell->queue_iterator->removeAndGetNext(cache_lock); } auto & offsets = files[key]; diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index 8c6a9396b43..7a25632be68 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -220,10 +220,10 @@ private: using CachedFiles = std::unordered_map; CachedFiles files; - FileCachePriorityPtr main_priority; + std::unique_ptr main_priority; FileCacheRecords stash_records; - FileCachePriorityPtr stash_priority; + std::unique_ptr stash_priority; size_t max_stash_element_size; size_t enable_cache_hits_threshold; diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index fe925fd275d..59ce3c0aebb 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -59,7 +59,7 @@ public: /// Deletes an existing cached record. And to avoid pointer suspension /// the iterator should automatically point to the next record. - virtual void remove(std::lock_guard &) = 0; + virtual void removeAndGetNext(std::lock_guard &) = 0; virtual void incrementSize(size_t, std::lock_guard &) = 0; }; diff --git a/src/Common/LRUFileCachePriority.h b/src/Common/LRUFileCachePriority.h index 250a55480f9..0f5755e1cb8 100644 --- a/src/Common/LRUFileCachePriority.h +++ b/src/Common/LRUFileCachePriority.h @@ -54,12 +54,10 @@ public: size_t hits() const override { return queue_iter->hits; } - void remove(std::lock_guard &) override + void removeAndGetNext(std::lock_guard &) override { - auto remove_iter = queue_iter; - queue_iter++; - file_cache->cache_size -= remove_iter->size; - file_cache->queue.erase(remove_iter); + file_cache->cache_size -= queue_iter->size; + queue_iter = file_cache->queue.erase(queue_iter); } void incrementSize(size_t size_increment, std::lock_guard &) override @@ -75,7 +73,7 @@ public: } private: - mutable LRUFileCachePriority * file_cache; + LRUFileCachePriority * file_cache; mutable LRUFileCachePriority::LRUQueueIterator queue_iter; }; diff --git a/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp b/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp index 3ac4ea07945..f21e2bd7642 100644 --- a/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp +++ b/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp @@ -35,7 +35,7 @@ ReadBufferFromRemoteFSGather::ReadBufferFromRemoteFSGather( with_cache = settings.remote_fs_cache && settings.enable_filesystem_cache - && (!IFileCache::isReadOnly() || settings.read_from_filesystem_cache_if_exists_otherwise_bypass_cache); + && (!FileCache::isReadOnly() || settings.read_from_filesystem_cache_if_exists_otherwise_bypass_cache); } SeekableReadBufferPtr ReadBufferFromRemoteFSGather::createImplementationBuffer(const String & path, size_t file_size) From 2ae02a49214f86ff2f00e59a6d599e21db7dc2ee Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Tue, 9 Aug 2022 20:38:49 +0800 Subject: [PATCH 546/672] fix style --- src/Common/FileCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index d97d20310c8..2c8e62ac124 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -651,7 +651,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc size_t offset; size_t size; - Segment(Key key_, size_t offset_, size_t size_) + Segment(Key key_, size_t offset_, size_t size_) : key(key_), offset(offset_), size(size_) {} }; From 9ba94e64f97d738e17c0d99dfb5ed7c556ac9956 Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Wed, 10 Aug 2022 16:11:06 +0800 Subject: [PATCH 547/672] fix --- src/Common/FileCache.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 2c8e62ac124..5a0145f0018 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -936,7 +936,7 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) std::lock_guard cache_lock(mutex); - std::vector to_remove; + std::vector to_remove; for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { const auto & key = it->key(); @@ -956,7 +956,7 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) || remove_persistent_files || allow_to_remove_persistent_segments_from_cache_by_default)) { - to_remove.emplace_back(file_segment.get()); + to_remove.emplace_back(file_segment); } } } From 9b7f87677dd4fa8f09c4c58c90630941a4d97746 Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Wed, 10 Aug 2022 22:04:43 +0800 Subject: [PATCH 548/672] fix --- src/Common/FileCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 5a0145f0018..47b7d57ae66 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -63,7 +63,7 @@ static bool isQueryInitialized() { return CurrentThread::isInitialized() && CurrentThread::get().getQueryContext() - && CurrentThread::getQueryId().size() != 0; + && !CurrentThread::getQueryId().empty(); } bool FileCache::isReadOnly() From 1aa7bbcbbd8b8c0bbd4f4dca6bc35ebde7837945 Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Wed, 10 Aug 2022 23:19:26 +0800 Subject: [PATCH 549/672] fix unique_ptr --- src/Common/IFileCachePriority.h | 2 +- src/Common/LRUFileCachePriority.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 59ce3c0aebb..f80266f9eea 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -18,7 +18,7 @@ class IFileCachePriority public: class IIterator; using Key = FileCacheKey; - using ReadIterator = std::shared_ptr; + using ReadIterator = std::unique_ptr; using WriteIterator = std::shared_ptr; struct FileCacheRecord diff --git a/src/Common/LRUFileCachePriority.cpp b/src/Common/LRUFileCachePriority.cpp index c54b65f6ee0..b4c4bfa338b 100644 --- a/src/Common/LRUFileCachePriority.cpp +++ b/src/Common/LRUFileCachePriority.cpp @@ -45,7 +45,7 @@ void LRUFileCachePriority::removeAll(std::lock_guard &) IFileCachePriority::ReadIterator LRUFileCachePriority::getLowestPriorityReadIterator(std::lock_guard &) { - return std::make_shared(this, queue.begin()); + return std::make_unique(this, queue.begin()); } IFileCachePriority::WriteIterator LRUFileCachePriority::getLowestPriorityWriteIterator(std::lock_guard &) From 7aff85fc798cd26732a31d80650d9e9c9833bf3b Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 10 Aug 2022 19:41:29 +0200 Subject: [PATCH 550/672] Fix test Dockerfile --- docker/test/stateless/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/test/stateless/Dockerfile b/docker/test/stateless/Dockerfile index 9141e89d744..c5d1ec4f12d 100644 --- a/docker/test/stateless/Dockerfile +++ b/docker/test/stateless/Dockerfile @@ -35,6 +35,7 @@ RUN apt-get update -y \ wget \ zstd \ file \ + pv \ && apt-get clean From 774ba4db230c2e098f5eb378e5c97a745b846a8e Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 10 Aug 2022 19:44:13 +0200 Subject: [PATCH 551/672] Remove old code --- docker/test/stateless_pytest/Dockerfile | 33 ------------------------- 1 file changed, 33 deletions(-) delete mode 100644 docker/test/stateless_pytest/Dockerfile diff --git a/docker/test/stateless_pytest/Dockerfile b/docker/test/stateless_pytest/Dockerfile deleted file mode 100644 index 789ee0e9b30..00000000000 --- a/docker/test/stateless_pytest/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -# rebuild in #33610 -# docker build -t clickhouse/stateless-pytest . -ARG FROM_TAG=latest -FROM clickhouse/test-base:$FROM_TAG - -RUN apt-get update -y && \ - apt-get install -y --no-install-recommends \ - python3-pip \ - python3-setuptools \ - python3-wheel \ - brotli \ - netcat-openbsd \ - postgresql-client \ - zstd - -RUN python3 -m pip install \ - wheel \ - pytest \ - pytest-html \ - pytest-json \ - pytest-randomly \ - pytest-rerunfailures \ - pytest-timeout \ - pytest-xdist \ - pandas \ - numpy \ - scipy - -CMD dpkg -i package_folder/clickhouse-common-static_*.deb; \ - dpkg -i package_folder/clickhouse-common-static-dbg_*.deb; \ - dpkg -i package_folder/clickhouse-server_*.deb; \ - dpkg -i package_folder/clickhouse-client_*.deb; \ - python3 -m pytest /usr/share/clickhouse-test/queries -n $(nproc) --reruns=1 --timeout=600 --json=test_output/report.json --html=test_output/report.html --self-contained-html From bb46bfa6d9e28cf4f48c9796e86f09a22314c07a Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 10 Aug 2022 21:20:52 +0300 Subject: [PATCH 552/672] Update process_functional_tests_result.py --- docker/test/util/process_functional_tests_result.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/util/process_functional_tests_result.py b/docker/test/util/process_functional_tests_result.py index 647989e8421..28f3e211157 100755 --- a/docker/test/util/process_functional_tests_result.py +++ b/docker/test/util/process_functional_tests_result.py @@ -86,7 +86,7 @@ def process_test_log(log_path): test_end = True test_results = [ - (test[0], test[1], test[2], "".join(test[3]))[:4096] for test in test_results + (test[0], test[1], test[2], "".join(test[3])[:4096]) for test in test_results ] return ( From ac371818f76e5469c859234b3668d530a64cf231 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 10 Aug 2022 20:21:15 +0200 Subject: [PATCH 553/672] Fix _csv.Error: field larger than field limit (131072) --- tests/ci/fast_test_check.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/ci/fast_test_check.py b/tests/ci/fast_test_check.py index 9852175ca92..6e3621f4e9d 100644 --- a/tests/ci/fast_test_check.py +++ b/tests/ci/fast_test_check.py @@ -31,6 +31,9 @@ from ccache_utils import get_ccache_if_not_exists, upload_ccache NAME = "Fast test" +# Will help to avoid errors like _csv.Error: field larger than field limit (131072) +csv.field_size_limit(sys.maxsize) + def get_fasttest_cmd( workspace, output_path, ccache_path, repo_path, pr_number, commit_sha, image From 9284b9b42f62fb84795652a1b58f656734102966 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 10 Aug 2022 20:25:51 +0200 Subject: [PATCH 554/672] tests: fix 00926_adaptive_index_granularity_pk/00489_pk_subexpression flakiness It is possible for toStartOfMinute() to give different result for 0 and 59, for partial timezones (timezone that does not starts from 00:00, like Africa/Monrovia). Before #36656 it fails for another reason, because of overflows [1], but now it fails because it simply return different minutes. [1]: https://github.com/ClickHouse/ClickHouse/pull/29953#discussion_r800550280 Simply pin the UTC there. Fixes: #37786 Signed-off-by: Azat Khuzhin --- .../0_stateless/00489_pk_subexpression.sql | 15 ++++++++++++++- .../00926_adaptive_index_granularity_pk.sql | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/00489_pk_subexpression.sql b/tests/queries/0_stateless/00489_pk_subexpression.sql index 6f76a13609c..700581a9def 100644 --- a/tests/queries/0_stateless/00489_pk_subexpression.sql +++ b/tests/queries/0_stateless/00489_pk_subexpression.sql @@ -1,7 +1,20 @@ DROP TABLE IF EXISTS pk; set allow_deprecated_syntax_for_merge_tree=1; -CREATE TABLE pk (d Date DEFAULT '2000-01-01', x DateTime, y UInt64, z UInt64) ENGINE = MergeTree(d, (toStartOfMinute(x), y, z), 1); +-- NOTE: here the timezone is pinned to UTC, to avoid issues with "partial +-- timezones" (timezones that does not starts from 00:00), like +-- Africa/Monrovia, for which toStartOfMinute(0) and toStartOfMinute(59) can +-- give different values: +-- +-- SELECT +-- toDateTime(0, 'Africa/Monrovia') AS sec0, +-- toDateTime(59, 'Africa/Monrovia') AS sec59 +-- +-- ┌────────────────sec0─┬───────────────sec59─┐ +-- │ 1969-12-31 23:15:30 │ 1969-12-31 23:16:29 │ +-- └─────────────────────┴─────────────────────┘ +-- +CREATE TABLE pk (d Date DEFAULT '2000-01-01', x DateTime, y UInt64, z UInt64) ENGINE = MergeTree(d, (toStartOfMinute(x, 'UTC'), y, z), 1); INSERT INTO pk (x, y, z) VALUES (1, 11, 1235), (2, 11, 4395), (3, 22, 3545), (4, 22, 6984), (5, 33, 4596), (61, 11, 4563), (62, 11, 4578), (63, 11, 3572), (64, 22, 5786), (65, 22, 5786), (66, 22, 2791), (67, 22, 2791), (121, 33, 2791), (122, 33, 2791), (123, 33, 1235), (124, 44, 4935), (125, 44, 4578), (126, 55, 5786), (127, 55, 2791), (128, 55, 1235); diff --git a/tests/queries/0_stateless/00926_adaptive_index_granularity_pk.sql b/tests/queries/0_stateless/00926_adaptive_index_granularity_pk.sql index ba34cfad299..47e1d3fea0f 100644 --- a/tests/queries/0_stateless/00926_adaptive_index_granularity_pk.sql +++ b/tests/queries/0_stateless/00926_adaptive_index_granularity_pk.sql @@ -4,7 +4,20 @@ SET send_logs_level = 'fatal'; SELECT '----00489----'; DROP TABLE IF EXISTS pk; -CREATE TABLE pk (d Date DEFAULT '2000-01-01', x DateTime, y UInt64, z UInt64) ENGINE = MergeTree() PARTITION BY d ORDER BY (toStartOfMinute(x), y, z) SETTINGS index_granularity_bytes=19, min_index_granularity_bytes=9, write_final_mark = 0; -- one row granule +-- NOTE: here the timezone is pinned to UTC, to avoid issues with "partial +-- timezones" (timezones that does not starts from 00:00), like +-- Africa/Monrovia, for which toStartOfMinute(0) and toStartOfMinute(59) can +-- give different values: +-- +-- SELECT +-- toDateTime(0, 'Africa/Monrovia') AS sec0, +-- toDateTime(59, 'Africa/Monrovia') AS sec59 +-- +-- ┌────────────────sec0─┬───────────────sec59─┐ +-- │ 1969-12-31 23:15:30 │ 1969-12-31 23:16:29 │ +-- └─────────────────────┴─────────────────────┘ +-- +CREATE TABLE pk (d Date DEFAULT '2000-01-01', x DateTime, y UInt64, z UInt64) ENGINE = MergeTree() PARTITION BY d ORDER BY (toStartOfMinute(x, 'UTC'), y, z) SETTINGS index_granularity_bytes=19, min_index_granularity_bytes=9, write_final_mark = 0; -- one row granule INSERT INTO pk (x, y, z) VALUES (1, 11, 1235), (2, 11, 4395), (3, 22, 3545), (4, 22, 6984), (5, 33, 4596), (61, 11, 4563), (62, 11, 4578), (63, 11, 3572), (64, 22, 5786), (65, 22, 5786), (66, 22, 2791), (67, 22, 2791), (121, 33, 2791), (122, 33, 2791), (123, 33, 1235), (124, 44, 4935), (125, 44, 4578), (126, 55, 5786), (127, 55, 2791), (128, 55, 1235); From 9f85d85e089e176ef0b784802048d090c1aa3577 Mon Sep 17 00:00:00 2001 From: Rich Raposa Date: Wed, 10 Aug 2022 13:08:40 -0600 Subject: [PATCH 555/672] The admonitions were missing section endings (#40073) --- docs/ru/development/build-osx.md | 2 ++ docs/ru/engines/table-engines/integrations/postgresql.md | 1 + .../mergetree-family/custom-partitioning-key.md | 2 ++ docs/ru/faq/general/dbms-naming.md | 1 + docs/ru/faq/general/index.md | 1 + docs/ru/faq/general/why-clickhouse-is-so-fast.md | 1 + docs/ru/faq/integration/index.md | 1 + docs/ru/faq/operations/index.md | 1 + docs/ru/getting-started/example-datasets/nyc-taxi.md | 1 + docs/ru/getting-started/example-datasets/ontime.md | 1 + docs/ru/operations/external-authenticators/kerberos.md | 1 + .../operations/server-configuration-parameters/settings.md | 4 ++++ docs/ru/operations/system-tables/mutations.md | 1 + .../sql-reference/aggregate-functions/reference/deltasum.md | 1 + .../aggregate-functions/reference/intervalLengthSum.md | 1 + docs/ru/sql-reference/data-types/nullable.md | 1 + .../external-dictionaries/external-dicts-dict-sources.md | 5 +++++ docs/ru/sql-reference/functions/geo/geohash.md | 2 ++ docs/ru/sql-reference/functions/type-conversion-functions.md | 1 + docs/ru/sql-reference/statements/alter/index.md | 3 ++- docs/ru/sql-reference/statements/create/user.md | 1 + docs/ru/sql-reference/statements/create/view.md | 1 + docs/ru/sql-reference/statements/optimize.md | 1 + docs/ru/sql-reference/statements/show.md | 1 + docs/ru/sql-reference/statements/truncate.md | 1 + docs/ru/sql-reference/statements/watch.md | 1 + docs/ru/sql-reference/table-functions/postgresql.md | 2 ++ 27 files changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/ru/development/build-osx.md b/docs/ru/development/build-osx.md index cc6927bebb8..205edce2b78 100644 --- a/docs/ru/development/build-osx.md +++ b/docs/ru/development/build-osx.md @@ -8,6 +8,7 @@ sidebar_label: Сборка на Mac OS X :::info "Вам не нужно собирать ClickHouse самостоятельно" Вы можете установить предварительно собранный ClickHouse, как описано в [Быстром старте](https://clickhouse.com/#quick-start). Следуйте инструкциям по установке для `macOS (Intel)` или `macOS (Apple Silicon)`. +::: Сборка должна запускаться с x86_64 (Intel) на macOS версии 10.15 (Catalina) и выше в последней версии компилятора Xcode's native AppleClang, Homebrew's vanilla Clang или в GCC-компиляторах. @@ -90,6 +91,7 @@ $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/ :::info "Note" Вам понадобится команда `sudo`. +::: 1. Создайте файл `/Library/LaunchDaemons/limit.maxfiles.plist` и поместите в него следующее: diff --git a/docs/ru/engines/table-engines/integrations/postgresql.md b/docs/ru/engines/table-engines/integrations/postgresql.md index f9702930a30..28debaf9c23 100644 --- a/docs/ru/engines/table-engines/integrations/postgresql.md +++ b/docs/ru/engines/table-engines/integrations/postgresql.md @@ -49,6 +49,7 @@ PostgreSQL массивы конвертируются в массивы ClickHo :::info "Внимание" Будьте внимательны, в PostgreSQL массивы, созданные как `type_name[]`, являются многомерными и могут содержать в себе разное количество измерений в разных строках одной таблицы. Внутри ClickHouse допустимы только многомерные массивы с одинаковым кол-вом измерений во всех строках таблицы. +::: Поддерживает несколько реплик, которые должны быть перечислены через `|`. Например: diff --git a/docs/ru/engines/table-engines/mergetree-family/custom-partitioning-key.md b/docs/ru/engines/table-engines/mergetree-family/custom-partitioning-key.md index 6b5d2e862bf..e30e771c4df 100644 --- a/docs/ru/engines/table-engines/mergetree-family/custom-partitioning-key.md +++ b/docs/ru/engines/table-engines/mergetree-family/custom-partitioning-key.md @@ -40,6 +40,7 @@ ORDER BY (CounterID, StartDate, intHash32(UserID)); :::info "Info" Не рекомендуется делать слишком гранулированное партиционирование – то есть задавать партиции по столбцу, в котором будет слишком большой разброс значений (речь идет о порядке более тысячи партиций). Это приведет к скоплению большого числа файлов и файловых дескрипторов в системе, что может значительно снизить производительность запросов `SELECT`. +::: Чтобы получить набор кусков и партиций таблицы, можно воспользоваться системной таблицей [system.parts](../../../engines/table-engines/mergetree-family/custom-partitioning-key.md#system_tables-parts). В качестве примера рассмотрим таблицу `visits`, в которой задано партиционирование по месяцам. Выполним `SELECT` для таблицы `system.parts`: @@ -80,6 +81,7 @@ WHERE table = 'visits' :::info "Info" Названия кусков для таблиц старого типа образуются следующим образом: `20190117_20190123_2_2_0` (минимальная дата _ максимальная дата _ номер минимального блока _ номер максимального блока _ уровень). +::: Как видно из примера выше, таблица содержит несколько отдельных кусков для одной и той же партиции (например, куски `201901_1_3_1` и `201901_1_9_2` принадлежат партиции `201901`). Это означает, что эти куски еще не были объединены – в файловой системе они хранятся отдельно. После того как будет выполнено автоматическое слияние данных (выполняется примерно спустя 10 минут после вставки данных), исходные куски будут объединены в один более крупный кусок и помечены как неактивные. diff --git a/docs/ru/faq/general/dbms-naming.md b/docs/ru/faq/general/dbms-naming.md index 9bc036cc2e4..dd58d89924e 100644 --- a/docs/ru/faq/general/dbms-naming.md +++ b/docs/ru/faq/general/dbms-naming.md @@ -14,3 +14,4 @@ sidebar_position: 10 :::info "Забавный факт" Спустя годы после того, как ClickHouse получил свое название, принцип комбинирования двух слов, каждое из которых имеет подходящий смысл, был признан лучшим способом назвать базу данных в [исследовании Andy Pavlo](https://www.cs.cmu.edu/~pavlo/blog/2020/03/on-naming-a-database-management-system.html), Associate Professor of Databases в Carnegie Mellon University. ClickHouse разделил награду "за лучшее название СУБД" с Postgres. +::: \ No newline at end of file diff --git a/docs/ru/faq/general/index.md b/docs/ru/faq/general/index.md index 81715a64acd..7d34cc643f2 100644 --- a/docs/ru/faq/general/index.md +++ b/docs/ru/faq/general/index.md @@ -20,5 +20,6 @@ sidebar_label: Общие вопросы :::info "Если вы не нашли то, что искали:" Загляните в другие категории F.A.Q. или поищите в остальных разделах документации, ориентируясь по оглавлению слева. +::: [Original article](https://clickhouse.com/docs/ru/faq/general/) diff --git a/docs/ru/faq/general/why-clickhouse-is-so-fast.md b/docs/ru/faq/general/why-clickhouse-is-so-fast.md index 0c74cb2cfa2..43b3c818249 100644 --- a/docs/ru/faq/general/why-clickhouse-is-so-fast.md +++ b/docs/ru/faq/general/why-clickhouse-is-so-fast.md @@ -60,3 +60,4 @@ sidebar_position: 8 - Ориентируйтесь на показатели, собранные при работе с реальными данными. - Проверяйте производительность в процессе CI. - Измеряйте и анализируйте всё, что только возможно. +::: diff --git a/docs/ru/faq/integration/index.md b/docs/ru/faq/integration/index.md index cc917718000..ee01688af6e 100644 --- a/docs/ru/faq/integration/index.md +++ b/docs/ru/faq/integration/index.md @@ -15,5 +15,6 @@ sidebar_label: Интеграция :::info "Если вы не нашли то, что искали" Загляните в другие подразделы F.A.Q. или поищите в остальных разделах документации, ориентируйтесь по оглавлению слева. +::: [Original article](https://clickhouse.com/docs/ru/faq/integration/) diff --git a/docs/ru/faq/operations/index.md b/docs/ru/faq/operations/index.md index bd5ba13e182..aab31ec3305 100644 --- a/docs/ru/faq/operations/index.md +++ b/docs/ru/faq/operations/index.md @@ -14,5 +14,6 @@ sidebar_label: Операции :::info "Если вы не нашли то, что искали" Загляните в другие подразделы F.A.Q. или поищите в остальных разделах документации, ориентируйтесь по оглавлению слева. +::: [Original article](https://clickhouse.com/docs/en/faq/operations/) diff --git a/docs/ru/getting-started/example-datasets/nyc-taxi.md b/docs/ru/getting-started/example-datasets/nyc-taxi.md index 991d7dafe05..9d9caa43b5e 100644 --- a/docs/ru/getting-started/example-datasets/nyc-taxi.md +++ b/docs/ru/getting-started/example-datasets/nyc-taxi.md @@ -293,6 +293,7 @@ $ clickhouse-client --query "SELECT COUNT(*) FROM datasets.trips_mergetree" :::info "Info" Если вы собираетесь выполнять запросы, приведенные ниже, то к имени таблицы нужно добавить имя базы, `datasets.trips_mergetree`. +::: ## Результаты на одном сервере {#rezultaty-na-odnom-servere} diff --git a/docs/ru/getting-started/example-datasets/ontime.md b/docs/ru/getting-started/example-datasets/ontime.md index b7c4a2f952c..8dab19c7bae 100644 --- a/docs/ru/getting-started/example-datasets/ontime.md +++ b/docs/ru/getting-started/example-datasets/ontime.md @@ -157,6 +157,7 @@ $ clickhouse-client --query "SELECT COUNT(*) FROM datasets.ontime" :::info "Info" Если вы собираетесь выполнять запросы, приведенные ниже, то к имени таблицы нужно добавить имя базы, `datasets.ontime`. +::: ## Запросы: {#zaprosy} diff --git a/docs/ru/operations/external-authenticators/kerberos.md b/docs/ru/operations/external-authenticators/kerberos.md index 9575d935836..197bf5a6047 100644 --- a/docs/ru/operations/external-authenticators/kerberos.md +++ b/docs/ru/operations/external-authenticators/kerberos.md @@ -99,6 +99,7 @@ ClickHouse предоставляет возможность аутентифи :::info "" Ещё раз отметим, что кроме `users.xml`, необходимо также включить Kerberos в `config.xml`. +::: ### Настройка Kerberos через SQL {#enabling-kerberos-using-sql} diff --git a/docs/ru/operations/server-configuration-parameters/settings.md b/docs/ru/operations/server-configuration-parameters/settings.md index 222c6bccfaf..f1d7280892e 100644 --- a/docs/ru/operations/server-configuration-parameters/settings.md +++ b/docs/ru/operations/server-configuration-parameters/settings.md @@ -174,6 +174,7 @@ ClickHouse проверяет условия для `min_part_size` и `min_part :::info "Примечание" Жесткое ограничение настраивается с помощью системных инструментов. +::: **Пример** @@ -706,6 +707,7 @@ ClickHouse поддерживает динамическое изменение :::info "Примечание" Параметры этих настроек могут быть изменены во время выполнения запросов и вступят в силу немедленно. Запросы, которые уже запущены, выполнятся без изменений. +::: Возможные значения: @@ -726,6 +728,7 @@ ClickHouse поддерживает динамическое изменение :::info "Примечание" Параметры этих настроек могут быть изменены во время выполнения запросов и вступят в силу немедленно. Запросы, которые уже запущены, выполнятся без изменений. +::: Возможные значения: @@ -746,6 +749,7 @@ ClickHouse поддерживает динамическое изменение :::info "Примечание" Параметры этих настроек могут быть изменены во время выполнения запросов и вступят в силу немедленно. Запросы, которые уже запущены, выполнятся без изменений. +::: Возможные значения: diff --git a/docs/ru/operations/system-tables/mutations.md b/docs/ru/operations/system-tables/mutations.md index f2047745c0c..f3810e29698 100644 --- a/docs/ru/operations/system-tables/mutations.md +++ b/docs/ru/operations/system-tables/mutations.md @@ -30,6 +30,7 @@ :::info "Замечание" Даже если `parts_to_do = 0`, для реплицированной таблицы возможна ситуация, когда мутация ещё не завершена из-за долго выполняющейся операции `INSERT`, которая добавляет данные, которые нужно будет мутировать. +::: Если во время мутации какого-либо куска возникли проблемы, заполняются следующие столбцы: diff --git a/docs/ru/sql-reference/aggregate-functions/reference/deltasum.md b/docs/ru/sql-reference/aggregate-functions/reference/deltasum.md index f46d7ca6111..49edc3932e0 100644 --- a/docs/ru/sql-reference/aggregate-functions/reference/deltasum.md +++ b/docs/ru/sql-reference/aggregate-functions/reference/deltasum.md @@ -8,6 +8,7 @@ sidebar_position: 141 :::info "Примечание" Чтобы эта функция работала должным образом, исходные данные должны быть отсортированы. В [материализованном представлении](../../../sql-reference/statements/create/view.md#materialized) вместо нее рекомендуется использовать [deltaSumTimestamp](../../../sql-reference/aggregate-functions/reference/deltasumtimestamp.md#agg_functions-deltasumtimestamp). +::: **Синтаксис** diff --git a/docs/ru/sql-reference/aggregate-functions/reference/intervalLengthSum.md b/docs/ru/sql-reference/aggregate-functions/reference/intervalLengthSum.md index 46dac961639..45b41bd13a3 100644 --- a/docs/ru/sql-reference/aggregate-functions/reference/intervalLengthSum.md +++ b/docs/ru/sql-reference/aggregate-functions/reference/intervalLengthSum.md @@ -20,6 +20,7 @@ intervalLengthSum(start, end) :::info "Примечание" Аргументы должны быть одного типа. В противном случае ClickHouse сгенерирует исключение. +::: **Возвращаемое значение** diff --git a/docs/ru/sql-reference/data-types/nullable.md b/docs/ru/sql-reference/data-types/nullable.md index f018409772d..31a3674af6b 100644 --- a/docs/ru/sql-reference/data-types/nullable.md +++ b/docs/ru/sql-reference/data-types/nullable.md @@ -26,6 +26,7 @@ sidebar_label: Nullable :::info "Info" Почти всегда использование `Nullable` снижает производительность, учитывайте это при проектировании своих баз. +::: ## Поиск NULL {#finding-null} diff --git a/docs/ru/sql-reference/dictionaries/external-dictionaries/external-dicts-dict-sources.md b/docs/ru/sql-reference/dictionaries/external-dictionaries/external-dicts-dict-sources.md index ac03dd39047..a80fedfbb24 100644 --- a/docs/ru/sql-reference/dictionaries/external-dictionaries/external-dicts-dict-sources.md +++ b/docs/ru/sql-reference/dictionaries/external-dictionaries/external-dicts-dict-sources.md @@ -464,6 +464,7 @@ SOURCE(ODBC( :::info "Примечание" Поля `table` и `query` не могут быть использованы вместе. Также обязательно должен быть один из источников данных: `table` или `query`. +::: ClickHouse получает от ODBC-драйвера информацию о квотировании и квотирует настройки в запросах к драйверу, поэтому имя таблицы нужно указывать в соответствии с регистром имени таблицы в базе данных. @@ -543,6 +544,7 @@ SOURCE(MYSQL( :::info "Примечание" Поля `table` или `where` не могут быть использованы вместе с полем `query`. Также обязательно должен быть один из источников данных: `table` или `query`. Явный параметр `secure` отсутствует. Автоматически поддержана работа в обоих случаях: когда установка SSL-соединения необходима и когда нет. +::: MySQL можно подключить на локальном хосте через сокеты, для этого необходимо задать `host` и `socket`. @@ -633,6 +635,7 @@ SOURCE(CLICKHOUSE( :::info "Примечание" Поля `table` или `where` не могут быть использованы вместе с полем `query`. Также обязательно должен быть один из источников данных: `table` или `query`. +::: ### MongoDB {#dicts-external_dicts_dict_sources-mongodb} @@ -748,6 +751,7 @@ SOURCE(REDIS( :::info "Примечание" Поля `column_family` или `where` не могут быть использованы вместе с полем `query`. Также обязательно должен быть один из источников данных: `column_family` или `query`. +::: ### PostgreSQL {#dicts-external_dicts_dict_sources-postgresql} @@ -804,3 +808,4 @@ SOURCE(POSTGRESQL( :::info "Примечание" Поля `table` или `where` не могут быть использованы вместе с полем `query`. Также обязательно должен быть один из источников данных: `table` или `query`. +::: diff --git a/docs/ru/sql-reference/functions/geo/geohash.md b/docs/ru/sql-reference/functions/geo/geohash.md index fe853cef267..933775dcfbe 100644 --- a/docs/ru/sql-reference/functions/geo/geohash.md +++ b/docs/ru/sql-reference/functions/geo/geohash.md @@ -86,6 +86,7 @@ geohashesInBox(longitude_min, latitude_min, longitude_max, latitude_max, precisi :::info "Замечание" Все передаваемые координаты должны быть одного и того же типа: либо `Float32`, либо `Float64`. +::: **Возвращаемые значения** @@ -96,6 +97,7 @@ geohashesInBox(longitude_min, latitude_min, longitude_max, latitude_max, precisi :::info "Замечание" Если возвращаемый массив содержит свыше 10 000 000 элементов, функция сгенерирует исключение. +::: **Пример** diff --git a/docs/ru/sql-reference/functions/type-conversion-functions.md b/docs/ru/sql-reference/functions/type-conversion-functions.md index 4e3bae9ddb7..a7279b548e2 100644 --- a/docs/ru/sql-reference/functions/type-conversion-functions.md +++ b/docs/ru/sql-reference/functions/type-conversion-functions.md @@ -1209,6 +1209,7 @@ SELECT toLowCardinality('1'); :::info "Примечание" Возвращаемое значение — это временная метка в UTC, а не в часовом поясе `DateTime64`. +::: **Синтаксис** diff --git a/docs/ru/sql-reference/statements/alter/index.md b/docs/ru/sql-reference/statements/alter/index.md index fd3f6c8adf6..0191c794e9c 100644 --- a/docs/ru/sql-reference/statements/alter/index.md +++ b/docs/ru/sql-reference/statements/alter/index.md @@ -25,7 +25,7 @@ ALTER TABLE [db].name [ON CLUSTER cluster] ADD|DROP|CLEAR|COMMENT|MODIFY COLUMN - [CONSTRAINT](../../../sql-reference/statements/alter/constraint.md) - [TTL](../../../sql-reference/statements/alter/ttl.md) - :::note + :::note Запрос `ALTER TABLE` поддерживается только для таблиц типа `*MergeTree`, а также `Merge` и `Distributed`. Запрос имеет несколько вариантов. ::: Следующие запросы `ALTER` управляют представлениями: @@ -77,5 +77,6 @@ ALTER TABLE [db.]table MATERIALIZE INDEX name IN PARTITION partition_name :::info "Примечание" Для всех запросов `ALTER` при `replication_alter_partitions_sync = 2` и неактивности некоторых реплик больше времени, заданного настройкой `replication_wait_for_inactive_replica_timeout`, генерируется исключение `UNFINISHED`. +::: Для запросов `ALTER TABLE ... UPDATE|DELETE` синхронность выполнения определяется настройкой [mutations_sync](../../../operations/settings/settings.md#mutations_sync). diff --git a/docs/ru/sql-reference/statements/create/user.md b/docs/ru/sql-reference/statements/create/user.md index d7da1748821..683e56a61b3 100644 --- a/docs/ru/sql-reference/statements/create/user.md +++ b/docs/ru/sql-reference/statements/create/user.md @@ -56,6 +56,7 @@ CREATE USER [IF NOT EXISTS | OR REPLACE] name1 [ON CLUSTER cluster_name1] :::info "Внимание" ClickHouse трактует конструкцию `user_name@'address'` как имя пользователя целиком. То есть технически вы можете создать несколько пользователей с одинаковыми `user_name`, но разными частями конструкции после `@`, но лучше так не делать. +::: ## Секция GRANTEES {#grantees} diff --git a/docs/ru/sql-reference/statements/create/view.md b/docs/ru/sql-reference/statements/create/view.md index 9a2db0ac2de..a0193cea21c 100644 --- a/docs/ru/sql-reference/statements/create/view.md +++ b/docs/ru/sql-reference/statements/create/view.md @@ -88,6 +88,7 @@ LIVE-представления работают по тому же принци - `LIVE VIEW` не обновляется, если в исходном запросе используются несколько таблиц. В случаях, когда `LIVE VIEW` не обновляется автоматически, чтобы обновлять его принудительно с заданной периодичностью, используйте [WITH REFRESH](#live-view-with-refresh). +::: ### Отслеживание изменений LIVE-представлений {#live-view-monitoring} diff --git a/docs/ru/sql-reference/statements/optimize.md b/docs/ru/sql-reference/statements/optimize.md index b0b71ae412c..61480a0c1ab 100644 --- a/docs/ru/sql-reference/statements/optimize.md +++ b/docs/ru/sql-reference/statements/optimize.md @@ -30,6 +30,7 @@ ClickHouse не оповещает клиента. Чтобы включить :::info "Примечание" Если значение настройки `replication_alter_partitions_sync` равно `2` и некоторые реплики не активны больше времени, заданного настройкой `replication_wait_for_inactive_replica_timeout`, то генерируется исключение `UNFINISHED`. +::: ## Выражение BY {#by-expression} diff --git a/docs/ru/sql-reference/statements/show.md b/docs/ru/sql-reference/statements/show.md index 0926cc0863e..3e7560b0882 100644 --- a/docs/ru/sql-reference/statements/show.md +++ b/docs/ru/sql-reference/statements/show.md @@ -368,6 +368,7 @@ SHOW ACCESS :::info "Note" По запросу `SHOW CLUSTER name` вы получите содержимое таблицы system.clusters для этого кластера. +::: ### Синтаксис {#show-cluster-syntax} diff --git a/docs/ru/sql-reference/statements/truncate.md b/docs/ru/sql-reference/statements/truncate.md index 63cf6271d72..cac6c261f21 100644 --- a/docs/ru/sql-reference/statements/truncate.md +++ b/docs/ru/sql-reference/statements/truncate.md @@ -19,3 +19,4 @@ TRUNCATE TABLE [IF EXISTS] [db.]name [ON CLUSTER cluster] :::info "Примечание" Если значение настройки `replication_alter_partitions_sync` равно `2` и некоторые реплики не активны больше времени, заданного настройкой `replication_wait_for_inactive_replica_timeout`, то генерируется исключение `UNFINISHED`. +::: diff --git a/docs/ru/sql-reference/statements/watch.md b/docs/ru/sql-reference/statements/watch.md index 227a6e26c02..f925e25b0d5 100644 --- a/docs/ru/sql-reference/statements/watch.md +++ b/docs/ru/sql-reference/statements/watch.md @@ -104,3 +104,4 @@ WATCH lv EVENTS LIMIT 1; :::info "Примечание" При отслеживании [LIVE VIEW](./create/view.md#live-view) через интерфейс HTTP следует использовать формат [JSONEachRowWithProgress](../../interfaces/formats.md#jsoneachrowwithprogress). Постоянные сообщения об изменениях будут добавлены в поток вывода для поддержания активности долговременного HTTP-соединения до тех пор, пока результат запроса изменяется. Проомежуток времени между сообщениями об изменениях управляется настройкой[live_view_heartbeat_interval](./create/view.md#live-view-settings). +::: \ No newline at end of file diff --git a/docs/ru/sql-reference/table-functions/postgresql.md b/docs/ru/sql-reference/table-functions/postgresql.md index 3408951c690..2e1e5314f91 100644 --- a/docs/ru/sql-reference/table-functions/postgresql.md +++ b/docs/ru/sql-reference/table-functions/postgresql.md @@ -28,6 +28,7 @@ postgresql('host:port', 'database', 'table', 'user', 'password'[, `schema`]) :::info "Примечание" В запросах `INSERT` для того чтобы отличить табличную функцию `postgresql(...)` от таблицы со списком имен столбцов вы должны указывать ключевые слова `FUNCTION` или `TABLE FUNCTION`. См. примеры ниже. +::: ## Особенности реализации {#implementation-details} @@ -43,6 +44,7 @@ PostgreSQL массивы конвертируются в массивы ClickHo :::info "Примечание" Будьте внимательны, в PostgreSQL массивы, созданные как `type_name[]`, являются многомерными и могут содержать в себе разное количество измерений в разных строках одной таблицы. Внутри ClickHouse допустипы только многомерные массивы с одинаковым кол-вом измерений во всех строках таблицы. +::: Поддерживает несколько реплик, которые должны быть перечислены через `|`. Например: From 6a30c23252a41ada572bad7e54e13535d295a0df Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 10 Aug 2022 21:48:00 +0200 Subject: [PATCH 556/672] tests/performance: cover sparse_hashed dictionary (#40027) Signed-off-by: Azat Khuzhin Signed-off-by: Azat Khuzhin --- tests/performance/hashed_dictionary.xml | 193 ++++++++++++------------ 1 file changed, 100 insertions(+), 93 deletions(-) diff --git a/tests/performance/hashed_dictionary.xml b/tests/performance/hashed_dictionary.xml index cf1cdac6df1..01ee35c8ed4 100644 --- a/tests/performance/hashed_dictionary.xml +++ b/tests/performance/hashed_dictionary.xml @@ -1,72 +1,4 @@ - - CREATE TABLE simple_key_hashed_dictionary_source_table - ( - id UInt64, - value_int UInt64, - value_string String, - value_decimal Decimal64(8), - value_string_nullable Nullable(String) - ) ENGINE = Memory; - - - - CREATE TABLE complex_key_hashed_dictionary_source_table - ( - id UInt64, - id_key String, - value_int UInt64, - value_string String, - value_decimal Decimal64(8), - value_string_nullable Nullable(String) - ) ENGINE = Memory; - - - - CREATE DICTIONARY simple_key_hashed_dictionary - ( - id UInt64, - value_int UInt64, - value_string String, - value_decimal Decimal64(8), - value_string_nullable Nullable(String) - ) - PRIMARY KEY id - SOURCE(CLICKHOUSE(DB 'default' TABLE 'simple_key_hashed_dictionary_source_table')) - LAYOUT(HASHED()) - LIFETIME(MIN 0 MAX 1000); - - - - CREATE DICTIONARY complex_key_hashed_dictionary - ( - id UInt64, - id_key String, - value_int UInt64, - value_string String, - value_decimal Decimal64(8), - value_string_nullable Nullable(String) - ) - PRIMARY KEY id, id_key - SOURCE(CLICKHOUSE(DB 'default' TABLE 'complex_key_hashed_dictionary_source_table')) - LAYOUT(COMPLEX_KEY_HASHED()) - LIFETIME(MIN 0 MAX 1000); - - - - INSERT INTO simple_key_hashed_dictionary_source_table - SELECT number, number, toString(number), toDecimal64(number, 8), toString(number) - FROM system.numbers - LIMIT 5000000; - - - - INSERT INTO complex_key_hashed_dictionary_source_table - SELECT number, toString(number), number, toString(number), toDecimal64(number, 8), toString(number) - FROM system.numbers - LIMIT 5000000; - - column_name @@ -85,54 +17,129 @@ 7500000 + + + layout_suffix + + HASHED + SPARSE_HASHED + + + + CREATE TABLE simple_key_dictionary_source_table + ( + id UInt64, + value_int UInt64, + value_string String, + value_decimal Decimal64(8), + value_string_nullable Nullable(String) + ) ENGINE = Memory + + + + CREATE TABLE complex_key_dictionary_source_table + ( + id UInt64, + id_key String, + value_int UInt64, + value_string String, + value_decimal Decimal64(8), + value_string_nullable Nullable(String) + ) ENGINE = Memory + + + + CREATE DICTIONARY IF NOT EXISTS simple_key_{layout_suffix}_dictionary + ( + id UInt64, + value_int UInt64, + value_string String, + value_decimal Decimal64(8), + value_string_nullable Nullable(String) + ) + PRIMARY KEY id + SOURCE(CLICKHOUSE(TABLE 'simple_key_dictionary_source_table')) + LAYOUT({layout_suffix}()) + LIFETIME(0) + + + + CREATE DICTIONARY IF NOT EXISTS complex_key_{layout_suffix}_dictionary + ( + id UInt64, + id_key String, + value_int UInt64, + value_string String, + value_decimal Decimal64(8), + value_string_nullable Nullable(String) + ) + PRIMARY KEY id, id_key + SOURCE(CLICKHOUSE(TABLE 'complex_key_dictionary_source_table')) + LAYOUT(COMPLEX_KEY_{layout_suffix}()) + LIFETIME(0) + + + + INSERT INTO simple_key_dictionary_source_table + SELECT number, number, toString(number), toDecimal64(number, 8), toString(number) + FROM system.numbers + LIMIT 5000000 + + + + INSERT INTO complex_key_dictionary_source_table + SELECT number, toString(number), number, toString(number), toDecimal64(number, 8), toString(number) + FROM system.numbers + LIMIT 5000000 + + + SYSTEM RELOAD DICTIONARY simple_key_{layout_suffix}_dictionary + SYSTEM RELOAD DICTIONARY complex_key_{layout_suffix}_dictionary + + SYSTEM RELOAD DICTIONARY simple_key_{layout_suffix}_dictionary + SYSTEM RELOAD DICTIONARY complex_key_{layout_suffix}_dictionary + WITH rand64() % toUInt64({elements_count}) as key - SELECT dictGet('default.simple_key_hashed_dictionary', {column_name}, key) + SELECT dictGet('default.simple_key_{layout_suffix}_dictionary', {column_name}, key) FROM system.numbers LIMIT {elements_count} - FORMAT Null; + FORMAT Null WITH rand64() % toUInt64({elements_count}) as key - SELECT dictHas('default.simple_key_hashed_dictionary', key) + SELECT dictHas('default.simple_key_{layout_suffix}_dictionary', key) FROM system.numbers LIMIT {elements_count} - FORMAT Null; + FORMAT Null + SELECT * FROM simple_key_{layout_suffix}_dictionary FORMAT Null + - SELECT * FROM simple_key_hashed_dictionary - FORMAT Null; + WITH (rand64() % toUInt64({elements_count}), toString(rand64() % toUInt64({elements_count}))) as key + SELECT dictGet('default.complex_key_{layout_suffix}_dictionary', {column_name}, key) + FROM system.numbers + LIMIT {elements_count} + FORMAT Null WITH (rand64() % toUInt64({elements_count}), toString(rand64() % toUInt64({elements_count}))) as key - SELECT dictGet('default.complex_key_hashed_dictionary', {column_name}, key) + SELECT dictHas('default.complex_key_{layout_suffix}_dictionary', key) FROM system.numbers LIMIT {elements_count} - FORMAT Null; + FORMAT Null - - WITH (rand64() % toUInt64({elements_count}), toString(rand64() % toUInt64({elements_count}))) as key - SELECT dictHas('default.complex_key_hashed_dictionary', key) - FROM system.numbers - LIMIT {elements_count} - FORMAT Null; - + SELECT * FROM complex_key_{layout_suffix}_dictionary FORMAT Null - - SELECT * FROM complex_key_hashed_dictionary - FORMAT Null; - - - DROP TABLE IF EXISTS simple_key_hashed_dictionary_source_table; - DROP TABLE IF EXISTS complex_key_hashed_dictionary_source_table; - - DROP DICTIONARY IF EXISTS simple_key_hashed_dictionary; - DROP DICTIONARY IF EXISTS complex_key_hashed_dictionary; + DROP TABLE IF EXISTS simple_key_dictionary_source_table + DROP TABLE IF EXISTS complex_key_dictionary_source_table + DROP DICTIONARY IF EXISTS simple_key_{layout_suffix}_dictionary + DROP DICTIONARY IF EXISTS complex_key_{layout_suffix}_dictionary From ce9c0c2da3459d63c5a95c7495f47e7467624012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Wed, 10 Aug 2022 21:53:11 +0200 Subject: [PATCH 557/672] Style --- src/Interpreters/UserDefinedSQLFunctionFactory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Interpreters/UserDefinedSQLFunctionFactory.cpp b/src/Interpreters/UserDefinedSQLFunctionFactory.cpp index db11ee12b03..2f876f00cc3 100644 --- a/src/Interpreters/UserDefinedSQLFunctionFactory.cpp +++ b/src/Interpreters/UserDefinedSQLFunctionFactory.cpp @@ -163,6 +163,6 @@ std::vector UserDefinedSQLFunctionFactory::getAllRegisteredNames() bool UserDefinedSQLFunctionFactory::empty() const { std::lock_guard lock(mutex); - return function_name_to_create_query.size() == 0; + return function_name_to_create_query.empty(); } } From d7a545e30d27079c81c3ad9c0c37b04de9f56e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Wed, 10 Aug 2022 22:05:09 +0200 Subject: [PATCH 558/672] Try to optimize CurrentMemoryTracker alloc and free --- src/Common/CurrentThread.cpp | 7 ------- src/Common/CurrentThread.h | 7 ++++++- src/Common/ThreadStatus.cpp | 10 ++++++++-- src/Common/ThreadStatus.h | 10 ++++++++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Common/CurrentThread.cpp b/src/Common/CurrentThread.cpp index e3f6b63b28a..a2cdf9c8361 100644 --- a/src/Common/CurrentThread.cpp +++ b/src/Common/CurrentThread.cpp @@ -43,13 +43,6 @@ ProfileEvents::Counters & CurrentThread::getProfileEvents() return current_thread ? current_thread->performance_counters : ProfileEvents::global_counters; } -MemoryTracker * CurrentThread::getMemoryTracker() -{ - if (unlikely(!current_thread)) - return nullptr; - return ¤t_thread->memory_tracker; -} - void CurrentThread::updateProgressIn(const Progress & value) { if (unlikely(!current_thread)) diff --git a/src/Common/CurrentThread.h b/src/Common/CurrentThread.h index fa52fafa9e2..cbe60365798 100644 --- a/src/Common/CurrentThread.h +++ b/src/Common/CurrentThread.h @@ -54,7 +54,12 @@ public: static void updatePerformanceCounters(); static ProfileEvents::Counters & getProfileEvents(); - static MemoryTracker * getMemoryTracker(); + inline ALWAYS_INLINE static MemoryTracker * getMemoryTracker() + { + if (unlikely(!current_thread)) + return nullptr; + return ¤t_thread->memory_tracker; + } /// Update read and write rows (bytes) statistics (used in system.query_thread_log) static void updateProgressIn(const Progress & value); diff --git a/src/Common/ThreadStatus.cpp b/src/Common/ThreadStatus.cpp index 4dd32f7ff10..98f78cada5c 100644 --- a/src/Common/ThreadStatus.cpp +++ b/src/Common/ThreadStatus.cpp @@ -24,9 +24,15 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } - -thread_local ThreadStatus * current_thread = nullptr; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++20-compat" +#endif +thread_local ThreadStatus constinit * current_thread = nullptr; thread_local ThreadStatus * main_thread = nullptr; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif #if !defined(SANITIZER) namespace diff --git a/src/Common/ThreadStatus.h b/src/Common/ThreadStatus.h index 7c22d3b8335..594e86ffa2e 100644 --- a/src/Common/ThreadStatus.h +++ b/src/Common/ThreadStatus.h @@ -102,8 +102,14 @@ public: using ThreadGroupStatusPtr = std::shared_ptr; - -extern thread_local ThreadStatus * current_thread; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++20-compat" +#endif +extern thread_local constinit ThreadStatus * current_thread; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif /** Encapsulates all per-thread info (ProfileEvents, MemoryTracker, query_id, query context, etc.). * The object must be created in thread function and destroyed in the same thread before the exit. From 5934c6519f210eba32a83abd377664e6ce3e3bd6 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 11 Aug 2022 00:06:01 +0200 Subject: [PATCH 559/672] Regenerate changelogs and update versions --- docs/changelogs/v22.3.1.1262-prestable.md | 2 +- docs/changelogs/v22.3.2.2-lts.md | 2 +- docs/changelogs/v22.3.3.44-lts.md | 2 +- docs/changelogs/v22.3.4.20-lts.md | 2 +- docs/changelogs/v22.3.5.5-lts.md | 2 +- docs/changelogs/v22.3.6.5-lts.md | 2 +- docs/changelogs/v22.3.7.28-lts.md | 2 +- docs/changelogs/v22.4.1.2305-prestable.md | 10 +++++----- docs/changelogs/v22.4.2.1-stable.md | 2 +- docs/changelogs/v22.4.3.3-stable.md | 2 +- docs/changelogs/v22.4.4.7-stable.md | 2 +- docs/changelogs/v22.4.5.9-stable.md | 2 +- docs/changelogs/v22.5.1.2079-stable.md | 8 ++++---- docs/changelogs/v22.6.1.1985-stable.md | 8 ++++---- docs/changelogs/v22.6.2.12-stable.md | 2 +- docs/changelogs/v22.6.3.35-stable.md | 2 +- docs/changelogs/v22.6.4.35-stable.md | 2 +- utils/list-versions/version_date.tsv | 3 +++ 18 files changed, 30 insertions(+), 27 deletions(-) diff --git a/docs/changelogs/v22.3.1.1262-prestable.md b/docs/changelogs/v22.3.1.1262-prestable.md index 0058396d634..6c16a92ed26 100644 --- a/docs/changelogs/v22.3.1.1262-prestable.md +++ b/docs/changelogs/v22.3.1.1262-prestable.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.3.1.1262-prestable FIXME as compared to v22.2.1.2139-prestable +### ClickHouse release v22.3.1.1262-prestable (92ab33f560e) FIXME as compared to v22.2.1.2139-prestable (75366fc95e5) #### Backward Incompatible Change * Improvement the toDatetime function overflows. When the date string is very large, it will be converted to 1970. [#32898](https://github.com/ClickHouse/ClickHouse/pull/32898) ([HaiBo Li](https://github.com/marising)). diff --git a/docs/changelogs/v22.3.2.2-lts.md b/docs/changelogs/v22.3.2.2-lts.md index b755db300c8..b873926e068 100644 --- a/docs/changelogs/v22.3.2.2-lts.md +++ b/docs/changelogs/v22.3.2.2-lts.md @@ -5,5 +5,5 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.3.2.2-lts FIXME as compared to v22.3.1.1262-prestable +### ClickHouse release v22.3.2.2-lts (89a621679c6) FIXME as compared to v22.3.1.1262-prestable (92ab33f560e) diff --git a/docs/changelogs/v22.3.3.44-lts.md b/docs/changelogs/v22.3.3.44-lts.md index 4cd48eefa5a..d0da3397d1d 100644 --- a/docs/changelogs/v22.3.3.44-lts.md +++ b/docs/changelogs/v22.3.3.44-lts.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.3.3.44-lts FIXME as compared to v22.3.2.2-lts +### ClickHouse release v22.3.3.44-lts (abb756d3ca2) FIXME as compared to v22.3.2.2-lts (89a621679c6) #### Bug Fix * Backported in [#35928](https://github.com/ClickHouse/ClickHouse/issues/35928): Added settings `input_format_ipv4_default_on_conversion_error`, `input_format_ipv6_default_on_conversion_error` to allow insert of invalid ip address values as default into tables. Closes [#35726](https://github.com/ClickHouse/ClickHouse/issues/35726). [#35733](https://github.com/ClickHouse/ClickHouse/pull/35733) ([Maksim Kita](https://github.com/kitaisreal)). diff --git a/docs/changelogs/v22.3.4.20-lts.md b/docs/changelogs/v22.3.4.20-lts.md index d820adbdbec..34a8cd8c25a 100644 --- a/docs/changelogs/v22.3.4.20-lts.md +++ b/docs/changelogs/v22.3.4.20-lts.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.3.4.20-lts FIXME as compared to v22.3.3.44-lts +### ClickHouse release v22.3.4.20-lts (ecbaf001f49) FIXME as compared to v22.3.3.44-lts (abb756d3ca2) #### Build/Testing/Packaging Improvement * - Add `_le_` method for ClickHouseVersion - Fix auto_version for existing tag - docker_server now support getting version from tags - Add python unit tests to backport workflow. [#36028](https://github.com/ClickHouse/ClickHouse/pull/36028) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). diff --git a/docs/changelogs/v22.3.5.5-lts.md b/docs/changelogs/v22.3.5.5-lts.md index 7deff1be416..fc1332b68c6 100644 --- a/docs/changelogs/v22.3.5.5-lts.md +++ b/docs/changelogs/v22.3.5.5-lts.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.3.5.5-lts FIXME as compared to v22.3.4.20-lts +### ClickHouse release v22.3.5.5-lts (438b4a81f77) FIXME as compared to v22.3.4.20-lts (ecbaf001f49) #### Bug Fix (user-visible misbehaviour in official stable or prestable release) diff --git a/docs/changelogs/v22.3.6.5-lts.md b/docs/changelogs/v22.3.6.5-lts.md index 4b4772c611a..535fa0e7ad3 100644 --- a/docs/changelogs/v22.3.6.5-lts.md +++ b/docs/changelogs/v22.3.6.5-lts.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.3.6.5-lts FIXME as compared to v22.3.5.5-lts +### ClickHouse release v22.3.6.5-lts (3e44e824cff) FIXME as compared to v22.3.5.5-lts (438b4a81f77) #### Bug Fix (user-visible misbehaviour in official stable or prestable release) diff --git a/docs/changelogs/v22.3.7.28-lts.md b/docs/changelogs/v22.3.7.28-lts.md index 14cb8628f09..5a6900e7fa4 100644 --- a/docs/changelogs/v22.3.7.28-lts.md +++ b/docs/changelogs/v22.3.7.28-lts.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.3.7.28-lts FIXME as compared to v22.3.6.5-lts +### ClickHouse release v22.3.7.28-lts (420bdfa2751) FIXME as compared to v22.3.6.5-lts (3e44e824cff) #### Bug Fix (user-visible misbehavior in official stable or prestable release) diff --git a/docs/changelogs/v22.4.1.2305-prestable.md b/docs/changelogs/v22.4.1.2305-prestable.md index c202b0b9331..da1ba4a42bd 100644 --- a/docs/changelogs/v22.4.1.2305-prestable.md +++ b/docs/changelogs/v22.4.1.2305-prestable.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.4.1.2305-prestable FIXME as compared to v22.3.1.1262-prestable +### ClickHouse release v22.4.1.2305-prestable (77a82cc090d) FIXME as compared to v22.3.1.1262-prestable (92ab33f560e) #### Backward Incompatible Change * Function `yandexConsistentHash` (consistent hashing algorithm by Konstantin "kostik" Oblakov) is renamed to `kostikConsistentHash`. The old name is left as an alias for compatibility. Although this change is backward compatible, we may remove the alias in subsequent releases, that's why it's recommended to update the usages of this function in your apps. [#35553](https://github.com/ClickHouse/ClickHouse/pull/35553) ([Alexey Milovidov](https://github.com/alexey-milovidov)). @@ -68,7 +68,7 @@ sidebar_label: 2022 * For lts releases packages will be pushed to both lts and stable repos. [#35382](https://github.com/ClickHouse/ClickHouse/pull/35382) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). * Support uuid for postgres engines. Closes [#35384](https://github.com/ClickHouse/ClickHouse/issues/35384). [#35403](https://github.com/ClickHouse/ClickHouse/pull/35403) ([Kseniia Sumarokova](https://github.com/kssenii)). * Add arguments `--user`, `--password`, `--host`, `--port` for clickhouse-diagnostics. [#35422](https://github.com/ClickHouse/ClickHouse/pull/35422) ([李扬](https://github.com/taiyang-li)). -* fix INSERT INTO table FROM INFILE does not display progress bar. [#35429](https://github.com/ClickHouse/ClickHouse/pull/35429) ([xiedeyantu](https://github.com/xiedeyantu)). +* fix INSERT INTO table FROM INFILE does not display progress bar. [#35429](https://github.com/ClickHouse/ClickHouse/pull/35429) ([chen](https://github.com/xiedeyantu)). * Allow server to bind to low-numbered ports (e.g. 443). ClickHouse installation script will set `cap_net_bind_service` to the binary file. [#35451](https://github.com/ClickHouse/ClickHouse/pull/35451) ([Alexey Milovidov](https://github.com/alexey-milovidov)). * Add settings `input_format_orc_case_insensitive_column_matching`, `input_format_arrow_case_insensitive_column_matching`, and `input_format_parquet_case_insensitive_column_matching` which allows ClickHouse to use case insensitive matching of columns while reading data from ORC, Arrow or Parquet files. [#35459](https://github.com/ClickHouse/ClickHouse/pull/35459) ([Antonio Andelic](https://github.com/antonio2368)). * - Add explicit table info to the scan node of query plan and pipeline. [#35460](https://github.com/ClickHouse/ClickHouse/pull/35460) ([何李夫](https://github.com/helifu)). @@ -106,7 +106,7 @@ sidebar_label: 2022 * ASTPartition::formatImpl should output ALL while executing ALTER TABLE t DETACH PARTITION ALL. [#35987](https://github.com/ClickHouse/ClickHouse/pull/35987) ([awakeljw](https://github.com/awakeljw)). * `clickhouse-keeper` starts answering 4-letter commands before getting the quorum. [#35992](https://github.com/ClickHouse/ClickHouse/pull/35992) ([Antonio Andelic](https://github.com/antonio2368)). * Fix wrong assertion in replxx which happens when navigating back the history when the first line of input is a newline. Mark as improvement because it only affects debug build. This fixes [#34511](https://github.com/ClickHouse/ClickHouse/issues/34511). [#36007](https://github.com/ClickHouse/ClickHouse/pull/36007) ([Amos Bird](https://github.com/amosbird)). -* If someone writes DEFAULT NULL in table definition, make data type Nullable. [#35887](https://github.com/ClickHouse/ClickHouse/issues/35887). [#36058](https://github.com/ClickHouse/ClickHouse/pull/36058) ([xiedeyantu](https://github.com/xiedeyantu)). +* If someone writes DEFAULT NULL in table definition, make data type Nullable. [#35887](https://github.com/ClickHouse/ClickHouse/issues/35887). [#36058](https://github.com/ClickHouse/ClickHouse/pull/36058) ([chen](https://github.com/xiedeyantu)). * Added `thread_id` and `query_id` columns to `system.zookeeper_log` table. [#36074](https://github.com/ClickHouse/ClickHouse/pull/36074) ([Alexander Tokmakov](https://github.com/tavplubix)). * Auto assign numbers for Enum elements. [#36101](https://github.com/ClickHouse/ClickHouse/pull/36101) ([awakeljw](https://github.com/awakeljw)). * Reset thread name in `ThreadPool` to `ThreadPoolIdle` after job is done. This is to avoid displaying the old thread name for idle threads. This closes [#36114](https://github.com/ClickHouse/ClickHouse/issues/36114). [#36115](https://github.com/ClickHouse/ClickHouse/pull/36115) ([Alexey Milovidov](https://github.com/alexey-milovidov)). @@ -331,7 +331,7 @@ sidebar_label: 2022 * ci: replace directory system log tables artifacts with tsv [#35773](https://github.com/ClickHouse/ClickHouse/pull/35773) ([Azat Khuzhin](https://github.com/azat)). * One more try to resurrect build hash [#35774](https://github.com/ClickHouse/ClickHouse/pull/35774) ([alesapin](https://github.com/alesapin)). * Refactoring QueryPipeline [#35789](https://github.com/ClickHouse/ClickHouse/pull/35789) ([Amos Bird](https://github.com/amosbird)). -* Delete duplicate code [#35798](https://github.com/ClickHouse/ClickHouse/pull/35798) ([xiedeyantu](https://github.com/xiedeyantu)). +* Delete duplicate code [#35798](https://github.com/ClickHouse/ClickHouse/pull/35798) ([chen](https://github.com/xiedeyantu)). * remove unused variable [#35800](https://github.com/ClickHouse/ClickHouse/pull/35800) ([flynn](https://github.com/ucasfl)). * Make `SortDescription::column_name` always non-empty [#35805](https://github.com/ClickHouse/ClickHouse/pull/35805) ([Nikita Taranov](https://github.com/nickitat)). * Fix latest_error referenced before assignment [#35807](https://github.com/ClickHouse/ClickHouse/pull/35807) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). @@ -417,7 +417,7 @@ sidebar_label: 2022 * Revert reverting "Fix crash in ParallelReadBuffer" [#36212](https://github.com/ClickHouse/ClickHouse/pull/36212) ([Kruglov Pavel](https://github.com/Avogar)). * Make stateless tests with s3 always green [#36214](https://github.com/ClickHouse/ClickHouse/pull/36214) ([Alexander Tokmakov](https://github.com/tavplubix)). * Add Tyler Hannan to contributors [#36216](https://github.com/ClickHouse/ClickHouse/pull/36216) ([Tyler Hannan](https://github.com/tylerhannan)). -* Fix the repeated call of func to get the table when drop table [#36248](https://github.com/ClickHouse/ClickHouse/pull/36248) ([xiedeyantu](https://github.com/xiedeyantu)). +* Fix the repeated call of func to get the table when drop table [#36248](https://github.com/ClickHouse/ClickHouse/pull/36248) ([chen](https://github.com/xiedeyantu)). * Split test 01675_data_type_coroutine into 2 tests to prevent possible timeouts [#36250](https://github.com/ClickHouse/ClickHouse/pull/36250) ([Kruglov Pavel](https://github.com/Avogar)). * Merge TRUSTED_CONTRIBUTORS in lambda and import in check [#36252](https://github.com/ClickHouse/ClickHouse/pull/36252) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). * Fix exception "File segment can be completed only by downloader" in tests [#36253](https://github.com/ClickHouse/ClickHouse/pull/36253) ([Kseniia Sumarokova](https://github.com/kssenii)). diff --git a/docs/changelogs/v22.4.2.1-stable.md b/docs/changelogs/v22.4.2.1-stable.md index fb77d3fee9b..2bd245e00e7 100644 --- a/docs/changelogs/v22.4.2.1-stable.md +++ b/docs/changelogs/v22.4.2.1-stable.md @@ -5,5 +5,5 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.4.2.1-stable FIXME as compared to v22.4.1.2305-prestable +### ClickHouse release v22.4.2.1-stable (b34ebdc36ae) FIXME as compared to v22.4.1.2305-prestable (77a82cc090d) diff --git a/docs/changelogs/v22.4.3.3-stable.md b/docs/changelogs/v22.4.3.3-stable.md index 4baa63672ab..3b0f1e11cd5 100644 --- a/docs/changelogs/v22.4.3.3-stable.md +++ b/docs/changelogs/v22.4.3.3-stable.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.4.3.3-stable FIXME as compared to v22.4.2.1-stable +### ClickHouse release v22.4.3.3-stable (def956d6299) FIXME as compared to v22.4.2.1-stable (b34ebdc36ae) #### Bug Fix (user-visible misbehaviour in official stable or prestable release) diff --git a/docs/changelogs/v22.4.4.7-stable.md b/docs/changelogs/v22.4.4.7-stable.md index 71e077ac071..a0bc92db3e8 100644 --- a/docs/changelogs/v22.4.4.7-stable.md +++ b/docs/changelogs/v22.4.4.7-stable.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.4.4.7-stable FIXME as compared to v22.4.3.3-stable +### ClickHouse release v22.4.4.7-stable (ba44414f9b3) FIXME as compared to v22.4.3.3-stable (def956d6299) #### Bug Fix (user-visible misbehaviour in official stable or prestable release) diff --git a/docs/changelogs/v22.4.5.9-stable.md b/docs/changelogs/v22.4.5.9-stable.md index 636ad2ed3ac..a80dfd01a2b 100644 --- a/docs/changelogs/v22.4.5.9-stable.md +++ b/docs/changelogs/v22.4.5.9-stable.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.4.5.9-stable FIXME as compared to v22.4.4.7-stable +### ClickHouse release v22.4.5.9-stable (059ef6cadcd) FIXME as compared to v22.4.4.7-stable (ba44414f9b3) #### Bug Fix (user-visible misbehaviour in official stable or prestable release) diff --git a/docs/changelogs/v22.5.1.2079-stable.md b/docs/changelogs/v22.5.1.2079-stable.md index dfdcad64561..f6ca6fcc478 100644 --- a/docs/changelogs/v22.5.1.2079-stable.md +++ b/docs/changelogs/v22.5.1.2079-stable.md @@ -5,10 +5,10 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.5.1.2079-stable FIXME as compared to v22.4.1.2305-prestable +### ClickHouse release v22.5.1.2079-stable (df0cb062098) FIXME as compared to v22.4.1.2305-prestable (77a82cc090d) #### Backward Incompatible Change -* Updated the BoringSSL module to the official FIPS compliant version. This makes ClickHouse FIPS compliant. [#35914](https://github.com/ClickHouse/ClickHouse/pull/35914) ([Meena-Renganathan](https://github.com/Meena-Renganathan)). +* Updated the BoringSSL module to the official FIPS compliant version. This makes ClickHouse FIPS compliant. [#35914](https://github.com/ClickHouse/ClickHouse/pull/35914) ([Deleted user](https://github.com/ghost)). * Now, background merges, mutations and `OPTIMIZE` will not increment `SelectedRows` and `SelectedBytes` metrics. They (still) will increment `MergedRows` and `MergedUncompressedBytes` as it was before. [#37040](https://github.com/ClickHouse/ClickHouse/pull/37040) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). #### New Feature @@ -20,7 +20,7 @@ sidebar_label: 2022 * Parse collations in CREATE TABLE, throw exception or ignore. closes [#35892](https://github.com/ClickHouse/ClickHouse/issues/35892). [#36271](https://github.com/ClickHouse/ClickHouse/pull/36271) ([yuuch](https://github.com/yuuch)). * Add aliases JSONLines and NDJSON for JSONEachRow. Closes [#36303](https://github.com/ClickHouse/ClickHouse/issues/36303). [#36327](https://github.com/ClickHouse/ClickHouse/pull/36327) ([flynn](https://github.com/ucasfl)). * Set parts_to_delay_insert and parts_to_throw_insert as query-level settings. If they are defined, they can override table-level settings. [#36371](https://github.com/ClickHouse/ClickHouse/pull/36371) ([Memo](https://github.com/Joeywzr)). -* temporary table can show total rows and total bytes. [#36401](https://github.com/ClickHouse/ClickHouse/issues/36401). [#36439](https://github.com/ClickHouse/ClickHouse/pull/36439) ([xiedeyantu](https://github.com/xiedeyantu)). +* temporary table can show total rows and total bytes. [#36401](https://github.com/ClickHouse/ClickHouse/issues/36401). [#36439](https://github.com/ClickHouse/ClickHouse/pull/36439) ([chen](https://github.com/xiedeyantu)). * Added new hash function - wyHash64. [#36467](https://github.com/ClickHouse/ClickHouse/pull/36467) ([olevino](https://github.com/olevino)). * Window function nth_value was added. [#36601](https://github.com/ClickHouse/ClickHouse/pull/36601) ([Nikolay](https://github.com/ndchikin)). * Add MySQLDump input format. It reads all data from INSERT queries belonging to one table in dump. If there are more than one table, by default it reads data from the first one. [#36667](https://github.com/ClickHouse/ClickHouse/pull/36667) ([Kruglov Pavel](https://github.com/Avogar)). @@ -212,7 +212,7 @@ sidebar_label: 2022 * Update version after release [#36502](https://github.com/ClickHouse/ClickHouse/pull/36502) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). * Followup on [#36172](https://github.com/ClickHouse/ClickHouse/issues/36172) password hash salt feature [#36510](https://github.com/ClickHouse/ClickHouse/pull/36510) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). * Update version_date.tsv after v22.4.2.1-stable [#36533](https://github.com/ClickHouse/ClickHouse/pull/36533) ([github-actions[bot]](https://github.com/apps/github-actions)). -* fix log should print 'from' path [#36535](https://github.com/ClickHouse/ClickHouse/pull/36535) ([xiedeyantu](https://github.com/xiedeyantu)). +* fix log should print 'from' path [#36535](https://github.com/ClickHouse/ClickHouse/pull/36535) ([chen](https://github.com/xiedeyantu)). * Add function bin tests for Int/UInt128/UInt256 [#36537](https://github.com/ClickHouse/ClickHouse/pull/36537) ([Memo](https://github.com/Joeywzr)). * Fix 01161_all_system_tables [#36539](https://github.com/ClickHouse/ClickHouse/pull/36539) ([Antonio Andelic](https://github.com/antonio2368)). * Update PULL_REQUEST_TEMPLATE.md [#36543](https://github.com/ClickHouse/ClickHouse/pull/36543) ([Ivan Blinkov](https://github.com/blinkov)). diff --git a/docs/changelogs/v22.6.1.1985-stable.md b/docs/changelogs/v22.6.1.1985-stable.md index eeb4078eb04..9d930b8a0bb 100644 --- a/docs/changelogs/v22.6.1.1985-stable.md +++ b/docs/changelogs/v22.6.1.1985-stable.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.6.1.1985-stable FIXME as compared to v22.5.1.2079-stable +### ClickHouse release v22.6.1.1985-stable (7000c4e0033) FIXME as compared to v22.5.1.2079-stable (df0cb062098) #### Backward Incompatible Change * Changes how settings using `seconds` as type are parsed to support floating point values (for example: `max_execution_time=0.5`). Infinity or NaN values will throw an exception. [#37187](https://github.com/ClickHouse/ClickHouse/pull/37187) ([Raúl Marín](https://github.com/Algunenano)). @@ -78,7 +78,7 @@ sidebar_label: 2022 * Allow to use String type instead of Binary in Arrow/Parquet/ORC formats. This PR introduces 3 new settings for it: `output_format_arrow_string_as_string`, `output_format_parquet_string_as_string`, `output_format_orc_string_as_string`. Default value for all settings is `false`. [#37327](https://github.com/ClickHouse/ClickHouse/pull/37327) ([Kruglov Pavel](https://github.com/Avogar)). * Apply setting `input_format_max_rows_to_read_for_schema_inference` for all read rows in total from all files in globs. Previously setting `input_format_max_rows_to_read_for_schema_inference` was applied for each file in glob separately and in case of huge number of nulls we could read first `input_format_max_rows_to_read_for_schema_inference` rows from each file and get nothing. Also increase default value for this setting to 25000. [#37332](https://github.com/ClickHouse/ClickHouse/pull/37332) ([Kruglov Pavel](https://github.com/Avogar)). * allows providing `NULL`/`NOT NULL` right after type in column declaration. [#37337](https://github.com/ClickHouse/ClickHouse/pull/37337) ([Igor Nikonov](https://github.com/devcrafter)). -* optimize file segment PARTIALLY_DOWNLOADED get read buffer. [#37338](https://github.com/ClickHouse/ClickHouse/pull/37338) ([xiedeyantu](https://github.com/xiedeyantu)). +* optimize file segment PARTIALLY_DOWNLOADED get read buffer. [#37338](https://github.com/ClickHouse/ClickHouse/pull/37338) ([chen](https://github.com/xiedeyantu)). * Allow to prune the list of files via virtual columns such as `_file` and `_path` when reading from S3. This is for [#37174](https://github.com/ClickHouse/ClickHouse/issues/37174) , [#23494](https://github.com/ClickHouse/ClickHouse/issues/23494). [#37356](https://github.com/ClickHouse/ClickHouse/pull/37356) ([Amos Bird](https://github.com/amosbird)). * Try to improve short circuit functions processing to fix problems with stress tests. [#37384](https://github.com/ClickHouse/ClickHouse/pull/37384) ([Kruglov Pavel](https://github.com/Avogar)). * Closes [#37395](https://github.com/ClickHouse/ClickHouse/issues/37395). [#37415](https://github.com/ClickHouse/ClickHouse/pull/37415) ([Memo](https://github.com/Joeywzr)). @@ -117,7 +117,7 @@ sidebar_label: 2022 * Remove recursive submodules, because we don't need them and they can be confusing. Add style check to prevent recursive submodules. This closes [#32821](https://github.com/ClickHouse/ClickHouse/issues/32821). [#37616](https://github.com/ClickHouse/ClickHouse/pull/37616) ([Alexey Milovidov](https://github.com/alexey-milovidov)). * Add docs spellcheck to CI. [#37790](https://github.com/ClickHouse/ClickHouse/pull/37790) ([Vladimir C](https://github.com/vdimir)). * Fix overly aggressive stripping which removed the embedded hash required for checking the consistency of the executable. [#37993](https://github.com/ClickHouse/ClickHouse/pull/37993) ([Robert Schulze](https://github.com/rschu1ze)). -* fix MacOS build compressor faild. [#38007](https://github.com/ClickHouse/ClickHouse/pull/38007) ([xiedeyantu](https://github.com/xiedeyantu)). +* fix MacOS build compressor faild. [#38007](https://github.com/ClickHouse/ClickHouse/pull/38007) ([chen](https://github.com/xiedeyantu)). #### Bug Fix (user-visible misbehavior in official stable or prestable release) @@ -166,7 +166,7 @@ sidebar_label: 2022 * Fix possible incorrect result of `SELECT ... WITH FILL` in the case when `ORDER BY` should be applied after `WITH FILL` result (e.g. for outer query). Incorrect result was caused by optimization for `ORDER BY` expressions ([#35623](https://github.com/ClickHouse/ClickHouse/issues/35623)). Closes [#37904](https://github.com/ClickHouse/ClickHouse/issues/37904). [#37959](https://github.com/ClickHouse/ClickHouse/pull/37959) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). * Add missing default columns when pushing to the target table in WindowView, fix [#37815](https://github.com/ClickHouse/ClickHouse/issues/37815). [#37965](https://github.com/ClickHouse/ClickHouse/pull/37965) ([vxider](https://github.com/Vxider)). * Fixed a stack overflow issue that would cause compilation to fail. [#37996](https://github.com/ClickHouse/ClickHouse/pull/37996) ([Han Shukai](https://github.com/KinderRiven)). -* when open enable_filesystem_query_cache_limit, throw Reserved cache size exceeds the remaining cache size. [#38004](https://github.com/ClickHouse/ClickHouse/pull/38004) ([xiedeyantu](https://github.com/xiedeyantu)). +* when open enable_filesystem_query_cache_limit, throw Reserved cache size exceeds the remaining cache size. [#38004](https://github.com/ClickHouse/ClickHouse/pull/38004) ([chen](https://github.com/xiedeyantu)). * Query, containing ORDER BY ... WITH FILL, can generate extra rows when multiple WITH FILL columns are present. [#38074](https://github.com/ClickHouse/ClickHouse/pull/38074) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). #### Bug Fix (user-visible misbehaviour in official stable or prestable release) diff --git a/docs/changelogs/v22.6.2.12-stable.md b/docs/changelogs/v22.6.2.12-stable.md index 224367b994a..a97492ffe27 100644 --- a/docs/changelogs/v22.6.2.12-stable.md +++ b/docs/changelogs/v22.6.2.12-stable.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.6.2.12-stable FIXME as compared to v22.6.1.1985-stable +### ClickHouse release v22.6.2.12-stable (1fc97f10cbf) FIXME as compared to v22.6.1.1985-stable (7000c4e0033) #### Improvement * Backported in [#38484](https://github.com/ClickHouse/ClickHouse/issues/38484): Improve the stability for hive storage integration test. Move the data prepare step into test.py. [#38260](https://github.com/ClickHouse/ClickHouse/pull/38260) ([lgbo](https://github.com/lgbo-ustc)). diff --git a/docs/changelogs/v22.6.3.35-stable.md b/docs/changelogs/v22.6.3.35-stable.md index 584aeafc48e..e3eee326915 100644 --- a/docs/changelogs/v22.6.3.35-stable.md +++ b/docs/changelogs/v22.6.3.35-stable.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.6.3.35-stable FIXME as compared to v22.6.2.12-stable +### ClickHouse release v22.6.3.35-stable (d5566f2f2dd) FIXME as compared to v22.6.2.12-stable (1fc97f10cbf) #### Bug Fix * Backported in [#38812](https://github.com/ClickHouse/ClickHouse/issues/38812): Fix crash when executing GRANT ALL ON *.* with ON CLUSTER. It was broken in https://github.com/ClickHouse/ClickHouse/pull/35767. This closes [#38618](https://github.com/ClickHouse/ClickHouse/issues/38618). [#38674](https://github.com/ClickHouse/ClickHouse/pull/38674) ([Vitaly Baranov](https://github.com/vitlibar)). diff --git a/docs/changelogs/v22.6.4.35-stable.md b/docs/changelogs/v22.6.4.35-stable.md index d70d20d6134..b6c63d94eab 100644 --- a/docs/changelogs/v22.6.4.35-stable.md +++ b/docs/changelogs/v22.6.4.35-stable.md @@ -5,7 +5,7 @@ sidebar_label: 2022 # 2022 Changelog -### ClickHouse release v22.6.4.35-stable FIXME as compared to v22.6.3.35-stable +### ClickHouse release v22.6.4.35-stable (b9202cae6f4) FIXME as compared to v22.6.3.35-stable (d5566f2f2dd) #### Build/Testing/Packaging Improvement * Backported in [#38822](https://github.com/ClickHouse/ClickHouse/issues/38822): - Change `all|noarch` packages to architecture-dependent - Fix some documentation for it - Push aarch64|arm64 packages to artifactory and release assets - Fixes [#36443](https://github.com/ClickHouse/ClickHouse/issues/36443). [#38580](https://github.com/ClickHouse/ClickHouse/pull/38580) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). diff --git a/utils/list-versions/version_date.tsv b/utils/list-versions/version_date.tsv index 3cb248efa55..0ab5349b97b 100644 --- a/utils/list-versions/version_date.tsv +++ b/utils/list-versions/version_date.tsv @@ -1,3 +1,4 @@ +v22.7.3.5-stable 2022-08-10 v22.7.2.15-stable 2022-08-03 v22.7.1.2484-stable 2022-07-21 v22.6.5.22-stable 2022-08-09 @@ -5,6 +6,7 @@ v22.6.4.35-stable 2022-07-25 v22.6.3.35-stable 2022-07-06 v22.6.2.12-stable 2022-06-29 v22.6.1.1985-stable 2022-06-16 +v22.5.4.19-stable 2022-08-10 v22.5.3.21-stable 2022-07-25 v22.5.2.53-stable 2022-07-07 v22.5.1.2079-stable 2022-05-19 @@ -13,6 +15,7 @@ v22.4.5.9-stable 2022-05-06 v22.4.4.7-stable 2022-04-29 v22.4.3.3-stable 2022-04-26 v22.4.2.1-stable 2022-04-22 +v22.3.11.12-lts 2022-08-10 v22.3.10.22-lts 2022-08-03 v22.3.9.19-lts 2022-07-25 v22.3.8.39-lts 2022-07-07 From f47873769e620d896442ccb9220acdc2c32b1525 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 11 Aug 2022 00:18:08 +0200 Subject: [PATCH 560/672] Add omitted changelogs --- docs/changelogs/v22.3.10.22-lts.md | 30 +++++++++++++++++++ docs/changelogs/v22.3.11.12-lts.md | 20 +++++++++++++ docs/changelogs/v22.3.8.39-lts.md | 32 ++++++++++++++++++++ docs/changelogs/v22.3.9.19-lts.md | 24 +++++++++++++++ docs/changelogs/v22.4.6.53-stable.md | 44 ++++++++++++++++++++++++++++ docs/changelogs/v22.5.2.53-stable.md | 40 +++++++++++++++++++++++++ docs/changelogs/v22.5.3.21-stable.md | 24 +++++++++++++++ docs/changelogs/v22.5.4.19-stable.md | 29 ++++++++++++++++++ docs/changelogs/v22.7.3.5-stable.md | 18 ++++++++++++ 9 files changed, 261 insertions(+) create mode 100644 docs/changelogs/v22.3.10.22-lts.md create mode 100644 docs/changelogs/v22.3.11.12-lts.md create mode 100644 docs/changelogs/v22.3.8.39-lts.md create mode 100644 docs/changelogs/v22.3.9.19-lts.md create mode 100644 docs/changelogs/v22.4.6.53-stable.md create mode 100644 docs/changelogs/v22.5.2.53-stable.md create mode 100644 docs/changelogs/v22.5.3.21-stable.md create mode 100644 docs/changelogs/v22.5.4.19-stable.md create mode 100644 docs/changelogs/v22.7.3.5-stable.md diff --git a/docs/changelogs/v22.3.10.22-lts.md b/docs/changelogs/v22.3.10.22-lts.md new file mode 100644 index 00000000000..48009cb4f67 --- /dev/null +++ b/docs/changelogs/v22.3.10.22-lts.md @@ -0,0 +1,30 @@ +--- +sidebar_position: 1 +sidebar_label: 2022 +--- + +# 2022 Changelog + +### ClickHouse release v22.3.10.22-lts (25886f517d4) FIXME as compared to v22.3.9.19-lts (7976930b82e) + +#### Bug Fix +* Backported in [#39761](https://github.com/ClickHouse/ClickHouse/issues/39761): Fix seeking while reading from encrypted disk. This PR fixes [#38381](https://github.com/ClickHouse/ClickHouse/issues/38381). [#39687](https://github.com/ClickHouse/ClickHouse/pull/39687) ([Vitaly Baranov](https://github.com/vitlibar)). + +#### Bug Fix (user-visible misbehavior in official stable or prestable release) + +* Backported in [#39206](https://github.com/ClickHouse/ClickHouse/issues/39206): Fix reading of sparse columns from `MergeTree` tables that store their data in S3. [#37978](https://github.com/ClickHouse/ClickHouse/pull/37978) ([Anton Popov](https://github.com/CurtizJ)). +* Backported in [#39381](https://github.com/ClickHouse/ClickHouse/issues/39381): Fixed error `Not found column Type in block` in selects with `PREWHERE` and read-in-order optimizations. [#39157](https://github.com/ClickHouse/ClickHouse/pull/39157) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* Backported in [#39588](https://github.com/ClickHouse/ClickHouse/issues/39588): Fix data race and possible heap-buffer-overflow in Avro format. Closes [#39094](https://github.com/ClickHouse/ClickHouse/issues/39094) Closes [#33652](https://github.com/ClickHouse/ClickHouse/issues/33652). [#39498](https://github.com/ClickHouse/ClickHouse/pull/39498) ([Kruglov Pavel](https://github.com/Avogar)). +* Backported in [#39610](https://github.com/ClickHouse/ClickHouse/issues/39610): Fix bug with maxsplit argument for splitByChar, which was not working correctly. [#39552](https://github.com/ClickHouse/ClickHouse/pull/39552) ([filimonov](https://github.com/filimonov)). +* Backported in [#39834](https://github.com/ClickHouse/ClickHouse/issues/39834): Fix `CANNOT_READ_ALL_DATA` exception with `local_filesystem_read_method=pread_threadpool`. This bug affected only Linux kernel version 5.9 and 5.10 according to [man](https://manpages.debian.org/testing/manpages-dev/preadv2.2.en.html#BUGS). [#39800](https://github.com/ClickHouse/ClickHouse/pull/39800) ([Anton Popov](https://github.com/CurtizJ)). + +#### Bug Fix (user-visible misbehaviour in official stable or prestable release) + +* Backported in [#39238](https://github.com/ClickHouse/ClickHouse/issues/39238): Fix performance regression of scalar query optimization. [#35986](https://github.com/ClickHouse/ClickHouse/pull/35986) ([Amos Bird](https://github.com/amosbird)). +* Backported in [#39531](https://github.com/ClickHouse/ClickHouse/issues/39531): Fix some issues with async reads from remote filesystem which happened when reading low cardinality. [#36763](https://github.com/ClickHouse/ClickHouse/pull/36763) ([Kseniia Sumarokova](https://github.com/kssenii)). + +#### NOT FOR CHANGELOG / INSIGNIFICANT + +* Replace MemoryTrackerBlockerInThread to LockMemoryExceptionInThread [#39619](https://github.com/ClickHouse/ClickHouse/pull/39619) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). +* Change mysql-odbc url [#39702](https://github.com/ClickHouse/ClickHouse/pull/39702) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + diff --git a/docs/changelogs/v22.3.11.12-lts.md b/docs/changelogs/v22.3.11.12-lts.md new file mode 100644 index 00000000000..b10de69e234 --- /dev/null +++ b/docs/changelogs/v22.3.11.12-lts.md @@ -0,0 +1,20 @@ +--- +sidebar_position: 1 +sidebar_label: 2022 +--- + +# 2022 Changelog + +### ClickHouse release v22.3.11.12-lts (137c5f72657) FIXME as compared to v22.3.10.22-lts (25886f517d4) + +#### Build/Testing/Packaging Improvement +* Backported in [#39881](https://github.com/ClickHouse/ClickHouse/issues/39881): Former packages used to install systemd.service file to `/etc`. The files there are marked as `conf` and are not cleaned out, and not updated automatically. This PR cleans them out. [#39323](https://github.com/ClickHouse/ClickHouse/pull/39323) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + +#### Bug Fix (user-visible misbehavior in official stable or prestable release) + +* Backported in [#39336](https://github.com/ClickHouse/ClickHouse/issues/39336): Fix `parallel_view_processing=1` with `optimize_trivial_insert_select=1`. Fix `max_insert_threads` while pushing to views. [#38731](https://github.com/ClickHouse/ClickHouse/pull/38731) ([Azat Khuzhin](https://github.com/azat)). + +#### NO CL ENTRY + +* NO CL ENTRY: 'Revert "Backport [#39687](https://github.com/ClickHouse/ClickHouse/issues/39687) to 22.3: Fix seeking while reading from encrypted disk"'. [#40052](https://github.com/ClickHouse/ClickHouse/pull/40052) ([Alexey Milovidov](https://github.com/alexey-milovidov)). + diff --git a/docs/changelogs/v22.3.8.39-lts.md b/docs/changelogs/v22.3.8.39-lts.md new file mode 100644 index 00000000000..893e8762e9c --- /dev/null +++ b/docs/changelogs/v22.3.8.39-lts.md @@ -0,0 +1,32 @@ +--- +sidebar_position: 1 +sidebar_label: 2022 +--- + +# 2022 Changelog + +### ClickHouse release v22.3.8.39-lts (6bcf982f58b) FIXME as compared to v22.3.7.28-lts (420bdfa2751) + +#### Build/Testing/Packaging Improvement +* Backported in [#38826](https://github.com/ClickHouse/ClickHouse/issues/38826): - Change `all|noarch` packages to architecture-dependent - Fix some documentation for it - Push aarch64|arm64 packages to artifactory and release assets - Fixes [#36443](https://github.com/ClickHouse/ClickHouse/issues/36443). [#38580](https://github.com/ClickHouse/ClickHouse/pull/38580) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + +#### Bug Fix (user-visible misbehavior in official stable or prestable release) + +* Backported in [#38453](https://github.com/ClickHouse/ClickHouse/issues/38453): Fix bug with nested short-circuit functions that led to execution of arguments even if condition is false. Closes [#38040](https://github.com/ClickHouse/ClickHouse/issues/38040). [#38173](https://github.com/ClickHouse/ClickHouse/pull/38173) ([Kruglov Pavel](https://github.com/Avogar)). +* Backported in [#38710](https://github.com/ClickHouse/ClickHouse/issues/38710): Fix incorrect result of distributed queries with `DISTINCT` and `LIMIT`. Fixes [#38282](https://github.com/ClickHouse/ClickHouse/issues/38282). [#38371](https://github.com/ClickHouse/ClickHouse/pull/38371) ([Anton Popov](https://github.com/CurtizJ)). +* Backported in [#38689](https://github.com/ClickHouse/ClickHouse/issues/38689): Now it's possible to start a clickhouse-server and attach/detach tables even for tables with the incorrect values of IPv4/IPv6 representation. Proper fix for issue [#35156](https://github.com/ClickHouse/ClickHouse/issues/35156). [#38590](https://github.com/ClickHouse/ClickHouse/pull/38590) ([alesapin](https://github.com/alesapin)). +* Backported in [#38776](https://github.com/ClickHouse/ClickHouse/issues/38776): `rankCorr` function will work correctly if some arguments are NaNs. This closes [#38396](https://github.com/ClickHouse/ClickHouse/issues/38396). [#38722](https://github.com/ClickHouse/ClickHouse/pull/38722) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* Backported in [#38780](https://github.com/ClickHouse/ClickHouse/issues/38780): Fix use-after-free for Map combinator that leads to incorrect result. [#38748](https://github.com/ClickHouse/ClickHouse/pull/38748) ([Azat Khuzhin](https://github.com/azat)). + +#### Bug Fix (user-visible misbehaviour in official stable or prestable release) + +* Backported in [#36818](https://github.com/ClickHouse/ClickHouse/issues/36818): Fix projection analysis which might lead to wrong query result when IN subquery is used. This fixes [#35336](https://github.com/ClickHouse/ClickHouse/issues/35336). [#35631](https://github.com/ClickHouse/ClickHouse/pull/35631) ([Amos Bird](https://github.com/amosbird)). +* Backported in [#38467](https://github.com/ClickHouse/ClickHouse/issues/38467): - Fix potential error with literals in `WHERE` for join queries. Close [#36279](https://github.com/ClickHouse/ClickHouse/issues/36279). [#36542](https://github.com/ClickHouse/ClickHouse/pull/36542) ([Vladimir C](https://github.com/vdimir)). + +#### NOT FOR CHANGELOG / INSIGNIFICANT + +* Try to fix some trash [#37303](https://github.com/ClickHouse/ClickHouse/pull/37303) ([Alexander Tokmakov](https://github.com/tavplubix)). +* Update docker-compose to try get rid of v1 errors [#38394](https://github.com/ClickHouse/ClickHouse/pull/38394) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). +* Trying backport useful features for CI [#38510](https://github.com/ClickHouse/ClickHouse/pull/38510) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). +* Fix backports diff [#38703](https://github.com/ClickHouse/ClickHouse/pull/38703) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + diff --git a/docs/changelogs/v22.3.9.19-lts.md b/docs/changelogs/v22.3.9.19-lts.md new file mode 100644 index 00000000000..c00b9bfc8eb --- /dev/null +++ b/docs/changelogs/v22.3.9.19-lts.md @@ -0,0 +1,24 @@ +--- +sidebar_position: 1 +sidebar_label: 2022 +--- + +# 2022 Changelog + +### ClickHouse release v22.3.9.19-lts (7976930b82e) FIXME as compared to v22.3.8.39-lts (6bcf982f58b) + +#### Bug Fix (user-visible misbehavior in official stable or prestable release) + +* Backported in [#39097](https://github.com/ClickHouse/ClickHouse/issues/39097): Any allocations inside OvercommitTracker may lead to deadlock. Logging was not very informative so it's easier just to remove logging. Fixes [#37794](https://github.com/ClickHouse/ClickHouse/issues/37794). [#39030](https://github.com/ClickHouse/ClickHouse/pull/39030) ([Dmitry Novik](https://github.com/novikd)). +* Backported in [#39080](https://github.com/ClickHouse/ClickHouse/issues/39080): Fix bug in filesystem cache that could happen in some corner case which coincided with cache capacity hitting the limit. Closes [#39066](https://github.com/ClickHouse/ClickHouse/issues/39066). [#39070](https://github.com/ClickHouse/ClickHouse/pull/39070) ([Kseniia Sumarokova](https://github.com/kssenii)). +* Backported in [#39149](https://github.com/ClickHouse/ClickHouse/issues/39149): Fix error `Block structure mismatch` which could happen for INSERT into table with attached MATERIALIZED VIEW and enabled setting `extremes = 1`. Closes [#29759](https://github.com/ClickHouse/ClickHouse/issues/29759) and [#38729](https://github.com/ClickHouse/ClickHouse/issues/38729). [#39125](https://github.com/ClickHouse/ClickHouse/pull/39125) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). +* Backported in [#39372](https://github.com/ClickHouse/ClickHouse/issues/39372): Declare RabbitMQ queue without default arguments `x-max-length` and `x-overflow`. [#39259](https://github.com/ClickHouse/ClickHouse/pull/39259) ([rnbondarenko](https://github.com/rnbondarenko)). +* Backported in [#39379](https://github.com/ClickHouse/ClickHouse/issues/39379): Fix segmentation fault in MaterializedPostgreSQL database engine, which could happen if some exception occurred at replication initialisation. Closes [#36939](https://github.com/ClickHouse/ClickHouse/issues/36939). [#39272](https://github.com/ClickHouse/ClickHouse/pull/39272) ([Kseniia Sumarokova](https://github.com/kssenii)). +* Backported in [#39351](https://github.com/ClickHouse/ClickHouse/issues/39351): Fix incorrect fetch postgresql tables query fro PostgreSQL database engine. Closes [#33502](https://github.com/ClickHouse/ClickHouse/issues/33502). [#39283](https://github.com/ClickHouse/ClickHouse/pull/39283) ([Kseniia Sumarokova](https://github.com/kssenii)). + +#### NOT FOR CHANGELOG / INSIGNIFICANT + +* Reproduce and a little bit better fix for LC dict right offset. [#36856](https://github.com/ClickHouse/ClickHouse/pull/36856) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). +* Retry docker buildx commands with progressive sleep in between [#38898](https://github.com/ClickHouse/ClickHouse/pull/38898) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). +* Add docker_server.py running to backport and release CIs [#39011](https://github.com/ClickHouse/ClickHouse/pull/39011) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + diff --git a/docs/changelogs/v22.4.6.53-stable.md b/docs/changelogs/v22.4.6.53-stable.md new file mode 100644 index 00000000000..cdd4c885574 --- /dev/null +++ b/docs/changelogs/v22.4.6.53-stable.md @@ -0,0 +1,44 @@ +--- +sidebar_position: 1 +sidebar_label: 2022 +--- + +# 2022 Changelog + +### ClickHouse release v22.4.6.53-stable (0625731c940) FIXME as compared to v22.4.5.9-stable (059ef6cadcd) + +#### New Feature +* Backported in [#38714](https://github.com/ClickHouse/ClickHouse/issues/38714): SALT is allowed for CREATE USER IDENTIFIED WITH sha256_hash. [#37377](https://github.com/ClickHouse/ClickHouse/pull/37377) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). + +#### Build/Testing/Packaging Improvement +* Backported in [#38828](https://github.com/ClickHouse/ClickHouse/issues/38828): - Change `all|noarch` packages to architecture-dependent - Fix some documentation for it - Push aarch64|arm64 packages to artifactory and release assets - Fixes [#36443](https://github.com/ClickHouse/ClickHouse/issues/36443). [#38580](https://github.com/ClickHouse/ClickHouse/pull/38580) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + +#### Bug Fix (user-visible misbehavior in official stable or prestable release) + +* Backported in [#37717](https://github.com/ClickHouse/ClickHouse/issues/37717): Fix unexpected errors with a clash of constant strings in aggregate function, prewhere and join. Close [#36891](https://github.com/ClickHouse/ClickHouse/issues/36891). [#37336](https://github.com/ClickHouse/ClickHouse/pull/37336) ([Vladimir C](https://github.com/vdimir)). +* Backported in [#37512](https://github.com/ClickHouse/ClickHouse/issues/37512): Fix logical error in normalizeUTF8 functions. Closes [#37298](https://github.com/ClickHouse/ClickHouse/issues/37298). [#37443](https://github.com/ClickHouse/ClickHouse/pull/37443) ([Maksim Kita](https://github.com/kitaisreal)). +* Backported in [#37941](https://github.com/ClickHouse/ClickHouse/issues/37941): Fix setting cast_ipv4_ipv6_default_on_conversion_error for internal cast function. Closes [#35156](https://github.com/ClickHouse/ClickHouse/issues/35156). [#37761](https://github.com/ClickHouse/ClickHouse/pull/37761) ([Maksim Kita](https://github.com/kitaisreal)). +* Backported in [#38452](https://github.com/ClickHouse/ClickHouse/issues/38452): Fix bug with nested short-circuit functions that led to execution of arguments even if condition is false. Closes [#38040](https://github.com/ClickHouse/ClickHouse/issues/38040). [#38173](https://github.com/ClickHouse/ClickHouse/pull/38173) ([Kruglov Pavel](https://github.com/Avogar)). +* Backported in [#38711](https://github.com/ClickHouse/ClickHouse/issues/38711): Fix incorrect result of distributed queries with `DISTINCT` and `LIMIT`. Fixes [#38282](https://github.com/ClickHouse/ClickHouse/issues/38282). [#38371](https://github.com/ClickHouse/ClickHouse/pull/38371) ([Anton Popov](https://github.com/CurtizJ)). +* Backported in [#38593](https://github.com/ClickHouse/ClickHouse/issues/38593): Fix parts removal (will be left forever if they had not been removed on server shutdown) after incorrect server shutdown. [#38486](https://github.com/ClickHouse/ClickHouse/pull/38486) ([Azat Khuzhin](https://github.com/azat)). +* Backported in [#38596](https://github.com/ClickHouse/ClickHouse/issues/38596): Fix table creation to avoid replication issues with pre-22.4 replicas. [#38541](https://github.com/ClickHouse/ClickHouse/pull/38541) ([Raúl Marín](https://github.com/Algunenano)). +* Backported in [#38686](https://github.com/ClickHouse/ClickHouse/issues/38686): Now it's possible to start a clickhouse-server and attach/detach tables even for tables with the incorrect values of IPv4/IPv6 representation. Proper fix for issue [#35156](https://github.com/ClickHouse/ClickHouse/issues/35156). [#38590](https://github.com/ClickHouse/ClickHouse/pull/38590) ([alesapin](https://github.com/alesapin)). +* Backported in [#38663](https://github.com/ClickHouse/ClickHouse/issues/38663): Adapt some more nodes to avoid issues with pre-22.4 replicas. [#38627](https://github.com/ClickHouse/ClickHouse/pull/38627) ([Raúl Marín](https://github.com/Algunenano)). +* Backported in [#38777](https://github.com/ClickHouse/ClickHouse/issues/38777): `rankCorr` function will work correctly if some arguments are NaNs. This closes [#38396](https://github.com/ClickHouse/ClickHouse/issues/38396). [#38722](https://github.com/ClickHouse/ClickHouse/pull/38722) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* Backported in [#38781](https://github.com/ClickHouse/ClickHouse/issues/38781): Fix use-after-free for Map combinator that leads to incorrect result. [#38748](https://github.com/ClickHouse/ClickHouse/pull/38748) ([Azat Khuzhin](https://github.com/azat)). + +#### Bug Fix (user-visible misbehaviour in official stable or prestable release) + +* Backported in [#37456](https://github.com/ClickHouse/ClickHouse/issues/37456): Server might fail to start if it cannot resolve hostname of external ClickHouse dictionary. It's fixed. Fixes [#36451](https://github.com/ClickHouse/ClickHouse/issues/36451). [#36463](https://github.com/ClickHouse/ClickHouse/pull/36463) ([Alexander Tokmakov](https://github.com/tavplubix)). +* Backported in [#38468](https://github.com/ClickHouse/ClickHouse/issues/38468): - Fix potential error with literals in `WHERE` for join queries. Close [#36279](https://github.com/ClickHouse/ClickHouse/issues/36279). [#36542](https://github.com/ClickHouse/ClickHouse/pull/36542) ([Vladimir C](https://github.com/vdimir)). +* Backported in [#37363](https://github.com/ClickHouse/ClickHouse/issues/37363): Fixed problem with infs in `quantileTDigest`. Fixes [#32107](https://github.com/ClickHouse/ClickHouse/issues/32107). [#37021](https://github.com/ClickHouse/ClickHouse/pull/37021) ([Vladimir Chebotarev](https://github.com/excitoon)). + +#### NOT FOR CHANGELOG / INSIGNIFICANT + +* Integration tests [#36866](https://github.com/ClickHouse/ClickHouse/pull/36866) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). +* Try to fix some trash [#37303](https://github.com/ClickHouse/ClickHouse/pull/37303) ([Alexander Tokmakov](https://github.com/tavplubix)). +* Update protobuf files for kafka and rabbitmq [fix integration tests] [#37884](https://github.com/ClickHouse/ClickHouse/pull/37884) ([Nikita Taranov](https://github.com/nickitat)). +* Try fix `test_grpc_protocol/test.py::test_progress` [#37908](https://github.com/ClickHouse/ClickHouse/pull/37908) ([Alexander Tokmakov](https://github.com/tavplubix)). +* Update docker-compose to try get rid of v1 errors [#38394](https://github.com/ClickHouse/ClickHouse/pull/38394) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). +* Fix backports diff [#38703](https://github.com/ClickHouse/ClickHouse/pull/38703) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + diff --git a/docs/changelogs/v22.5.2.53-stable.md b/docs/changelogs/v22.5.2.53-stable.md new file mode 100644 index 00000000000..10b64632680 --- /dev/null +++ b/docs/changelogs/v22.5.2.53-stable.md @@ -0,0 +1,40 @@ +--- +sidebar_position: 1 +sidebar_label: 2022 +--- + +# 2022 Changelog + +### ClickHouse release v22.5.2.53-stable (5fd600fda9e) FIXME as compared to v22.5.1.2079-stable (df0cb062098) + +#### New Feature +* Backported in [#38713](https://github.com/ClickHouse/ClickHouse/issues/38713): SALT is allowed for CREATE USER IDENTIFIED WITH sha256_hash. [#37377](https://github.com/ClickHouse/ClickHouse/pull/37377) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). + +#### Build/Testing/Packaging Improvement +* Backported in [#38827](https://github.com/ClickHouse/ClickHouse/issues/38827): - Change `all|noarch` packages to architecture-dependent - Fix some documentation for it - Push aarch64|arm64 packages to artifactory and release assets - Fixes [#36443](https://github.com/ClickHouse/ClickHouse/issues/36443). [#38580](https://github.com/ClickHouse/ClickHouse/pull/38580) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + +#### Bug Fix (user-visible misbehavior in official stable or prestable release) + +* Backported in [#37716](https://github.com/ClickHouse/ClickHouse/issues/37716): Fix unexpected errors with a clash of constant strings in aggregate function, prewhere and join. Close [#36891](https://github.com/ClickHouse/ClickHouse/issues/36891). [#37336](https://github.com/ClickHouse/ClickHouse/pull/37336) ([Vladimir C](https://github.com/vdimir)). +* Backported in [#37408](https://github.com/ClickHouse/ClickHouse/issues/37408): Throw an exception when GROUPING SETS used with ROLLUP or CUBE. [#37367](https://github.com/ClickHouse/ClickHouse/pull/37367) ([Dmitry Novik](https://github.com/novikd)). +* Backported in [#37513](https://github.com/ClickHouse/ClickHouse/issues/37513): Fix logical error in normalizeUTF8 functions. Closes [#37298](https://github.com/ClickHouse/ClickHouse/issues/37298). [#37443](https://github.com/ClickHouse/ClickHouse/pull/37443) ([Maksim Kita](https://github.com/kitaisreal)). +* Backported in [#37942](https://github.com/ClickHouse/ClickHouse/issues/37942): Fix setting cast_ipv4_ipv6_default_on_conversion_error for internal cast function. Closes [#35156](https://github.com/ClickHouse/ClickHouse/issues/35156). [#37761](https://github.com/ClickHouse/ClickHouse/pull/37761) ([Maksim Kita](https://github.com/kitaisreal)). +* Backported in [#38451](https://github.com/ClickHouse/ClickHouse/issues/38451): Fix bug with nested short-circuit functions that led to execution of arguments even if condition is false. Closes [#38040](https://github.com/ClickHouse/ClickHouse/issues/38040). [#38173](https://github.com/ClickHouse/ClickHouse/pull/38173) ([Kruglov Pavel](https://github.com/Avogar)). +* Backported in [#38544](https://github.com/ClickHouse/ClickHouse/issues/38544): Do not allow recursive usage of OvercommitTracker during logging. Fixes [#37794](https://github.com/ClickHouse/ClickHouse/issues/37794) cc @tavplubix @davenger. [#38246](https://github.com/ClickHouse/ClickHouse/pull/38246) ([Dmitry Novik](https://github.com/novikd)). +* Backported in [#38708](https://github.com/ClickHouse/ClickHouse/issues/38708): Fix incorrect result of distributed queries with `DISTINCT` and `LIMIT`. Fixes [#38282](https://github.com/ClickHouse/ClickHouse/issues/38282). [#38371](https://github.com/ClickHouse/ClickHouse/pull/38371) ([Anton Popov](https://github.com/CurtizJ)). +* Backported in [#38595](https://github.com/ClickHouse/ClickHouse/issues/38595): Fix parts removal (will be left forever if they had not been removed on server shutdown) after incorrect server shutdown. [#38486](https://github.com/ClickHouse/ClickHouse/pull/38486) ([Azat Khuzhin](https://github.com/azat)). +* Backported in [#38598](https://github.com/ClickHouse/ClickHouse/issues/38598): Fix table creation to avoid replication issues with pre-22.4 replicas. [#38541](https://github.com/ClickHouse/ClickHouse/pull/38541) ([Raúl Marín](https://github.com/Algunenano)). +* Backported in [#38688](https://github.com/ClickHouse/ClickHouse/issues/38688): Now it's possible to start a clickhouse-server and attach/detach tables even for tables with the incorrect values of IPv4/IPv6 representation. Proper fix for issue [#35156](https://github.com/ClickHouse/ClickHouse/issues/35156). [#38590](https://github.com/ClickHouse/ClickHouse/pull/38590) ([alesapin](https://github.com/alesapin)). +* Backported in [#38664](https://github.com/ClickHouse/ClickHouse/issues/38664): Adapt some more nodes to avoid issues with pre-22.4 replicas. [#38627](https://github.com/ClickHouse/ClickHouse/pull/38627) ([Raúl Marín](https://github.com/Algunenano)). +* Backported in [#38779](https://github.com/ClickHouse/ClickHouse/issues/38779): `rankCorr` function will work correctly if some arguments are NaNs. This closes [#38396](https://github.com/ClickHouse/ClickHouse/issues/38396). [#38722](https://github.com/ClickHouse/ClickHouse/pull/38722) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* Backported in [#38783](https://github.com/ClickHouse/ClickHouse/issues/38783): Fix use-after-free for Map combinator that leads to incorrect result. [#38748](https://github.com/ClickHouse/ClickHouse/pull/38748) ([Azat Khuzhin](https://github.com/azat)). + +#### NOT FOR CHANGELOG / INSIGNIFICANT + +* Try to fix some trash [#37303](https://github.com/ClickHouse/ClickHouse/pull/37303) ([Alexander Tokmakov](https://github.com/tavplubix)). +* Update protobuf files for kafka and rabbitmq [fix integration tests] [#37884](https://github.com/ClickHouse/ClickHouse/pull/37884) ([Nikita Taranov](https://github.com/nickitat)). +* Try fix `test_grpc_protocol/test.py::test_progress` [#37908](https://github.com/ClickHouse/ClickHouse/pull/37908) ([Alexander Tokmakov](https://github.com/tavplubix)). +* Try to fix BC check [#38178](https://github.com/ClickHouse/ClickHouse/pull/38178) ([Kruglov Pavel](https://github.com/Avogar)). +* Update docker-compose to try get rid of v1 errors [#38394](https://github.com/ClickHouse/ClickHouse/pull/38394) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). +* Fix backports diff [#38703](https://github.com/ClickHouse/ClickHouse/pull/38703) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + diff --git a/docs/changelogs/v22.5.3.21-stable.md b/docs/changelogs/v22.5.3.21-stable.md new file mode 100644 index 00000000000..7c4717575d8 --- /dev/null +++ b/docs/changelogs/v22.5.3.21-stable.md @@ -0,0 +1,24 @@ +--- +sidebar_position: 1 +sidebar_label: 2022 +--- + +# 2022 Changelog + +### ClickHouse release v22.5.3.21-stable (e03724efec5) FIXME as compared to v22.5.2.53-stable (5fd600fda9e) + +#### Bug Fix (user-visible misbehavior in official stable or prestable release) + +* Backported in [#38241](https://github.com/ClickHouse/ClickHouse/issues/38241): Fix possible crash in `Distributed` async insert in case of removing a replica from config. [#38029](https://github.com/ClickHouse/ClickHouse/pull/38029) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). +* Backported in [#39098](https://github.com/ClickHouse/ClickHouse/issues/39098): Any allocations inside OvercommitTracker may lead to deadlock. Logging was not very informative so it's easier just to remove logging. Fixes [#37794](https://github.com/ClickHouse/ClickHouse/issues/37794). [#39030](https://github.com/ClickHouse/ClickHouse/pull/39030) ([Dmitry Novik](https://github.com/novikd)). +* Backported in [#39078](https://github.com/ClickHouse/ClickHouse/issues/39078): Fix bug in filesystem cache that could happen in some corner case which coincided with cache capacity hitting the limit. Closes [#39066](https://github.com/ClickHouse/ClickHouse/issues/39066). [#39070](https://github.com/ClickHouse/ClickHouse/pull/39070) ([Kseniia Sumarokova](https://github.com/kssenii)). +* Backported in [#39152](https://github.com/ClickHouse/ClickHouse/issues/39152): Fix error `Block structure mismatch` which could happen for INSERT into table with attached MATERIALIZED VIEW and enabled setting `extremes = 1`. Closes [#29759](https://github.com/ClickHouse/ClickHouse/issues/29759) and [#38729](https://github.com/ClickHouse/ClickHouse/issues/38729). [#39125](https://github.com/ClickHouse/ClickHouse/pull/39125) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). +* Backported in [#39274](https://github.com/ClickHouse/ClickHouse/issues/39274): Fixed error `Not found column Type in block` in selects with `PREWHERE` and read-in-order optimizations. [#39157](https://github.com/ClickHouse/ClickHouse/pull/39157) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* Backported in [#39369](https://github.com/ClickHouse/ClickHouse/issues/39369): Declare RabbitMQ queue without default arguments `x-max-length` and `x-overflow`. [#39259](https://github.com/ClickHouse/ClickHouse/pull/39259) ([rnbondarenko](https://github.com/rnbondarenko)). +* Backported in [#39350](https://github.com/ClickHouse/ClickHouse/issues/39350): Fix incorrect fetch postgresql tables query fro PostgreSQL database engine. Closes [#33502](https://github.com/ClickHouse/ClickHouse/issues/33502). [#39283](https://github.com/ClickHouse/ClickHouse/pull/39283) ([Kseniia Sumarokova](https://github.com/kssenii)). + +#### NOT FOR CHANGELOG / INSIGNIFICANT + +* Retry docker buildx commands with progressive sleep in between [#38898](https://github.com/ClickHouse/ClickHouse/pull/38898) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). +* Add docker_server.py running to backport and release CIs [#39011](https://github.com/ClickHouse/ClickHouse/pull/39011) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + diff --git a/docs/changelogs/v22.5.4.19-stable.md b/docs/changelogs/v22.5.4.19-stable.md new file mode 100644 index 00000000000..05296a178eb --- /dev/null +++ b/docs/changelogs/v22.5.4.19-stable.md @@ -0,0 +1,29 @@ +--- +sidebar_position: 1 +sidebar_label: 2022 +--- + +# 2022 Changelog + +### ClickHouse release v22.5.4.19-stable (c893bba830e) FIXME as compared to v22.5.3.21-stable (e03724efec5) + +#### Bug Fix +* Backported in [#39748](https://github.com/ClickHouse/ClickHouse/issues/39748): Fix seeking while reading from encrypted disk. This PR fixes [#38381](https://github.com/ClickHouse/ClickHouse/issues/38381). [#39687](https://github.com/ClickHouse/ClickHouse/pull/39687) ([Vitaly Baranov](https://github.com/vitlibar)). + +#### Build/Testing/Packaging Improvement +* Backported in [#39882](https://github.com/ClickHouse/ClickHouse/issues/39882): Former packages used to install systemd.service file to `/etc`. The files there are marked as `conf` and are not cleaned out, and not updated automatically. This PR cleans them out. [#39323](https://github.com/ClickHouse/ClickHouse/pull/39323) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + +#### Bug Fix (user-visible misbehavior in official stable or prestable release) + +* Backported in [#39209](https://github.com/ClickHouse/ClickHouse/issues/39209): Fix reading of sparse columns from `MergeTree` tables that store their data in S3. [#37978](https://github.com/ClickHouse/ClickHouse/pull/37978) ([Anton Popov](https://github.com/CurtizJ)). +* Backported in [#39589](https://github.com/ClickHouse/ClickHouse/issues/39589): Fix data race and possible heap-buffer-overflow in Avro format. Closes [#39094](https://github.com/ClickHouse/ClickHouse/issues/39094) Closes [#33652](https://github.com/ClickHouse/ClickHouse/issues/33652). [#39498](https://github.com/ClickHouse/ClickHouse/pull/39498) ([Kruglov Pavel](https://github.com/Avogar)). +* Backported in [#39611](https://github.com/ClickHouse/ClickHouse/issues/39611): Fix bug with maxsplit argument for splitByChar, which was not working correctly. [#39552](https://github.com/ClickHouse/ClickHouse/pull/39552) ([filimonov](https://github.com/filimonov)). +* Backported in [#39790](https://github.com/ClickHouse/ClickHouse/issues/39790): Fix wrong index analysis with tuples and operator `IN`, which could lead to wrong query result. [#39752](https://github.com/ClickHouse/ClickHouse/pull/39752) ([Anton Popov](https://github.com/CurtizJ)). +* Backported in [#39835](https://github.com/ClickHouse/ClickHouse/issues/39835): Fix `CANNOT_READ_ALL_DATA` exception with `local_filesystem_read_method=pread_threadpool`. This bug affected only Linux kernel version 5.9 and 5.10 according to [man](https://manpages.debian.org/testing/manpages-dev/preadv2.2.en.html#BUGS). [#39800](https://github.com/ClickHouse/ClickHouse/pull/39800) ([Anton Popov](https://github.com/CurtizJ)). + +#### NOT FOR CHANGELOG / INSIGNIFICANT + +* Fix reading from s3 in some corner cases [#38239](https://github.com/ClickHouse/ClickHouse/pull/38239) ([Anton Popov](https://github.com/CurtizJ)). +* Replace MemoryTrackerBlockerInThread to LockMemoryExceptionInThread [#39619](https://github.com/ClickHouse/ClickHouse/pull/39619) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). +* Change mysql-odbc url [#39702](https://github.com/ClickHouse/ClickHouse/pull/39702) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + diff --git a/docs/changelogs/v22.7.3.5-stable.md b/docs/changelogs/v22.7.3.5-stable.md new file mode 100644 index 00000000000..92fe37e4821 --- /dev/null +++ b/docs/changelogs/v22.7.3.5-stable.md @@ -0,0 +1,18 @@ +--- +sidebar_position: 1 +sidebar_label: 2022 +--- + +# 2022 Changelog + +### ClickHouse release v22.7.3.5-stable (e140b8b5f3a) FIXME as compared to v22.7.2.15-stable (f843089624e) + +#### Build/Testing/Packaging Improvement +* Backported in [#39884](https://github.com/ClickHouse/ClickHouse/issues/39884): Former packages used to install systemd.service file to `/etc`. The files there are marked as `conf` and are not cleaned out, and not updated automatically. This PR cleans them out. [#39323](https://github.com/ClickHouse/ClickHouse/pull/39323) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). +* Backported in [#39884](https://github.com/ClickHouse/ClickHouse/issues/39884): Former packages used to install systemd.service file to `/etc`. The files there are marked as `conf` and are not cleaned out, and not updated automatically. This PR cleans them out. [#39323](https://github.com/ClickHouse/ClickHouse/pull/39323) ([Mikhail f. Shiryaev](https://github.com/Felixoid)). + +#### Bug Fix (user-visible misbehavior in official stable or prestable release) + +* Backported in [#40045](https://github.com/ClickHouse/ClickHouse/issues/40045): Fix big memory usage during fetches. Fixes [#39915](https://github.com/ClickHouse/ClickHouse/issues/39915). [#39990](https://github.com/ClickHouse/ClickHouse/pull/39990) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). +* Backported in [#40045](https://github.com/ClickHouse/ClickHouse/issues/40045): Fix big memory usage during fetches. Fixes [#39915](https://github.com/ClickHouse/ClickHouse/issues/39915). [#39990](https://github.com/ClickHouse/ClickHouse/pull/39990) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). + From b268e4206a806f83d2fc9589a96a8269e5f7f74d Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Wed, 10 Aug 2022 20:09:43 -0400 Subject: [PATCH 561/672] remove indent as it causes codeblock --- docs/en/sql-reference/table-functions/index.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/en/sql-reference/table-functions/index.md b/docs/en/sql-reference/table-functions/index.md index a51312324f0..95c0d2f8494 100644 --- a/docs/en/sql-reference/table-functions/index.md +++ b/docs/en/sql-reference/table-functions/index.md @@ -9,13 +9,13 @@ Table functions are methods for constructing tables. You can use table functions in: -- [FROM](../../sql-reference/statements/select/from.md) clause of the `SELECT` query. +- [FROM](../../sql-reference/statements/select/from.md) clause of the `SELECT` query. - The method for creating a temporary table that is available only in the current query. The table is deleted when the query finishes. + The method for creating a temporary table that is available only in the current query. The table is deleted when the query finishes. - [CREATE TABLE AS table_function()](../../sql-reference/statements/create/table.md) query. - It's one of the methods of creating a table. + It's one of the methods of creating a table. - [INSERT INTO TABLE FUNCTION](../../sql-reference/statements/insert-into.md#inserting-into-table-function) query. @@ -38,4 +38,3 @@ You can’t use table functions if the [allow_ddl](../../operations/settings/per | [s3](../../sql-reference/table-functions/s3.md) | Creates a [S3](../../engines/table-engines/integrations/s3.md)-engine table. | | [sqlite](../../sql-reference/table-functions/sqlite.md) | Creates a [sqlite](../../engines/table-engines/integrations/sqlite.md)-engine table. | -[Original article](https://clickhouse.com/docs/en/sql-reference/table-functions/) From c323f648040f5e6bef9d33a14c22315d26322e3a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 11 Aug 2022 03:29:46 +0200 Subject: [PATCH 562/672] Fix Fasttest --- docker/test/fasttest/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/test/fasttest/Dockerfile b/docker/test/fasttest/Dockerfile index d74f99cf54b..7f7a8008d4e 100644 --- a/docker/test/fasttest/Dockerfile +++ b/docker/test/fasttest/Dockerfile @@ -16,7 +16,8 @@ RUN apt-get update \ python3-requests \ python3-termcolor \ unixodbc \ - --yes --no-install-recommends + pv \ + --yes --no-install-recommends RUN pip3 install numpy scipy pandas Jinja2 From 84cd867aa8e5ddc47b61d2614921c6b9941b82d6 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Thu, 11 Aug 2022 10:46:06 +0800 Subject: [PATCH 563/672] materialize column instead of handling column in hash method --- src/Common/ColumnsHashing.h | 51 ++-------------- .../Transforms/IntersectOrExceptTransform.cpp | 9 ++- ...81_intersect_except_const_column.reference | 61 +++++++++++++++++++ .../02381_intersect_except_const_column.sql | 13 ++++ .../02381_intersect_hash_method.reference | 30 --------- .../02381_intersect_hash_method.sql | 3 - 6 files changed, 88 insertions(+), 79 deletions(-) create mode 100644 tests/queries/0_stateless/02381_intersect_except_const_column.reference create mode 100644 tests/queries/0_stateless/02381_intersect_except_const_column.sql delete mode 100644 tests/queries/0_stateless/02381_intersect_hash_method.reference delete mode 100644 tests/queries/0_stateless/02381_intersect_hash_method.sql diff --git a/src/Common/ColumnsHashing.h b/src/Common/ColumnsHashing.h index 711c1c4096c..c3a087c0a6e 100644 --- a/src/Common/ColumnsHashing.h +++ b/src/Common/ColumnsHashing.h @@ -6,20 +6,15 @@ #include #include #include -#include -#include #include #include -#include #include #include #include -#include #include #include -#include namespace DB @@ -42,36 +37,16 @@ struct HashMethodOneNumber using Base = columns_hashing_impl::HashMethodBase; const char * vec; - FieldType const_value; - std::function get_key_holder_impl; /// If the keys of a fixed length then key_sizes contains their lengths, empty otherwise. HashMethodOneNumber(const ColumnRawPtrs & key_columns, const Sizes & /*key_sizes*/, const HashMethodContextPtr &) { vec = key_columns[0]->getRawData().data; - if (isColumnConst(*key_columns[0])) - { - const_value = unalignedLoad(vec); - get_key_holder_impl = [this](size_t /*row*/) { return const_value; }; - } - else - { - get_key_holder_impl = [this](size_t row) { return unalignedLoad(vec + row * sizeof(FieldType)); }; - } } explicit HashMethodOneNumber(const IColumn * column) { vec = column->getRawData().data; - if (isColumnConst(*column)) - { - const_value = unalignedLoad(vec); - get_key_holder_impl = [this](size_t /*row*/) { return const_value; }; - } - else - { - get_key_holder_impl = [this](size_t row) { return unalignedLoad(vec + row * sizeof(FieldType)); }; - } } /// Creates context. Method is called once and result context is used in all threads. @@ -89,7 +64,7 @@ struct HashMethodOneNumber using Base::getHash; /// (const Data & data, size_t row, Arena & pool) -> size_t /// Is used for default implementation in HashMethodBase. - FieldType getKeyHolder(size_t row, Arena &) const { return get_key_holder_impl(row); } + FieldType getKeyHolder(size_t row, Arena &) const { return unalignedLoad(vec + row * sizeof(FieldType)); } const FieldType * getKeyData() const { return reinterpret_cast(vec); } }; @@ -105,28 +80,19 @@ struct HashMethodString const IColumn::Offset * offsets; const UInt8 * chars; - std::function get_key_holder_impl; HashMethodString(const ColumnRawPtrs & key_columns, const Sizes & /*key_sizes*/, const HashMethodContextPtr &) { - const IColumn * column = key_columns[0]; - bool column_is_const = isColumnConst(*column); - if (column_is_const) - column = &assert_cast(*column).getDataColumn(); - - const ColumnString & column_string = assert_cast(*column); + const IColumn & column = *key_columns[0]; + const ColumnString & column_string = assert_cast(column); offsets = column_string.getOffsets().data(); chars = column_string.getChars().data(); - - if (column_is_const) - get_key_holder_impl = [this](size_t /*row*/) { return StringRef(chars, offsets[0] - 1); }; - else - get_key_holder_impl = [this](size_t row) { return StringRef(chars + offsets[row - 1], offsets[row] - offsets[row - 1] - 1); }; } auto getKeyHolder(ssize_t row, [[maybe_unused]] Arena & pool) const { - StringRef key = get_key_holder_impl(row); + StringRef key(chars + offsets[row - 1], offsets[row] - offsets[row - 1] - 1); + if constexpr (place_string_to_arena) { return ArenaKeyHolder{key, pool}; @@ -153,7 +119,6 @@ struct HashMethodFixedString size_t n; const ColumnFixedString::Chars * chars; - std::function get_key_holder_impl; HashMethodFixedString(const ColumnRawPtrs & key_columns, const Sizes & /*key_sizes*/, const HashMethodContextPtr &) { @@ -161,15 +126,11 @@ struct HashMethodFixedString const ColumnFixedString & column_string = assert_cast(column); n = column_string.getN(); chars = &column_string.getChars(); - if (isColumnConst(column)) - get_key_holder_impl = [this](size_t /*row*/) { return StringRef(&(*chars)[0], n); }; - else - get_key_holder_impl = [this](size_t row) { return StringRef(&(*chars)[row * n], n); }; } auto getKeyHolder(size_t row, [[maybe_unused]] Arena & pool) const { - StringRef key = get_key_holder_impl(row); + StringRef key(&(*chars)[row * n], n); if constexpr (place_string_to_arena) { diff --git a/src/Processors/Transforms/IntersectOrExceptTransform.cpp b/src/Processors/Transforms/IntersectOrExceptTransform.cpp index 3e39123ae4b..1ac82e99cf2 100644 --- a/src/Processors/Transforms/IntersectOrExceptTransform.cpp +++ b/src/Processors/Transforms/IntersectOrExceptTransform.cpp @@ -128,7 +128,11 @@ void IntersectOrExceptTransform::accumulate(Chunk chunk) column_ptrs.reserve(key_columns_pos.size()); for (auto pos : key_columns_pos) + { + /// Hash methods expect non-const column + columns[pos] = columns[pos]->convertToFullColumnIfConst(); column_ptrs.emplace_back(columns[pos].get()); + } if (!data) data.emplace(); @@ -160,8 +164,11 @@ void IntersectOrExceptTransform::filter(Chunk & chunk) column_ptrs.reserve(key_columns_pos.size()); for (auto pos : key_columns_pos) + { + /// Hash methods expect non-const column + columns[pos] = columns[pos]->convertToFullColumnIfConst(); column_ptrs.emplace_back(columns[pos].get()); - + } if (!data) data.emplace(); diff --git a/tests/queries/0_stateless/02381_intersect_except_const_column.reference b/tests/queries/0_stateless/02381_intersect_except_const_column.reference new file mode 100644 index 00000000000..290835b412e --- /dev/null +++ b/tests/queries/0_stateless/02381_intersect_except_const_column.reference @@ -0,0 +1,61 @@ +fooooo +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 diff --git a/tests/queries/0_stateless/02381_intersect_except_const_column.sql b/tests/queries/0_stateless/02381_intersect_except_const_column.sql new file mode 100644 index 00000000000..b10f913dd1e --- /dev/null +++ b/tests/queries/0_stateless/02381_intersect_except_const_column.sql @@ -0,0 +1,13 @@ +-- Test: crash the server +SELECT 'fooooo' INTERSECT SELECT 'fooooo'; +SELECT 'fooooo' EXCEPT SELECT 'fooooo'; + +-- Test: intersect return incorrect result for const column +SELECT 1 FROM numbers(10) INTERSECT SELECT 1 FROM numbers(10); +SELECT toString(1) FROM numbers(10) INTERSECT SELECT toString(1) FROM numbers(10); +SELECT '1' FROM numbers(10) INTERSECT SELECT '1' FROM numbers(10); + +-- Test: except return incorrect result for const column +SELECT 2 FROM numbers(10) EXCEPT SELECT 1 FROM numbers(5); +SELECT toString(2) FROM numbers(10) EXCEPT SELECT toString(1) FROM numbers(5); +SELECT '2' FROM numbers(10) EXCEPT SELECT '1' FROM numbers(5); \ No newline at end of file diff --git a/tests/queries/0_stateless/02381_intersect_hash_method.reference b/tests/queries/0_stateless/02381_intersect_hash_method.reference deleted file mode 100644 index ac8f48bbb7b..00000000000 --- a/tests/queries/0_stateless/02381_intersect_hash_method.reference +++ /dev/null @@ -1,30 +0,0 @@ -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 diff --git a/tests/queries/0_stateless/02381_intersect_hash_method.sql b/tests/queries/0_stateless/02381_intersect_hash_method.sql deleted file mode 100644 index 1154718c686..00000000000 --- a/tests/queries/0_stateless/02381_intersect_hash_method.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT 1 FROM numbers(10) INTERSECT SELECT 1 FROM numbers(10); -SELECT toString(1) FROM numbers(10) INTERSECT SELECT toString(1) FROM numbers(10); -SELECT '1' FROM numbers(10) INTERSECT SELECT '1' FROM numbers(10); From 1a31f82d1e144acb8ab911348bd476008f7b7946 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 11 Aug 2022 07:44:42 +0200 Subject: [PATCH 564/672] Add a test for #10575 --- ...counting_resources_in_subqueries.reference | 0 ...00175_counting_resources_in_subqueries.sql | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/queries/1_stateful/00175_counting_resources_in_subqueries.reference create mode 100644 tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql diff --git a/tests/queries/1_stateful/00175_counting_resources_in_subqueries.reference b/tests/queries/1_stateful/00175_counting_resources_in_subqueries.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql b/tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql new file mode 100644 index 00000000000..a6e441a8471 --- /dev/null +++ b/tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql @@ -0,0 +1,20 @@ +-- the work for scalar subquery is properly accounted: +SET max_rows_to_read = 1000000; +SELECT 1 = (SELECT count() FROM test.hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } + +-- the work for subquery in IN is properly accounted: +SET max_rows_to_read = 1000000; +SELECT 1 IN (SELECT count() FROM hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } + +-- this query reads from the table twice: +SET max_rows_to_read = 15000000; +SELECT count() IN (SELECT count() FROM hits WHERE NOT ignore(AdvEngineID)) FROM hits WHERE NOT ignore(AdvEngineID); -- { serverError 158 } + +-- the resources are properly accounted even if the subquery is evaluated in advance to facilitate the index analysis. +-- this query is using index and filter out the second reading pass. +SET max_rows_to_read = 1000000; +SELECT count() FROM hits WHERE CounterID > (SELECT count() FROM hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } + +-- this query is using index but have to read all the data twice. +SET max_rows_to_read = 15000000; +SELECT count() FROM hits WHERE CounterID < (SELECT count() FROM hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } From 394a71dcfd9846cbbc6a3a284b3336ffd78a3e4d Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Wed, 10 Aug 2022 21:11:16 +0000 Subject: [PATCH 565/672] More correct fix + tests --- src/Core/DecimalFunctions.h | 4 ---- src/Functions/FunctionBinaryArithmetic.h | 12 ++++++++++++ .../0_stateless/00700_decimal_arithm.reference | 11 +++++++++++ tests/queries/0_stateless/00700_decimal_arithm.sql | 10 ++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/Core/DecimalFunctions.h b/src/Core/DecimalFunctions.h index 9aaf1ed7b55..331df9aa637 100644 --- a/src/Core/DecimalFunctions.h +++ b/src/Core/DecimalFunctions.h @@ -292,11 +292,7 @@ inline auto binaryOpResult(const DecimalType & tx, const DecimalType & ty) if constexpr (is_multiply) scale = tx.getScale() + ty.getScale(); else if constexpr (is_division) - { scale = tx.getScale(); - if (scale * 2 > max_precision) - throw Exception("Overflow during decimal division", ErrorCodes::DECIMAL_OVERFLOW); - } else scale = (tx.getScale() > ty.getScale() ? tx.getScale() : ty.getScale()); diff --git a/src/Functions/FunctionBinaryArithmetic.h b/src/Functions/FunctionBinaryArithmetic.h index 4aaaf37e6cf..91878f6e192 100644 --- a/src/Functions/FunctionBinaryArithmetic.h +++ b/src/Functions/FunctionBinaryArithmetic.h @@ -39,6 +39,7 @@ #include #include #include +#include #if USE_EMBEDDED_COMPILER # pragma GCC diagnostic push @@ -1179,6 +1180,17 @@ public: { if constexpr (IsDataTypeDecimal && IsDataTypeDecimal) { + if constexpr (is_division) + { + if (context->getSettingsRef().decimal_check_overflow) + { + /// relay on "knowledge" of current decimal division implementation to avoid overflow upfront based on operand's scale + /// for more detains see issue #30341 + /// TODO: comment with more detailed explanation + if (left.getScale() + right.getScale() > ResultDataType::maxPrecision()) + throw Exception("Overflow during decimal division", ErrorCodes::DECIMAL_OVERFLOW); + } + } ResultDataType result_type = decimalResultType(left, right); type_res = std::make_shared(result_type.getPrecision(), result_type.getScale()); } diff --git a/tests/queries/0_stateless/00700_decimal_arithm.reference b/tests/queries/0_stateless/00700_decimal_arithm.reference index a41ef5b0557..811946c87e0 100644 --- a/tests/queries/0_stateless/00700_decimal_arithm.reference +++ b/tests/queries/0_stateless/00700_decimal_arithm.reference @@ -35,3 +35,14 @@ 0 \N \N 0 \N \N 0 \N \N +-- { echoOn } +SELECT toDecimal128(1, 38) / toDecimal128(1, 0) SETTINGS decimal_check_overflow=1; +1 +SELECT toDecimal128(1, 38) / toDecimal128(1, 1) SETTINGS decimal_check_overflow=1; -- { serverError DECIMAL_OVERFLOW } +SELECT toDecimal128(1, 38) / toDecimal128(1, 1) SETTINGS decimal_check_overflow=0; +-0.02084710076281539039012382229530463436 +SELECT toDecimal128(1, 37) / toDecimal128(1, 1) SETTINGS decimal_check_overflow=1; +1 +SELECT toDecimal128(1, 19) / toDecimal128(1, 19) SETTINGS decimal_check_overflow=1; +1 +SELECT toDecimal128(1, 20) / toDecimal128(1, 19) SETTINGS decimal_check_overflow=1; -- { serverError DECIMAL_OVERFLOW } diff --git a/tests/queries/0_stateless/00700_decimal_arithm.sql b/tests/queries/0_stateless/00700_decimal_arithm.sql index c305a850e5b..d24b593dac1 100644 --- a/tests/queries/0_stateless/00700_decimal_arithm.sql +++ b/tests/queries/0_stateless/00700_decimal_arithm.sql @@ -82,4 +82,14 @@ SELECT toDecimal32(0, 4) AS x, multiIf(x = 0, NULL, intDivOrZero(1, x)), multiIf SELECT toDecimal64(0, 8) AS x, multiIf(x = 0, NULL, intDivOrZero(1, x)), multiIf(x = 0, NULL, intDivOrZero(x, 0)); SELECT toDecimal64(0, 18) AS x, multiIf(x = 0, NULL, intDivOrZero(1, x)), multiIf(x = 0, NULL, intDivOrZero(x, 0)); +-- { echoOn } +SELECT toDecimal128(1, 38) / toDecimal128(1, 0) SETTINGS decimal_check_overflow=1; +SELECT toDecimal128(1, 38) / toDecimal128(1, 1) SETTINGS decimal_check_overflow=1; -- { serverError DECIMAL_OVERFLOW } +SELECT toDecimal128(1, 38) / toDecimal128(1, 1) SETTINGS decimal_check_overflow=0; +SELECT toDecimal128(1, 37) / toDecimal128(1, 1) SETTINGS decimal_check_overflow=1; + +SELECT toDecimal128(1, 19) / toDecimal128(1, 19) SETTINGS decimal_check_overflow=1; +SELECT toDecimal128(1, 20) / toDecimal128(1, 19) SETTINGS decimal_check_overflow=1; -- { serverError DECIMAL_OVERFLOW } +-- { echoOff } + DROP TABLE IF EXISTS decimal; From 0069a949c4997b66a614e0a1dad694191a546671 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 11 Aug 2022 10:04:31 +0200 Subject: [PATCH 566/672] Fix test --- .../1_stateful/00175_counting_resources_in_subqueries.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql b/tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql index a6e441a8471..d63429522e0 100644 --- a/tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql +++ b/tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql @@ -4,17 +4,17 @@ SELECT 1 = (SELECT count() FROM test.hits WHERE NOT ignore(AdvEngineID)); -- { s -- the work for subquery in IN is properly accounted: SET max_rows_to_read = 1000000; -SELECT 1 IN (SELECT count() FROM hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } +SELECT 1 IN (SELECT count() FROM test.hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } -- this query reads from the table twice: SET max_rows_to_read = 15000000; -SELECT count() IN (SELECT count() FROM hits WHERE NOT ignore(AdvEngineID)) FROM hits WHERE NOT ignore(AdvEngineID); -- { serverError 158 } +SELECT count() IN (SELECT count() FROM test.hits WHERE NOT ignore(AdvEngineID)) FROM test.hits WHERE NOT ignore(AdvEngineID); -- { serverError 158 } -- the resources are properly accounted even if the subquery is evaluated in advance to facilitate the index analysis. -- this query is using index and filter out the second reading pass. SET max_rows_to_read = 1000000; -SELECT count() FROM hits WHERE CounterID > (SELECT count() FROM hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } +SELECT count() FROM test.hits WHERE CounterID > (SELECT count() FROM test.hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } -- this query is using index but have to read all the data twice. SET max_rows_to_read = 15000000; -SELECT count() FROM hits WHERE CounterID < (SELECT count() FROM hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } +SELECT count() FROM test.hits WHERE CounterID < (SELECT count() FROM test.hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } From 5337d21fb498f374fce8abb0cac9d2e0fe6b7bd7 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Thu, 11 Aug 2022 08:08:20 +0000 Subject: [PATCH 567/672] Mute test. --- tests/queries/0_stateless/00725_memory_tracking.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/00725_memory_tracking.sql b/tests/queries/0_stateless/00725_memory_tracking.sql index b7356f0a6aa..ee81502ad83 100644 --- a/tests/queries/0_stateless/00725_memory_tracking.sql +++ b/tests/queries/0_stateless/00725_memory_tracking.sql @@ -1,4 +1,4 @@ --- Tags: no-replicated-database +-- Tags: no-replicated-database, no-tsan, no-asan, no-msan SELECT least(value, 0) FROM system.metrics WHERE metric = 'MemoryTracking'; SELECT length(range(100000000)); From aa42a42e0f5120d587e8f68bb27a1d1ddfcdfc08 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Thu, 11 Aug 2022 08:08:27 +0000 Subject: [PATCH 568/672] Fix documentation of "modulo(a, b)" Fixes #39287 ClickHouse uses the same semantics for modulo on floats as Python, i.e. 4.2 % 2.0 = 0.2 and not as previously documented: 4.2 % 2.0 --> (drop decimal places) --> 4 % 2 = 0. Fixed the documentation. --- docs/en/sql-reference/functions/arithmetic-functions.md | 6 +++--- docs/ru/sql-reference/functions/arithmetic-functions.md | 2 +- docs/zh/sql-reference/functions/arithmetic-functions.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/en/sql-reference/functions/arithmetic-functions.md b/docs/en/sql-reference/functions/arithmetic-functions.md index b8d2f171bc8..45df5f7f227 100644 --- a/docs/en/sql-reference/functions/arithmetic-functions.md +++ b/docs/en/sql-reference/functions/arithmetic-functions.md @@ -55,9 +55,9 @@ Differs from ‘intDiv’ in that it returns zero when dividing by zero or when ## modulo(a, b), a % b operator -Calculates the remainder after division. -If arguments are floating-point numbers, they are pre-converted to integers by dropping the decimal portion. -The remainder is taken in the same sense as in C++. Truncated division is used for negative numbers. +Calculates the remainder when dividing `a` by `b`. +The result type is an integer if both inputs are integers. If one of the inputs is a floating-point number, the result is a floating-point number. +The remainder is computed like in C++. Truncated division is used for negative numbers. An exception is thrown when dividing by zero or when dividing a minimal negative number by minus one. ## moduloOrZero(a, b) diff --git a/docs/ru/sql-reference/functions/arithmetic-functions.md b/docs/ru/sql-reference/functions/arithmetic-functions.md index c8f2e31cb0b..19af81e609d 100644 --- a/docs/ru/sql-reference/functions/arithmetic-functions.md +++ b/docs/ru/sql-reference/functions/arithmetic-functions.md @@ -56,7 +56,7 @@ SELECT toTypeName(0), toTypeName(0 + 0), toTypeName(0 + 0 + 0), toTypeName(0 + 0 ## modulo(a, b), оператор a % b {#modulo} Вычисляет остаток от деления. -Если аргументы - числа с плавающей запятой, то они предварительно преобразуются в целые числа, путём отбрасывания дробной части. +Тип результата - целое число, если оба входа - целые числа. Если один из входов является числом с плавающей точкой, результатом будет число с плавающей точкой. Берётся остаток в том же смысле, как это делается в C++. По факту, для отрицательных чисел, используется truncated division. При делении на ноль или при делении минимального отрицательного числа на минус единицу, кидается исключение. diff --git a/docs/zh/sql-reference/functions/arithmetic-functions.md b/docs/zh/sql-reference/functions/arithmetic-functions.md index 15bec0d2107..acba761b619 100644 --- a/docs/zh/sql-reference/functions/arithmetic-functions.md +++ b/docs/zh/sql-reference/functions/arithmetic-functions.md @@ -54,7 +54,7 @@ SELECT toTypeName(0), toTypeName(0 + 0), toTypeName(0 + 0 + 0), toTypeName(0 + 0 ## modulo(a, b), a % b operator {#modulo} 计算除法后的余数。 -如果参数是浮点数,则通过删除小数部分将它们预转换为整数。 +如果两个输入都是整数,结果类型是整数。如果其中一个输入是浮点数,则结果是浮点数。 其余部分与C++中的含义相同。截断除法用于负数。 除以零或将最小负数除以-1时抛出异常。 From 1f10c4be8cb6193693c0bf27cc84f8eee5b8191a Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Thu, 11 Aug 2022 11:25:33 +0200 Subject: [PATCH 569/672] Update docs/ru/sql-reference/functions/arithmetic-functions.md Co-authored-by: Nikolay Degterinsky <43110995+evillique@users.noreply.github.com> --- docs/ru/sql-reference/functions/arithmetic-functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ru/sql-reference/functions/arithmetic-functions.md b/docs/ru/sql-reference/functions/arithmetic-functions.md index 19af81e609d..ba4340093b4 100644 --- a/docs/ru/sql-reference/functions/arithmetic-functions.md +++ b/docs/ru/sql-reference/functions/arithmetic-functions.md @@ -56,7 +56,7 @@ SELECT toTypeName(0), toTypeName(0 + 0), toTypeName(0 + 0 + 0), toTypeName(0 + 0 ## modulo(a, b), оператор a % b {#modulo} Вычисляет остаток от деления. -Тип результата - целое число, если оба входа - целые числа. Если один из входов является числом с плавающей точкой, результатом будет число с плавающей точкой. +Тип результата - целое число, если оба аргумента - целые числа. Если один из аргументов является числом с плавающей точкой, результатом будет число с плавающей точкой. Берётся остаток в том же смысле, как это делается в C++. По факту, для отрицательных чисел, используется truncated division. При делении на ноль или при делении минимального отрицательного числа на минус единицу, кидается исключение. From 9f455329994fa93ba96be984a28f08befb436d28 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 11 Aug 2022 12:05:16 +0200 Subject: [PATCH 570/672] Use a job ID as ref text --- tests/ci/report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ci/report.py b/tests/ci/report.py index f4569f75b82..7d84185b863 100644 --- a/tests/ci/report.py +++ b/tests/ci/report.py @@ -239,8 +239,8 @@ def create_test_html_report( ) raw_log_name = os.path.basename(raw_log_url) - if raw_log_name.endswith("?check_suite_focus=true"): - raw_log_name = "Job (github actions)" + if "?" in raw_log_name: + raw_log_name = raw_log_name.split("?")[0] result = HTML_BASE_TEST_TEMPLATE.format( title=_format_header(header, branch_name), From 261ccc35cf2004c2f0603717a42a95e0ecbe8b2c Mon Sep 17 00:00:00 2001 From: Vladimir C Date: Thu, 11 Aug 2022 12:18:44 +0200 Subject: [PATCH 571/672] Rename SettingAutoWrapper, add comment to readBinary Co-authored-by: Azat Khuzhin Co-authored-by: Maksim Kita --- src/Core/Settings.h | 2 +- src/Core/SettingsFields.cpp | 6 ++--- src/Core/SettingsFields.h | 24 ++++++++++++------- .../02381_setting_value_auto.reference | 8 +++---- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 727e45e3e50..f5108031dfb 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -212,7 +212,7 @@ static constexpr UInt64 operator""_GiB(unsigned long long value) \ M(Bool, insert_deduplicate, true, "For INSERT queries in the replicated table, specifies that deduplication of insertings blocks should be performed", 0) \ \ - M(UInt64WithAuto, insert_quorum, 0, "For INSERT queries in the replicated table, wait writing for the specified number of replicas and linearize the addition of the data. 0 - disabled.", 0) \ + M(UInt64Auto, insert_quorum, 0, "For INSERT queries in the replicated table, wait writing for the specified number of replicas and linearize the addition of the data. 0 - disabled.", 0) \ M(Milliseconds, insert_quorum_timeout, 600000, "", 0) \ M(Bool, insert_quorum_parallel, true, "For quorum INSERT queries - enable to make parallel inserts without linearizability", 0) \ M(UInt64, select_sequential_consistency, 0, "For SELECT queries from the replicated table, throw an exception if the replica does not have a chunk written with the quorum; do not read the parts that have not yet been written with the quorum.", 0) \ diff --git a/src/Core/SettingsFields.cpp b/src/Core/SettingsFields.cpp index 86b20da9e8c..5b1b6b10cc2 100644 --- a/src/Core/SettingsFields.cpp +++ b/src/Core/SettingsFields.cpp @@ -153,9 +153,9 @@ template struct SettingFieldNumber; template struct SettingFieldNumber; template struct SettingFieldNumber; -template struct SettingWithAuto>; -template struct SettingWithAuto>; -template struct SettingWithAuto>; +template struct SettingAutoWrapper>; +template struct SettingAutoWrapper>; +template struct SettingAutoWrapper>; namespace { diff --git a/src/Core/SettingsFields.h b/src/Core/SettingsFields.h index 0fcc1d6783f..68c6e85796e 100644 --- a/src/Core/SettingsFields.h +++ b/src/Core/SettingsFields.h @@ -66,7 +66,7 @@ using SettingFieldBool = SettingFieldNumber; * but when serializing 'auto' old version will see binary representation of the default value. */ template -struct SettingWithAuto +struct SettingAutoWrapper { constexpr static auto keyword = "auto"; static bool isAuto(const Field & f) { return f.getType() == Field::Types::String && f.safeGet() == keyword; } @@ -78,17 +78,17 @@ struct SettingWithAuto bool is_auto = false; bool changed = false; - explicit SettingWithAuto() : is_auto(true) {} - explicit SettingWithAuto(Type val) : is_auto(false) { base = Base(val); } + explicit SettingAutoWrapper() : is_auto(true) {} + explicit SettingAutoWrapper(Type val) : is_auto(false) { base = Base(val); } - explicit SettingWithAuto(const Field & f) + explicit SettingAutoWrapper(const Field & f) : is_auto(isAuto(f)) { if (!is_auto) base = Base(f); } - SettingWithAuto & operator=(const Field & f) + SettingAutoWrapper & operator=(const Field & f) { changed = true; if (is_auto = isAuto(f); !is_auto) @@ -115,16 +115,22 @@ struct SettingWithAuto base.writeBinary(out); } + /* + * That it is fine to reset `is_auto` here and to use default value in case `is_auto` + * because settings will be serialized only if changed. + * If they were changed they were requested to use explicit value instead of `auto`. + * And so interactions between client-server, and server-server (distributed queries), should be OK. + */ void readBinary(ReadBuffer & in) { changed = true; is_auto = false; base.readBinary(in); } Type valueOr(Type default_value) const { return is_auto ? default_value : base.value; } }; -using SettingFieldUInt64WithAuto = SettingWithAuto; -using SettingFieldInt64WithAuto = SettingWithAuto; -using SettingFieldFloatWithAuto = SettingWithAuto; +using SettingFieldUInt64Auto = SettingAutoWrapper; +using SettingFieldInt64Auto = SettingAutoWrapper; +using SettingFieldFloatAuto = SettingAutoWrapper; -/* Similar to SettingFieldUInt64WithAuto with small differences to behave like regular UInt64, supported to compatibility. +/* Similar to SettingFieldUInt64Auto with small differences to behave like regular UInt64, supported to compatibility. * When setting to 'auto' it becomes equal to the number of processor cores without taking into account SMT. * A value of 0 is also treated as 'auto', so 'auto' is parsed and serialized in the same way as 0. */ diff --git a/tests/queries/0_stateless/02381_setting_value_auto.reference b/tests/queries/0_stateless/02381_setting_value_auto.reference index 72c87cf6f7d..acc5025da5e 100644 --- a/tests/queries/0_stateless/02381_setting_value_auto.reference +++ b/tests/queries/0_stateless/02381_setting_value_auto.reference @@ -1,4 +1,4 @@ -0 0 UInt64WithAuto -auto 1 UInt64WithAuto -0 1 UInt64WithAuto -1 1 UInt64WithAuto +0 0 UInt64Auto +auto 1 UInt64Auto +0 1 UInt64Auto +1 1 UInt64Auto From 61ad12279e20d58be2870df775b4197bb996c03f Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 11 Aug 2022 10:24:12 +0000 Subject: [PATCH 572/672] Delete files DictionaryJoinAdapter.h/cpp Follow-up for https://github.com/ClickHouse/ClickHouse/pull/38956 --- src/Interpreters/DictionaryJoinAdapter.cpp | 8 -------- src/Interpreters/DictionaryJoinAdapter.h | 7 ------- src/Interpreters/ExpressionAnalyzer.cpp | 1 - 3 files changed, 16 deletions(-) delete mode 100644 src/Interpreters/DictionaryJoinAdapter.cpp delete mode 100644 src/Interpreters/DictionaryJoinAdapter.h diff --git a/src/Interpreters/DictionaryJoinAdapter.cpp b/src/Interpreters/DictionaryJoinAdapter.cpp deleted file mode 100644 index bf0ad373204..00000000000 --- a/src/Interpreters/DictionaryJoinAdapter.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - - -namespace DB -{ - - -} diff --git a/src/Interpreters/DictionaryJoinAdapter.h b/src/Interpreters/DictionaryJoinAdapter.h deleted file mode 100644 index dade5da94e6..00000000000 --- a/src/Interpreters/DictionaryJoinAdapter.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - - -namespace DB -{ - -} diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index 0d4fc28c5ba..105d46eed1f 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include From d1051d822c02804292ac1e9486c104247e37418f Mon Sep 17 00:00:00 2001 From: Jianmei Zhang <66244986+zhangjmruc@users.noreply.github.com> Date: Thu, 11 Aug 2022 18:39:40 +0800 Subject: [PATCH 573/672] Use getSerializedFileExtension() to get correct file extension for index (#40095) --- src/Storages/MergeTree/MutateTask.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index d52948d71c1..63aabd20115 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -490,7 +490,8 @@ static NameSet collectFilesToSkip( for (const auto & index : indices_to_recalc) { - files_to_skip.insert(index->getFileName() + ".idx"); + /// Since MinMax index has .idx2 extension, we need to add correct extension. + files_to_skip.insert(index->getFileName() + index->getSerializedFileExtension()); files_to_skip.insert(index->getFileName() + mrk_extension); } From 96776d30287914a3bc0eba38dfdf2965c9bbec70 Mon Sep 17 00:00:00 2001 From: Vladimir C Date: Thu, 11 Aug 2022 13:15:28 +0200 Subject: [PATCH 574/672] Trim trailing whitespaces in SettingsFields.h --- src/Core/SettingsFields.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/SettingsFields.h b/src/Core/SettingsFields.h index 68c6e85796e..f01ac37d3cc 100644 --- a/src/Core/SettingsFields.h +++ b/src/Core/SettingsFields.h @@ -115,7 +115,7 @@ struct SettingAutoWrapper base.writeBinary(out); } - /* + /* * That it is fine to reset `is_auto` here and to use default value in case `is_auto` * because settings will be serialized only if changed. * If they were changed they were requested to use explicit value instead of `auto`. From fa8fab2e8f15b446467393d66cdc4fa77216193a Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Thu, 28 Jul 2022 17:40:09 +0800 Subject: [PATCH 575/672] Fix KeyCondition with other filters --- src/Interpreters/InterpreterSelectQuery.cpp | 11 ++++-- src/Interpreters/InterpreterSelectQuery.h | 2 ++ .../optimizePrimaryKeyCondition.cpp | 35 ++++++++++++------- .../QueryPlan/ReadFromMergeTree.cpp | 28 ++++++++------- src/Processors/QueryPlan/ReadFromMergeTree.h | 17 +++++---- src/Storages/MergeTree/KeyCondition.cpp | 31 +++++++++++++--- src/Storages/MergeTree/KeyCondition.h | 16 +++++++-- src/Storages/MergeTree/MergeTreeData.cpp | 31 ++++++++-------- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 6 ++-- .../MergeTree/MergeTreeDataSelectExecutor.h | 3 +- .../MergeTree/MergeTreeIndexMinMax.cpp | 4 +-- src/Storages/MergeTree/PartitionPruner.h | 4 +-- src/Storages/SelectQueryInfo.h | 4 ++- src/Storages/StorageMerge.cpp | 2 +- src/Storages/StorageMerge.h | 8 +++-- ...10_projection_additional_filters.reference | 1 + .../01710_projection_additional_filters.sql | 9 +++++ .../01710_projection_row_policy.reference | 1 + .../01710_projection_row_policy.sql | 13 +++++++ 19 files changed, 153 insertions(+), 73 deletions(-) create mode 100644 tests/queries/0_stateless/01710_projection_additional_filters.reference create mode 100644 tests/queries/0_stateless/01710_projection_additional_filters.sql create mode 100644 tests/queries/0_stateless/01710_projection_row_policy.reference create mode 100644 tests/queries/0_stateless/01710_projection_row_policy.sql diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 1d009ec3f3b..205ec049975 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -608,11 +608,15 @@ InterpreterSelectQuery::InterpreterSelectQuery( if (storage) { + query_info.filter_asts.clear(); + /// Fix source_header for filter actions. if (row_policy_filter) { filter_info = generateFilterActions( table_id, row_policy_filter, context, storage, storage_snapshot, metadata_snapshot, required_columns); + + query_info.filter_asts.push_back(row_policy_filter); } if (query_info.additional_filter_ast) @@ -621,6 +625,8 @@ InterpreterSelectQuery::InterpreterSelectQuery( table_id, query_info.additional_filter_ast, context, storage, storage_snapshot, metadata_snapshot, required_columns); additional_filter_info->do_remove_column = true; + + query_info.filter_asts.push_back(query_info.additional_filter_ast); } source_header = storage_snapshot->getSampleBlockForColumns(required_columns); @@ -2002,8 +2008,7 @@ void InterpreterSelectQuery::executeFetchColumns(QueryProcessingStage::Enum proc && storage && storage->getName() != "MaterializedMySQL" && !storage->hasLightweightDeletedMask() - && !row_policy_filter - && !query_info.additional_filter_ast + && query_info.filter_asts.empty() && processing_stage == QueryProcessingStage::FetchColumns && query_analyzer->hasAggregation() && (query_analyzer->aggregates().size() == 1) @@ -2103,7 +2108,7 @@ void InterpreterSelectQuery::executeFetchColumns(QueryProcessingStage::Enum proc && !query.limit_with_ties && !query.prewhere() && !query.where() - && !query_info.additional_filter_ast + && query_info.filter_asts.empty() && !query.groupBy() && !query.having() && !query.orderBy() diff --git a/src/Interpreters/InterpreterSelectQuery.h b/src/Interpreters/InterpreterSelectQuery.h index f2cdcbba9ed..a94c9cb5462 100644 --- a/src/Interpreters/InterpreterSelectQuery.h +++ b/src/Interpreters/InterpreterSelectQuery.h @@ -127,6 +127,8 @@ public: /// It will set shard_num and shard_count to the client_info void setProperClientInfo(size_t replica_num, size_t replica_count); + FilterDAGInfoPtr getAdditionalQueryInfo() const { return additional_filter_info; } + static SortDescription getSortDescription(const ASTSelectQuery & query, const ContextPtr & context); static UInt64 getLimitForSorting(const ASTSelectQuery & query, const ContextPtr & context); diff --git a/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp b/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp index e559c23bbaf..7d682c408e5 100644 --- a/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp +++ b/src/Processors/QueryPlan/Optimizations/optimizePrimaryKeyCondition.cpp @@ -1,9 +1,10 @@ #include +#include #include #include #include #include -#include +#include namespace DB::QueryPlanOptimizations { @@ -16,33 +17,41 @@ void optimizePrimaryKeyCondition(QueryPlan::Node & root) size_t next_child = 0; }; - std::stack stack; - stack.push({.node = &root}); + std::deque stack; + stack.push_back({.node = &root}); while (!stack.empty()) { - auto & frame = stack.top(); + auto & frame = stack.back(); /// Traverse all children first. if (frame.next_child < frame.node->children.size()) { - stack.push({.node = frame.node->children[frame.next_child]}); + stack.push_back({.node = frame.node->children[frame.next_child]}); ++frame.next_child; continue; } - if (auto * filter_step = typeid_cast(frame.node->step.get())) + auto add_filter = [&](auto & storage) { - auto * child = frame.node->children.at(0); - if (auto * read_from_merge_tree = typeid_cast(child->step.get())) - read_from_merge_tree->addFilter(filter_step->getExpression(), filter_step->getFilterColumnName()); + for (auto iter=stack.rbegin() + 1; iter!=stack.rend(); ++iter) + { + if (auto * filter_step = typeid_cast(iter->node->step.get())) + storage.addFilter(filter_step->getExpression(), filter_step->getFilterColumnName()); + else if (typeid_cast(iter->node->step.get())) + ; + else + break; + } + }; - if (auto * read_from_merge = typeid_cast(child->step.get())) - read_from_merge->addFilter(filter_step->getExpression(), filter_step->getFilterColumnName()); - } + if (auto * read_from_merge_tree = typeid_cast(frame.node->step.get())) + add_filter(*read_from_merge_tree); + else if (auto * read_from_merge = typeid_cast(frame.node->step.get())) + add_filter(*read_from_merge); - stack.pop(); + stack.pop_back(); } } diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index 0d6f591b43a..14b06f9704b 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -835,8 +835,7 @@ MergeTreeDataSelectAnalysisResultPtr ReadFromMergeTree::selectRangesToRead(Merge return selectRangesToRead( std::move(parts), prewhere_info, - added_filter, - added_filter_column_name, + added_filter_nodes, storage_snapshot->metadata, storage_snapshot->getMetadataForQuery(), query_info, @@ -852,8 +851,7 @@ MergeTreeDataSelectAnalysisResultPtr ReadFromMergeTree::selectRangesToRead(Merge MergeTreeDataSelectAnalysisResultPtr ReadFromMergeTree::selectRangesToRead( MergeTreeData::DataPartsVector parts, const PrewhereInfoPtr & prewhere_info, - const ActionsDAGPtr & added_filter, - const std::string & added_filter_column_name, + const ActionDAGNodes & added_filter_nodes, const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, @@ -895,17 +893,23 @@ MergeTreeDataSelectAnalysisResultPtr ReadFromMergeTree::selectRangesToRead( ActionDAGNodes nodes; if (prewhere_info) { - const auto & node = prewhere_info->prewhere_actions->findInOutputs(prewhere_info->prewhere_column_name); - nodes.nodes.push_back(&node); + { + const auto & node = prewhere_info->prewhere_actions->findInOutputs(prewhere_info->prewhere_column_name); + nodes.nodes.push_back(&node); + } + + if (prewhere_info->row_level_filter) + { + const auto & node = prewhere_info->row_level_filter->findInOutputs(prewhere_info->row_level_column_name); + nodes.nodes.push_back(&node); + } } - if (added_filter) - { - const auto & node = added_filter->findInOutputs(added_filter_column_name); - nodes.nodes.push_back(&node); - } + for (const auto & node : added_filter_nodes.nodes) + nodes.nodes.push_back(node); - key_condition.emplace(std::move(nodes), query_info.syntax_analyzer_result, query_info.prepared_sets, context, primary_key_columns, primary_key.expression); + key_condition.emplace( + std::move(nodes), query_info.syntax_analyzer_result, query_info.prepared_sets, context, primary_key_columns, primary_key.expression); } else { diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.h b/src/Processors/QueryPlan/ReadFromMergeTree.h index 1ba68b3fdb3..318f5a4b91f 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.h +++ b/src/Processors/QueryPlan/ReadFromMergeTree.h @@ -116,8 +116,14 @@ public: void addFilter(ActionsDAGPtr expression, std::string column_name) { - added_filter = std::move(expression); - added_filter_column_name = std::move(column_name); + added_filter_dags.push_back(expression); + added_filter_nodes.nodes.push_back(&expression->findInOutputs(column_name)); + } + + void addFilterNodes(const ActionDAGNodes & filter_nodes) + { + for (const auto & node : filter_nodes.nodes) + added_filter_nodes.nodes.push_back(node); } StorageID getStorageID() const { return data.getStorageID(); } @@ -128,8 +134,7 @@ public: static MergeTreeDataSelectAnalysisResultPtr selectRangesToRead( MergeTreeData::DataPartsVector parts, const PrewhereInfoPtr & prewhere_info, - const ActionsDAGPtr & added_filter, - const std::string & added_filter_column_name, + const ActionDAGNodes & added_filter_nodes, const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, @@ -160,8 +165,8 @@ private: PrewhereInfoPtr prewhere_info; ExpressionActionsSettings actions_settings; - ActionsDAGPtr added_filter; - std::string added_filter_column_name; + std::vector added_filter_dags; + ActionDAGNodes added_filter_nodes; StorageSnapshotPtr storage_snapshot; StorageMetadataPtr metadata_for_reading; diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 7128558b734..b42fe49a1d0 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -854,6 +854,7 @@ static NameSet getAllSubexpressionNames(const ExpressionActions & key_expr) KeyCondition::KeyCondition( const ASTPtr & query, + const ASTs & additional_filter_asts, TreeRewriterResultPtr syntax_analyzer_result, PreparedSetsPtr prepared_sets_, ContextPtr context, @@ -883,13 +884,35 @@ KeyCondition::KeyCondition( array_joined_columns.insert(name); const ASTSelectQuery & select = query->as(); - if (select.where() || select.prewhere()) + + ASTs filters; + if (select.where()) + filters.push_back(select.where()); + + if (select.prewhere()) + filters.push_back(select.prewhere()); + + for (const auto & filter_ast : additional_filter_asts) + filters.push_back(filter_ast); + + if (!filters.empty()) { ASTPtr filter_query; - if (select.where() && select.prewhere()) - filter_query = makeASTFunction("and", select.where(), select.prewhere()); + if (filters.size() == 1) + { + filter_query = filters.front(); + } else - filter_query = select.where() ? select.where() : select.prewhere(); + { + auto function = std::make_shared(); + + function->name = "and"; + function->arguments = std::make_shared(); + function->children.push_back(function->arguments); + function->arguments->children = std::move(filters); + + filter_query = function; + } /** When non-strictly monotonic functions are employed in functional index (e.g. ORDER BY toStartOfHour(dateTime)), * the use of NOT operator in predicate will result in the indexing algorithm leave out some data. diff --git a/src/Storages/MergeTree/KeyCondition.h b/src/Storages/MergeTree/KeyCondition.h index 3c2089a56d7..586bc43f791 100644 --- a/src/Storages/MergeTree/KeyCondition.h +++ b/src/Storages/MergeTree/KeyCondition.h @@ -208,6 +208,7 @@ public: /// Does not take into account the SAMPLE section. all_columns - the set of all columns of the table. KeyCondition( const ASTPtr & query, + const ASTs & additional_filter_asts, TreeRewriterResultPtr syntax_analyzer_result, PreparedSetsPtr prepared_sets_, ContextPtr context, @@ -223,9 +224,18 @@ public: const ExpressionActionsPtr & key_expr_, bool single_point_ = false, bool strict_ = false) - : KeyCondition(query_info.query, query_info.syntax_analyzer_result, query_info.prepared_sets, - context, key_column_names, key_expr_, single_point_, strict_) - {} + : KeyCondition( + query_info.query, + query_info.filter_asts, + query_info.syntax_analyzer_result, + query_info.prepared_sets, + context, + key_column_names, + key_expr_, + single_point_, + strict_) + { + } KeyCondition( ActionDAGNodes dag_nodes, diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 594b4a32f9c..63705fbcdf5 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -5142,8 +5142,7 @@ static void selectBestProjection( const MergeTreeDataSelectExecutor & reader, const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, - const ActionsDAGPtr & added_filter, - const std::string & added_filter_column_name, + const ActionDAGNodes & added_filter_nodes, const Names & required_columns, ProjectionCandidate & candidate, ContextPtr query_context, @@ -5174,8 +5173,7 @@ static void selectBestProjection( storage_snapshot->metadata, candidate.desc->metadata, query_info, - added_filter, - added_filter_column_name, + added_filter_nodes, query_context, settings.max_threads, max_added_blocks); @@ -5198,8 +5196,7 @@ static void selectBestProjection( storage_snapshot->metadata, storage_snapshot->metadata, query_info, // TODO syntax_analysis_result set in index - added_filter, - added_filter_column_name, + added_filter_nodes, query_context, settings.max_threads, max_added_blocks); @@ -5524,6 +5521,14 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg const auto & before_where = analysis_result.before_where; const auto & where_column_name = analysis_result.where_column_name; + /// For PK analysis + ActionDAGNodes added_filter_nodes; + if (auto additional_filter_info = select.getAdditionalQueryInfo()) + added_filter_nodes.nodes.push_back(&additional_filter_info->actions->findInOutputs(additional_filter_info->column_name)); + + if (before_where) + added_filter_nodes.nodes.push_back(&before_where->findInOutputs(where_column_name)); + bool can_use_aggregate_projection = true; /// If the first stage of the query pipeline is more complex than Aggregating - Expression - Filter - ReadFromStorage, /// we cannot use aggregate projection. @@ -5750,7 +5755,7 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg query_info.minmax_count_projection_block = getMinMaxCountProjectionBlock( metadata_snapshot, minmax_count_projection_candidate->required_columns, - analysis_result.prewhere_info || analysis_result.before_where, + !query_info.filter_asts.empty() || analysis_result.prewhere_info || analysis_result.before_where, query_info, parts, normal_parts, @@ -5792,8 +5797,7 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg metadata_snapshot, metadata_snapshot, query_info, - before_where, - where_column_name, + added_filter_nodes, query_context, settings.max_threads, max_added_blocks); @@ -5825,8 +5829,7 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg metadata_snapshot, metadata_snapshot, query_info, - before_where, - where_column_name, + added_filter_nodes, query_context, settings.max_threads, max_added_blocks); @@ -5852,8 +5855,7 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg reader, storage_snapshot, query_info, - before_where, - where_column_name, + added_filter_nodes, analysis_result.required_columns, candidate, query_context, @@ -5874,8 +5876,7 @@ std::optional MergeTreeData::getQueryProcessingStageWithAgg reader, storage_snapshot, query_info, - before_where, - where_column_name, + added_filter_nodes, analysis_result.required_columns, candidate, query_context, diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index ba3505b5886..c5f546a9c36 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -1273,8 +1273,7 @@ MergeTreeDataSelectAnalysisResultPtr MergeTreeDataSelectExecutor::estimateNumMar const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, - const ActionsDAGPtr & added_filter, - const std::string & added_filter_column_name, + const ActionDAGNodes & added_filter_nodes, ContextPtr context, unsigned num_streams, std::shared_ptr max_block_numbers_to_read) const @@ -1295,8 +1294,7 @@ MergeTreeDataSelectAnalysisResultPtr MergeTreeDataSelectExecutor::estimateNumMar return ReadFromMergeTree::selectRangesToRead( std::move(parts), query_info.prewhere_info, - added_filter, - added_filter_column_name, + added_filter_nodes, metadata_snapshot_base, metadata_snapshot, query_info, diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h index 899cf1f2862..bb44f260eec 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h @@ -60,8 +60,7 @@ public: const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, - const ActionsDAGPtr & added_filter, - const std::string & added_filter_column_name, + const ActionDAGNodes & added_filter_nodes, ContextPtr context, unsigned num_streams, std::shared_ptr max_block_numbers_to_read = nullptr) const; diff --git a/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp b/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp index 05319ecc62e..b190ac2b2fd 100644 --- a/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp +++ b/src/Storages/MergeTree/MergeTreeIndexMinMax.cpp @@ -157,9 +157,7 @@ void MergeTreeIndexAggregatorMinMax::update(const Block & block, size_t * pos, s MergeTreeIndexConditionMinMax::MergeTreeIndexConditionMinMax( - const IndexDescription & index, - const SelectQueryInfo & query, - ContextPtr context) + const IndexDescription & index, const SelectQueryInfo & query, ContextPtr context) : index_data_types(index.data_types) , condition(query, context, index.column_names, index.expression) { diff --git a/src/Storages/MergeTree/PartitionPruner.h b/src/Storages/MergeTree/PartitionPruner.h index 675fef1433d..9953c52b593 100644 --- a/src/Storages/MergeTree/PartitionPruner.h +++ b/src/Storages/MergeTree/PartitionPruner.h @@ -26,9 +26,7 @@ private: public: PartitionPruner(const StorageMetadataPtr & metadata, const SelectQueryInfo & query_info, ContextPtr context, bool strict) : partition_key(MergeTreePartition::adjustPartitionKey(metadata, context)) - , partition_condition( - query_info.query, query_info.syntax_analyzer_result, query_info.prepared_sets, - context, partition_key.column_names, partition_key.expression, true /* single_point */, strict) + , partition_condition(query_info, context, partition_key.column_names, partition_key.expression, true /* single_point */, strict) , useless(strict ? partition_condition.anyUnknownOrAlwaysTrue() : partition_condition.alwaysUnknownOrTrue()) { } diff --git a/src/Storages/SelectQueryInfo.h b/src/Storages/SelectQueryInfo.h index 4a3db2e8497..909da5bebba 100644 --- a/src/Storages/SelectQueryInfo.h +++ b/src/Storages/SelectQueryInfo.h @@ -156,9 +156,11 @@ struct SelectQueryInfo TreeRewriterResultPtr syntax_analyzer_result; /// This is an additional filer applied to current table. - /// It is needed only for additional PK filtering. ASTPtr additional_filter_ast; + /// It is needed for PK analysis based on row_level_policy and additional_filters. + ASTs filter_asts; + ReadInOrderOptimizerPtr order_optimizer; /// Can be modified while reading from storage InputOrderInfoPtr input_order_info; diff --git a/src/Storages/StorageMerge.cpp b/src/Storages/StorageMerge.cpp index 666717e50a0..2bedf406b7d 100644 --- a/src/Storages/StorageMerge.cpp +++ b/src/Storages/StorageMerge.cpp @@ -542,7 +542,7 @@ QueryPipelineBuilderPtr ReadFromMerge::createSources( return {}; if (auto * read_from_merge_tree = typeid_cast(plan.getRootNode()->step.get())) - read_from_merge_tree->addFilter(added_filter, added_filter_column_name); + read_from_merge_tree->addFilterNodes(added_filter_nodes); builder = plan.buildQueryPipeline( QueryPlanOptimizationSettings::fromContext(modified_context), diff --git a/src/Storages/StorageMerge.h b/src/Storages/StorageMerge.h index d2f94ac6b88..6bf68660803 100644 --- a/src/Storages/StorageMerge.h +++ b/src/Storages/StorageMerge.h @@ -140,8 +140,8 @@ public: void addFilter(ActionsDAGPtr expression, std::string column_name) { - added_filter = std::move(expression); - added_filter_column_name = std::move(column_name); + added_filter_dags.push_back(expression); + added_filter_nodes.nodes.push_back(&expression->findInOutputs(column_name)); } private: @@ -160,7 +160,9 @@ private: ContextMutablePtr context; QueryProcessingStage::Enum common_processed_stage; - ActionsDAGPtr added_filter; + std::vector added_filter_dags; + ActionDAGNodes added_filter_nodes; + std::string added_filter_column_name; struct AliasData diff --git a/tests/queries/0_stateless/01710_projection_additional_filters.reference b/tests/queries/0_stateless/01710_projection_additional_filters.reference new file mode 100644 index 00000000000..06b63ea6c2f --- /dev/null +++ b/tests/queries/0_stateless/01710_projection_additional_filters.reference @@ -0,0 +1 @@ +0 0 0 diff --git a/tests/queries/0_stateless/01710_projection_additional_filters.sql b/tests/queries/0_stateless/01710_projection_additional_filters.sql new file mode 100644 index 00000000000..1633b48ba7e --- /dev/null +++ b/tests/queries/0_stateless/01710_projection_additional_filters.sql @@ -0,0 +1,9 @@ +DROP TABLE IF EXISTS t; + +CREATE TABLE t(a UInt32, b UInt32) ENGINE = MergeTree PARTITION BY a ORDER BY a; + +INSERT INTO t SELECT number % 10, number FROM numbers(10000); + +SELECT count(), min(a), max(a) FROM t SETTINGS additional_table_filters = {'t' : '0'}; + +DROP TABLE t; diff --git a/tests/queries/0_stateless/01710_projection_row_policy.reference b/tests/queries/0_stateless/01710_projection_row_policy.reference new file mode 100644 index 00000000000..06b63ea6c2f --- /dev/null +++ b/tests/queries/0_stateless/01710_projection_row_policy.reference @@ -0,0 +1 @@ +0 0 0 diff --git a/tests/queries/0_stateless/01710_projection_row_policy.sql b/tests/queries/0_stateless/01710_projection_row_policy.sql new file mode 100644 index 00000000000..a54cc50b9e9 --- /dev/null +++ b/tests/queries/0_stateless/01710_projection_row_policy.sql @@ -0,0 +1,13 @@ +DROP TABLE IF EXISTS t; + +CREATE TABLE t(a UInt32, b UInt32) ENGINE = MergeTree PARTITION BY a ORDER BY a; + +INSERT INTO t SELECT number % 10, number FROM numbers(10000); + +CREATE ROW POLICY OR REPLACE rp ON t FOR SELECT USING 0 TO ALL; + +SELECT count(), min(a), max(a) FROM t; + +DROP ROW POLICY rp ON t; + +DROP TABLE t; From 11a274e9901f475bff7459356eb9c2820a683a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Thu, 11 Aug 2022 13:27:53 +0200 Subject: [PATCH 576/672] Clean up constinit usage and add a comment --- cmake/warnings.cmake | 1 + src/Common/ThreadStatus.cpp | 8 -------- src/Common/ThreadStatus.h | 16 +++++++++------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/cmake/warnings.cmake b/cmake/warnings.cmake index 994f14c6149..a8f12fe26dd 100644 --- a/cmake/warnings.cmake +++ b/cmake/warnings.cmake @@ -23,6 +23,7 @@ if (COMPILER_CLANG) no_warning(zero-length-array) no_warning(c++98-compat-pedantic) no_warning(c++98-compat) + no_warning(c++20-compat) # Use constinit in C++20 without warnings no_warning(conversion) no_warning(ctad-maybe-unsupported) # clang 9+, linux-only no_warning(disabled-macro-expansion) diff --git a/src/Common/ThreadStatus.cpp b/src/Common/ThreadStatus.cpp index 98f78cada5c..423a44c97d6 100644 --- a/src/Common/ThreadStatus.cpp +++ b/src/Common/ThreadStatus.cpp @@ -24,15 +24,7 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wc++20-compat" -#endif thread_local ThreadStatus constinit * current_thread = nullptr; -thread_local ThreadStatus * main_thread = nullptr; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif #if !defined(SANITIZER) namespace diff --git a/src/Common/ThreadStatus.h b/src/Common/ThreadStatus.h index 594e86ffa2e..0b01f43a226 100644 --- a/src/Common/ThreadStatus.h +++ b/src/Common/ThreadStatus.h @@ -102,14 +102,16 @@ public: using ThreadGroupStatusPtr = std::shared_ptr; -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wc++20-compat" -#endif +/** + * We use **constinit** here to tell the compiler the current_thread variable is initialized. + * If we didn't help the compiler, then it would most likely add a check before every use of the variable to initialize it if needed. + * Instead it will trust that we are doing the right thing (and we do initialize it to nullptr) and emit more optimal code. + * This is noticeable in functions like CurrentMemoryTracker::free and CurrentMemoryTracker::allocImpl + * See also: + * - https://en.cppreference.com/w/cpp/language/constinit + * - https://github.com/ClickHouse/ClickHouse/pull/40078 + */ extern thread_local constinit ThreadStatus * current_thread; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif /** Encapsulates all per-thread info (ProfileEvents, MemoryTracker, query_id, query context, etc.). * The object must be created in thread function and destroyed in the same thread before the exit. From 372481e7701890f05950176d1105bb59b48b8b1a Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 11 Aug 2022 15:01:32 +0200 Subject: [PATCH 577/672] Rework S3Helper a little bit --- tests/ci/ast_fuzzer_check.py | 3 +-- tests/ci/build_check.py | 8 ++++---- tests/ci/build_report_check.py | 3 +-- tests/ci/ccache_utils.py | 4 ++-- tests/ci/codebrowser_check.py | 8 ++++---- tests/ci/compatibility_check.py | 4 ++-- tests/ci/docker_images_check.py | 4 ++-- tests/ci/docker_manifests_merge.py | 4 ++-- tests/ci/docker_server.py | 6 +++--- tests/ci/docs_check.py | 4 ++-- tests/ci/docs_release.py | 4 ++-- tests/ci/env_helper.py | 1 + tests/ci/fast_test_check.py | 4 ++-- tests/ci/functional_test_check.py | 6 +++--- tests/ci/integration_test_check.py | 4 ++-- tests/ci/keeper_jepsen_check.py | 6 +++--- tests/ci/performance_comparison_check.py | 6 +++--- tests/ci/push_to_artifactory.py | 4 ++-- tests/ci/s3_helper.py | 24 ++++++++++++++++++++---- tests/ci/split_build_smoke_check.py | 4 ++-- tests/ci/stress_check.py | 6 +++--- tests/ci/style_check.py | 4 ++-- tests/ci/unit_tests_check.py | 4 ++-- 23 files changed, 70 insertions(+), 55 deletions(-) diff --git a/tests/ci/ast_fuzzer_check.py b/tests/ci/ast_fuzzer_check.py index 82e7a3271c1..9f3ddbe9932 100644 --- a/tests/ci/ast_fuzzer_check.py +++ b/tests/ci/ast_fuzzer_check.py @@ -12,7 +12,6 @@ from env_helper import ( GITHUB_RUN_URL, REPORTS_PATH, REPO_COPY, - S3_URL, TEMP_PATH, ) from s3_helper import S3Helper @@ -118,7 +117,7 @@ if __name__ == "__main__": "core.gz": os.path.join(workspace_path, "core.gz"), } - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() for f in paths: try: paths[f] = s3_helper.upload_test_report_to_s3(paths[f], s3_prefix + "/" + f) diff --git a/tests/ci/build_check.py b/tests/ci/build_check.py index 2463ec669dd..610877a5508 100644 --- a/tests/ci/build_check.py +++ b/tests/ci/build_check.py @@ -15,7 +15,7 @@ from env_helper import ( IMAGES_PATH, REPO_COPY, S3_BUILDS_BUCKET, - S3_URL, + S3_DOWNLOAD, TEMP_PATH, ) from s3_helper import S3Helper @@ -143,9 +143,9 @@ def check_for_success_run( for url in build_results: url_escaped = url.replace("+", "%2B").replace(" ", "%20") if BUILD_LOG_NAME in url: - log_url = f"{S3_URL}/{S3_BUILDS_BUCKET}/{url_escaped}" + log_url = f"{S3_DOWNLOAD}/{S3_BUILDS_BUCKET}/{url_escaped}" else: - build_urls.append(f"{S3_URL}/{S3_BUILDS_BUCKET}/{url_escaped}") + build_urls.append(f"{S3_DOWNLOAD}/{S3_BUILDS_BUCKET}/{url_escaped}") if not log_url: # log is uploaded the last, so if there's no log we need to rerun the build return @@ -249,7 +249,7 @@ def main(): logging.info("Repo copy path %s", REPO_COPY) - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() version = get_version_from_repo(git=Git(True)) release_or_pr, performance_pr = get_release_or_pr(pr_info, version) diff --git a/tests/ci/build_report_check.py b/tests/ci/build_report_check.py index 3155b4fd56d..7ebebb881bc 100644 --- a/tests/ci/build_report_check.py +++ b/tests/ci/build_report_check.py @@ -14,7 +14,6 @@ from env_helper import ( GITHUB_RUN_URL, GITHUB_SERVER_URL, REPORTS_PATH, - S3_URL, TEMP_PATH, ) from report import create_build_html_report @@ -245,7 +244,7 @@ def main(): logging.error("No success builds, failing check") sys.exit(1) - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() branch_url = f"{GITHUB_SERVER_URL}/{GITHUB_REPOSITORY}/commits/master" branch_name = "master" diff --git a/tests/ci/ccache_utils.py b/tests/ci/ccache_utils.py index fd3589e1bb3..cfe07363589 100644 --- a/tests/ci/ccache_utils.py +++ b/tests/ci/ccache_utils.py @@ -10,7 +10,7 @@ from pathlib import Path import requests # type: ignore from compress_files import decompress_fast, compress_fast -from env_helper import S3_URL, S3_BUILDS_BUCKET +from env_helper import S3_DOWNLOAD, S3_BUILDS_BUCKET DOWNLOAD_RETRIES_COUNT = 5 @@ -74,7 +74,7 @@ def get_ccache_if_not_exists( for obj in objects: if ccache_name in obj: logging.info("Found ccache on path %s", obj) - url = f"{S3_URL}/{S3_BUILDS_BUCKET}/{obj}" + url = f"{S3_DOWNLOAD}/{S3_BUILDS_BUCKET}/{obj}" compressed_cache = os.path.join(temp_path, os.path.basename(obj)) dowload_file_with_progress(url, compressed_cache) diff --git a/tests/ci/codebrowser_check.py b/tests/ci/codebrowser_check.py index 121339d9971..97036c6fc7b 100644 --- a/tests/ci/codebrowser_check.py +++ b/tests/ci/codebrowser_check.py @@ -7,7 +7,7 @@ import logging from github import Github -from env_helper import IMAGES_PATH, REPO_COPY, S3_TEST_REPORTS_BUCKET, S3_URL +from env_helper import IMAGES_PATH, REPO_COPY, S3_TEST_REPORTS_BUCKET, S3_DOWNLOAD from stopwatch import Stopwatch from upload_result_helper import upload_results from s3_helper import S3Helper @@ -23,7 +23,7 @@ def get_run_command(repo_path, output_path, image): cmd = ( "docker run " + f"--volume={repo_path}:/repo_folder " f"--volume={output_path}:/test_output " - f"-e 'DATA={S3_URL}/{S3_TEST_REPORTS_BUCKET}/codebrowser/data' {image}" + f"-e 'DATA={S3_DOWNLOAD}/{S3_TEST_REPORTS_BUCKET}/codebrowser/data' {image}" ) return cmd @@ -41,7 +41,7 @@ if __name__ == "__main__": os.makedirs(temp_path) docker_image = get_image_with_version(IMAGES_PATH, "clickhouse/codebrowser") - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() result_path = os.path.join(temp_path, "result_path") if not os.path.exists(result_path): @@ -70,7 +70,7 @@ if __name__ == "__main__": ) index_html = ( - '' + '' "HTML report" ) diff --git a/tests/ci/compatibility_check.py b/tests/ci/compatibility_check.py index fc7584536ef..39d027ad3c0 100644 --- a/tests/ci/compatibility_check.py +++ b/tests/ci/compatibility_check.py @@ -8,7 +8,7 @@ import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import PRInfo @@ -169,7 +169,7 @@ if __name__ == "__main__": subprocess.check_call(f"sudo chown -R ubuntu:ubuntu {temp_path}", shell=True) - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() state, description, test_results, additional_logs = process_result( result_path, server_log_path ) diff --git a/tests/ci/docker_images_check.py b/tests/ci/docker_images_check.py index 76ebbb78c7b..181555f3a94 100644 --- a/tests/ci/docker_images_check.py +++ b/tests/ci/docker_images_check.py @@ -14,7 +14,7 @@ from github import Github from clickhouse_helper import ClickHouseHelper, prepare_tests_results_for_clickhouse from commit_status_helper import post_commit_status -from env_helper import GITHUB_WORKSPACE, RUNNER_TEMP, GITHUB_RUN_URL, S3_URL +from env_helper import GITHUB_WORKSPACE, RUNNER_TEMP, GITHUB_RUN_URL from get_robot_token import get_best_robot_token, get_parameter_from_ssm from pr_info import PRInfo from s3_helper import S3Helper @@ -460,7 +460,7 @@ def main(): with open(changed_json, "w", encoding="utf-8") as images_file: json.dump(result_images, images_file) - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() s3_path_prefix = ( str(pr_info.number) + "/" + pr_info.sha + "/" + NAME.lower().replace(" ", "_") diff --git a/tests/ci/docker_manifests_merge.py b/tests/ci/docker_manifests_merge.py index 78f236be786..09b7a99da78 100644 --- a/tests/ci/docker_manifests_merge.py +++ b/tests/ci/docker_manifests_merge.py @@ -11,7 +11,7 @@ from github import Github from clickhouse_helper import ClickHouseHelper, prepare_tests_results_for_clickhouse from commit_status_helper import post_commit_status -from env_helper import RUNNER_TEMP, S3_URL +from env_helper import RUNNER_TEMP from get_robot_token import get_best_robot_token, get_parameter_from_ssm from pr_info import PRInfo from s3_helper import S3Helper @@ -203,7 +203,7 @@ def main(): json.dump(changed_images, ci) pr_info = PRInfo() - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() url = upload_results(s3_helper, pr_info.number, pr_info.sha, test_results, [], NAME) diff --git a/tests/ci/docker_server.py b/tests/ci/docker_server.py index 64172b90ebc..e0053f09664 100644 --- a/tests/ci/docker_server.py +++ b/tests/ci/docker_server.py @@ -16,7 +16,7 @@ from build_check import get_release_or_pr from clickhouse_helper import ClickHouseHelper, prepare_tests_results_for_clickhouse from commit_status_helper import post_commit_status from docker_images_check import DockerImage -from env_helper import CI, GITHUB_RUN_URL, RUNNER_TEMP, S3_BUILDS_BUCKET, S3_URL +from env_helper import CI, GITHUB_RUN_URL, RUNNER_TEMP, S3_BUILDS_BUCKET, S3_DOWNLOAD from get_robot_token import get_best_robot_token, get_parameter_from_ssm from git_helper import Git from pr_info import PRInfo @@ -309,7 +309,7 @@ def main(): pr_info = PRInfo() release_or_pr, _ = get_release_or_pr(pr_info, args.version) args.bucket_prefix = ( - f"{S3_URL}/{S3_BUILDS_BUCKET}/{release_or_pr}/{pr_info.sha}" + f"{S3_DOWNLOAD}/{S3_BUILDS_BUCKET}/{release_or_pr}/{pr_info.sha}" ) if args.push: @@ -335,7 +335,7 @@ def main(): status = "failure" pr_info = pr_info or PRInfo() - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() url = upload_results(s3_helper, pr_info.number, pr_info.sha, test_results, [], NAME) diff --git a/tests/ci/docs_check.py b/tests/ci/docs_check.py index f260d1f1e50..c95770b646d 100644 --- a/tests/ci/docs_check.py +++ b/tests/ci/docs_check.py @@ -6,7 +6,7 @@ import os import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, S3_URL +from env_helper import TEMP_PATH, REPO_COPY from s3_helper import S3Helper from pr_info import PRInfo from get_robot_token import get_best_robot_token @@ -120,7 +120,7 @@ if __name__ == "__main__": else: lines.append(("Non zero exit code", "FAIL")) - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() ch_helper = ClickHouseHelper() report_url = upload_results( diff --git a/tests/ci/docs_release.py b/tests/ci/docs_release.py index 96b0e7048c6..355e4af7426 100644 --- a/tests/ci/docs_release.py +++ b/tests/ci/docs_release.py @@ -7,7 +7,7 @@ import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, CLOUDFLARE_TOKEN, S3_URL +from env_helper import TEMP_PATH, REPO_COPY, CLOUDFLARE_TOKEN from s3_helper import S3Helper from pr_info import PRInfo from get_robot_token import get_best_robot_token @@ -106,7 +106,7 @@ if __name__ == "__main__": else: lines.append(("Non zero exit code", "FAIL")) - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() report_url = upload_results( s3_helper, pr_info.number, pr_info.sha, lines, additional_files, NAME diff --git a/tests/ci/env_helper.py b/tests/ci/env_helper.py index b6541205ed3..12c21398781 100644 --- a/tests/ci/env_helper.py +++ b/tests/ci/env_helper.py @@ -23,6 +23,7 @@ REPORTS_PATH = os.getenv("REPORTS_PATH", p.abspath(p.join(module_dir, "./reports REPO_COPY = os.getenv("REPO_COPY", git_root) RUNNER_TEMP = os.getenv("RUNNER_TEMP", p.abspath(p.join(module_dir, "./tmp"))) S3_URL = os.getenv("S3_URL", "https://s3.amazonaws.com") +S3_DOWNLOAD = os.getenv("S3_DOWNLOAD", S3_URL) S3_BUILDS_BUCKET = os.getenv("S3_BUILDS_BUCKET", "clickhouse-builds") S3_TEST_REPORTS_BUCKET = os.getenv("S3_TEST_REPORTS_BUCKET", "clickhouse-test-reports") diff --git a/tests/ci/fast_test_check.py b/tests/ci/fast_test_check.py index 84d9d3f16d8..1b71a967a9b 100644 --- a/tests/ci/fast_test_check.py +++ b/tests/ci/fast_test_check.py @@ -9,7 +9,7 @@ import atexit from github import Github -from env_helper import CACHES_PATH, TEMP_PATH, S3_URL +from env_helper import CACHES_PATH, TEMP_PATH from pr_info import FORCE_TESTS_LABEL, PRInfo from s3_helper import S3Helper from get_robot_token import get_best_robot_token @@ -105,7 +105,7 @@ if __name__ == "__main__": docker_image = get_image_with_version(temp_path, "clickhouse/fasttest") - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() workspace = os.path.join(temp_path, "fasttest-workspace") if not os.path.exists(workspace): diff --git a/tests/ci/functional_test_check.py b/tests/ci/functional_test_check.py index bcfeaa9973a..f5db7a10f14 100644 --- a/tests/ci/functional_test_check.py +++ b/tests/ci/functional_test_check.py @@ -10,7 +10,7 @@ import atexit from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_DOWNLOAD from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import FORCE_TESTS_LABEL, PRInfo @@ -88,7 +88,7 @@ def get_run_command( envs = [ f"-e MAX_RUN_TIME={int(0.9 * kill_timeout)}", - f'-e S3_URL="{S3_URL}/clickhouse-datasets"', + f'-e S3_URL="{S3_DOWNLOAD}/clickhouse-datasets"', ] if flaky_check: @@ -314,7 +314,7 @@ if __name__ == "__main__": subprocess.check_call(f"sudo chown -R ubuntu:ubuntu {temp_path}", shell=True) - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() state, description, test_results, additional_logs = process_results( result_path, server_log_path diff --git a/tests/ci/integration_test_check.py b/tests/ci/integration_test_check.py index 49a95748f6c..3709a7271d7 100644 --- a/tests/ci/integration_test_check.py +++ b/tests/ci/integration_test_check.py @@ -10,7 +10,7 @@ import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import PRInfo @@ -249,7 +249,7 @@ if __name__ == "__main__": ch_helper = ClickHouseHelper() mark_flaky_tests(ch_helper, check_name, test_results) - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() report_url = upload_results( s3_helper, pr_info.number, diff --git a/tests/ci/keeper_jepsen_check.py b/tests/ci/keeper_jepsen_check.py index af44b87b897..a0695d3283a 100644 --- a/tests/ci/keeper_jepsen_check.py +++ b/tests/ci/keeper_jepsen_check.py @@ -9,7 +9,7 @@ import boto3 from github import Github import requests -from env_helper import REPO_COPY, TEMP_PATH, S3_BUILDS_BUCKET, S3_URL +from env_helper import REPO_COPY, TEMP_PATH, S3_BUILDS_BUCKET, S3_DOWNLOAD from stopwatch import Stopwatch from upload_result_helper import upload_results from s3_helper import S3Helper @@ -192,7 +192,7 @@ if __name__ == "__main__": # run (see .github/workflows/jepsen.yml) So we cannot add explicit # dependency on a build job and using busy loop on it's results. For the # same reason we are using latest docker image. - build_url = f"{S3_URL}/{S3_BUILDS_BUCKET}/{release_or_pr}/{pr_info.sha}/{build_name}/clickhouse" + build_url = f"{S3_DOWNLOAD}/{S3_BUILDS_BUCKET}/{release_or_pr}/{pr_info.sha}/{build_name}/clickhouse" head = requests.head(build_url) counter = 0 while head.status_code != 200: @@ -248,7 +248,7 @@ if __name__ == "__main__": description = "No Jepsen output log" test_result = [("No Jepsen output log", "FAIL")] - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() report_url = upload_results( s3_helper, pr_info.number, diff --git a/tests/ci/performance_comparison_check.py b/tests/ci/performance_comparison_check.py index ce5226aeb04..40befc78de2 100644 --- a/tests/ci/performance_comparison_check.py +++ b/tests/ci/performance_comparison_check.py @@ -15,7 +15,7 @@ from github import Github from commit_status_helper import get_commit, post_commit_status from ci_config import CI_CONFIG from docker_pull_helper import get_image_with_version -from env_helper import GITHUB_EVENT_PATH, GITHUB_RUN_URL, S3_BUILDS_BUCKET, S3_URL +from env_helper import GITHUB_EVENT_PATH, GITHUB_RUN_URL, S3_BUILDS_BUCKET, S3_DOWNLOAD from get_robot_token import get_best_robot_token, get_parameter_from_ssm from pr_info import PRInfo from rerun_helper import RerunHelper @@ -86,7 +86,7 @@ if __name__ == "__main__": docker_env = "" - docker_env += f" -e S3_URL={S3_URL}/{S3_BUILDS_BUCKET}" + docker_env += f" -e S3_URL={S3_DOWNLOAD}/{S3_BUILDS_BUCKET}" docker_env += f" -e BUILD_NAME={required_build}" if pr_info.number == 0: @@ -197,7 +197,7 @@ if __name__ == "__main__": } s3_prefix = f"{pr_info.number}/{pr_info.sha}/{check_name_prefix}/" - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() uploaded = {} # type: Dict[str, str] for name, path in paths.items(): try: diff --git a/tests/ci/push_to_artifactory.py b/tests/ci/push_to_artifactory.py index b04fa723580..08d0a67f87b 100755 --- a/tests/ci/push_to_artifactory.py +++ b/tests/ci/push_to_artifactory.py @@ -9,7 +9,7 @@ from typing import Dict, List, Tuple from artifactory import ArtifactorySaaSPath # type: ignore from build_download_helper import dowload_build_with_progress -from env_helper import RUNNER_TEMP, S3_BUILDS_BUCKET, S3_URL +from env_helper import RUNNER_TEMP, S3_BUILDS_BUCKET, S3_DOWNLOAD from git_helper import TAG_REGEXP, commit, removeprefix, removesuffix @@ -98,7 +98,7 @@ class Packages: class S3: template = ( - f"{S3_URL}" + f"{S3_DOWNLOAD}" # "clickhouse-builds/" f"{S3_BUILDS_BUCKET}/" # "33333/" or "21.11/" from --release, if pull request is omitted diff --git a/tests/ci/s3_helper.py b/tests/ci/s3_helper.py index 483d6aee60e..24ff013d69a 100644 --- a/tests/ci/s3_helper.py +++ b/tests/ci/s3_helper.py @@ -9,7 +9,14 @@ from multiprocessing.dummy import Pool import boto3 # type: ignore -from env_helper import S3_TEST_REPORTS_BUCKET, S3_BUILDS_BUCKET, RUNNER_TEMP, CI, S3_URL +from env_helper import ( + S3_TEST_REPORTS_BUCKET, + S3_BUILDS_BUCKET, + RUNNER_TEMP, + CI, + S3_URL, + S3_DOWNLOAD, +) from compress_files import compress_file_fast @@ -33,9 +40,11 @@ def _flatten_list(lst): class S3Helper: - def __init__(self, host): + def __init__(self, host=S3_URL, download_host=S3_DOWNLOAD): self.session = boto3.session.Session(region_name="us-east-1") self.client = self.session.client("s3", endpoint_url=host) + self.host = host + self.download_host = download_host def _upload_file_to_s3(self, bucket_name, file_path, s3_path): logging.debug( @@ -98,7 +107,7 @@ class S3Helper: logging.info("Upload %s to %s. Meta: %s", file_path, s3_path, metadata) # last two replacements are specifics of AWS urls: # https://jamesd3142.wordpress.com/2018/02/28/amazon-s3-and-the-plus-symbol/ - url = f"{S3_URL}/{bucket_name}/{s3_path}" + url = f"{self.download_host}/{bucket_name}/{s3_path}" return url.replace("+", "%2B").replace(" ", "%20") def upload_test_report_to_s3(self, file_path, s3_path): @@ -170,7 +179,7 @@ class S3Helper: t = time.time() except Exception as ex: logging.critical("Failed to upload file, expcetion %s", ex) - return f"{S3_URL}/{bucket_name}/{s3_path}" + return f"{self.download_host}/{bucket_name}/{s3_path}" p = Pool(256) @@ -279,6 +288,13 @@ class S3Helper: return result + def exists(self, key, bucket=S3_BUILDS_BUCKET): + try: + self.client.head_object(Bucket=bucket, Key=key) + return True + except Exception: + return False + @staticmethod def copy_file_to_local(bucket_name, file_path, s3_path): local_path = os.path.abspath( diff --git a/tests/ci/split_build_smoke_check.py b/tests/ci/split_build_smoke_check.py index 5052b6b362e..c6bf1051c87 100644 --- a/tests/ci/split_build_smoke_check.py +++ b/tests/ci/split_build_smoke_check.py @@ -7,7 +7,7 @@ import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import PRInfo @@ -126,7 +126,7 @@ if __name__ == "__main__": ) ch_helper = ClickHouseHelper() - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() report_url = upload_results( s3_helper, pr_info.number, diff --git a/tests/ci/stress_check.py b/tests/ci/stress_check.py index 6073b03f8a6..6a53b63c9d9 100644 --- a/tests/ci/stress_check.py +++ b/tests/ci/stress_check.py @@ -8,7 +8,7 @@ import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_DOWNLOAD from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import PRInfo @@ -31,7 +31,7 @@ def get_run_command( ): cmd = ( "docker run --cap-add=SYS_PTRACE " - f"-e S3_URL='{S3_URL}/clickhouse-datasets' " + f"-e S3_URL='{S3_DOWNLOAD}/clickhouse-datasets' " f"--volume={build_path}:/package_folder " f"--volume={result_folder}:/test_output " f"--volume={repo_tests_path}:/usr/share/clickhouse-test " @@ -148,7 +148,7 @@ if __name__ == "__main__": subprocess.check_call(f"sudo chown -R ubuntu:ubuntu {temp_path}", shell=True) - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() state, description, test_results, additional_logs = process_results( result_path, server_log_path, run_log_path ) diff --git a/tests/ci/style_check.py b/tests/ci/style_check.py index db286ec7f6c..23a1dd467d7 100644 --- a/tests/ci/style_check.py +++ b/tests/ci/style_check.py @@ -15,7 +15,7 @@ from clickhouse_helper import ( ) from commit_status_helper import post_commit_status, update_mergeable_check from docker_pull_helper import get_image_with_version -from env_helper import GITHUB_WORKSPACE, RUNNER_TEMP, S3_URL +from env_helper import GITHUB_WORKSPACE, RUNNER_TEMP from get_robot_token import get_best_robot_token from github_helper import GitHub from git_helper import git_runner @@ -166,7 +166,7 @@ if __name__ == "__main__": os.makedirs(temp_path) docker_image = get_image_with_version(temp_path, "clickhouse/style-test") - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() cmd = ( f"docker run -u $(id -u ${{USER}}):$(id -g ${{USER}}) --cap-add=SYS_PTRACE " diff --git a/tests/ci/unit_tests_check.py b/tests/ci/unit_tests_check.py index 95011b728e9..eadb07c729c 100644 --- a/tests/ci/unit_tests_check.py +++ b/tests/ci/unit_tests_check.py @@ -7,7 +7,7 @@ import subprocess from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_URL +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import PRInfo @@ -147,7 +147,7 @@ if __name__ == "__main__": subprocess.check_call(f"sudo chown -R ubuntu:ubuntu {temp_path}", shell=True) - s3_helper = S3Helper(S3_URL) + s3_helper = S3Helper() state, description, test_results, additional_logs = process_result(test_output) ch_helper = ClickHouseHelper() From 99a38e41aa911c3061c3a36e0ecec935341643a7 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Thu, 11 Aug 2022 19:02:41 +0800 Subject: [PATCH 578/672] processor profile --- src/Interpreters/ProcessorsProfileLog.cpp | 12 +++++++++ src/Interpreters/ProcessorsProfileLog.h | 10 ++++++- src/Interpreters/executeQuery.cpp | 9 +++++++ src/Processors/IProcessor.h | 27 +++++++++++++++++++ src/Processors/Port.h | 11 ++++++++ .../02210_processors_profile_log.reference | 18 ++++++++----- .../02210_processors_profile_log.sql | 6 ++++- .../02210_processors_profile_log_2.reference | 5 ++++ .../02210_processors_profile_log_2.sh | 19 +++++++++++++ 9 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 tests/queries/0_stateless/02210_processors_profile_log_2.reference create mode 100755 tests/queries/0_stateless/02210_processors_profile_log_2.sh diff --git a/src/Interpreters/ProcessorsProfileLog.cpp b/src/Interpreters/ProcessorsProfileLog.cpp index 0589f78105d..f6ce801605a 100644 --- a/src/Interpreters/ProcessorsProfileLog.cpp +++ b/src/Interpreters/ProcessorsProfileLog.cpp @@ -26,12 +26,18 @@ NamesAndTypesList ProcessorProfileLogElement::getNamesAndTypes() {"id", std::make_shared()}, {"parent_ids", std::make_shared(std::make_shared())}, + {"plan_step", std::make_shared()}, + {"plan_group", std::make_shared()}, {"query_id", std::make_shared()}, {"name", std::make_shared(std::make_shared())}, {"elapsed_us", std::make_shared()}, {"input_wait_elapsed_us", std::make_shared()}, {"output_wait_elapsed_us", std::make_shared()}, + {"input_rows", std::make_shared()}, + {"input_bytes", std::make_shared()}, + {"output_rows", std::make_shared()}, + {"output_bytes", std::make_shared()}, }; } @@ -52,11 +58,17 @@ void ProcessorProfileLogElement::appendToBlock(MutableColumns & columns) const columns[i++]->insert(parent_ids_array); } + columns[i++]->insert(plan_step); + columns[i++]->insert(plan_group); columns[i++]->insertData(query_id.data(), query_id.size()); columns[i++]->insertData(processor_name.data(), processor_name.size()); columns[i++]->insert(elapsed_us); columns[i++]->insert(input_wait_elapsed_us); columns[i++]->insert(output_wait_elapsed_us); + columns[i++]->insert(input_rows); + columns[i++]->insert(input_bytes); + columns[i++]->insert(output_rows); + columns[i++]->insert(output_bytes); } ProcessorsProfileLog::ProcessorsProfileLog(ContextPtr context_, const String & database_name_, diff --git a/src/Interpreters/ProcessorsProfileLog.h b/src/Interpreters/ProcessorsProfileLog.h index 33cb3988e9f..07837bdd10e 100644 --- a/src/Interpreters/ProcessorsProfileLog.h +++ b/src/Interpreters/ProcessorsProfileLog.h @@ -13,9 +13,12 @@ struct ProcessorProfileLogElement time_t event_time{}; Decimal64 event_time_microseconds{}; - UInt64 id; + UInt64 id{}; std::vector parent_ids; + UInt64 plan_step{}; + UInt64 plan_group{}; + String query_id; String processor_name; @@ -26,6 +29,11 @@ struct ProcessorProfileLogElement /// IProcessor::PortFull UInt32 output_wait_elapsed_us{}; + size_t input_rows{}; + size_t input_bytes{}; + size_t output_rows{}; + size_t output_bytes{}; + static std::string name() { return "ProcessorsProfileLog"; } static NamesAndTypesList getNamesAndTypes(); static NamesAndAliases getNamesAndAliases() { return {}; } diff --git a/src/Interpreters/executeQuery.cpp b/src/Interpreters/executeQuery.cpp index d6f9b9af0b0..cdddd28adeb 100644 --- a/src/Interpreters/executeQuery.cpp +++ b/src/Interpreters/executeQuery.cpp @@ -924,12 +924,21 @@ static std::tuple executeQueryImpl( processor_elem.id = get_proc_id(*processor); processor_elem.parent_ids = std::move(parents); + processor_elem.plan_step = reinterpret_cast(processor->getQueryPlanStep()); + processor_elem.plan_group = processor->getQueryPlanStepGroup(); + processor_elem.processor_name = processor->getName(); processor_elem.elapsed_us = processor->getElapsedUs(); processor_elem.input_wait_elapsed_us = processor->getInputWaitElapsedUs(); processor_elem.output_wait_elapsed_us = processor->getOutputWaitElapsedUs(); + auto stats = processor->getProcessorDataStats(); + processor_elem.input_rows = stats.input_rows; + processor_elem.input_bytes = stats.input_bytes; + processor_elem.output_rows = stats.output_rows; + processor_elem.output_bytes = stats.output_bytes; + processors_profile_log->add(processor_elem); } } diff --git a/src/Processors/IProcessor.h b/src/Processors/IProcessor.h index 9d885002cfe..6d17db69c9e 100644 --- a/src/Processors/IProcessor.h +++ b/src/Processors/IProcessor.h @@ -307,6 +307,33 @@ public: uint64_t getInputWaitElapsedUs() const { return input_wait_elapsed_us; } uint64_t getOutputWaitElapsedUs() const { return output_wait_elapsed_us; } + struct ProcessorDataStats + { + size_t input_rows = 0; + size_t input_bytes = 0; + size_t output_rows = 0; + size_t output_bytes = 0; + }; + + ProcessorDataStats getProcessorDataStats() const + { + ProcessorDataStats stats; + + for (const auto & input : inputs) + { + stats.input_rows += input.rows; + stats.input_bytes += input.bytes; + } + + for (const auto & output : outputs) + { + stats.output_rows += output.rows; + stats.output_bytes += output.bytes; + } + + return stats; + } + struct ReadProgressCounters { uint64_t read_rows = 0; diff --git a/src/Processors/Port.h b/src/Processors/Port.h index 06f9059c9c3..e3fb0e3e342 100644 --- a/src/Processors/Port.h +++ b/src/Processors/Port.h @@ -254,6 +254,10 @@ protected: if (likely(update_info)) update_info->update(); } + + /// For processors_profile_log + size_t rows = 0; + size_t bytes = 0; }; /// Invariants: @@ -300,6 +304,9 @@ public: chunk.dumpStructure()); } + rows += data->chunk.getNumRows(); + bytes += data->chunk.bytes(); + return std::move(*data); } @@ -422,6 +429,10 @@ public: std::uintptr_t flags = 0; *data = std::move(data_); + + rows += data->chunk.getNumRows(); + bytes += data->chunk.bytes(); + state->push(data, flags); } diff --git a/tests/queries/0_stateless/02210_processors_profile_log.reference b/tests/queries/0_stateless/02210_processors_profile_log.reference index 1a7dd64d657..181022d2421 100644 --- a/tests/queries/0_stateless/02210_processors_profile_log.reference +++ b/tests/queries/0_stateless/02210_processors_profile_log.reference @@ -25,13 +25,17 @@ SELECT -- NullSource/LazyOutputFormatLazyOutputFormat are the outputs -- so they cannot starts to execute before sleep(1) will be executed. input_wait_elapsed_us>1e6) - elapsed + elapsed, + input_rows, + input_bytes, + output_rows, + output_bytes FROM system.processors_profile_log WHERE query_id = query_id_ ORDER BY name; -ExpressionTransform 1 -LazyOutputFormat 1 -LimitsCheckingTransform 1 -NullSource 1 -NullSource 1 -SourceFromSingleChunk 1 +ExpressionTransform 1 1 1 1 1 +LazyOutputFormat 1 1 1 0 0 +LimitsCheckingTransform 1 1 1 1 1 +NullSource 1 0 0 0 0 +NullSource 1 0 0 0 0 +SourceFromSingleChunk 1 0 0 1 1 diff --git a/tests/queries/0_stateless/02210_processors_profile_log.sql b/tests/queries/0_stateless/02210_processors_profile_log.sql index 160f8009262..44e563ef57b 100644 --- a/tests/queries/0_stateless/02210_processors_profile_log.sql +++ b/tests/queries/0_stateless/02210_processors_profile_log.sql @@ -22,7 +22,11 @@ SELECT -- NullSource/LazyOutputFormatLazyOutputFormat are the outputs -- so they cannot starts to execute before sleep(1) will be executed. input_wait_elapsed_us>1e6) - elapsed + elapsed, + input_rows, + input_bytes, + output_rows, + output_bytes FROM system.processors_profile_log WHERE query_id = query_id_ ORDER BY name; diff --git a/tests/queries/0_stateless/02210_processors_profile_log_2.reference b/tests/queries/0_stateless/02210_processors_profile_log_2.reference new file mode 100644 index 00000000000..b5d3e561efb --- /dev/null +++ b/tests/queries/0_stateless/02210_processors_profile_log_2.reference @@ -0,0 +1,5 @@ +499999500000 +AggregatingTransform 1000002 8000016 2 16 +ExpressionTransform 1 8 1 8 +ExpressionTransform 1000000 8000000 1000000 8000000 +NumbersMt 3 24 1000003 8000024 diff --git a/tests/queries/0_stateless/02210_processors_profile_log_2.sh b/tests/queries/0_stateless/02210_processors_profile_log_2.sh new file mode 100755 index 00000000000..e2345898a08 --- /dev/null +++ b/tests/queries/0_stateless/02210_processors_profile_log_2.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -eo pipefail + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +QUERY_ID=$(${CLICKHOUSE_CLIENT} -q "select lower(hex(reverse(reinterpretAsString(generateUUIDv4()))))") + + +${CLICKHOUSE_CLIENT} --query_id "${QUERY_ID}" < Date: Thu, 11 Aug 2022 15:27:00 +0200 Subject: [PATCH 579/672] fix --- docker/test/stress/run.sh | 2 ++ tests/config/config.d/merge_tree_old_dirs_cleanup.xml | 8 ++++++++ tests/config/config.d/merge_tree_settings.xml | 4 ---- tests/config/install.sh | 1 + 4 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 tests/config/config.d/merge_tree_old_dirs_cleanup.xml diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index 6c32e9a73ea..31dd2330c63 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -314,6 +314,8 @@ else # Avoid "Setting allow_deprecated_database_ordinary is neither a builtin setting..." rm -f /etc/clickhouse-server/users.d/database_ordinary.xml ||: + # Disable aggressive cleanup of tmp dirs (it worked incorrectly before 22.8) + rm -f /etc/clickhouse-server/users.d/merge_tree_old_dirs_cleanup.xml ||: start diff --git a/tests/config/config.d/merge_tree_old_dirs_cleanup.xml b/tests/config/config.d/merge_tree_old_dirs_cleanup.xml new file mode 100644 index 00000000000..41932cb6d61 --- /dev/null +++ b/tests/config/config.d/merge_tree_old_dirs_cleanup.xml @@ -0,0 +1,8 @@ + + + + 1 + + 10 + + diff --git a/tests/config/config.d/merge_tree_settings.xml b/tests/config/config.d/merge_tree_settings.xml index e71d0574b67..f277c18fa3f 100644 --- a/tests/config/config.d/merge_tree_settings.xml +++ b/tests/config/config.d/merge_tree_settings.xml @@ -2,9 +2,5 @@ 10 - - 1 - - 10 diff --git a/tests/config/install.sh b/tests/config/install.sh index 478601620e1..3f570353d25 100755 --- a/tests/config/install.sh +++ b/tests/config/install.sh @@ -29,6 +29,7 @@ ln -sf $SRC_PATH/config.d/graphite.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/database_atomic.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/max_concurrent_queries.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/merge_tree_settings.xml $DEST_SERVER_PATH/config.d/ +ln -sf $SRC_PATH/config.d/merge_tree_old_dirs_cleanup.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/test_cluster_with_incorrect_pw.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/keeper_port.xml $DEST_SERVER_PATH/config.d/ ln -sf $SRC_PATH/config.d/logging_no_rotate.xml $DEST_SERVER_PATH/config.d/ From f9326b00c84df47e28e184c35aff656005d254c4 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Thu, 11 Aug 2022 16:26:41 +0200 Subject: [PATCH 580/672] PODArray assign empty array fix --- src/Common/PODArray.h | 5 ++--- src/Common/tests/gtest_pod_array.cpp | 29 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/Common/PODArray.h b/src/Common/PODArray.h index 44be28eab75..d3232d833ee 100644 --- a/src/Common/PODArray.h +++ b/src/Common/PODArray.h @@ -703,10 +703,9 @@ public: size_t bytes_to_copy = this->byte_size(required_capacity); if (bytes_to_copy) - { memcpy(this->c_start, reinterpret_cast(&*from_begin), bytes_to_copy); - this->c_end = this->c_start + bytes_to_copy; - } + + this->c_end = this->c_start + bytes_to_copy; } // ISO C++ has strict ambiguity rules, thus we cannot apply TAllocatorParams here. diff --git a/src/Common/tests/gtest_pod_array.cpp b/src/Common/tests/gtest_pod_array.cpp index b5ec45c3e5d..82a6f7589b8 100644 --- a/src/Common/tests/gtest_pod_array.cpp +++ b/src/Common/tests/gtest_pod_array.cpp @@ -484,6 +484,35 @@ TEST(Common, PODArrayInsertFromItself) } } +TEST(Common, PODArrayAssign) +{ + { + PaddedPODArray array; + array.push_back(1); + array.push_back(2); + + array.assign({1, 2, 3}); + + ASSERT_EQ(array.size(), 3); + ASSERT_EQ(array, PaddedPODArray({1, 2, 3})); + } + { + PaddedPODArray array; + array.push_back(1); + array.push_back(2); + + array.assign({}); + + ASSERT_TRUE(array.empty()); + } + { + PaddedPODArray array; + array.assign({}); + + ASSERT_TRUE(array.empty()); + } +} + TEST(Common, PODNoOverallocation) { /// Check that PaddedPODArray allocates for smaller number of elements than the power of two due to padding. From 59108345e99ed7ec3cb0d43aa1f46db569a6f9df Mon Sep 17 00:00:00 2001 From: Arthur Passos Date: Thu, 11 Aug 2022 13:28:07 -0300 Subject: [PATCH 581/672] Use one CaresPTRResolver per thread --- src/Common/CaresPTRResolver.cpp | 7 ++++++- src/Common/DNSPTRResolverProvider.cpp | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Common/CaresPTRResolver.cpp b/src/Common/CaresPTRResolver.cpp index f6228e97c02..d6474368a80 100644 --- a/src/Common/CaresPTRResolver.cpp +++ b/src/Common/CaresPTRResolver.cpp @@ -35,8 +35,13 @@ namespace DB * See https://github.com/grpc/grpc/blob/master/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc#L1187 * That means it's safe to init it here, but we should be cautious when introducing new code that depends on c-ares and even updates * to grpc. As discussed in https://github.com/ClickHouse/ClickHouse/pull/37827#discussion_r919189085, c-ares should be adapted to be atomic + * + * Since C++ 11 static objects are initialized in a thread safe manner. The static qualifier also makes sure + * it'll be called/ initialized only once. * */ - if (ares_library_init(ARES_LIB_INIT_ALL) != ARES_SUCCESS || ares_init(&channel) != ARES_SUCCESS) + static const auto library_init_result = ares_library_init(ARES_LIB_INIT_ALL); + + if (library_init_result != ARES_SUCCESS || ares_init(&channel) != ARES_SUCCESS) { throw DB::Exception("Failed to initialize c-ares", DB::ErrorCodes::DNS_ERROR); } diff --git a/src/Common/DNSPTRResolverProvider.cpp b/src/Common/DNSPTRResolverProvider.cpp index 41c73f4f36f..97d601a3a78 100644 --- a/src/Common/DNSPTRResolverProvider.cpp +++ b/src/Common/DNSPTRResolverProvider.cpp @@ -5,9 +5,8 @@ namespace DB { std::shared_ptr DNSPTRResolverProvider::get() { - static auto cares_resolver = std::make_shared( + return std::make_shared( CaresPTRResolver::provider_token {} ); - return cares_resolver; } } From cad311565c94d6799d4d854655a7ce470169379f Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 11 Aug 2022 21:57:04 +0300 Subject: [PATCH 582/672] Update 02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.sh --- .../02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.sh b/tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.sh index 43f6d62bd10..65025858e20 100755 --- a/tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.sh +++ b/tests/queries/0_stateless/02390_prometheus_ClickHouseStatusInfo_DictionaryStatus.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Tags: no-ordinary-database CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From 06e42a1b23e4400903e0dfcf990514ed9f073961 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 11 Aug 2022 21:27:26 +0200 Subject: [PATCH 583/672] run tests with Ordinary database in flaky check --- docker/test/stateless/run.sh | 13 ++++++++----- ...93_system_parts_race_condition_drop_zookeeper.sh | 1 - .../0_stateless/01161_information_schema.sql | 1 + .../0_stateless/01172_transaction_counters.sql | 1 + 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docker/test/stateless/run.sh b/docker/test/stateless/run.sh index 075f588cae3..818f95a0216 100755 --- a/docker/test/stateless/run.sh +++ b/docker/test/stateless/run.sh @@ -84,13 +84,9 @@ function run_tests() # more idiologically correct. read -ra ADDITIONAL_OPTIONS <<< "${ADDITIONAL_OPTIONS:-}" - # Skip these tests, because they fail when we rerun them multiple times + # Use random order in flaky check if [ "$NUM_TRIES" -gt "1" ]; then ADDITIONAL_OPTIONS+=('--order=random') - ADDITIONAL_OPTIONS+=('--skip') - ADDITIONAL_OPTIONS+=('00000_no_tests_to_skip') - # Note that flaky check must be ran in parallel, but for now we run - # everything in parallel except DatabaseReplicated. See below. fi if [[ -n "$USE_S3_STORAGE_FOR_MERGE_TREE" ]] && [[ "$USE_S3_STORAGE_FOR_MERGE_TREE" -eq 1 ]]; then @@ -129,6 +125,13 @@ function run_tests() export -f run_tests +if [ "$NUM_TRIES" -gt "1" ]; then + # We don't run tests with Ordinary database in PRs, only in master. + # So run new/changed tests with Ordinary at least once in flaky check. + timeout "$MAX_RUN_TIME" bash -c 'NUM_TRIES=1; USE_DATABASE_ORDINARY=1; run_tests' \ + | sed 's/All tests have finished//' | sed 's/No tests were run//' ||: +fi + timeout "$MAX_RUN_TIME" bash -c run_tests ||: echo "Files in current directory" diff --git a/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh b/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh index d5d43d3c293..55ef2edd42b 100755 --- a/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh +++ b/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh @@ -61,7 +61,6 @@ function thread6() done } - # https://stackoverflow.com/questions/9954794/execute-a-shell-function-with-timeout export -f thread1; export -f thread2; diff --git a/tests/queries/0_stateless/01161_information_schema.sql b/tests/queries/0_stateless/01161_information_schema.sql index b6b10efb001..ed77ef1c1c2 100644 --- a/tests/queries/0_stateless/01161_information_schema.sql +++ b/tests/queries/0_stateless/01161_information_schema.sql @@ -6,6 +6,7 @@ create view v (n Nullable(Int32), f Float64) as select n, f from t; create materialized view mv engine=Null as select * from system.one; create temporary table tmp (d Date, dt DateTime, dtms DateTime64(3)); + -- FIXME #28687 select * from information_schema.schemata where schema_name ilike 'information_schema'; -- SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE (TABLE_SCHEMA=currentDatabase() OR TABLE_SCHEMA='') AND TABLE_NAME NOT LIKE '%inner%'; diff --git a/tests/queries/0_stateless/01172_transaction_counters.sql b/tests/queries/0_stateless/01172_transaction_counters.sql index cc6212ae4c1..ee00029501a 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.sql +++ b/tests/queries/0_stateless/01172_transaction_counters.sql @@ -1,5 +1,6 @@ -- Tags: no-s3-storage, no-ordinary-database -- FIXME this test fails with S3 due to a bug in DiskCacheWrapper + drop table if exists txn_counters; create table txn_counters (n Int64, creation_tid DEFAULT transactionID()) engine=MergeTree order by n; From 687ea5dd762799db9c742dbf13a5333c39809f07 Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Thu, 11 Aug 2022 22:13:22 +0200 Subject: [PATCH 584/672] Update FileCache.cpp --- src/Common/FileCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 47b7d57ae66..ca826a6e359 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -1064,7 +1064,7 @@ void FileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_lock if (!parsed) { - LOG_WARNING(log, "Unexpected file: ", offset_it->path().string()); + LOG_WARNING(log, "Unexpected file: {}", offset_it->path().string()); continue; /// Or just remove? Some unexpected file. } From 004a4d4947d53ac49d221d302004e913e06af091 Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Thu, 11 Aug 2022 22:15:33 +0200 Subject: [PATCH 585/672] Update FileCache.cpp --- src/Common/FileCache.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index ca826a6e359..5a59bb6182a 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -1044,8 +1044,13 @@ void FileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_lock fs::directory_iterator key_it{key_prefix_it->path()}; for (; key_it != fs::directory_iterator(); ++key_it) { - key = Key(unhexUInt(key_it->path().filename().string().data())); + if (!key_it->is_directory()) + { + LOG_WARNING(log, "Unexpected file: {}. Expected a directory", key_it->path().string()); + continue; + } + key = Key(unhexUInt(key_it->path().filename().string().data())); fs::directory_iterator offset_it{key_it->path()}; for (; offset_it != fs::directory_iterator(); ++offset_it) { From 55ff5463227f29e669cfcbd7766d0afffdb508cf Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Thu, 11 Aug 2022 20:42:59 +0000 Subject: [PATCH 586/672] Fix typo --- docs/en/sql-reference/functions/type-conversion-functions.md | 2 +- docs/ru/sql-reference/functions/type-conversion-functions.md | 2 +- docs/zh/sql-reference/functions/type-conversion-functions.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/sql-reference/functions/type-conversion-functions.md b/docs/en/sql-reference/functions/type-conversion-functions.md index 3612de7a0d4..ecdf34bf7ee 100644 --- a/docs/en/sql-reference/functions/type-conversion-functions.md +++ b/docs/en/sql-reference/functions/type-conversion-functions.md @@ -1241,7 +1241,7 @@ Same as for [parseDateTime64BestEffort](#parsedatetime64besteffort), except that ## toLowCardinality -Converts input parameter to the [LowCardianlity](../../sql-reference/data-types/lowcardinality.md) version of same data type. +Converts input parameter to the [LowCardinality](../../sql-reference/data-types/lowcardinality.md) version of same data type. To convert data from the `LowCardinality` data type use the [CAST](#type_conversion_function-cast) function. For example, `CAST(x as String)`. diff --git a/docs/ru/sql-reference/functions/type-conversion-functions.md b/docs/ru/sql-reference/functions/type-conversion-functions.md index 679aa00073e..7635bda78e6 100644 --- a/docs/ru/sql-reference/functions/type-conversion-functions.md +++ b/docs/ru/sql-reference/functions/type-conversion-functions.md @@ -1162,7 +1162,7 @@ FORMAT PrettyCompactMonoBlock; ## toLowCardinality {#tolowcardinality} -Преобразует входные данные в версию [LowCardianlity](../data-types/lowcardinality.md) того же типа данных. +Преобразует входные данные в версию [LowCardinality](../data-types/lowcardinality.md) того же типа данных. Чтобы преобразовать данные из типа `LowCardinality`, используйте функцию [CAST](#type_conversion_function-cast). Например, `CAST(x as String)`. diff --git a/docs/zh/sql-reference/functions/type-conversion-functions.md b/docs/zh/sql-reference/functions/type-conversion-functions.md index b72dc438e0d..d2330df6cb1 100644 --- a/docs/zh/sql-reference/functions/type-conversion-functions.md +++ b/docs/zh/sql-reference/functions/type-conversion-functions.md @@ -512,7 +512,7 @@ SELECT parseDateTimeBestEffort('10 20:19') ## toLowCardinality {#tolowcardinality} -把输入值转换为[LowCardianlity](../data-types/lowcardinality.md)的相同类型的数据。 +把输入值转换为[LowCardinality](../data-types/lowcardinality.md)的相同类型的数据。 如果要把`LowCardinality`类型的数据转换为其他类型,使用[CAST](#type_conversion_function-cast)函数。比如:`CAST(x as String)`。 From 06cb8737a8d7bfb43a5637301740504121c44c52 Mon Sep 17 00:00:00 2001 From: alexX512 Date: Thu, 11 Aug 2022 22:38:13 +0000 Subject: [PATCH 587/672] Delete TODO --- src/Common/CacheBase.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Common/CacheBase.h b/src/Common/CacheBase.h index 40c41d9f087..af6cd8cef65 100644 --- a/src/Common/CacheBase.h +++ b/src/Common/CacheBase.h @@ -37,7 +37,6 @@ public: using Mapped = TMapped; using MappedPtr = std::shared_ptr; - /// TODO: Rewrite to custom struct with fields for all cache policies. CacheBase(size_t max_size, size_t max_elements_size = 0, String cache_policy_name = "", double size_ratio = 0.5) { auto on_weight_loss_function = [&](size_t weight_loss) { onRemoveOverflowWeightLoss(weight_loss); }; From b46622cf3c751a7e51c4789c72983a3e46ae61f2 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Fri, 12 Aug 2022 09:46:36 +0200 Subject: [PATCH 588/672] Don't use envs for static links --- tests/ci/functional_test_check.py | 5 +++-- tests/ci/stress_check.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/ci/functional_test_check.py b/tests/ci/functional_test_check.py index f5db7a10f14..388f93f34ec 100644 --- a/tests/ci/functional_test_check.py +++ b/tests/ci/functional_test_check.py @@ -10,7 +10,7 @@ import atexit from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_DOWNLOAD +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import FORCE_TESTS_LABEL, PRInfo @@ -88,7 +88,8 @@ def get_run_command( envs = [ f"-e MAX_RUN_TIME={int(0.9 * kill_timeout)}", - f'-e S3_URL="{S3_DOWNLOAD}/clickhouse-datasets"', + # a static link, don't use S3_URL or S3_DOWNLOAD + '-e S3_URL="https://s3.amazonaws.com/clickhouse-datasets"', ] if flaky_check: diff --git a/tests/ci/stress_check.py b/tests/ci/stress_check.py index 6a53b63c9d9..8cf7881102c 100644 --- a/tests/ci/stress_check.py +++ b/tests/ci/stress_check.py @@ -8,7 +8,7 @@ import sys from github import Github -from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH, S3_DOWNLOAD +from env_helper import TEMP_PATH, REPO_COPY, REPORTS_PATH from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import PRInfo @@ -31,7 +31,8 @@ def get_run_command( ): cmd = ( "docker run --cap-add=SYS_PTRACE " - f"-e S3_URL='{S3_DOWNLOAD}/clickhouse-datasets' " + # a static link, don't use S3_URL or S3_DOWNLOAD + "-e S3_URL='https://s3.amazonaws.com/clickhouse-datasets' " f"--volume={build_path}:/package_folder " f"--volume={result_folder}:/test_output " f"--volume={repo_tests_path}:/usr/share/clickhouse-test " From d3e8ad9e7e37df7d8ab23dbfaa6e71c8e7c5dc01 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 12 Aug 2022 10:56:11 +0200 Subject: [PATCH 589/672] Fix keeper-bench in case of error during scheduling a thread Signed-off-by: Azat Khuzhin --- utils/keeper-bench/Runner.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/keeper-bench/Runner.cpp b/utils/keeper-bench/Runner.cpp index e89670c4ec3..b43ad62bc5b 100644 --- a/utils/keeper-bench/Runner.cpp +++ b/utils/keeper-bench/Runner.cpp @@ -167,6 +167,7 @@ void Runner::runBenchmark() } catch (...) { + shutdown = true; pool.wait(); throw; } From 044aa4d9237a3ba97707e56b7b814ba3944b1414 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 12 Aug 2022 11:28:16 +0200 Subject: [PATCH 590/672] fix --- tests/clickhouse-test | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 952fc7fb0a9..23de1aba6af 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -182,6 +182,15 @@ def get_db_engine(args, database_name): return "" # Will use default engine +def get_create_database_settings(args, testcase_args): + create_database_settings = dict() + if testcase_args: + create_database_settings["log_comment"] = testcase_args.testcase_basename + if args.db_engine == "Ordinary": + create_database_settings["allow_deprecated_database_ordinary"] = 1 + return create_database_settings + + def get_zookeeper_session_uptime(args): try: if args.replicated_database: @@ -537,9 +546,7 @@ class TestCase: clickhouse_execute( args, "CREATE DATABASE " + database + get_db_engine(testcase_args, database), - settings={ - "log_comment": testcase_args.testcase_basename, - }, + settings=get_create_database_settings(args, testcase_args), ) os.environ["CLICKHOUSE_DATABASE"] = database @@ -1763,6 +1770,7 @@ def main(args): args, f"CREATE DATABASE IF NOT EXISTS {db_name} " f"{get_db_engine(args, db_name)}", + settings=get_create_database_settings(args, None), ) except HTTPError as e: total_time = (datetime.now() - start_time).total_seconds() From 40f44c21c5d2e03389ca305eb468d53dbd21dea2 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Fri, 12 Aug 2022 17:28:43 +0800 Subject: [PATCH 591/672] Fix test sorting order --- tests/queries/0_stateless/02210_processors_profile_log_2.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02210_processors_profile_log_2.sh b/tests/queries/0_stateless/02210_processors_profile_log_2.sh index e2345898a08..ab4a7309266 100755 --- a/tests/queries/0_stateless/02210_processors_profile_log_2.sh +++ b/tests/queries/0_stateless/02210_processors_profile_log_2.sh @@ -16,4 +16,4 @@ EOF ${CLICKHOUSE_CLIENT} -q "SYSTEM FLUSH LOGS" -${CLICKHOUSE_CLIENT} -q "select any(name) name, sum(input_rows), sum(input_bytes), sum(output_rows), sum(output_bytes) from system.processors_profile_log where query_id = '${QUERY_ID}' group by plan_step, plan_group order by name" +${CLICKHOUSE_CLIENT} -q "select any(name) name, sum(input_rows), sum(input_bytes), sum(output_rows), sum(output_bytes) from system.processors_profile_log where query_id = '${QUERY_ID}' group by plan_step, plan_group order by name, sum(input_rows), sum(input_bytes), sum(output_rows), sum(output_bytes)" From 0a9c3c6e8b896361d1630a2b809bbed618db9aa8 Mon Sep 17 00:00:00 2001 From: vsrsvas <48986896+vsrsvas@users.noreply.github.com> Date: Fri, 12 Aug 2022 16:18:51 +0530 Subject: [PATCH 592/672] Update replicated.md --- docs/en/engines/database-engines/replicated.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/engines/database-engines/replicated.md b/docs/en/engines/database-engines/replicated.md index 8ddff32bb2c..110b799c6be 100644 --- a/docs/en/engines/database-engines/replicated.md +++ b/docs/en/engines/database-engines/replicated.md @@ -81,7 +81,7 @@ Creating a distributed table and inserting the data: ``` sql node2 :) CREATE TABLE r.d (n UInt64) ENGINE=Distributed('r','r','rmt', n % 2); -node3 :) INSERT INTO r SELECT * FROM numbers(10); +node3 :) INSERT INTO r.d SELECT * FROM numbers(10); node1 :) SELECT materialize(hostName()) AS host, groupArray(n) FROM r.d GROUP BY host; ``` @@ -120,4 +120,4 @@ node2 :) SELECT materialize(hostName()) AS host, groupArray(n) FROM r.d GROUP BY │ node2 │ [1,3,5,7,9] │ │ node4 │ [0,2,4,6,8] │ └───────┴───────────────┘ -``` \ No newline at end of file +``` From 467ef7bbc27d2e746a6dc33a6d173b439dc2c48c Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 12 Aug 2022 14:30:18 +0300 Subject: [PATCH 593/672] Update run.sh --- docker/test/stress/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index 31dd2330c63..3f6f82741cc 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -315,7 +315,7 @@ else # Avoid "Setting allow_deprecated_database_ordinary is neither a builtin setting..." rm -f /etc/clickhouse-server/users.d/database_ordinary.xml ||: # Disable aggressive cleanup of tmp dirs (it worked incorrectly before 22.8) - rm -f /etc/clickhouse-server/users.d/merge_tree_old_dirs_cleanup.xml ||: + rm -f /etc/clickhouse-server/config.d/merge_tree_old_dirs_cleanup.xml ||: start From 17956cb668e2af2f9f19b5618820e9e5c843badd Mon Sep 17 00:00:00 2001 From: Nikita Taranov Date: Fri, 12 Aug 2022 14:28:35 +0200 Subject: [PATCH 594/672] Extend protocol with query parameters (#39906) --- programs/client/Client.cpp | 1 + src/Client/ClientBase.cpp | 9 ++- src/Client/Connection.cpp | 9 +++ src/Client/Connection.h | 1 + src/Client/HedgedConnections.cpp | 2 +- src/Client/IServerConnection.h | 1 + src/Client/LocalConnection.cpp | 4 + src/Client/LocalConnection.h | 1 + src/Client/MultiplexedConnections.cpp | 8 +- src/Client/Suggest.cpp | 3 +- src/Core/ProtocolDefines.h | 4 +- src/Interpreters/Context.cpp | 5 ++ src/Interpreters/Context.h | 5 ++ src/Interpreters/InterpreterSetQuery.cpp | 6 +- src/Parsers/ASTSetQuery.h | 5 +- src/Parsers/ParserSetQuery.cpp | 43 ++++++++-- src/Parsers/ParserSetQuery.h | 2 + src/QueryPipeline/RemoteInserter.cpp | 3 +- src/Server/HTTPHandler.cpp | 5 +- src/Server/TCPHandler.cpp | 27 +++++++ ...0955_complex_prepared_statements.reference | 1 + ...d_protocol_with_query_parameters.reference | 9 +++ ...7_extend_protocol_with_query_parameters.sh | 80 +++++++++++++++++++ 23 files changed, 213 insertions(+), 21 deletions(-) create mode 100644 tests/queries/0_stateless/02377_extend_protocol_with_query_parameters.reference create mode 100755 tests/queries/0_stateless/02377_extend_protocol_with_query_parameters.sh diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp index 584806951cf..9b1dbbe221a 100644 --- a/programs/client/Client.cpp +++ b/programs/client/Client.cpp @@ -133,6 +133,7 @@ std::vector Client::loadWarningMessages() std::vector messages; connection->sendQuery(connection_parameters.timeouts, "SELECT * FROM viewIfPermitted(SELECT message FROM system.warnings ELSE null('message String'))", + {} /* query_parameters */, "" /* query_id */, QueryProcessingStage::Complete, &global_context->getSettingsRef(), diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 977d2bca01f..468c49f22b7 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -740,8 +740,10 @@ void ClientBase::processOrdinaryQuery(const String & query_to_execute, ASTPtr pa /// Rewrite query only when we have query parameters. /// Note that if query is rewritten, comments in query are lost. /// But the user often wants to see comments in server logs, query log, processlist, etc. + /// For recent versions of the server query parameters will be transferred by network and applied on the server side. auto query = query_to_execute; - if (!query_parameters.empty()) + if (!query_parameters.empty() + && connection->getServerRevision(connection_parameters.timeouts) < DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS) { /// Replace ASTQueryParameter with ASTLiteral for prepared statements. ReplaceQueryParameterVisitor visitor(query_parameters); @@ -762,6 +764,7 @@ void ClientBase::processOrdinaryQuery(const String & query_to_execute, ASTPtr pa connection->sendQuery( connection_parameters.timeouts, query, + query_parameters, global_context->getCurrentQueryId(), query_processing_stage, &global_context->getSettingsRef(), @@ -1087,7 +1090,8 @@ bool ClientBase::receiveSampleBlock(Block & out, ColumnsDescription & columns_de void ClientBase::processInsertQuery(const String & query_to_execute, ASTPtr parsed_query) { auto query = query_to_execute; - if (!query_parameters.empty()) + if (!query_parameters.empty() + && connection->getServerRevision(connection_parameters.timeouts) < DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS) { /// Replace ASTQueryParameter with ASTLiteral for prepared statements. ReplaceQueryParameterVisitor visitor(query_parameters); @@ -1114,6 +1118,7 @@ void ClientBase::processInsertQuery(const String & query_to_execute, ASTPtr pars connection->sendQuery( connection_parameters.timeouts, query, + query_parameters, global_context->getCurrentQueryId(), query_processing_stage, &global_context->getSettingsRef(), diff --git a/src/Client/Connection.cpp b/src/Client/Connection.cpp index bbd4c380831..cd2b24a7c76 100644 --- a/src/Client/Connection.cpp +++ b/src/Client/Connection.cpp @@ -477,6 +477,7 @@ TablesStatusResponse Connection::getTablesStatus(const ConnectionTimeouts & time void Connection::sendQuery( const ConnectionTimeouts & timeouts, const String & query, + const NameToNameMap & query_parameters, const String & query_id_, UInt64 stage, const Settings * settings, @@ -569,6 +570,14 @@ void Connection::sendQuery( writeStringBinary(query, *out); + if (server_revision >= DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS) + { + Settings params; + for (const auto & [name, value] : query_parameters) + params.set(name, value); + params.write(*out, SettingsWriteFormat::STRINGS_WITH_FLAGS); + } + maybe_compressed_in.reset(); maybe_compressed_out.reset(); block_in.reset(); diff --git a/src/Client/Connection.h b/src/Client/Connection.h index c712fd730dd..8d839c62754 100644 --- a/src/Client/Connection.h +++ b/src/Client/Connection.h @@ -97,6 +97,7 @@ public: void sendQuery( const ConnectionTimeouts & timeouts, const String & query, + const NameToNameMap& query_parameters, const String & query_id_/* = "" */, UInt64 stage/* = QueryProcessingStage::Complete */, const Settings * settings/* = nullptr */, diff --git a/src/Client/HedgedConnections.cpp b/src/Client/HedgedConnections.cpp index 9f0ead79981..f1802467b57 100644 --- a/src/Client/HedgedConnections.cpp +++ b/src/Client/HedgedConnections.cpp @@ -183,7 +183,7 @@ void HedgedConnections::sendQuery( modified_settings.parallel_replica_offset = fd_to_replica_location[replica.packet_receiver->getFileDescriptor()].offset; } - replica.connection->sendQuery(timeouts, query, query_id, stage, &modified_settings, &client_info, with_pending_data, {}); + replica.connection->sendQuery(timeouts, query, /* query_parameters */ {}, query_id, stage, &modified_settings, &client_info, with_pending_data, {}); replica.change_replica_timeout.setRelative(timeouts.receive_data_timeout); replica.packet_receiver->setReceiveTimeout(hedged_connections_factory.getConnectionTimeouts().receive_timeout); }; diff --git a/src/Client/IServerConnection.h b/src/Client/IServerConnection.h index 542aecb9849..96cf1f119ba 100644 --- a/src/Client/IServerConnection.h +++ b/src/Client/IServerConnection.h @@ -86,6 +86,7 @@ public: virtual void sendQuery( const ConnectionTimeouts & timeouts, const String & query, + const NameToNameMap & query_parameters, const String & query_id_, UInt64 stage, const Settings * settings, diff --git a/src/Client/LocalConnection.cpp b/src/Client/LocalConnection.cpp index 425e54fb392..b10e24f1ae4 100644 --- a/src/Client/LocalConnection.cpp +++ b/src/Client/LocalConnection.cpp @@ -75,6 +75,7 @@ void LocalConnection::sendProfileEvents() void LocalConnection::sendQuery( const ConnectionTimeouts &, const String & query, + const NameToNameMap & query_parameters, const String & query_id, UInt64 stage, const Settings *, @@ -82,6 +83,9 @@ void LocalConnection::sendQuery( bool, std::function process_progress_callback) { + if (!query_parameters.empty()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "clickhouse local does not support query parameters"); + /// Suggestion comes without client_info. if (client_info) query_context = session.makeQueryContext(*client_info); diff --git a/src/Client/LocalConnection.h b/src/Client/LocalConnection.h index 1ebe4a1d901..dbdd3c127cb 100644 --- a/src/Client/LocalConnection.h +++ b/src/Client/LocalConnection.h @@ -94,6 +94,7 @@ public: void sendQuery( const ConnectionTimeouts & timeouts, const String & query, + const NameToNameMap & query_parameters, const String & query_id/* = "" */, UInt64 stage/* = QueryProcessingStage::Complete */, const Settings * settings/* = nullptr */, diff --git a/src/Client/MultiplexedConnections.cpp b/src/Client/MultiplexedConnections.cpp index b14ff9f2c8d..72cd4c46477 100644 --- a/src/Client/MultiplexedConnections.cpp +++ b/src/Client/MultiplexedConnections.cpp @@ -160,15 +160,15 @@ void MultiplexedConnections::sendQuery( if (enable_sample_offset_parallel_processing) modified_settings.parallel_replica_offset = i; - replica_states[i].connection->sendQuery(timeouts, query, query_id, - stage, &modified_settings, &client_info, with_pending_data, {}); + replica_states[i].connection->sendQuery( + timeouts, query, /* query_parameters */ {}, query_id, stage, &modified_settings, &client_info, with_pending_data, {}); } } else { /// Use single replica. - replica_states[0].connection->sendQuery(timeouts, query, query_id, - stage, &modified_settings, &client_info, with_pending_data, {}); + replica_states[0].connection->sendQuery( + timeouts, query, /* query_parameters */ {}, query_id, stage, &modified_settings, &client_info, with_pending_data, {}); } sent_query = true; diff --git a/src/Client/Suggest.cpp b/src/Client/Suggest.cpp index 1074adb2bd4..f8d41853566 100644 --- a/src/Client/Suggest.cpp +++ b/src/Client/Suggest.cpp @@ -138,7 +138,8 @@ void Suggest::load(ContextPtr context, const ConnectionParameters & connection_p void Suggest::fetch(IServerConnection & connection, const ConnectionTimeouts & timeouts, const std::string & query) { - connection.sendQuery(timeouts, query, "" /* query_id */, QueryProcessingStage::Complete, nullptr, nullptr, false, {}); + connection.sendQuery( + timeouts, query, {} /* query_parameters */, "" /* query_id */, QueryProcessingStage::Complete, nullptr, nullptr, false, {}); while (true) { diff --git a/src/Core/ProtocolDefines.h b/src/Core/ProtocolDefines.h index cf0a9d8b887..78585492c8e 100644 --- a/src/Core/ProtocolDefines.h +++ b/src/Core/ProtocolDefines.h @@ -52,7 +52,7 @@ /// NOTE: DBMS_TCP_PROTOCOL_VERSION has nothing common with VERSION_REVISION, /// later is just a number for server version (one number instead of commit SHA) /// for simplicity (sometimes it may be more convenient in some use cases). -#define DBMS_TCP_PROTOCOL_VERSION 54458 +#define DBMS_TCP_PROTOCOL_VERSION 54459 #define DBMS_MIN_PROTOCOL_VERSION_WITH_INITIAL_QUERY_START_TIME 54449 @@ -63,3 +63,5 @@ #define DBMS_MIN_PROTOCOL_VERSION_WITH_ADDENDUM 54458 #define DBMS_MIN_PROTOCOL_VERSION_WITH_QUOTA_KEY 54458 + +#define DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS 54459 diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index a5629b33d22..f03306b5426 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -2965,6 +2965,11 @@ void Context::setQueryParameter(const String & name, const String & value) throw Exception("Duplicate name " + backQuote(name) + " of query parameter", ErrorCodes::BAD_ARGUMENTS); } +void Context::addQueryParameters(const NameToNameMap & parameters) +{ + for (const auto & [name, value] : parameters) + query_parameters.insert_or_assign(name, value); +} void Context::addBridgeCommand(std::unique_ptr cmd) const { diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index cf508c7bfdb..9afbae46ce1 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -946,9 +946,14 @@ public: /// Query parameters for prepared statements. bool hasQueryParameters() const; const NameToNameMap & getQueryParameters() const; + + /// Throws if parameter with the given name already set. void setQueryParameter(const String & name, const String & value); void setQueryParameters(const NameToNameMap & parameters) { query_parameters = parameters; } + /// Overrides values of existing parameters. + void addQueryParameters(const NameToNameMap & parameters); + /// Add started bridge command. It will be killed after context destruction void addBridgeCommand(std::unique_ptr cmd) const; diff --git a/src/Interpreters/InterpreterSetQuery.cpp b/src/Interpreters/InterpreterSetQuery.cpp index 1c6a4236bf6..2bd8d648040 100644 --- a/src/Interpreters/InterpreterSetQuery.cpp +++ b/src/Interpreters/InterpreterSetQuery.cpp @@ -1,6 +1,6 @@ -#include #include #include +#include namespace DB { @@ -10,7 +10,9 @@ BlockIO InterpreterSetQuery::execute() { const auto & ast = query_ptr->as(); getContext()->checkSettingsConstraints(ast.changes); - getContext()->getSessionContext()->applySettingsChanges(ast.changes); + auto session_context = getContext()->getSessionContext(); + session_context->applySettingsChanges(ast.changes); + session_context->addQueryParameters(ast.query_parameters); return {}; } diff --git a/src/Parsers/ASTSetQuery.h b/src/Parsers/ASTSetQuery.h index 40a0b679650..4e3d9d227b6 100644 --- a/src/Parsers/ASTSetQuery.h +++ b/src/Parsers/ASTSetQuery.h @@ -1,8 +1,8 @@ #pragma once -#include +#include #include - +#include namespace DB { @@ -15,6 +15,7 @@ public: bool is_standalone = true; /// If false, this AST is a part of another query, such as SELECT. SettingsChanges changes; + NameToNameMap query_parameters; /** Get the text that identifies this element. */ String getID(char) const override { return "Set"; } diff --git a/src/Parsers/ParserSetQuery.cpp b/src/Parsers/ParserSetQuery.cpp index 0ff437bcfb1..20de785ac1b 100644 --- a/src/Parsers/ParserSetQuery.cpp +++ b/src/Parsers/ParserSetQuery.cpp @@ -5,13 +5,38 @@ #include #include -#include +#include +#include +#include +#include #include - +#include namespace DB { +namespace ErrorCodes +{ + extern const int BAD_ARGUMENTS; +} + +static NameToNameMap::value_type convertToQueryParameter(SettingChange change) +{ + auto name = change.name.substr(strlen(QUERY_PARAMETER_NAME_PREFIX)); + if (name.empty()) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Parameter name cannot be empty"); + + auto value = applyVisitor(FieldVisitorToString(), change.value); + /// writeQuoted is not always quoted in line with SQL standard https://github.com/ClickHouse/ClickHouse/blob/master/src/IO/WriteHelpers.h + if (value.starts_with('\'')) + { + ReadBufferFromOwnString buf(value); + readQuoted(value, buf); + } + return {name, value}; +} + + class ParserLiteralOrMap : public IParserBase { public: @@ -111,16 +136,23 @@ bool ParserSetQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) } SettingsChanges changes; + NameToNameMap query_parameters; while (true) { - if (!changes.empty() && !s_comma.ignore(pos)) + if ((!changes.empty() || !query_parameters.empty()) && !s_comma.ignore(pos)) break; - changes.push_back(SettingChange{}); + /// Either a setting or a parameter for prepared statement (if name starts with QUERY_PARAMETER_NAME_PREFIX) + SettingChange current; - if (!parseNameValuePair(changes.back(), pos, expected)) + if (!parseNameValuePair(current, pos, expected)) return false; + + if (current.name.starts_with(QUERY_PARAMETER_NAME_PREFIX)) + query_parameters.emplace(convertToQueryParameter(std::move(current))); + else + changes.push_back(std::move(current)); } auto query = std::make_shared(); @@ -128,6 +160,7 @@ bool ParserSetQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) query->is_standalone = !parse_only_internals; query->changes = std::move(changes); + query->query_parameters = std::move(query_parameters); return true; } diff --git a/src/Parsers/ParserSetQuery.h b/src/Parsers/ParserSetQuery.h index 0bc1cec3093..d9c69358ac2 100644 --- a/src/Parsers/ParserSetQuery.h +++ b/src/Parsers/ParserSetQuery.h @@ -9,6 +9,8 @@ namespace DB struct SettingChange; +constexpr char QUERY_PARAMETER_NAME_PREFIX[] = "param_"; + /** Query like this: * SET name1 = value1, name2 = value2, ... */ diff --git a/src/QueryPipeline/RemoteInserter.cpp b/src/QueryPipeline/RemoteInserter.cpp index 58fed6e5466..cd0485ec8e3 100644 --- a/src/QueryPipeline/RemoteInserter.cpp +++ b/src/QueryPipeline/RemoteInserter.cpp @@ -67,7 +67,8 @@ RemoteInserter::RemoteInserter( /** Send query and receive "header", that describes table structure. * Header is needed to know, what structure is required for blocks to be passed to 'write' method. */ - connection.sendQuery(timeouts, query, "", QueryProcessingStage::Complete, &settings, &modified_client_info, false, {}); + connection.sendQuery( + timeouts, query, /* query_parameters */ {}, "", QueryProcessingStage::Complete, &settings, &modified_client_info, false, {}); while (true) { diff --git a/src/Server/HTTPHandler.cpp b/src/Server/HTTPHandler.cpp index cdf856e87d5..5b8e17eb279 100644 --- a/src/Server/HTTPHandler.cpp +++ b/src/Server/HTTPHandler.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -1014,10 +1015,10 @@ bool DynamicQueryHandler::customizeQueryParam(ContextMutablePtr context, const s if (key == param_name) return true; /// do nothing - if (startsWith(key, "param_")) + if (startsWith(key, QUERY_PARAMETER_NAME_PREFIX)) { /// Save name and values of substitution in dictionary. - const String parameter_name = key.substr(strlen("param_")); + const String parameter_name = key.substr(strlen(QUERY_PARAMETER_NAME_PREFIX)); if (!context->getQueryParameters().contains(parameter_name)) context->setQueryParameter(parameter_name, value); diff --git a/src/Server/TCPHandler.cpp b/src/Server/TCPHandler.cpp index 2f16148e0a2..4c6eb1a253b 100644 --- a/src/Server/TCPHandler.cpp +++ b/src/Server/TCPHandler.cpp @@ -57,6 +57,7 @@ #include using namespace std::literals; +using namespace DB; namespace CurrentMetrics @@ -64,6 +65,23 @@ namespace CurrentMetrics extern const Metric QueryThread; } +namespace +{ +NameToNameMap convertToQueryParameters(const Settings & passed_params) +{ + NameToNameMap query_parameters; + for (const auto & param : passed_params) + { + std::string value; + ReadBufferFromOwnString buf(param.getValueString()); + readQuoted(value, buf); + query_parameters.emplace(param.getName(), value); + } + return query_parameters; +} + +} + namespace DB { @@ -1334,6 +1352,10 @@ void TCPHandler::receiveQuery() readStringBinary(state.query, *in); + Settings passed_params; + if (client_tcp_protocol_version >= DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS) + passed_params.read(*in, settings_format); + /// TODO Unify interserver authentication (and make sure that it's secure enough) if (is_interserver_mode) { @@ -1424,6 +1446,8 @@ void TCPHandler::receiveQuery() /// so we have to apply the changes first. query_context->setCurrentQueryId(state.query_id); + query_context->addQueryParameters(convertToQueryParameters(passed_params)); + /// For testing hedged requests if (unlikely(sleep_after_receiving_query.totalMilliseconds())) { @@ -1460,6 +1484,9 @@ void TCPHandler::receiveUnexpectedQuery() readStringBinary(skip_string, *in); + if (client_tcp_protocol_version >= DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS) + skip_settings.read(*in, settings_format); + throw NetException("Unexpected packet Query received from client", ErrorCodes::UNEXPECTED_PACKET_FROM_CLIENT); } diff --git a/tests/queries/0_stateless/00955_complex_prepared_statements.reference b/tests/queries/0_stateless/00955_complex_prepared_statements.reference index 701cc5f8781..257526768a0 100644 --- a/tests/queries/0_stateless/00955_complex_prepared_statements.reference +++ b/tests/queries/0_stateless/00955_complex_prepared_statements.reference @@ -4,3 +4,4 @@ [[10],[10],[10]] [10,10,10] [[10],[10],[10]] (10,'Test') (10,('dt',10)) 2015-02-15 Code: 457. +Code: 457. diff --git a/tests/queries/0_stateless/02377_extend_protocol_with_query_parameters.reference b/tests/queries/0_stateless/02377_extend_protocol_with_query_parameters.reference new file mode 100644 index 00000000000..f46cdb6e5e3 --- /dev/null +++ b/tests/queries/0_stateless/02377_extend_protocol_with_query_parameters.reference @@ -0,0 +1,9 @@ +42 hello 2022-08-04 18:30:53 {'2b95a497-3a5d-49af-bf85-15763318cde7':[1.2,3.4]} +UInt64 String DateTime Map(UUID, Array(Float32)) +42 [1,2,3] {'abc':22,'def':33} [[4,5,6],[7],[8,9]] {10:[11,12],13:[14,15]} {'ghj':{'klm':[16,17]},'nop':{'rst':[18]}} +5 +42 +13 +13 str 2022-08-04 18:30:53 {'10':[11,12],'13':[14,15]} +1 +1 diff --git a/tests/queries/0_stateless/02377_extend_protocol_with_query_parameters.sh b/tests/queries/0_stateless/02377_extend_protocol_with_query_parameters.sh new file mode 100755 index 00000000000..335af1bb6e6 --- /dev/null +++ b/tests/queries/0_stateless/02377_extend_protocol_with_query_parameters.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2154 + +unset CLICKHOUSE_LOG_COMMENT + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + + +$CLICKHOUSE_CLIENT \ + --param_num="42" \ + --param_str="hello" \ + --param_date="2022-08-04 18:30:53" \ + --param_map="{'2b95a497-3a5d-49af-bf85-15763318cde7': [1.2, 3.4]}" \ + -q "select {num:UInt64}, {str:String}, {date:DateTime}, {map:Map(UUID, Array(Float32))}" + + +$CLICKHOUSE_CLIENT \ + --param_num="42" \ + --param_str="hello" \ + --param_date="2022-08-04 18:30:53" \ + --param_map="{'2b95a497-3a5d-49af-bf85-15763318cde7': [1.2, 3.4]}" \ + -q "select toTypeName({num:UInt64}), toTypeName({str:String}), toTypeName({date:DateTime}), toTypeName({map:Map(UUID, Array(Float32))})" + + +table_name="t_02377_extend_protocol_with_query_parameters_$RANDOM$RANDOM" +$CLICKHOUSE_CLIENT -n -q " + create table $table_name( + id Int64, + arr Array(UInt8), + map Map(String, UInt8), + mul_arr Array(Array(UInt8)), + map_arr Map(UInt8, Array(UInt8)), + map_map_arr Map(String, Map(String, Array(UInt8)))) + engine = MergeTree + order by (id)" + + +$CLICKHOUSE_CLIENT \ + --param_id="42" \ + --param_arr="[1, 2, 3]" \ + --param_map="{'abc': 22, 'def': 33}" \ + --param_mul_arr="[[4, 5, 6], [7], [8, 9]]" \ + --param_map_arr="{10: [11, 12], 13: [14, 15]}" \ + --param_map_map_arr="{'ghj': {'klm': [16, 17]}, 'nop': {'rst': [18]}}" \ + -q "insert into $table_name values({id: Int64}, {arr: Array(UInt8)}, {map: Map(String, UInt8)}, {mul_arr: Array(Array(UInt8))}, {map_arr: Map(UInt8, Array(UInt8))}, {map_map_arr: Map(String, Map(String, Array(UInt8)))})" + + +$CLICKHOUSE_CLIENT -q "select * from $table_name" + + +$CLICKHOUSE_CLIENT \ + --param_tbl="numbers" \ + --param_db="system" \ + --param_col="number" \ + -q "select {col:Identifier} from {db:Identifier}.{tbl:Identifier} limit 1 offset 5" + + +# it is possible to set parameter for the current session +$CLICKHOUSE_CLIENT -n -q "set param_n = 42; select {n: UInt8}" +# and it will not be visible to other sessions +$CLICKHOUSE_CLIENT -n -q "select {n: UInt8} -- { serverError 456 }" + + +# the same parameter could be set multiple times within one session (new value overrides the previous one) +$CLICKHOUSE_CLIENT -n -q "set param_n = 12; set param_n = 13; select {n: UInt8}" + + +# but multiple different parameters could be defined within each session +$CLICKHOUSE_CLIENT -n -q " + set param_a = 13, param_b = 'str'; + set param_c = '2022-08-04 18:30:53'; + set param_d = '{\'10\': [11, 12], \'13\': [14, 15]}'; + select {a: UInt32}, {b: String}, {c: DateTime}, {d: Map(String, Array(UInt8))}" + +# empty parameter name is not allowed +$CLICKHOUSE_CLIENT --param_="" -q "select 1" 2>&1 | grep -c 'Code: 36' +$CLICKHOUSE_CLIENT -q "set param_ = ''" 2>&1 | grep -c 'Code: 36' From 0395dca7bed721dc02334ae4bdded80331e11ce6 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 12 Aug 2022 15:30:46 +0200 Subject: [PATCH 595/672] set sync_request_timeout to 10 to avoid reconnections in tests --- src/Client/ClientBase.cpp | 6 +++--- src/Client/Connection.cpp | 12 +++++------- src/Client/Connection.h | 9 +++------ src/Client/ConnectionParameters.cpp | 2 ++ src/Client/IServerConnection.h | 2 +- src/Client/LocalConnection.h | 2 +- src/IO/ConnectionTimeouts.h | 4 ++++ tests/config/client_config.xml | 3 +++ 8 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 977d2bca01f..580fa9ac6dd 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -1486,7 +1486,7 @@ void ClientBase::processParsedSingleQuery(const String & full_query, const Strin if (with_output && with_output->settings_ast) apply_query_settings(*with_output->settings_ast); - if (!connection->checkConnected()) + if (!connection->checkConnected(connection_parameters.timeouts)) connect(); ASTPtr input_function; @@ -1830,7 +1830,7 @@ bool ClientBase::executeMultiQuery(const String & all_queries_text) have_error = false; - if (!connection->checkConnected()) + if (!connection->checkConnected(connection_parameters.timeouts)) connect(); } @@ -2047,7 +2047,7 @@ void ClientBase::runInteractive() /// Client-side exception during query execution can result in the loss of /// sync in the connection protocol. /// So we reconnect and allow to enter the next query. - if (!connection->checkConnected()) + if (!connection->checkConnected(connection_parameters.timeouts)) connect(); } } diff --git a/src/Client/Connection.cpp b/src/Client/Connection.cpp index bbd4c380831..621f8e7d127 100644 --- a/src/Client/Connection.cpp +++ b/src/Client/Connection.cpp @@ -69,8 +69,7 @@ Connection::Connection(const String & host_, UInt16 port_, const String & cluster_secret_, const String & client_name_, Protocol::Compression compression_, - Protocol::Secure secure_, - Poco::Timespan sync_request_timeout_) + Protocol::Secure secure_) : host(host_), port(port_), default_database(default_database_) , user(user_), password(password_), quota_key(quota_key_) , cluster(cluster_) @@ -78,7 +77,6 @@ Connection::Connection(const String & host_, UInt16 port_, , client_name(client_name_) , compression(compression_) , secure(secure_) - , sync_request_timeout(sync_request_timeout_) , log_wrapper(*this) { /// Don't connect immediately, only on first need. @@ -386,7 +384,7 @@ void Connection::forceConnected(const ConnectionTimeouts & timeouts) { connect(timeouts); } - else if (!ping()) + else if (!ping(timeouts)) { LOG_TRACE(log_wrapper.get(), "Connection was closed, will reconnect."); connect(timeouts); @@ -406,11 +404,11 @@ void Connection::sendClusterNameAndSalt() } #endif -bool Connection::ping() +bool Connection::ping(const ConnectionTimeouts & timeouts) { try { - TimeoutSetter timeout_setter(*socket, sync_request_timeout, true); + TimeoutSetter timeout_setter(*socket, timeouts.sync_request_timeout, true); UInt64 pong = 0; writeVarUInt(Protocol::Client::Ping, *out); @@ -454,7 +452,7 @@ TablesStatusResponse Connection::getTablesStatus(const ConnectionTimeouts & time if (!connected) connect(timeouts); - TimeoutSetter timeout_setter(*socket, sync_request_timeout, true); + TimeoutSetter timeout_setter(*socket, timeouts.sync_request_timeout, true); writeVarUInt(Protocol::Client::TablesStatusRequest, *out); request.write(*out, server_revision); diff --git a/src/Client/Connection.h b/src/Client/Connection.h index c712fd730dd..1a3bcd45f1f 100644 --- a/src/Client/Connection.h +++ b/src/Client/Connection.h @@ -56,8 +56,7 @@ public: const String & cluster_secret_, const String & client_name_, Protocol::Compression compression_, - Protocol::Secure secure_, - Poco::Timespan sync_request_timeout_ = Poco::Timespan(DBMS_DEFAULT_SYNC_REQUEST_TIMEOUT_SEC, 0)); + Protocol::Secure secure_); ~Connection() override; @@ -124,7 +123,7 @@ public: bool isConnected() const override { return connected; } - bool checkConnected() override { return connected && ping(); } + bool checkConnected(const ConnectionTimeouts & timeouts) override { return connected && ping(timeouts); } void disconnect() override; @@ -207,8 +206,6 @@ private: */ ThrottlerPtr throttler; - Poco::Timespan sync_request_timeout; - /// From where to read query execution result. std::shared_ptr maybe_compressed_in; std::unique_ptr block_in; @@ -253,7 +250,7 @@ private: #if USE_SSL void sendClusterNameAndSalt(); #endif - bool ping(); + bool ping(const ConnectionTimeouts & timeouts); Block receiveData(); Block receiveLogData(); diff --git a/src/Client/ConnectionParameters.cpp b/src/Client/ConnectionParameters.cpp index f6720405eb0..6d538fee307 100644 --- a/src/Client/ConnectionParameters.cpp +++ b/src/Client/ConnectionParameters.cpp @@ -69,6 +69,8 @@ ConnectionParameters::ConnectionParameters(const Poco::Util::AbstractConfigurati Poco::Timespan(config.getInt("send_timeout", DBMS_DEFAULT_SEND_TIMEOUT_SEC), 0), Poco::Timespan(config.getInt("receive_timeout", DBMS_DEFAULT_RECEIVE_TIMEOUT_SEC), 0), Poco::Timespan(config.getInt("tcp_keep_alive_timeout", 0), 0)); + + timeouts.sync_request_timeout = Poco::Timespan(config.getInt("sync_request_timeout", DBMS_DEFAULT_SYNC_REQUEST_TIMEOUT_SEC), 0); } ConnectionParameters::ConnectionParameters(const Poco::Util::AbstractConfiguration & config) diff --git a/src/Client/IServerConnection.h b/src/Client/IServerConnection.h index 542aecb9849..bd864e60cc7 100644 --- a/src/Client/IServerConnection.h +++ b/src/Client/IServerConnection.h @@ -121,7 +121,7 @@ public: virtual bool isConnected() const = 0; /// Check if connection is still active with ping request. - virtual bool checkConnected() = 0; + virtual bool checkConnected(const ConnectionTimeouts & /*timeouts*/) = 0; /** Disconnect. * This may be used, if connection is left in unsynchronised state diff --git a/src/Client/LocalConnection.h b/src/Client/LocalConnection.h index 1ebe4a1d901..e9ef02dd39a 100644 --- a/src/Client/LocalConnection.h +++ b/src/Client/LocalConnection.h @@ -121,7 +121,7 @@ public: bool isConnected() const override { return true; } - bool checkConnected() override { return true; } + bool checkConnected(const ConnectionTimeouts & /*timeouts*/) override { return true; } void disconnect() override {} diff --git a/src/IO/ConnectionTimeouts.h b/src/IO/ConnectionTimeouts.h index 42b800f15d0..0f4f88ad08e 100644 --- a/src/IO/ConnectionTimeouts.h +++ b/src/IO/ConnectionTimeouts.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -22,6 +23,9 @@ struct ConnectionTimeouts Poco::Timespan hedged_connection_timeout; Poco::Timespan receive_data_timeout; + /// Timeout for synchronous request-result protocol call (like Ping or TablesStatus) + Poco::Timespan sync_request_timeout = DBMS_DEFAULT_SYNC_REQUEST_TIMEOUT_SEC; + ConnectionTimeouts() = default; ConnectionTimeouts(Poco::Timespan connection_timeout_, diff --git a/tests/config/client_config.xml b/tests/config/client_config.xml index fd5a85d8c4f..a54d5152419 100644 --- a/tests/config/client_config.xml +++ b/tests/config/client_config.xml @@ -11,4 +11,7 @@ + + + 10 From 54862cd6d573171aa76eb0c9c2fdd230b341500c Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 12 Aug 2022 15:40:35 +0200 Subject: [PATCH 596/672] fix --- tests/clickhouse-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 23de1aba6af..e40432aaca0 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1566,7 +1566,7 @@ def collect_build_flags(args): args, "SELECT value FROM system.settings WHERE name = 'allow_deprecated_database_ordinary'", ) - if value == b"1": + if value == b"1" or args.db_engine == "Ordinary": result.append(BuildFlags.ORDINARY_DATABASE) value = int( From 07163eb79a39c99e080e1d61a8560b6481b1c337 Mon Sep 17 00:00:00 2001 From: Arthur Passos Date: Fri, 12 Aug 2022 10:49:39 -0300 Subject: [PATCH 597/672] Remove c-ares lib de-initialization --- src/Common/CaresPTRResolver.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Common/CaresPTRResolver.cpp b/src/Common/CaresPTRResolver.cpp index d6474368a80..376d3665f7e 100644 --- a/src/Common/CaresPTRResolver.cpp +++ b/src/Common/CaresPTRResolver.cpp @@ -50,7 +50,12 @@ namespace DB CaresPTRResolver::~CaresPTRResolver() { ares_destroy(channel); - ares_library_cleanup(); + /* + * Library initialization is currently done only once in the constructor. Multiple instances of CaresPTRResolver + * will be used in the lifetime of ClickHouse, thus it's problematic to have de-init here. + * In a practical view, it makes little to no sense to de-init a DNS library since DNS requests will happen + * until the end of the program. Hence, ares_library_cleanup() will not be called. + * */ } std::vector CaresPTRResolver::resolve(const std::string & ip) From fe7e8dbc3c81c9f1635c80c6a0097a00276ffa56 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 12 Aug 2022 16:28:21 +0200 Subject: [PATCH 598/672] fix my favorite ctor --- src/IO/ConnectionTimeouts.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IO/ConnectionTimeouts.h b/src/IO/ConnectionTimeouts.h index 0f4f88ad08e..95a2ded6466 100644 --- a/src/IO/ConnectionTimeouts.h +++ b/src/IO/ConnectionTimeouts.h @@ -24,7 +24,7 @@ struct ConnectionTimeouts Poco::Timespan receive_data_timeout; /// Timeout for synchronous request-result protocol call (like Ping or TablesStatus) - Poco::Timespan sync_request_timeout = DBMS_DEFAULT_SYNC_REQUEST_TIMEOUT_SEC; + Poco::Timespan sync_request_timeout = Poco::Timespan(DBMS_DEFAULT_SYNC_REQUEST_TIMEOUT_SEC, 0); ConnectionTimeouts() = default; From 5768e40ef7167e7cbfe8bf44c1cc4a5b027fa3e2 Mon Sep 17 00:00:00 2001 From: Igor Nikonov <954088+devcrafter@users.noreply.github.com> Date: Fri, 12 Aug 2022 17:18:02 +0200 Subject: [PATCH 599/672] Fix incorrect queries in example Setting insert_deduplication_token: fix incorrect queries in example --- docs/en/operations/settings/settings.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/operations/settings/settings.md b/docs/en/operations/settings/settings.md index 73ad5984b2d..ae0d2eeb595 100644 --- a/docs/en/operations/settings/settings.md +++ b/docs/en/operations/settings/settings.md @@ -1289,14 +1289,14 @@ ENGINE = MergeTree ORDER BY A SETTINGS non_replicated_deduplication_window = 100; -INSERT INTO test_table Values SETTINGS insert_deduplication_token = 'test' (1); +INSERT INTO test_table SETTINGS insert_deduplication_token = 'test' VALUES (1); -- the next insert won't be deduplicated because insert_deduplication_token is different -INSERT INTO test_table Values SETTINGS insert_deduplication_token = 'test1' (1); +INSERT INTO test_table SETTINGS insert_deduplication_token = 'test1' VALUES (1); -- the next insert will be deduplicated because insert_deduplication_token -- is the same as one of the previous -INSERT INTO test_table Values SETTINGS insert_deduplication_token = 'test' (2); +INSERT INTO test_table SETTINGS insert_deduplication_token = 'test' VALUES (2); SELECT * FROM test_table From 364088d47b2d9a133ab42520b61bba904324880a Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Fri, 12 Aug 2022 14:40:09 -0400 Subject: [PATCH 600/672] fix note --- docs/en/engines/table-engines/mergetree-family/mergetree.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/engines/table-engines/mergetree-family/mergetree.md b/docs/en/engines/table-engines/mergetree-family/mergetree.md index 9339ec2454f..42378d2ad07 100644 --- a/docs/en/engines/table-engines/mergetree-family/mergetree.md +++ b/docs/en/engines/table-engines/mergetree-family/mergetree.md @@ -482,9 +482,11 @@ For example: ## Projections {#projections} Projections are like [materialized views](../../../sql-reference/statements/create/view.md#materialized) but defined in part-level. It provides consistency guarantees along with automatic usage in queries. -::: note + +:::note When you are implementing projections you should also consider the [force_optimize_projection](../../../operations/settings/settings.md#force-optimize-projection) setting. ::: + Projections are not supported in the `SELECT` statements with the [FINAL](../../../sql-reference/statements/select/from.md#select-from-final) modifier. ### Projection Query {#projection-query} From 586682e1166606464dfb1e3c2eb091292dbeab48 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 12 Aug 2022 22:01:45 +0300 Subject: [PATCH 601/672] Update tests/config/client_config.xml Co-authored-by: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> --- tests/config/client_config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/config/client_config.xml b/tests/config/client_config.xml index a54d5152419..a93935ee111 100644 --- a/tests/config/client_config.xml +++ b/tests/config/client_config.xml @@ -12,6 +12,6 @@ - + 10 From a88e7131027eb75643cfd07003b2739b08214723 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 12 Aug 2022 21:22:11 +0200 Subject: [PATCH 602/672] Disable zero-copy replication by default --- src/Storages/MergeTree/MergeTreeSettings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/MergeTreeSettings.h b/src/Storages/MergeTree/MergeTreeSettings.h index d8dfa017b7f..5acdfedb26d 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -128,7 +128,7 @@ struct Settings; M(UInt64, concurrent_part_removal_threshold, 100, "Activate concurrent part removal (see 'max_part_removal_threads') only if the number of inactive data parts is at least this.", 0) \ M(String, storage_policy, "default", "Name of storage disk policy", 0) \ M(Bool, allow_nullable_key, false, "Allow Nullable types as primary keys.", 0) \ - M(Bool, allow_remote_fs_zero_copy_replication, true, "Allow Zero-copy replication over remote fs.", 0) \ + M(Bool, allow_remote_fs_zero_copy_replication, false, "Allow Zero-copy replication over remote fs.", 0) \ M(String, remote_fs_zero_copy_zookeeper_path, "/clickhouse/zero_copy", "ZooKeeper path for Zero-copy table-independet info.", 0) \ M(Bool, remote_fs_zero_copy_path_compatible_mode, false, "Run zero-copy in compatible mode during conversion process.", 0) \ M(Bool, remove_empty_parts, true, "Remove empty parts after they were pruned by TTL, mutation, or collapsing merge algorithm.", 0) \ From 0e450787f34fdd1877989bf6ed17ce48130e6cd0 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 12 Aug 2022 21:24:27 +0200 Subject: [PATCH 603/672] Edit the documentation --- src/Storages/MergeTree/MergeTreeSettings.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeSettings.h b/src/Storages/MergeTree/MergeTreeSettings.h index 5acdfedb26d..89081fe924f 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -128,9 +128,6 @@ struct Settings; M(UInt64, concurrent_part_removal_threshold, 100, "Activate concurrent part removal (see 'max_part_removal_threads') only if the number of inactive data parts is at least this.", 0) \ M(String, storage_policy, "default", "Name of storage disk policy", 0) \ M(Bool, allow_nullable_key, false, "Allow Nullable types as primary keys.", 0) \ - M(Bool, allow_remote_fs_zero_copy_replication, false, "Allow Zero-copy replication over remote fs.", 0) \ - M(String, remote_fs_zero_copy_zookeeper_path, "/clickhouse/zero_copy", "ZooKeeper path for Zero-copy table-independet info.", 0) \ - M(Bool, remote_fs_zero_copy_path_compatible_mode, false, "Run zero-copy in compatible mode during conversion process.", 0) \ M(Bool, remove_empty_parts, true, "Remove empty parts after they were pruned by TTL, mutation, or collapsing merge algorithm.", 0) \ M(Bool, assign_part_uuids, false, "Generate UUIDs for parts. Before enabling check that all replicas support new format.", 0) \ M(Int64, max_partitions_to_read, -1, "Limit the max number of partitions that can be accessed in one query. <= 0 means unlimited. This setting is the default that can be overridden by the query-level setting with the same name.", 0) \ @@ -143,6 +140,9 @@ struct Settings; M(UInt64, part_moves_between_shards_enable, 0, "Experimental/Incomplete feature to move parts between shards. Does not take into account sharding expressions.", 0) \ M(UInt64, part_moves_between_shards_delay_seconds, 30, "Time to wait before/after moving parts between shards.", 0) \ M(Bool, use_metadata_cache, false, "Experimental feature to speed up parts loading process by using MergeTree metadata cache", 0) \ + M(Bool, allow_remote_fs_zero_copy_replication, false, "Don't use this setting in production, because it is not ready.", 0) \ + M(String, remote_fs_zero_copy_zookeeper_path, "/clickhouse/zero_copy", "ZooKeeper path for Zero-copy table-independet info.", 0) \ + M(Bool, remote_fs_zero_copy_path_compatible_mode, false, "Run zero-copy in compatible mode during conversion process.", 0) \ \ /** Obsolete settings. Kept for backward compatibility only. */ \ M(UInt64, min_relative_delay_to_yield_leadership, 120, "Obsolete setting, does nothing.", 0) \ From 6b54cdffafa091254b8cd0cc25284b4950ebb6f3 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 12 Aug 2022 21:39:14 +0200 Subject: [PATCH 604/672] Update test --- .../queries/0_stateless/02381_client_prints_server_side_time.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/0_stateless/02381_client_prints_server_side_time.sh b/tests/queries/0_stateless/02381_client_prints_server_side_time.sh index 2b296828e37..e6cd63da95d 100755 --- a/tests/queries/0_stateless/02381_client_prints_server_side_time.sh +++ b/tests/queries/0_stateless/02381_client_prints_server_side_time.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Tags: no-tsan, no-asan, no-ubsan, no-msan, no-debug, no-s3-storage CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From 7f61f30f15d6fadb1364066e595b85f0c2695887 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Fri, 12 Aug 2022 23:30:32 +0200 Subject: [PATCH 605/672] Improve assignment and logging for cherry-pick and backport steps --- tests/ci/cherry_pick.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/ci/cherry_pick.py b/tests/ci/cherry_pick.py index 5382106d26b..d8e4b8fe3a5 100644 --- a/tests/ci/cherry_pick.py +++ b/tests/ci/cherry_pick.py @@ -206,8 +206,13 @@ Merge it only if you intend to backport changes to the target branch, otherwise ) self.cherrypick_pr.add_to_labels(Labels.LABEL_CHERRYPICK) self.cherrypick_pr.add_to_labels(Labels.LABEL_DO_NOT_TEST) - if self.pr.assignee is not None: - self.cherrypick_pr.add_to_assignees(self.pr.assignee) + if self.pr.assignees: + logging.info( + "Assing to assignees of the original PR: %s", + ", ".join(user.login for user in self.pr.assignees), + ) + self.cherrypick_pr.add_to_assignees(self.pr.assignees) + logging.info("Assign to the author of the original PR: %s", self.pr.user.login) self.cherrypick_pr.add_to_assignees(self.pr.user) def create_backport(self): @@ -239,8 +244,13 @@ Merge it only if you intend to backport changes to the target branch, otherwise head=self.backport_branch, ) self.backport_pr.add_to_labels(Labels.LABEL_BACKPORT) - if self.pr.assignee is not None: - self.cherrypick_pr.add_to_assignees(self.pr.assignee) + if self.pr.assignees: + logging.info( + "Assing to assignees of the original PR: %s", + ", ".join(user.login for user in self.pr.assignees), + ) + self.cherrypick_pr.add_to_assignees(self.pr.assignees) + logging.info("Assign to the author of the original PR: %s", self.pr.user.login) self.backport_pr.add_to_assignees(self.pr.user) @property From 1bf98f41716b664b669b4e8cd9a2a2642d9bb15d Mon Sep 17 00:00:00 2001 From: Denny Crane Date: Fri, 12 Aug 2022 18:50:49 -0300 Subject: [PATCH 606/672] test for Decimal aggregateFunction normalization #39420 --- ...366_decimal_agg_state_conversion.reference | 3 + .../02366_decimal_agg_state_conversion.sql | 62 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 tests/queries/0_stateless/02366_decimal_agg_state_conversion.reference create mode 100644 tests/queries/0_stateless/02366_decimal_agg_state_conversion.sql diff --git a/tests/queries/0_stateless/02366_decimal_agg_state_conversion.reference b/tests/queries/0_stateless/02366_decimal_agg_state_conversion.reference new file mode 100644 index 00000000000..88822fec8d0 --- /dev/null +++ b/tests/queries/0_stateless/02366_decimal_agg_state_conversion.reference @@ -0,0 +1,3 @@ +1.100001 +0.000001 +19 0.1 diff --git a/tests/queries/0_stateless/02366_decimal_agg_state_conversion.sql b/tests/queries/0_stateless/02366_decimal_agg_state_conversion.sql new file mode 100644 index 00000000000..edcab325d91 --- /dev/null +++ b/tests/queries/0_stateless/02366_decimal_agg_state_conversion.sql @@ -0,0 +1,62 @@ +select sumMerge(y) from +( + select cast(x, 'AggregateFunction(sum, Decimal(50, 10))') y from + ( + select arrayReduce('sumState', [toDecimal256('0.000001', 10), toDecimal256('1.1', 10)]) x + ) +); + +select minMerge(y) from +( + select cast(x, 'AggregateFunction(min, Decimal(18, 10))') y from + ( + select arrayReduce('minState', [toDecimal64('0.000001', 10), toDecimal64('1.1', 10)]) x + ) +); + + +drop table if exists consumer_02366; +drop table if exists producer_02366; +drop table if exists mv_02366; + +CREATE TABLE consumer_02366 +( + `id` UInt16, + `dec` AggregateFunction(argMin, Decimal(24, 10), UInt16) +) +ENGINE = AggregatingMergeTree +PRIMARY KEY id +ORDER BY id; + +CREATE TABLE producer_02366 +( + `id` UInt16, + `dec` String +) +ENGINE = MergeTree +PRIMARY KEY id +ORDER BY id; + +CREATE MATERIALIZED VIEW mv_02366 TO consumer_02366 AS +SELECT + id, + argMinState(dec, id) AS dec +FROM +( + SELECT + id, + toDecimal128(dec, 10) AS dec + FROM producer_02366 +) +GROUP BY id; + +INSERT INTO producer_02366 (*) VALUES (19, '.1'); + +SELECT + id, + finalizeAggregation(dec) +FROM consumer_02366; + +drop table consumer_02366; +drop table producer_02366; +drop table mv_02366; From 197a9d3c982aa1ace5da60b49ec59571a09c73a1 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 12 Aug 2022 23:51:44 +0200 Subject: [PATCH 607/672] Update test references --- .../01700_system_zookeeper_path_in.reference | 2 - ...21_system_zookeeper_unrestricted.reference | 8 --- ...stem_zookeeper_unrestricted_like.reference | 72 ++++++++++++++++--- 3 files changed, 64 insertions(+), 18 deletions(-) diff --git a/tests/queries/0_stateless/01700_system_zookeeper_path_in.reference b/tests/queries/0_stateless/01700_system_zookeeper_path_in.reference index dcee18b33e0..e491dd9e091 100644 --- a/tests/queries/0_stateless/01700_system_zookeeper_path_in.reference +++ b/tests/queries/0_stateless/01700_system_zookeeper_path_in.reference @@ -12,5 +12,3 @@ blocks failed_parts last_part parallel -shared -shared diff --git a/tests/queries/0_stateless/02221_system_zookeeper_unrestricted.reference b/tests/queries/0_stateless/02221_system_zookeeper_unrestricted.reference index bd0c9cee464..c53187a963c 100644 --- a/tests/queries/0_stateless/02221_system_zookeeper_unrestricted.reference +++ b/tests/queries/0_stateless/02221_system_zookeeper_unrestricted.reference @@ -60,15 +60,7 @@ quorum quorum replicas replicas -shared -shared -shared -shared table_shared_id table_shared_id temp temp -zero_copy_hdfs -zero_copy_hdfs -zero_copy_s3 -zero_copy_s3 diff --git a/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference b/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference index f95d60dc07b..b613caa8ac0 100644 --- a/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference +++ b/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference @@ -1,75 +1,131 @@ 1 +1 +alter_partition_version alter_partition_version block_numbers +block_numbers +blocks blocks columns columns +columns +columns +failed_parts failed_parts flags +flags +host host is_active is_lost +is_lost +last_part last_part leader_election +leader_election +leader_election-0 leader_election-0 log +log log_pointer +log_pointer +max_processed_insert_time max_processed_insert_time metadata metadata +metadata +metadata +metadata_version metadata_version min_unprocessed_insert_time +min_unprocessed_insert_time +mutation_pointer mutation_pointer mutations +mutations +nonincrement_block_numbers nonincrement_block_numbers parallel +parallel +part_moves_shard part_moves_shard parts +parts +pinned_part_uuids pinned_part_uuids queue +queue +quorum quorum replicas -shared -shared +replicas +table_shared_id table_shared_id temp -zero_copy_hdfs -zero_copy_s3 +temp ------------------------- 1 +1 +alter_partition_version alter_partition_version block_numbers +block_numbers +blocks blocks columns columns +columns +columns +failed_parts failed_parts flags +flags +host host is_active is_lost +is_lost +last_part last_part leader_election +leader_election +leader_election-0 leader_election-0 log +log log_pointer +log_pointer +max_processed_insert_time max_processed_insert_time metadata metadata +metadata +metadata +metadata_version metadata_version min_unprocessed_insert_time +min_unprocessed_insert_time +mutation_pointer mutation_pointer mutations +mutations +nonincrement_block_numbers nonincrement_block_numbers parallel +parallel +part_moves_shard part_moves_shard parts +parts +pinned_part_uuids pinned_part_uuids queue +queue +quorum quorum replicas -shared -shared +replicas +table_shared_id table_shared_id temp -zero_copy_hdfs -zero_copy_s3 +temp From 79442c84b5e15fbd10a1343607e74949f04a5e10 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 13 Aug 2022 02:04:34 +0200 Subject: [PATCH 608/672] Fix test reference --- ...stem_zookeeper_unrestricted_like.reference | 72 +++---------------- 1 file changed, 8 insertions(+), 64 deletions(-) diff --git a/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference b/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference index b613caa8ac0..f95d60dc07b 100644 --- a/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference +++ b/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference @@ -1,131 +1,75 @@ 1 -1 alter_partition_version -alter_partition_version -block_numbers block_numbers blocks -blocks -columns -columns columns columns failed_parts -failed_parts flags -flags -host host is_active is_lost -is_lost -last_part last_part leader_election -leader_election -leader_election-0 leader_election-0 log -log -log_pointer log_pointer max_processed_insert_time -max_processed_insert_time -metadata -metadata metadata metadata metadata_version -metadata_version -min_unprocessed_insert_time min_unprocessed_insert_time mutation_pointer -mutation_pointer -mutations mutations nonincrement_block_numbers -nonincrement_block_numbers -parallel parallel part_moves_shard -part_moves_shard -parts parts pinned_part_uuids -pinned_part_uuids -queue queue quorum -quorum replicas -replicas -table_shared_id +shared +shared table_shared_id temp -temp +zero_copy_hdfs +zero_copy_s3 ------------------------- 1 -1 -alter_partition_version alter_partition_version block_numbers -block_numbers -blocks blocks columns columns -columns -columns -failed_parts failed_parts flags -flags -host host is_active is_lost -is_lost -last_part last_part leader_election -leader_election -leader_election-0 leader_election-0 log -log -log_pointer log_pointer max_processed_insert_time -max_processed_insert_time -metadata -metadata metadata metadata metadata_version -metadata_version -min_unprocessed_insert_time min_unprocessed_insert_time mutation_pointer -mutation_pointer -mutations mutations nonincrement_block_numbers -nonincrement_block_numbers -parallel parallel part_moves_shard -part_moves_shard -parts parts pinned_part_uuids -pinned_part_uuids -queue queue quorum -quorum replicas -replicas -table_shared_id +shared +shared table_shared_id temp -temp +zero_copy_hdfs +zero_copy_s3 From 76c2bcd2582513e33d31ce7fee82012fb2238f3f Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 13 Aug 2022 02:20:31 +0200 Subject: [PATCH 609/672] Fix test --- .../1_stateful/00175_counting_resources_in_subqueries.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql b/tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql index d63429522e0..fe7837d7ff1 100644 --- a/tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql +++ b/tests/queries/1_stateful/00175_counting_resources_in_subqueries.sql @@ -16,5 +16,5 @@ SET max_rows_to_read = 1000000; SELECT count() FROM test.hits WHERE CounterID > (SELECT count() FROM test.hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } -- this query is using index but have to read all the data twice. -SET max_rows_to_read = 15000000; +SET max_rows_to_read = 10000000; SELECT count() FROM test.hits WHERE CounterID < (SELECT count() FROM test.hits WHERE NOT ignore(AdvEngineID)); -- { serverError 158 } From 2f40db6cdee7ba92b38677866fd865de5b78fa82 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 13 Aug 2022 03:38:26 +0200 Subject: [PATCH 610/672] Update test reference --- .../02221_system_zookeeper_unrestricted_like.reference | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference b/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference index f95d60dc07b..0ac6e838903 100644 --- a/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference +++ b/tests/queries/0_stateless/02221_system_zookeeper_unrestricted_like.reference @@ -29,12 +29,8 @@ pinned_part_uuids queue quorum replicas -shared -shared table_shared_id temp -zero_copy_hdfs -zero_copy_s3 ------------------------- 1 alter_partition_version @@ -67,9 +63,5 @@ pinned_part_uuids queue quorum replicas -shared -shared table_shared_id temp -zero_copy_hdfs -zero_copy_s3 From 296835e2d38f7eb750573a0e82cca1bed85f29fa Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Fri, 12 Aug 2022 18:55:40 -0700 Subject: [PATCH 611/672] Fix Endian issue in FileEncrption for s390x --- src/IO/ReadHelpers.h | 21 ++++++++++++--------- src/IO/WriteHelpers.h | 17 ++++++++++------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/IO/ReadHelpers.h b/src/IO/ReadHelpers.h index 7a5df1ed5ac..502e76a6c5e 100644 --- a/src/IO/ReadHelpers.h +++ b/src/IO/ReadHelpers.h @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -1027,15 +1028,17 @@ requires is_arithmetic_v && (sizeof(T) <= 8) inline void readBinaryBigEndian(T & x, ReadBuffer & buf) /// Assuming little endian architecture. { readPODBinary(x, buf); - - if constexpr (sizeof(x) == 1) - return; - else if constexpr (sizeof(x) == 2) - x = __builtin_bswap16(x); - else if constexpr (sizeof(x) == 4) - x = __builtin_bswap32(x); - else if constexpr (sizeof(x) == 8) - x = __builtin_bswap64(x); + if constexpr (std::endian::native == std::endian::little) + { + if constexpr (sizeof(x) == 1) + return; + else if constexpr (sizeof(x) == 2) + x = __builtin_bswap16(x); + else if constexpr (sizeof(x) == 4) + x = __builtin_bswap32(x); + else if constexpr (sizeof(x) == 8) + x = __builtin_bswap64(x); + } } template diff --git a/src/IO/WriteHelpers.h b/src/IO/WriteHelpers.h index 2903a70b61a..f9892ac6194 100644 --- a/src/IO/WriteHelpers.h +++ b/src/IO/WriteHelpers.h @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -1110,13 +1111,15 @@ template requires is_arithmetic_v && (sizeof(T) <= 8) inline void writeBinaryBigEndian(T x, WriteBuffer & buf) /// Assuming little endian architecture. { - if constexpr (sizeof(x) == 2) - x = __builtin_bswap16(x); - else if constexpr (sizeof(x) == 4) - x = __builtin_bswap32(x); - else if constexpr (sizeof(x) == 8) - x = __builtin_bswap64(x); - + if constexpr (std::endian::native == std::endian::little) + { + if constexpr (sizeof(x) == 2) + x = __builtin_bswap16(x); + else if constexpr (sizeof(x) == 4) + x = __builtin_bswap32(x); + else if constexpr (sizeof(x) == 8) + x = __builtin_bswap64(x); + } writePODBinary(x, buf); } From 2b7fe3dea3ed77cd86f9abba50c895056095184d Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 13 Aug 2022 05:54:13 +0300 Subject: [PATCH 612/672] Remove obsolete "build Debian package" instruction --- docs/en/development/build.md | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/docs/en/development/build.md b/docs/en/development/build.md index e12884b61c4..cea6354094b 100644 --- a/docs/en/development/build.md +++ b/docs/en/development/build.md @@ -135,28 +135,6 @@ export PATH=/home/milovidov/work/cmake-3.22.2-linux-x86_64/bin/:${PATH} hash cmake ``` -## How to Build ClickHouse Debian Package {#how-to-build-clickhouse-debian-package} - -### Install Git {#install-git} - -``` bash -sudo apt-get update -sudo apt-get install git python debhelper lsb-release fakeroot sudo debian-archive-keyring debian-keyring -``` - -### Checkout ClickHouse Sources {#checkout-clickhouse-sources-1} - -``` bash -git clone --recursive --branch master https://github.com/ClickHouse/ClickHouse.git -cd ClickHouse -``` - -### Run Release Script {#run-release-script} - -``` bash -./release -``` - ## You Don’t Have to Build ClickHouse {#you-dont-have-to-build-clickhouse} ClickHouse is available in pre-built binaries and packages. Binaries are portable and can be run on any Linux flavour. From a716d78264a136211311c92a2f573b4ca23af742 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 13 Aug 2022 06:06:01 +0200 Subject: [PATCH 613/672] Support build with clang-16 --- CMakeLists.txt | 3 ++- cmake/warnings.cmake | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d5ce1996f3..e3eff050015 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,8 @@ if (COMPILER_CLANG) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Xclang -fuse-ctor-homing") endif() endif() + + no_warning(enum-constexpr-conversion) # breaks Protobuf in clang-16 endif () # If compiler has support for -Wreserved-identifier. It is difficult to detect by clang version, @@ -606,4 +608,3 @@ if (NATIVE_BUILD_TARGETS COMMAND ${CMAKE_COMMAND} --build "${NATIVE_BUILD_DIR}" --target ${NATIVE_BUILD_TARGETS} COMMAND_ECHO STDOUT) endif () - diff --git a/cmake/warnings.cmake b/cmake/warnings.cmake index a8f12fe26dd..042935a835f 100644 --- a/cmake/warnings.cmake +++ b/cmake/warnings.cmake @@ -42,6 +42,7 @@ if (COMPILER_CLANG) no_warning(weak-template-vtables) no_warning(weak-vtables) no_warning(thread-safety-negative) # experimental flag, too many false positives + no_warning(enum-constexpr-conversion) # breaks magic-enum library in clang-16 # TODO Enable conversion, sign-conversion, double-promotion warnings. elseif (COMPILER_GCC) # Add compiler options only to c++ compiler From a6a51f4fb8d90dbffef4da29f406e84c1ed378fd Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 13 Aug 2022 06:39:20 +0200 Subject: [PATCH 614/672] Minor build changes --- cmake/limit_jobs.cmake | 12 ++++++------ contrib/boost | 2 +- docs/en/development/build-cross-riscv.md | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmake/limit_jobs.cmake b/cmake/limit_jobs.cmake index 96c6b75bc43..a8f105b8987 100644 --- a/cmake/limit_jobs.cmake +++ b/cmake/limit_jobs.cmake @@ -3,7 +3,7 @@ # set (MAX_LINKER_MEMORY 3500 CACHE INTERNAL "") # include (cmake/limit_jobs.cmake) -cmake_host_system_information(RESULT AVAILABLE_PHYSICAL_MEMORY QUERY AVAILABLE_PHYSICAL_MEMORY) # Not available under freebsd +cmake_host_system_information(RESULT TOTAL_PHYSICAL_MEMORY QUERY TOTAL_PHYSICAL_MEMORY) # Not available under freebsd cmake_host_system_information(RESULT NUMBER_OF_LOGICAL_CORES QUERY NUMBER_OF_LOGICAL_CORES) # 1 if not set @@ -12,8 +12,8 @@ option(PARALLEL_COMPILE_JOBS "Maximum number of concurrent compilation jobs" "") # 1 if not set option(PARALLEL_LINK_JOBS "Maximum number of concurrent link jobs" "") -if (NOT PARALLEL_COMPILE_JOBS AND AVAILABLE_PHYSICAL_MEMORY AND MAX_COMPILER_MEMORY) - math(EXPR PARALLEL_COMPILE_JOBS ${AVAILABLE_PHYSICAL_MEMORY}/${MAX_COMPILER_MEMORY}) +if (NOT PARALLEL_COMPILE_JOBS AND TOTAL_PHYSICAL_MEMORY AND MAX_COMPILER_MEMORY) + math(EXPR PARALLEL_COMPILE_JOBS ${TOTAL_PHYSICAL_MEMORY}/${MAX_COMPILER_MEMORY}) if (NOT PARALLEL_COMPILE_JOBS) set (PARALLEL_COMPILE_JOBS 1) @@ -27,8 +27,8 @@ if (PARALLEL_COMPILE_JOBS AND (NOT NUMBER_OF_LOGICAL_CORES OR PARALLEL_COMPILE_J endif () -if (NOT PARALLEL_LINK_JOBS AND AVAILABLE_PHYSICAL_MEMORY AND MAX_LINKER_MEMORY) - math(EXPR PARALLEL_LINK_JOBS ${AVAILABLE_PHYSICAL_MEMORY}/${MAX_LINKER_MEMORY}) +if (NOT PARALLEL_LINK_JOBS AND TOTAL_PHYSICAL_MEMORY AND MAX_LINKER_MEMORY) + math(EXPR PARALLEL_LINK_JOBS ${TOTAL_PHYSICAL_MEMORY}/${MAX_LINKER_MEMORY}) if (NOT PARALLEL_LINK_JOBS) set (PARALLEL_LINK_JOBS 1) @@ -54,6 +54,6 @@ endif () if (PARALLEL_COMPILE_JOBS OR PARALLEL_LINK_JOBS) message(STATUS - "${CMAKE_CURRENT_SOURCE_DIR}: Have ${AVAILABLE_PHYSICAL_MEMORY} megabytes of memory. + "${CMAKE_CURRENT_SOURCE_DIR}: Have ${TOTAL_PHYSICAL_MEMORY} megabytes of memory. Limiting concurrent linkers jobs to ${PARALLEL_LINK_JOBS} and compiler jobs to ${PARALLEL_COMPILE_JOBS} (system has ${NUMBER_OF_LOGICAL_CORES} logical cores)") endif () diff --git a/contrib/boost b/contrib/boost index c0807e83f28..03d9ec9cd15 160000 --- a/contrib/boost +++ b/contrib/boost @@ -1 +1 @@ -Subproject commit c0807e83f2824e8dd67a15b355496a9b784cdcd5 +Subproject commit 03d9ec9cd159d14bd0b17c05138098451a1ea606 diff --git a/docs/en/development/build-cross-riscv.md b/docs/en/development/build-cross-riscv.md index a17063e7d8d..a0b31ff131a 100644 --- a/docs/en/development/build-cross-riscv.md +++ b/docs/en/development/build-cross-riscv.md @@ -23,7 +23,7 @@ sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ``` bash cd ClickHouse mkdir build-riscv64 -CC=clang-14 CXX=clang++-14 cmake . -Bbuild-riscv64 -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/linux/toolchain-riscv64.cmake -DGLIBC_COMPATIBILITY=OFF -DENABLE_LDAP=OFF -DOPENSSL_NO_ASM=ON -DENABLE_JEMALLOC=ON -DENABLE_PARQUET=OFF -DENABLE_ORC=OFF -DUSE_UNWIND=OFF -DENABLE_GRPC=OFF -DENABLE_HDFS=OFF -DENABLE_MYSQL=OFF +CC=clang-14 CXX=clang++-14 cmake . -Bbuild-riscv64 -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/linux/toolchain-riscv64.cmake -DGLIBC_COMPATIBILITY=OFF -DENABLE_LDAP=OFF -DOPENSSL_NO_ASM=ON -DENABLE_JEMALLOC=ON -DENABLE_PARQUET=OFF -DUSE_UNWIND=OFF -DENABLE_GRPC=OFF -DENABLE_HDFS=OFF -DENABLE_MYSQL=OFF ninja -C build-riscv64 ``` From 31dbb681854066d8734271b17d7cb9a2314417c2 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 13 Aug 2022 08:04:24 +0200 Subject: [PATCH 615/672] Fix array signed const positive subscript operator --- src/Functions/array/arrayElement.cpp | 19 +++--- ...rray_signed_const_positive_index.reference | 58 +++++++++++++++++++ ...2383_array_signed_const_positive_index.sql | 36 ++++++++++++ 3 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 tests/queries/0_stateless/02383_array_signed_const_positive_index.reference create mode 100644 tests/queries/0_stateless/02383_array_signed_const_positive_index.sql diff --git a/src/Functions/array/arrayElement.cpp b/src/Functions/array/arrayElement.cpp index d3255d6412e..a7b27bae268 100644 --- a/src/Functions/array/arrayElement.cpp +++ b/src/Functions/array/arrayElement.cpp @@ -475,10 +475,11 @@ ColumnPtr FunctionArrayElement::executeNumberConst( auto col_res = ColumnVector::create(); - if (index.getType() == Field::Types::UInt64) + if (index.getType() == Field::Types::UInt64 + || (index.getType() == Field::Types::Int64 && get(index) >= 0)) { ArrayElementNumImpl::template vectorConst( - col_nested->getData(), col_array->getOffsets(), safeGet(index) - 1, col_res->getData(), builder); + col_nested->getData(), col_array->getOffsets(), get(index) - 1, col_res->getData(), builder); } else if (index.getType() == Field::Types::Int64) { @@ -537,12 +538,13 @@ FunctionArrayElement::executeStringConst(const ColumnsWithTypeAndName & argument auto col_res = ColumnString::create(); - if (index.getType() == Field::Types::UInt64) + if (index.getType() == Field::Types::UInt64 + || (index.getType() == Field::Types::Int64 && get(index) >= 0)) ArrayElementStringImpl::vectorConst( col_nested->getChars(), col_array->getOffsets(), col_nested->getOffsets(), - safeGet(index) - 1, + get(index) - 1, col_res->getChars(), col_res->getOffsets(), builder); @@ -551,7 +553,7 @@ FunctionArrayElement::executeStringConst(const ColumnsWithTypeAndName & argument col_nested->getChars(), col_array->getOffsets(), col_nested->getOffsets(), - -(UInt64(safeGet(index)) + 1), + -(UInt64(get(index)) + 1), col_res->getChars(), col_res->getOffsets(), builder); @@ -600,12 +602,13 @@ ColumnPtr FunctionArrayElement::executeGenericConst( const auto & col_nested = col_array->getData(); auto col_res = col_nested.cloneEmpty(); - if (index.getType() == Field::Types::UInt64) + if (index.getType() == Field::Types::UInt64 + || (index.getType() == Field::Types::Int64 && get(index) >= 0)) ArrayElementGenericImpl::vectorConst( - col_nested, col_array->getOffsets(), safeGet(index) - 1, *col_res, builder); + col_nested, col_array->getOffsets(), get(index) - 1, *col_res, builder); else if (index.getType() == Field::Types::Int64) ArrayElementGenericImpl::vectorConst( - col_nested, col_array->getOffsets(), -(static_cast(safeGet(index) + 1)), *col_res, builder); + col_nested, col_array->getOffsets(), -(static_cast(get(index) + 1)), *col_res, builder); else throw Exception("Illegal type of array index", ErrorCodes::LOGICAL_ERROR); diff --git a/tests/queries/0_stateless/02383_array_signed_const_positive_index.reference b/tests/queries/0_stateless/02383_array_signed_const_positive_index.reference new file mode 100644 index 00000000000..0d236990b8b --- /dev/null +++ b/tests/queries/0_stateless/02383_array_signed_const_positive_index.reference @@ -0,0 +1,58 @@ +-- { echo } + +SELECT materialize([[13]])[1::Int8]; +[13] +SELECT materialize([['Hello']])[1::Int8]; +['Hello'] +SELECT materialize([13])[1::Int8]; +13 +SELECT materialize(['Hello'])[1::Int8]; +Hello +SELECT materialize([[13], [14]])[2::Int8]; +[14] +SELECT materialize([['Hello'], ['world']])[2::Int8]; +['world'] +SELECT materialize([13, 14])[2::Int8]; +14 +SELECT materialize(['Hello', 'world'])[2::Int8]; +world +SELECT materialize([[13], [14]])[3::Int8]; +[] +SELECT materialize([['Hello'], ['world']])[3::Int8]; +[] +SELECT materialize([13, 14])[3::Int8]; +0 +SELECT materialize(['Hello', 'world'])[3::Int8]; + +SELECT materialize([[13], [14]])[0::Int8]; +[] +SELECT materialize([['Hello'], ['world']])[0::Int8]; +[] +SELECT materialize([13, 14])[0::Int8]; +0 +SELECT materialize(['Hello', 'world'])[0::Int8]; + +SELECT materialize([[13], [14]])[-1]; +[14] +SELECT materialize([['Hello'], ['world']])[-1]; +['world'] +SELECT materialize([13, 14])[-1]; +14 +SELECT materialize(['Hello', 'world'])[-1]; +world +SELECT materialize([[13], [14]])[-9223372036854775808]; +[] +SELECT materialize([['Hello'], ['world']])[-9223372036854775808]; +[] +SELECT materialize([13, 14])[-9223372036854775808]; +0 +SELECT materialize(['Hello', 'world'])[-9223372036854775808]; + +SELECT materialize([[toNullable(13)], [14]])[-9223372036854775808]; +[] +SELECT materialize([['Hello'], [toNullable('world')]])[-9223372036854775808]; +[] +SELECT materialize([13, toNullable(14)])[-9223372036854775808]; +\N +SELECT materialize(['Hello', toLowCardinality('world')])[-9223372036854775808]; + diff --git a/tests/queries/0_stateless/02383_array_signed_const_positive_index.sql b/tests/queries/0_stateless/02383_array_signed_const_positive_index.sql new file mode 100644 index 00000000000..4f92215f4c0 --- /dev/null +++ b/tests/queries/0_stateless/02383_array_signed_const_positive_index.sql @@ -0,0 +1,36 @@ +-- { echo } + +SELECT materialize([[13]])[1::Int8]; +SELECT materialize([['Hello']])[1::Int8]; +SELECT materialize([13])[1::Int8]; +SELECT materialize(['Hello'])[1::Int8]; + +SELECT materialize([[13], [14]])[2::Int8]; +SELECT materialize([['Hello'], ['world']])[2::Int8]; +SELECT materialize([13, 14])[2::Int8]; +SELECT materialize(['Hello', 'world'])[2::Int8]; + +SELECT materialize([[13], [14]])[3::Int8]; +SELECT materialize([['Hello'], ['world']])[3::Int8]; +SELECT materialize([13, 14])[3::Int8]; +SELECT materialize(['Hello', 'world'])[3::Int8]; + +SELECT materialize([[13], [14]])[0::Int8]; +SELECT materialize([['Hello'], ['world']])[0::Int8]; +SELECT materialize([13, 14])[0::Int8]; +SELECT materialize(['Hello', 'world'])[0::Int8]; + +SELECT materialize([[13], [14]])[-1]; +SELECT materialize([['Hello'], ['world']])[-1]; +SELECT materialize([13, 14])[-1]; +SELECT materialize(['Hello', 'world'])[-1]; + +SELECT materialize([[13], [14]])[-9223372036854775808]; +SELECT materialize([['Hello'], ['world']])[-9223372036854775808]; +SELECT materialize([13, 14])[-9223372036854775808]; +SELECT materialize(['Hello', 'world'])[-9223372036854775808]; + +SELECT materialize([[toNullable(13)], [14]])[-9223372036854775808]; +SELECT materialize([['Hello'], [toNullable('world')]])[-9223372036854775808]; +SELECT materialize([13, toNullable(14)])[-9223372036854775808]; +SELECT materialize(['Hello', toLowCardinality('world')])[-9223372036854775808]; From 923e2f22ef9e6e3d8574874605d10d650de4791d Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Mon, 8 Aug 2022 13:12:57 +0200 Subject: [PATCH 616/672] tests/stress: fix dmesg reading After #39939 there is still an error: + dmesg -T dmesg: read kernel buffer failed: Operation not permitted [1]: https://s3.amazonaws.com/clickhouse-test-reports/39939/a1981f21a153810f072af67bb6007dc1f4367c22/stress_test__debug_.html Since it is not allowed to access dmesg by default, one of the following is required: - --cap-add syslog - --cap-add cap_sys_admin - --privileged I decided to use as little capabilities as possible, even though it is not that important on CI, but it is a rule of thumb. Signed-off-by: Azat Khuzhin --- tests/ci/stress_check.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ci/stress_check.py b/tests/ci/stress_check.py index 8cf7881102c..497df013cf4 100644 --- a/tests/ci/stress_check.py +++ b/tests/ci/stress_check.py @@ -33,6 +33,8 @@ def get_run_command( "docker run --cap-add=SYS_PTRACE " # a static link, don't use S3_URL or S3_DOWNLOAD "-e S3_URL='https://s3.amazonaws.com/clickhouse-datasets' " + # For dmesg + "--cap-add syslog " f"--volume={build_path}:/package_folder " f"--volume={result_folder}:/test_output " f"--volume={repo_tests_path}:/usr/share/clickhouse-test " From c6bb4ae575d0f7e185a33b678167bd9fb6181390 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 00:45:59 +0200 Subject: [PATCH 617/672] clickhouse-test: enable ZooKeeper tests by default --- tests/clickhouse-test | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 0804f6d1fb8..e7a2f93f1bb 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1759,10 +1759,7 @@ def main(args): stop_time = time() + args.global_time_limit if args.zookeeper is None: - try: - args.zookeeper = int(extract_key(" --key zookeeper | grep . | wc -l")) > 0 - except ValueError: - args.zookeeper = False + args.zookeeper = True if args.shard is None: args.shard = bool(extract_key(' --key listen_host | grep -E "127.0.0.2|::"')) From 158332f61d8b6b59f14a8a1a6db53f39370e0d89 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 01:01:59 +0200 Subject: [PATCH 618/672] clickhouse-test: enable ZooKeeper tests by default --- tests/clickhouse-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index e7a2f93f1bb..03ade3f66c8 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -1759,7 +1759,7 @@ def main(args): stop_time = time() + args.global_time_limit if args.zookeeper is None: - args.zookeeper = True + args.zookeeper = True if args.shard is None: args.shard = bool(extract_key(' --key listen_host | grep -E "127.0.0.2|::"')) From 91042e9dd8cd48e013c59340903340e294bb2c51 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 01:43:24 +0200 Subject: [PATCH 619/672] Update test configs --- .../configs/config.d/storage_conf.xml | 1 + .../configs/config.d/storage_conf.xml | 1 + .../test_s3_zero_copy_replication/configs/config.d/s3.xml | 1 + tests/integration/test_s3_zero_copy_ttl/configs/s3.xml | 4 ++++ 4 files changed, 7 insertions(+) diff --git a/tests/integration/test_replicated_merge_tree_hdfs_zero_copy/configs/config.d/storage_conf.xml b/tests/integration/test_replicated_merge_tree_hdfs_zero_copy/configs/config.d/storage_conf.xml index 55e0988218f..1b1ead2d7cb 100644 --- a/tests/integration/test_replicated_merge_tree_hdfs_zero_copy/configs/config.d/storage_conf.xml +++ b/tests/integration/test_replicated_merge_tree_hdfs_zero_copy/configs/config.d/storage_conf.xml @@ -59,6 +59,7 @@ 1024000 1 + true 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 bfd7366b9dc..f4a34256ef8 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 @@ -21,6 +21,7 @@ 0 + true diff --git a/tests/integration/test_s3_zero_copy_replication/configs/config.d/s3.xml b/tests/integration/test_s3_zero_copy_replication/configs/config.d/s3.xml index 181144b0473..bc5273036cb 100644 --- a/tests/integration/test_s3_zero_copy_replication/configs/config.d/s3.xml +++ b/tests/integration/test_s3_zero_copy_replication/configs/config.d/s3.xml @@ -69,6 +69,7 @@ 1024 1 + true diff --git a/tests/integration/test_s3_zero_copy_ttl/configs/s3.xml b/tests/integration/test_s3_zero_copy_ttl/configs/s3.xml index c4889186e38..dcf912af1c7 100644 --- a/tests/integration/test_s3_zero_copy_ttl/configs/s3.xml +++ b/tests/integration/test_s3_zero_copy_ttl/configs/s3.xml @@ -22,5 +22,9 @@ + + true + +
From 42c358aa3c3289f58774219d0b78d10cc2fc0f09 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 01:47:36 +0200 Subject: [PATCH 620/672] Add warning message --- programs/server/Server.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index 21bf7ee4355..f891d04fe51 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -623,6 +623,13 @@ static void sanityChecks(Server & server) if (!enoughSpaceInDirectory(logs_parent, 1ull << 30)) server.context()->addWarningMessage("Available disk space for logs at server startup is too low (1GiB): " + String(logs_parent)); } + + if (server.context()->getMergeTreeSettings().allow_remote_fs_zero_copy_replication) + { + server.context()->addWarningMessage("The setting 'allow_remote_fs_zero_copy_replication' is enabled for MergeTree tables." + " But the feature of 'zero-copy replication' is under development and is not ready for production." + " The usage of this feature can lead to data corruption and loss. The setting should be disabled in production."); + } } catch (...) { From f0c8998471b3c0229069221839ff77476c7ce0f4 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 01:49:00 +0200 Subject: [PATCH 621/672] Add warning message --- programs/server/Server.cpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index f891d04fe51..cdb828840bd 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -613,27 +613,39 @@ static void sanityChecks(Server & server) { if (getAvailableMemoryAmount() < (2l << 30)) server.context()->addWarningMessage("Available memory at server startup is too low (2GiB)."); + } + catch (...) + { + } + try + { if (!enoughSpaceInDirectory(data_path, 1ull << 30)) server.context()->addWarningMessage("Available disk space for data at server startup is too low (1GiB): " + String(data_path)); + } + catch (...) + { + } + try + { if (!logs_path.empty()) { auto logs_parent = fs::path(logs_path).parent_path(); if (!enoughSpaceInDirectory(logs_parent, 1ull << 30)) server.context()->addWarningMessage("Available disk space for logs at server startup is too low (1GiB): " + String(logs_parent)); } - - if (server.context()->getMergeTreeSettings().allow_remote_fs_zero_copy_replication) - { - server.context()->addWarningMessage("The setting 'allow_remote_fs_zero_copy_replication' is enabled for MergeTree tables." - " But the feature of 'zero-copy replication' is under development and is not ready for production." - " The usage of this feature can lead to data corruption and loss. The setting should be disabled in production."); - } } catch (...) { } + + if (server.context()->getMergeTreeSettings().allow_remote_fs_zero_copy_replication) + { + server.context()->addWarningMessage("The setting 'allow_remote_fs_zero_copy_replication' is enabled for MergeTree tables." + " But the feature of 'zero-copy replication' is under development and is not ready for production." + " The usage of this feature can lead to data corruption and loss. The setting should be disabled in production."); + } } int Server::main(const std::vector & /*args*/) From 53ce2986dea68f77905586f676ed639367001910 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 03:33:42 +0200 Subject: [PATCH 622/672] Display server-side time in clickhouse-benchmark by default --- programs/benchmark/Benchmark.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/programs/benchmark/Benchmark.cpp b/programs/benchmark/Benchmark.cpp index 5d82386e2c8..f22236764b9 100644 --- a/programs/benchmark/Benchmark.cpp +++ b/programs/benchmark/Benchmark.cpp @@ -65,12 +65,13 @@ public: bool randomize_, size_t max_iterations_, double max_time_, const String & json_path_, size_t confidence_, const String & query_id_, const String & query_to_execute_, bool continue_on_errors_, - bool reconnect_, bool print_stacktrace_, const Settings & settings_) + bool reconnect_, bool display_client_side_time_, bool print_stacktrace_, const Settings & settings_) : round_robin(round_robin_), concurrency(concurrency_), delay(delay_), queue(concurrency), randomize(randomize_), cumulative(cumulative_), max_iterations(max_iterations_), max_time(max_time_), json_path(json_path_), confidence(confidence_), query_id(query_id_), query_to_execute(query_to_execute_), continue_on_errors(continue_on_errors_), reconnect(reconnect_), + display_client_side_time(display_client_side_time_), print_stacktrace(print_stacktrace_), settings(settings_), shared_context(Context::createShared()), global_context(Context::createGlobal(shared_context.get())), pool(concurrency) @@ -166,6 +167,7 @@ private: String query_to_execute; bool continue_on_errors; bool reconnect; + bool display_client_side_time; bool print_stacktrace; const Settings & settings; SharedContextHolder shared_context; @@ -408,8 +410,8 @@ private: true /*check embedded stack trace*/) << std::endl; size_t info_index = round_robin ? 0 : connection_index; - comparison_info_per_interval[info_index]->errors++; - comparison_info_total[info_index]->errors++; + ++comparison_info_per_interval[info_index]->errors; + ++comparison_info_total[info_index]->errors; } } // Count failed queries toward executed, so that we'd reach @@ -443,7 +445,9 @@ private: executor.finish(); - double seconds = watch.elapsedSeconds(); + double seconds = (display_client_side_time || progress.elapsed_ns == 0) + ? watch.elapsedSeconds() + : progress.elapsed_ns / 1e9; std::lock_guard lock(mutex); @@ -630,22 +634,23 @@ int mainEntryClickHouseBenchmark(int argc, char ** argv) ("stage", value()->default_value("complete"), "request query processing up to specified stage: complete,fetch_columns,with_mergeable_state,with_mergeable_state_after_aggregation,with_mergeable_state_after_aggregation_and_limit") ("iterations,i", value()->default_value(0), "amount of queries to be executed") ("timelimit,t", value()->default_value(0.), "stop launch of queries after specified time limit") - ("randomize,r", value()->default_value(false), "randomize order of execution") + ("randomize,r", "randomize order of execution") ("json", value()->default_value(""), "write final report to specified file in JSON format") ("host,h", value()->multitoken(), "list of hosts") ("port", value()->multitoken(), "list of ports") - ("roundrobin", "Instead of comparing queries for different --host/--port just pick one random --host/--port for every query and send query to it.") - ("cumulative", "prints cumulative data instead of data per interval") - ("secure,s", "Use TLS connection") + ("roundrobin", "Instead of comparing queries for different --host/--port just pick one random --host/--port for every query and send query to it.") + ("cumulative", "prints cumulative data instead of data per interval") + ("secure,s", "Use TLS connection") ("user,u", value()->default_value(env_user_str.value_or("default")), "") ("password", value()->default_value(env_password_str.value_or("")), "") - ("quota_key", value()->default_value(env_quota_key_str.value_or("")), "") + ("quota_key", value()->default_value(env_quota_key_str.value_or("")), "") ("database", value()->default_value("default"), "") - ("stacktrace", "print stack traces of exceptions") + ("stacktrace", "print stack traces of exceptions") ("confidence", value()->default_value(5), "set the level of confidence for T-test [0=80%, 1=90%, 2=95%, 3=98%, 4=99%, 5=99.5%(default)") - ("query_id", value()->default_value(""), "") + ("query_id", value()->default_value(""), "") ("continue_on_errors", "continue testing even if a query fails") - ("reconnect", "establish new connection for every query") + ("reconnect", "establish new connection for every query") + ("client-side-time", "display the time including network communication instead of server-side time; note that for server versions before 22.8 we always display client-side time") ; Settings settings; @@ -690,7 +695,7 @@ int mainEntryClickHouseBenchmark(int argc, char ** argv) options["password"].as(), options["quota_key"].as(), options["stage"].as(), - options["randomize"].as(), + options.count("randomize"), options["iterations"].as(), options["timelimit"].as(), options["json"].as(), @@ -699,6 +704,7 @@ int mainEntryClickHouseBenchmark(int argc, char ** argv) options["query"].as(), options.count("continue_on_errors"), options.count("reconnect"), + options.count("client-side-time"), print_stacktrace, settings); return benchmark.run(); From b5e3d908e28f808cbb7d5e8336c7bfbc49f28144 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 04:11:17 +0200 Subject: [PATCH 623/672] Fix tests --- cmake/linux/toolchain-riscv64.cmake | 3 --- contrib/cctz | 2 +- .../configs/config.d/storage_conf.xml | 3 +++ tests/integration/test_s3_zero_copy_ttl/configs/s3.xml | 9 ++++----- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cmake/linux/toolchain-riscv64.cmake b/cmake/linux/toolchain-riscv64.cmake index cb0a9482a72..bb8820408ab 100644 --- a/cmake/linux/toolchain-riscv64.cmake +++ b/cmake/linux/toolchain-riscv64.cmake @@ -18,9 +18,6 @@ set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --gcc-toolchain=${TOOLCHAIN_PATH}") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --gcc-toolchain=${TOOLCHAIN_PATH}") set (CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} --gcc-toolchain=${TOOLCHAIN_PATH}") -set (CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=bfd") -set (CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=bfd") - set (HAS_PRE_1970_EXITCODE "0" CACHE STRING "Result from TRY_RUN" FORCE) set (HAS_PRE_1970_EXITCODE__TRYRUN_OUTPUT "" CACHE STRING "Output from TRY_RUN" FORCE) diff --git a/contrib/cctz b/contrib/cctz index 8c71d74bdf7..49c656c62fb 160000 --- a/contrib/cctz +++ b/contrib/cctz @@ -1 +1 @@ -Subproject commit 8c71d74bdf76c3fa401da845089ae60a6c0aeefa +Subproject commit 49c656c62fbd36a1bc20d64c476853bdb7cf7bb9 diff --git a/tests/integration/test_azure_blob_storage_zero_copy_replication/configs/config.d/storage_conf.xml b/tests/integration/test_azure_blob_storage_zero_copy_replication/configs/config.d/storage_conf.xml index 4235083f5ca..cb87abcc693 100644 --- a/tests/integration/test_azure_blob_storage_zero_copy_replication/configs/config.d/storage_conf.xml +++ b/tests/integration/test_azure_blob_storage_zero_copy_replication/configs/config.d/storage_conf.xml @@ -43,4 +43,7 @@ test_cluster + + true + diff --git a/tests/integration/test_s3_zero_copy_ttl/configs/s3.xml b/tests/integration/test_s3_zero_copy_ttl/configs/s3.xml index dcf912af1c7..7b426faf66b 100644 --- a/tests/integration/test_s3_zero_copy_ttl/configs/s3.xml +++ b/tests/integration/test_s3_zero_copy_ttl/configs/s3.xml @@ -21,10 +21,9 @@ - - - true - - + + + true + From e774d28c4308f92f5c381125c939f03d928d95a5 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 04:16:48 +0200 Subject: [PATCH 624/672] Fix style --- contrib/cctz | 2 +- programs/benchmark/Benchmark.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contrib/cctz b/contrib/cctz index 8c71d74bdf7..49c656c62fb 160000 --- a/contrib/cctz +++ b/contrib/cctz @@ -1 +1 @@ -Subproject commit 8c71d74bdf76c3fa401da845089ae60a6c0aeefa +Subproject commit 49c656c62fbd36a1bc20d64c476853bdb7cf7bb9 diff --git a/programs/benchmark/Benchmark.cpp b/programs/benchmark/Benchmark.cpp index f22236764b9..fcfce7bc2a3 100644 --- a/programs/benchmark/Benchmark.cpp +++ b/programs/benchmark/Benchmark.cpp @@ -644,13 +644,13 @@ int mainEntryClickHouseBenchmark(int argc, char ** argv) ("user,u", value()->default_value(env_user_str.value_or("default")), "") ("password", value()->default_value(env_password_str.value_or("")), "") ("quota_key", value()->default_value(env_quota_key_str.value_or("")), "") - ("database", value()->default_value("default"), "") - ("stacktrace", "print stack traces of exceptions") - ("confidence", value()->default_value(5), "set the level of confidence for T-test [0=80%, 1=90%, 2=95%, 3=98%, 4=99%, 5=99.5%(default)") - ("query_id", value()->default_value(""), "") + ("database", value()->default_value("default"), "") + ("stacktrace", "print stack traces of exceptions") + ("confidence", value()->default_value(5), "set the level of confidence for T-test [0=80%, 1=90%, 2=95%, 3=98%, 4=99%, 5=99.5%(default)") + ("query_id", value()->default_value(""), "") ("continue_on_errors", "continue testing even if a query fails") - ("reconnect", "establish new connection for every query") - ("client-side-time", "display the time including network communication instead of server-side time; note that for server versions before 22.8 we always display client-side time") + ("reconnect", "establish new connection for every query") + ("client-side-time", "display the time including network communication instead of server-side time; note that for server versions before 22.8 we always display client-side time") ; Settings settings; From 10022ee974f3d1a1d20ad8a9362ed7888a49e900 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 04:28:30 +0200 Subject: [PATCH 625/672] Fix insufficient argument check for encryption functions --- src/Functions/FunctionsAES.h | 8 ++++---- .../0_stateless/02384_decrypt_bad_arguments.reference | 0 tests/queries/0_stateless/02384_decrypt_bad_arguments.sql | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 tests/queries/0_stateless/02384_decrypt_bad_arguments.reference create mode 100644 tests/queries/0_stateless/02384_decrypt_bad_arguments.sql diff --git a/src/Functions/FunctionsAES.h b/src/Functions/FunctionsAES.h index b12fcc00014..0d8e5a5546a 100644 --- a/src/Functions/FunctionsAES.h +++ b/src/Functions/FunctionsAES.h @@ -167,8 +167,8 @@ private: validateFunctionArgumentTypes(*this, arguments, FunctionArgumentDescriptors{ {"mode", &isStringOrFixedString, isColumnConst, "encryption mode string"}, - {"input", &isStringOrFixedString, nullptr, "plaintext"}, - {"key", &isStringOrFixedString, nullptr, "encryption key binary string"}, + {"input", &isStringOrFixedString, {}, "plaintext"}, + {"key", &isStringOrFixedString, {}, "encryption key binary string"}, }, optional_args ); @@ -439,8 +439,8 @@ private: validateFunctionArgumentTypes(*this, arguments, FunctionArgumentDescriptors{ {"mode", &isStringOrFixedString, isColumnConst, "decryption mode string"}, - {"input", nullptr, nullptr, "ciphertext"}, - {"key", &isStringOrFixedString, nullptr, "decryption key binary string"}, + {"input", &isStringOrFixedString, {}, "ciphertext"}, + {"key", &isStringOrFixedString, {}, "decryption key binary string"}, }, optional_args ); diff --git a/tests/queries/0_stateless/02384_decrypt_bad_arguments.reference b/tests/queries/0_stateless/02384_decrypt_bad_arguments.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02384_decrypt_bad_arguments.sql b/tests/queries/0_stateless/02384_decrypt_bad_arguments.sql new file mode 100644 index 00000000000..f4f6770f86c --- /dev/null +++ b/tests/queries/0_stateless/02384_decrypt_bad_arguments.sql @@ -0,0 +1 @@ +SELECT decrypt('aes-128-gcm', [1024, 65535, NULL, NULL, 9223372036854775807, 1048576, NULL], 'text', 'key', 'IV'); -- { serverError 43 } From 461e4cf4b36ac0c11f85346d777ae0a257da65a3 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 04:43:48 +0200 Subject: [PATCH 626/672] Revert submodule --- contrib/cctz | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/cctz b/contrib/cctz index 49c656c62fb..8c71d74bdf7 160000 --- a/contrib/cctz +++ b/contrib/cctz @@ -1 +1 @@ -Subproject commit 49c656c62fbd36a1bc20d64c476853bdb7cf7bb9 +Subproject commit 8c71d74bdf76c3fa401da845089ae60a6c0aeefa From dc97a30506bb629b90fbd70438eb1b451e83b8a7 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 04:44:13 +0200 Subject: [PATCH 627/672] Revert submodule --- contrib/cctz | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/cctz b/contrib/cctz index 49c656c62fb..8c71d74bdf7 160000 --- a/contrib/cctz +++ b/contrib/cctz @@ -1 +1 @@ -Subproject commit 49c656c62fbd36a1bc20d64c476853bdb7cf7bb9 +Subproject commit 8c71d74bdf76c3fa401da845089ae60a6c0aeefa From 7b907fe32e0940de005ae28c3de638871596580e Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 05:08:55 +0200 Subject: [PATCH 628/672] Update test --- tests/queries/0_stateless/02384_decrypt_bad_arguments.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/0_stateless/02384_decrypt_bad_arguments.sql b/tests/queries/0_stateless/02384_decrypt_bad_arguments.sql index f4f6770f86c..d29768558c4 100644 --- a/tests/queries/0_stateless/02384_decrypt_bad_arguments.sql +++ b/tests/queries/0_stateless/02384_decrypt_bad_arguments.sql @@ -1 +1,2 @@ +-- Tags: no-fasttest SELECT decrypt('aes-128-gcm', [1024, 65535, NULL, NULL, 9223372036854775807, 1048576, NULL], 'text', 'key', 'IV'); -- { serverError 43 } From ce2155cf79ab65aa0e84f67c9eea627dcb7eba37 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 05:40:48 +0200 Subject: [PATCH 629/672] Remove old code --- utils/ci/README.md | 143 --------------------- utils/ci/build-clang-from-sources.sh | 37 ------ utils/ci/build-debian-packages.sh | 8 -- utils/ci/build-gcc-from-sources.sh | 47 ------- utils/ci/build-normal.sh | 17 --- utils/ci/check-docker.sh | 7 - utils/ci/check-syntax.sh | 20 --- utils/ci/check-tzdata-updates.sh | 18 --- utils/ci/create-sources-tarball.sh | 10 -- utils/ci/default-config | 64 --------- utils/ci/docker-multiarch/LICENSE | 21 --- utils/ci/docker-multiarch/README.md | 53 -------- utils/ci/docker-multiarch/update.sh | 96 -------------- utils/ci/get-sources.sh | 18 --- utils/ci/install-compiler-from-packages.sh | 22 ---- utils/ci/install-compiler-from-sources.sh | 12 -- utils/ci/install-libraries.sh | 6 - utils/ci/install-os-packages.sh | 135 ------------------- utils/ci/prepare-docker-image-ubuntu.sh | 23 ---- utils/ci/prepare-toolchain.sh | 15 --- utils/ci/prepare-vagrant-image-freebsd.sh | 12 -- utils/ci/run-clickhouse-from-binaries.sh | 18 --- utils/ci/run-with-docker.sh | 9 -- utils/ci/run-with-vagrant.sh | 14 -- 24 files changed, 825 deletions(-) delete mode 100644 utils/ci/README.md delete mode 100755 utils/ci/build-clang-from-sources.sh delete mode 100755 utils/ci/build-debian-packages.sh delete mode 100755 utils/ci/build-gcc-from-sources.sh delete mode 100755 utils/ci/build-normal.sh delete mode 100755 utils/ci/check-docker.sh delete mode 100755 utils/ci/check-syntax.sh delete mode 100755 utils/ci/check-tzdata-updates.sh delete mode 100755 utils/ci/create-sources-tarball.sh delete mode 100644 utils/ci/default-config delete mode 100644 utils/ci/docker-multiarch/LICENSE delete mode 100644 utils/ci/docker-multiarch/README.md delete mode 100755 utils/ci/docker-multiarch/update.sh delete mode 100755 utils/ci/get-sources.sh delete mode 100755 utils/ci/install-compiler-from-packages.sh delete mode 100755 utils/ci/install-compiler-from-sources.sh delete mode 100755 utils/ci/install-libraries.sh delete mode 100755 utils/ci/install-os-packages.sh delete mode 100755 utils/ci/prepare-docker-image-ubuntu.sh delete mode 100755 utils/ci/prepare-toolchain.sh delete mode 100755 utils/ci/prepare-vagrant-image-freebsd.sh delete mode 100755 utils/ci/run-clickhouse-from-binaries.sh delete mode 100755 utils/ci/run-with-docker.sh delete mode 100755 utils/ci/run-with-vagrant.sh diff --git a/utils/ci/README.md b/utils/ci/README.md deleted file mode 100644 index a5245375237..00000000000 --- a/utils/ci/README.md +++ /dev/null @@ -1,143 +0,0 @@ -## Build and test ClickHouse on various platforms - -Quick and dirty scripts. - -Usage example: -``` -./run-with-docker.sh ubuntu:bionic jobs/quick-build/run.sh -``` - -Another example, check build on ARM 64: -``` -./prepare-docker-image-ubuntu.sh -./run-with-docker.sh multiarch/ubuntu-core:arm64-bionic jobs/quick-build/run.sh -``` - -Another example, check build on FreeBSD: -``` -./prepare-vagrant-image-freebsd.sh -./run-with-vagrant.sh freebsd jobs/quick-build/run.sh -``` - -Look at `default_config` and `jobs/quick-build/run.sh` - -Various possible options. We are not going to automate testing all of them. - -#### CPU architectures: -- x86_64; -- AArch64; -- PowerPC64LE. - -x86_64 is the main CPU architecture. We also have minimal support for AArch64 and PowerPC64LE. - -#### Operating systems: -- Linux; -- FreeBSD. - -We also target Mac OS X, but it's more difficult to test. -Linux is the main. FreeBSD is also supported as production OS. -Mac OS is intended only for development and have minimal support: client should work, server should just start. - -#### Linux distributions: -For build: -- Ubuntu Bionic; -- Ubuntu Trusty. - -For run: -- Ubuntu Hardy; -- CentOS 5 - -We should support almost any Linux to run ClickHouse. That's why we test also on old distributions. - -#### How to obtain sources: -- use sources from local working copy; -- clone sources from github; -- download source tarball. - -#### Compilers: -- gcc-7; -- gcc-8; -- clang-6; -- clang-svn. - -#### Compiler installation: -- from OS packages; -- build from sources. - -#### C++ standard library implementation: -- libc++; -- libstdc++ with C++11 ABI; -- libstdc++ with old ABI. - -When building with clang, libc++ is used. When building with gcc, we choose libstdc++ with C++11 ABI. - -#### Linkers: -- ldd; -- gold; - -When building with clang on x86_64, ldd is used. Otherwise we use gold. - -#### Build types: -- RelWithDebInfo; -- Debug; -- ASan; -- TSan. - -#### Build types, extra: -- -g0 for quick build; -- enable test coverage; -- debug tcmalloc. - -#### What to build: -- only `clickhouse` target; -- all targets; -- debian packages; - -We also have intent to build RPM and simple tgz packages. - -#### Where to get third-party libraries: -- from contrib directory (submodules); -- from OS packages. - -The only production option is to use libraries from contrib directory. -Using libraries from OS packages is discouraged, but we also support this option. - -#### Linkage types: -- static; -- shared; - -Static linking is the only option for production usage. -We also have support for shared linking, but it is intended only for developers. - -#### Make tools: -- make; -- ninja. - -#### Installation options: -- run built `clickhouse` binary directly; -- install from packages. - -#### How to obtain packages: -- build them; -- download from repository. - -#### Sanity checks: -- check that clickhouse binary has no dependencies on unexpected shared libraries; -- check that source code have no style violations. - -#### Tests: -- Functional tests; -- Integration tests; -- Unit tests; -- Simple sh/reference tests; -- Performance tests (note that they require predictable computing power); -- Tests for external dictionaries (should be moved to integration tests); -- Jepsen like tests for quorum inserts (not yet available in opensource). - -#### Tests extra: -- Run functional tests with Valgrind. - -#### Static analyzers: -- CppCheck; -- clang-tidy; -- Coverity. diff --git a/utils/ci/build-clang-from-sources.sh b/utils/ci/build-clang-from-sources.sh deleted file mode 100755 index 59eb966ada9..00000000000 --- a/utils/ci/build-clang-from-sources.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -./install-os-packages.sh svn -./install-os-packages.sh cmake - -mkdir "${WORKSPACE}/llvm" - -svn co "http://llvm.org/svn/llvm-project/llvm/${CLANG_SOURCES_BRANCH}" "${WORKSPACE}/llvm/llvm" -svn co "http://llvm.org/svn/llvm-project/cfe/${CLANG_SOURCES_BRANCH}" "${WORKSPACE}/llvm/llvm/tools/clang" -svn co "http://llvm.org/svn/llvm-project/lld/${CLANG_SOURCES_BRANCH}" "${WORKSPACE}/llvm/llvm/tools/lld" -svn co "http://llvm.org/svn/llvm-project/polly/${CLANG_SOURCES_BRANCH}" "${WORKSPACE}/llvm/llvm/tools/polly" -svn co "http://llvm.org/svn/llvm-project/clang-tools-extra/${CLANG_SOURCES_BRANCH}" "${WORKSPACE}/llvm/llvm/tools/clang/tools/extra" -svn co "http://llvm.org/svn/llvm-project/compiler-rt/${CLANG_SOURCES_BRANCH}" "${WORKSPACE}/llvm/llvm/projects/compiler-rt" -svn co "http://llvm.org/svn/llvm-project/libcxx/${CLANG_SOURCES_BRANCH}" "${WORKSPACE}/llvm/llvm/projects/libcxx" -svn co "http://llvm.org/svn/llvm-project/libcxxabi/${CLANG_SOURCES_BRANCH}" "${WORKSPACE}/llvm/llvm/projects/libcxxabi" - -mkdir "${WORKSPACE}/llvm/build" -cd "${WORKSPACE}/llvm/build" - -# NOTE You must build LLVM with the same ABI as ClickHouse. -# For example, if you compile ClickHouse with libc++, you must add -# -DLLVM_ENABLE_LIBCXX=1 -# to the line below. - -cmake -DCMAKE_BUILD_TYPE:STRING=Release -DLLVM_ENABLE_LIBCXX=1 -DLLVM_ENABLE_RTTI=1 ../llvm - -make -j $THREADS -$SUDO make install -hash clang - -cd ../../.. - -export CC=clang -export CXX=clang++ diff --git a/utils/ci/build-debian-packages.sh b/utils/ci/build-debian-packages.sh deleted file mode 100755 index 8726b675c4d..00000000000 --- a/utils/ci/build-debian-packages.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -[[ -d "${WORKSPACE}/sources" ]] || die "Run get-sources.sh first" - -./sources/release diff --git a/utils/ci/build-gcc-from-sources.sh b/utils/ci/build-gcc-from-sources.sh deleted file mode 100755 index 8886bb7afd7..00000000000 --- a/utils/ci/build-gcc-from-sources.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -./install-os-packages.sh curl - -if [[ "${GCC_SOURCES_VERSION}" == "latest" ]]; then - GCC_SOURCES_VERSION=$(curl -sSL https://ftpmirror.gnu.org/gcc/ | grep -oE 'gcc-[0-9]+(\.[0-9]+)+' | sort -Vr | head -n1) -fi - -GCC_VERSION_SHORT=$(echo "$GCC_SOURCES_VERSION" | grep -oE '[0-9]' | head -n1) - -echo "Will download ${GCC_SOURCES_VERSION} (short version: $GCC_VERSION_SHORT)." - -THREADS=$(grep -c ^processor /proc/cpuinfo) - -mkdir "${WORKSPACE}/gcc" -pushd "${WORKSPACE}/gcc" - -wget -nv https://ftpmirror.gnu.org/gcc/${GCC_SOURCES_VERSION}/${GCC_SOURCES_VERSION}.tar.xz -tar xf ${GCC_SOURCES_VERSION}.tar.xz -pushd ${GCC_SOURCES_VERSION} -./contrib/download_prerequisites -popd -mkdir gcc-build -pushd gcc-build -../${GCC_SOURCES_VERSION}/configure --enable-languages=c,c++ --disable-multilib -make -j $THREADS -$SUDO make install - -popd -popd - -$SUDO ln -sf /usr/local/bin/gcc /usr/local/bin/gcc-${GCC_VERSION_SHORT} -$SUDO ln -sf /usr/local/bin/g++ /usr/local/bin/g++-${GCC_VERSION_SHORT} -$SUDO ln -sf /usr/local/bin/gcc /usr/local/bin/cc -$SUDO ln -sf /usr/local/bin/g++ /usr/local/bin/c++ - -echo '/usr/local/lib64' | $SUDO tee /etc/ld.so.conf.d/10_local-lib64.conf -$SUDO ldconfig - -hash gcc g++ -gcc --version - -export CC=gcc-${GCC_VERSION_SHORT} -export CXX=g++-${GCC_VERSION_SHORT} diff --git a/utils/ci/build-normal.sh b/utils/ci/build-normal.sh deleted file mode 100755 index 328bd2c9f51..00000000000 --- a/utils/ci/build-normal.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -[[ -d "${WORKSPACE}/sources" ]] || die "Run get-sources.sh first" - -mkdir -p "${WORKSPACE}/build" -pushd "${WORKSPACE}/build" - -cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DENABLE_EMBEDDED_COMPILER=${ENABLE_EMBEDDED_COMPILER} $CMAKE_FLAGS ../sources - -[[ "$BUILD_TARGETS" != 'all' ]] && BUILD_TARGETS_STRING="--target $BUILD_TARGETS" - -cmake --build . $BUILD_TARGETS_STRING -- -j $THREADS - -popd diff --git a/utils/ci/check-docker.sh b/utils/ci/check-docker.sh deleted file mode 100755 index e4ffb11f643..00000000000 --- a/utils/ci/check-docker.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -command -v docker > /dev/null || die "You need to install Docker" -docker ps > /dev/null || die "You need to have access to Docker: run '$SUDO usermod -aG docker $USER' and relogin" diff --git a/utils/ci/check-syntax.sh b/utils/ci/check-syntax.sh deleted file mode 100755 index df233965f9e..00000000000 --- a/utils/ci/check-syntax.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -./install-os-packages.sh jq - -[[ -d "${WORKSPACE}/sources" ]] || die "Run get-sources.sh first" - -mkdir -p "${WORKSPACE}/build" -pushd "${WORKSPACE}/build" - -cmake -DCMAKE_BUILD_TYPE=Debug $CMAKE_FLAGS ../sources - -make -j $THREADS re2_st # Generated headers - -jq --raw-output '.[] | .command' compile_commands.json | grep -v -P -- '-c .+/contrib/' | sed -r -e 's/-o\s+\S+/-fsyntax-only/' > syntax-commands -xargs --arg-file=syntax-commands --max-procs=$THREADS --replace /bin/sh -c "{}" - -popd diff --git a/utils/ci/check-tzdata-updates.sh b/utils/ci/check-tzdata-updates.sh deleted file mode 100755 index 8686590f87c..00000000000 --- a/utils/ci/check-tzdata-updates.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -[[ -d "${WORKSPACE}/sources" ]] || die "Run get-sources.sh first" - -latest_tzdb_version=$(curl -s https://data.iana.org/time-zones/data/version); -tzdb_version_in_repo=$(cat "${WORKSPACE}/sources/contrib/cctz/testdata/version"); - -if [ "$tzdb_version_in_repo" = "$latest_tzdb_version" ]; -then - echo "No update for TZDB needed"; - exit 0; -else - echo "TZDB update required! Version in repo is ${tzdb_version_in_repo}, latest version is ${latest_tzdb_version}"; - exit 1 -fi; diff --git a/utils/ci/create-sources-tarball.sh b/utils/ci/create-sources-tarball.sh deleted file mode 100755 index bfbbf61e556..00000000000 --- a/utils/ci/create-sources-tarball.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -if [[ -d "${WORKSPACE}/sources" ]]; then - tar -c -z -f "${WORKSPACE}/sources.tar.gz" --directory "${WORKSPACE}/sources" . -else - die "Run get-sources first" -fi diff --git a/utils/ci/default-config b/utils/ci/default-config deleted file mode 100644 index b66121cc757..00000000000 --- a/utils/ci/default-config +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -if [[ -z "$INITIALIZED" ]]; then - -INITIALIZED=1 - -SCRIPTPATH=$(pwd) -WORKSPACE=${SCRIPTPATH}/workspace -PROJECT_ROOT=$(cd $SCRIPTPATH/.. && pwd) - -# Almost all scripts take no arguments. Arguments should be in config. - -# get-sources -SOURCES_METHOD=local # clone, local, tarball -SOURCES_CLONE_URL="https://github.com/ClickHouse/ClickHouse.git" -SOURCES_BRANCH="master" -SOURCES_COMMIT=HEAD # do checkout of this commit after clone - -# prepare-toolchain -COMPILER=gcc # gcc, clang -COMPILER_INSTALL_METHOD=packages # packages, sources -COMPILER_PACKAGE_VERSION=7 # or 6.0 for clang - -# install-compiler-from-sources -CLANG_SOURCES_BRANCH=trunk # or tags/RELEASE_600/final -GCC_SOURCES_VERSION=latest # or gcc-7.1.0 - -# install-libraries -ENABLE_EMBEDDED_COMPILER=1 - -# build -BUILD_METHOD=normal # normal, debian -BUILD_TARGETS=clickhouse # tagtet name, all; only for "normal" -BUILD_TYPE=RelWithDebInfo # RelWithDebInfo, Debug, ASan, TSan -CMAKE_FLAGS="" - -# prepare-docker-image-ubuntu -DOCKER_UBUNTU_VERSION=bionic -DOCKER_UBUNTU_ARCH=arm64 # How the architecture is named in a tarball at https://partner-images.canonical.com/core/ -DOCKER_UBUNTU_QUEMU_ARCH=aarch64 # How the architecture is named in QEMU -DOCKER_UBUNTU_TAG_ARCH=arm64 # How the architecture is named in Docker -DOCKER_UBUNTU_QEMU_VER=v2.9.1 -DOCKER_UBUNTU_REPO=multiarch/ubuntu-core - -THREADS=$(grep -c ^processor /proc/cpuinfo || nproc || sysctl -a | grep -F 'hw.ncpu' | grep -oE '[0-9]+') - -# All scripts should return 0 in case of success, 1 in case of permanent error, -# 2 in case of temporary error, any other code in case of permanent error. -function die { - echo ${1:-Error} - exit ${2:1} -} - -[[ $EUID -ne 0 ]] && SUDO=sudo - -./install-os-packages.sh prepare - -# Configuration parameters may be overridden with CONFIG environment variable pointing to config file. -[[ -n "$CONFIG" ]] && source $CONFIG - -mkdir -p $WORKSPACE - -fi diff --git a/utils/ci/docker-multiarch/LICENSE b/utils/ci/docker-multiarch/LICENSE deleted file mode 100644 index 60d0ea8fa82..00000000000 --- a/utils/ci/docker-multiarch/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Multiarch - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/utils/ci/docker-multiarch/README.md b/utils/ci/docker-multiarch/README.md deleted file mode 100644 index 9cc30c2dca8..00000000000 --- a/utils/ci/docker-multiarch/README.md +++ /dev/null @@ -1,53 +0,0 @@ -Source: https://github.com/multiarch/ubuntu-core -Commit: 3972a7794b40a965615abd710759d3ed439c9a55 - -# :earth_africa: ubuntu-core - -![](https://raw.githubusercontent.com/multiarch/dockerfile/master/logo.jpg) - -Multiarch Ubuntu images for Docker. - -Based on https://github.com/tianon/docker-brew-ubuntu-core/ - -* `multiarch/ubuntu-core` on [Docker Hub](https://hub.docker.com/r/multiarch/ubuntu-core/) -* [Available tags](https://hub.docker.com/r/multiarch/ubuntu-core/tags/) - -## Usage - -Once you need to configure binfmt-support on your Docker host. -This works locally or remotely (i.e using boot2docker or swarm). - -```console -# configure binfmt-support on the Docker host (works locally or remotely, i.e: using boot2docker) -$ docker run --rm --privileged multiarch/qemu-user-static:register --reset -``` - -Then you can run an `armhf` image from your `x86_64` Docker host. - -```console -$ docker run -it --rm multiarch/ubuntu-core:armhf-wily -root@a0818570f614:/# uname -a -Linux a0818570f614 4.1.13-boot2docker #1 SMP Fri Nov 20 19:05:50 UTC 2015 armv7l armv7l armv7l GNU/Linux -root@a0818570f614:/# exit -``` - -Or an `x86_64` image from your `x86_64` Docker host, directly, without qemu emulation. - -```console -$ docker run -it --rm multiarch/ubuntu-core:amd64-wily -root@27fe384370c9:/# uname -a -Linux 27fe384370c9 4.1.13-boot2docker #1 SMP Fri Nov 20 19:05:50 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux -root@27fe384370c9:/# -``` - -It also works for `arm64` - -```console -$ docker run -it --rm multiarch/ubuntu-core:arm64-wily -root@723fb9f184fa:/# uname -a -Linux 723fb9f184fa 4.1.13-boot2docker #1 SMP Fri Nov 20 19:05:50 UTC 2015 aarch64 aarch64 aarch64 GNU/Linux -``` - -## License - -MIT diff --git a/utils/ci/docker-multiarch/update.sh b/utils/ci/docker-multiarch/update.sh deleted file mode 100755 index 1348631bdcf..00000000000 --- a/utils/ci/docker-multiarch/update.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -# A POSIX variable -OPTIND=1 # Reset in case getopts has been used previously in the shell. - -while getopts "a:v:q:u:d:t:" opt; do - case "$opt" in - a) ARCH=$OPTARG - ;; - v) VERSION=$OPTARG - ;; - q) QEMU_ARCH=$OPTARG - ;; - u) QEMU_VER=$OPTARG - ;; - d) DOCKER_REPO=$OPTARG - ;; - t) TAG_ARCH=$OPTARG - ;; - esac -done - -thisTarBase="ubuntu-$VERSION-core-cloudimg-$ARCH" -thisTar="$thisTarBase-root.tar.gz" -baseUrl="https://partner-images.canonical.com/core/$VERSION" - - -# install qemu-user-static -if [ -n "${QEMU_ARCH}" ]; then - if [ ! -f x86_64_qemu-${QEMU_ARCH}-static.tar.gz ]; then - wget -nv -N https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VER}/x86_64_qemu-${QEMU_ARCH}-static.tar.gz - fi - tar -xvf x86_64_qemu-${QEMU_ARCH}-static.tar.gz -C $ROOTFS/usr/bin/ -fi - - -# get the image -if \ - wget -nv --spider "$baseUrl/current" \ - && wget -nv --spider "$baseUrl/current/$thisTar" \ - ; then - baseUrl+='/current' -fi -wget -nv -N "$baseUrl/"{{MD5,SHA{1,256}}SUMS{,.gpg},"$thisTarBase.manifest",'unpacked/build-info.txt'} || true -wget -nv -N "$baseUrl/$thisTar" - -# check checksum -if [ -f SHA256SUMS ]; then - sha256sum="$(sha256sum "$thisTar" | cut -d' ' -f1)" - if ! grep -q "$sha256sum" SHA256SUMS; then - echo >&2 "error: '$thisTar' has invalid SHA256" - exit 1 - fi -fi - -cat > Dockerfile <<-EOF - FROM scratch - ADD $thisTar / - ENV ARCH=${ARCH} UBUNTU_SUITE=${VERSION} DOCKER_REPO=${DOCKER_REPO} -EOF - -# add qemu-user-static binary -if [ -n "${QEMU_ARCH}" ]; then - cat >> Dockerfile <> Dockerfile <<-EOF - # a few minor docker-specific tweaks - # see https://github.com/docker/docker/blob/master/contrib/mkimage/debootstrap - RUN echo '#!/bin/sh' > /usr/sbin/policy-rc.d \\ - && echo 'exit 101' >> /usr/sbin/policy-rc.d \\ - && chmod +x /usr/sbin/policy-rc.d \\ - && dpkg-divert --local --rename --add /sbin/initctl \\ - && cp -a /usr/sbin/policy-rc.d /sbin/initctl \\ - && sed -i 's/^exit.*/exit 0/' /sbin/initctl \\ - && echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \\ - && echo 'DPkg::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' > /etc/apt/apt.conf.d/docker-clean \\ - && echo 'APT::Update::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' >> /etc/apt/apt.conf.d/docker-clean \\ - && echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' >> /etc/apt/apt.conf.d/docker-clean \\ - && echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/docker-no-languages \\ - && echo 'Acquire::GzipIndexes "true"; Acquire::CompressionTypes::Order:: "gz";' > /etc/apt/apt.conf.d/docker-gzip-indexes - - # enable the universe - RUN sed -i 's/^#\s*\(deb.*universe\)$/\1/g' /etc/apt/sources.list - - # overwrite this with 'CMD []' in a dependent Dockerfile - CMD ["/bin/bash"] -EOF - -docker build -t "${DOCKER_REPO}:${TAG_ARCH}-${VERSION}" . -docker run --rm "${DOCKER_REPO}:${TAG_ARCH}-${VERSION}" /bin/bash -ec "echo Hello from Ubuntu!" diff --git a/utils/ci/get-sources.sh b/utils/ci/get-sources.sh deleted file mode 100755 index ee57b0ec27d..00000000000 --- a/utils/ci/get-sources.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -if [[ "$SOURCES_METHOD" == "clone" ]]; then - ./install-os-packages.sh git - SOURCES_DIR="${WORKSPACE}/sources" - mkdir -p "${SOURCES_DIR}" - git clone --recursive --branch "$SOURCES_BRANCH" "$SOURCES_CLONE_URL" "${SOURCES_DIR}" - pushd "${SOURCES_DIR}" - git checkout --recurse-submodules "$SOURCES_COMMIT" - popd -elif [[ "$SOURCES_METHOD" == "local" ]]; then - ln -f -s "${PROJECT_ROOT}" "${WORKSPACE}/sources" -else - die "Unknown SOURCES_METHOD" -fi diff --git a/utils/ci/install-compiler-from-packages.sh b/utils/ci/install-compiler-from-packages.sh deleted file mode 100755 index 53909435a06..00000000000 --- a/utils/ci/install-compiler-from-packages.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -# TODO Install from PPA on older Ubuntu - -./install-os-packages.sh ${COMPILER}-${COMPILER_PACKAGE_VERSION} - -if [[ "$COMPILER" == "gcc" ]]; then - if command -v gcc-${COMPILER_PACKAGE_VERSION}; then export CC=gcc-${COMPILER_PACKAGE_VERSION} CXX=g++-${COMPILER_PACKAGE_VERSION}; - elif command -v gcc${COMPILER_PACKAGE_VERSION}; then export CC=gcc${COMPILER_PACKAGE_VERSION} CXX=g++${COMPILER_PACKAGE_VERSION}; - elif command -v gcc; then export CC=gcc CXX=g++; - fi -elif [[ "$COMPILER" == "clang" ]]; then - if command -v clang-${COMPILER_PACKAGE_VERSION}; then export CC=clang-${COMPILER_PACKAGE_VERSION} CXX=clang++-${COMPILER_PACKAGE_VERSION}; - elif command -v clang${COMPILER_PACKAGE_VERSION}; then export CC=clang${COMPILER_PACKAGE_VERSION} CXX=clang++${COMPILER_PACKAGE_VERSION}; - elif command -v clang; then export CC=clang CXX=clang++; - fi -else - die "Unknown compiler specified" -fi diff --git a/utils/ci/install-compiler-from-sources.sh b/utils/ci/install-compiler-from-sources.sh deleted file mode 100755 index 235898ed300..00000000000 --- a/utils/ci/install-compiler-from-sources.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -if [[ "$COMPILER" == "gcc" ]]; then - . build-gcc-from-sources.sh -elif [[ "$COMPILER" == "clang" ]]; then - . build-clang-from-sources.sh -else - die "Unknown COMPILER" -fi diff --git a/utils/ci/install-libraries.sh b/utils/ci/install-libraries.sh deleted file mode 100755 index 3c26e3b09b1..00000000000 --- a/utils/ci/install-libraries.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -./install-os-packages.sh libicu-dev diff --git a/utils/ci/install-os-packages.sh b/utils/ci/install-os-packages.sh deleted file mode 100755 index b4b0c74f30c..00000000000 --- a/utils/ci/install-os-packages.sh +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -# Dispatches package installation on various OS and distributives - -WHAT=$1 - -[[ $EUID -ne 0 ]] && SUDO=sudo - -command -v yum && PACKAGE_MANAGER=yum -command -v pkg && PACKAGE_MANAGER=pkg -command -v apt-get && PACKAGE_MANAGER=apt - - -case $PACKAGE_MANAGER in - apt) - case $WHAT in - prepare) - $SUDO apt-get update - ;; - svn) - $SUDO apt-get install -y subversion - ;; - gcc*) - $SUDO apt-get install -y $WHAT ${WHAT/cc/++} - ;; - clang*) - $SUDO apt-get install -y $WHAT libc++-dev libc++abi-dev - [[ $(uname -m) == "x86_64" ]] && $SUDO apt-get install -y ${WHAT/clang/lld} || true - ;; - git) - $SUDO apt-get install -y git - ;; - cmake) - $SUDO apt-get install -y cmake3 || $SUDO apt-get install -y cmake - ;; - ninja) - $SUDO apt-get install -y ninja-build - ;; - curl) - $SUDO apt-get install -y curl - ;; - jq) - $SUDO apt-get install -y jq - ;; - libicu-dev) - $SUDO apt-get install -y libicu-dev - ;; - llvm-libs*) - $SUDO apt-get install -y ${WHAT/llvm-libs/liblld}-dev ${WHAT/llvm-libs/libclang}-dev - ;; - qemu-user-static) - $SUDO apt-get install -y qemu-user-static - ;; - vagrant-virtualbox) - $SUDO apt-get install -y vagrant virtualbox - ;; - *) - echo "Unknown package"; exit 1; - ;; - esac - ;; - yum) - case $WHAT in - prepare) - ;; - svn) - $SUDO yum install -y subversion - ;; - gcc*) - $SUDO yum install -y gcc gcc-c++ libstdc++-static - ;; - git) - $SUDO yum install -y git - ;; - cmake) - $SUDO yum install -y cmake - ;; - ninja) - $SUDO yum install -y ninja-build - ;; - curl) - $SUDO yum install -y curl - ;; - jq) - $SUDO yum install -y jq - ;; - libicu-dev) - $SUDO yum install -y libicu-devel - ;; - *) - echo "Unknown package"; exit 1; - ;; - esac - ;; - pkg) - case $WHAT in - prepare) - ;; - svn) - $SUDO pkg install -y subversion - ;; - gcc*) - $SUDO pkg install -y ${WHAT/-/} - ;; - clang*) - $SUDO pkg install -y clang-devel - ;; - git) - $SUDO pkg install -y git - ;; - cmake) - $SUDO pkg install -y cmake - ;; - ninja) - $SUDO pkg install -y ninja-build - ;; - curl) - $SUDO pkg install -y curl - ;; - jq) - $SUDO pkg install -y jq - ;; - libicu-dev) - $SUDO pkg install -y icu - ;; - *) - echo "Unknown package"; exit 1; - ;; - esac - ;; - *) - echo "Unknown distributive"; exit 1; - ;; -esac diff --git a/utils/ci/prepare-docker-image-ubuntu.sh b/utils/ci/prepare-docker-image-ubuntu.sh deleted file mode 100755 index 2880d7fc1e6..00000000000 --- a/utils/ci/prepare-docker-image-ubuntu.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -./check-docker.sh - -# http://fl47l1n3.net/2015/12/24/binfmt/ -./install-os-packages.sh qemu-user-static - -pushd docker-multiarch - -$SUDO ./update.sh \ - -a "$DOCKER_UBUNTU_ARCH" \ - -v "$DOCKER_UBUNTU_VERSION" \ - -q "$DOCKER_UBUNTU_QUEMU_ARCH" \ - -u "$DOCKER_UBUNTU_QEMU_VER" \ - -d "$DOCKER_UBUNTU_REPO" \ - -t "$DOCKER_UBUNTU_TAG_ARCH" - -docker run --rm --privileged multiarch/qemu-user-static:register - -popd diff --git a/utils/ci/prepare-toolchain.sh b/utils/ci/prepare-toolchain.sh deleted file mode 100755 index 5edb19cc430..00000000000 --- a/utils/ci/prepare-toolchain.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -./install-os-packages.sh cmake -./install-os-packages.sh ninja - -if [[ "$COMPILER_INSTALL_METHOD" == "packages" ]]; then - . install-compiler-from-packages.sh -elif [[ "$COMPILER_INSTALL_METHOD" == "sources" ]]; then - . install-compiler-from-sources.sh -else - die "Unknown COMPILER_INSTALL_METHOD" -fi diff --git a/utils/ci/prepare-vagrant-image-freebsd.sh b/utils/ci/prepare-vagrant-image-freebsd.sh deleted file mode 100755 index 16c5e58c7c5..00000000000 --- a/utils/ci/prepare-vagrant-image-freebsd.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -source default-config - -./install-os-packages.sh vagrant-virtualbox - -pushd "vagrant-freebsd" -vagrant up -vagrant ssh-config > vagrant-ssh -ssh -F vagrant-ssh default 'uname -a' -popd diff --git a/utils/ci/run-clickhouse-from-binaries.sh b/utils/ci/run-clickhouse-from-binaries.sh deleted file mode 100755 index 5e9dc35869a..00000000000 --- a/utils/ci/run-clickhouse-from-binaries.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -# Usage example: -# ./run-with-docker.sh centos:centos6 ./run-clickhouse-from-binaries.sh - -source default-config - -SERVER_BIN="${WORKSPACE}/build/src/Server/clickhouse" -SERVER_CONF="${WORKSPACE}/sources/src/Server/config.xml" -SERVER_DATADIR="${WORKSPACE}/clickhouse" - -[[ -x "$SERVER_BIN" ]] || die "Run build-normal.sh first" -[[ -r "$SERVER_CONF" ]] || die "Run get-sources.sh first" - -mkdir -p "${SERVER_DATADIR}" - -$SERVER_BIN server --config-file "$SERVER_CONF" --pid-file="${WORKSPACE}/clickhouse.pid" -- --path "$SERVER_DATADIR" diff --git a/utils/ci/run-with-docker.sh b/utils/ci/run-with-docker.sh deleted file mode 100755 index 158961dd5da..00000000000 --- a/utils/ci/run-with-docker.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -mkdir -p /var/cache/ccache -DOCKER_ENV+=" --mount=type=bind,source=/var/cache/ccache,destination=/ccache -e CCACHE_DIR=/ccache " - -PROJECT_ROOT="$(cd "$(dirname "$0")/.."; pwd -P)" -[[ -n "$CONFIG" ]] && DOCKER_ENV="--env=CONFIG" -docker run -t --network=host --mount=type=bind,source=${PROJECT_ROOT},destination=/ClickHouse --workdir=/ClickHouse/ci $DOCKER_ENV "$1" "$2" diff --git a/utils/ci/run-with-vagrant.sh b/utils/ci/run-with-vagrant.sh deleted file mode 100755 index 620d38071eb..00000000000 --- a/utils/ci/run-with-vagrant.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -e -x - -[[ -r "vagrant-${1}/vagrant-ssh" ]] || die "Run prepare-vagrant-image-... first." - -pushd vagrant-$1 - -shopt -s extglob - -vagrant ssh -c "mkdir -p ClickHouse" -scp -q -F vagrant-ssh -r ../../!(*build*) default:~/ClickHouse -vagrant ssh -c "cd ClickHouse/ci; $2" - -popd From 5ce6c070057dd348e53e01d2624e8ff962b964b0 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 05:57:55 +0200 Subject: [PATCH 630/672] Set linker for RISC-V 64 --- cmake/linux/toolchain-riscv64.cmake | 5 +++++ docker/packager/binary/Dockerfile | 3 +++ 2 files changed, 8 insertions(+) diff --git a/cmake/linux/toolchain-riscv64.cmake b/cmake/linux/toolchain-riscv64.cmake index cb0a9482a72..02c3d0c97fc 100644 --- a/cmake/linux/toolchain-riscv64.cmake +++ b/cmake/linux/toolchain-riscv64.cmake @@ -21,6 +21,11 @@ set (CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} --gcc-toolchain=${TOOLCHAIN_PATH}") set (CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=bfd") set (CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=bfd") +# Currently, lld does not work with the error: +# ld.lld: error: section size decrease is too large +# But GNU BinUtils work. +set (LINKER_NAME "riscv64-linux-gnu-ld.bfd" CACHE STRING "Linker name" FORCE) + set (HAS_PRE_1970_EXITCODE "0" CACHE STRING "Result from TRY_RUN" FORCE) set (HAS_PRE_1970_EXITCODE__TRYRUN_OUTPUT "" CACHE STRING "Output from TRY_RUN" FORCE) diff --git a/docker/packager/binary/Dockerfile b/docker/packager/binary/Dockerfile index 8809d469b46..74919bb2100 100644 --- a/docker/packager/binary/Dockerfile +++ b/docker/packager/binary/Dockerfile @@ -41,6 +41,9 @@ RUN add-apt-repository ppa:ubuntu-toolchain-r/test --yes \ && apt-get install gcc-11 g++-11 --yes \ && apt-get clean +# A cross-linker for RISC-V 64 (we need it, because LLVM's LLD does not work): +RUN apt-get install binutils-riscv64-linux-gnu + # Architecture of the image when BuildKit/buildx is used ARG TARGETARCH ARG NFPM_VERSION=2.16.0 From a4cc33ab001775081f1c925b148d23855e93076c Mon Sep 17 00:00:00 2001 From: clickhouse-robot-curie <103293335+clickhouse-robot-curie@users.noreply.github.com> Date: Sun, 14 Aug 2022 07:04:49 +0200 Subject: [PATCH 631/672] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 20340883853..b173add94e3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![ClickHouse — open source distributed column-oriented DBMS](https://github.com/ClickHouse/ClickHouse/raw/master/website/images/logo-400x240.png)](https://clickhouse.com) +[![ClickHouse — open source distributed column-oriented DBMS](https://github.com/ClickHouse/clickhouse-presentations/raw/master/images/logo-400x240.png)](https://clickhouse.com) ClickHouse® is an open-source column-oriented database management system that allows generating analytical data reports in real-time. From 216c1d8efaf6db612d43952674227a91a5be44f4 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 09:50:12 +0300 Subject: [PATCH 632/672] Revert "Use separate counter for RSS in global memory tracker." --- src/Common/MemoryTracker.cpp | 72 ++++++-------- src/Common/MemoryTracker.h | 6 +- src/Interpreters/AsynchronousMetrics.cpp | 94 +++++++++---------- src/Interpreters/AsynchronousMetrics.h | 1 - .../test.py | 3 +- .../0_stateless/00725_memory_tracking.sql | 2 +- 6 files changed, 74 insertions(+), 104 deletions(-) diff --git a/src/Common/MemoryTracker.cpp b/src/Common/MemoryTracker.cpp index c2f04b2190c..f467fccc514 100644 --- a/src/Common/MemoryTracker.cpp +++ b/src/Common/MemoryTracker.cpp @@ -84,7 +84,6 @@ static constexpr size_t log_peak_memory_usage_every = 1ULL << 30; MemoryTracker total_memory_tracker(nullptr, VariableContext::Global); -std::atomic MemoryTracker::rss; MemoryTracker::MemoryTracker(VariableContext level_) : parent(&total_memory_tracker), level(level_) {} MemoryTracker::MemoryTracker(MemoryTracker * parent_, VariableContext level_) : parent(parent_), level(level_) {} @@ -129,17 +128,6 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT if (MemoryTrackerBlockerInThread::isBlocked(level)) { - if (level == VariableContext::Global) - { - /// For global memory tracker always update memory usage. - amount.fetch_add(size, std::memory_order_relaxed); - rss.fetch_add(size, std::memory_order_relaxed); - - auto metric_loaded = metric.load(std::memory_order_relaxed); - if (metric_loaded != CurrentMetrics::end()) - CurrentMetrics::add(metric_loaded, size); - } - /// Since the MemoryTrackerBlockerInThread should respect the level, we should go to the next parent. if (auto * loaded_next = parent.load(std::memory_order_relaxed)) loaded_next->allocImpl(size, throw_if_memory_exceeded, @@ -160,6 +148,24 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT Int64 current_hard_limit = hard_limit.load(std::memory_order_relaxed); Int64 current_profiler_limit = profiler_limit.load(std::memory_order_relaxed); + /// Cap the limit to the total_memory_tracker, since it may include some drift + /// for user-level memory tracker. + /// + /// And since total_memory_tracker is reset to the process resident + /// memory peridically (in AsynchronousMetrics::update()), any limit can be + /// capped to it, to avoid possible drift. + if (unlikely(current_hard_limit + && will_be > current_hard_limit + && level == VariableContext::User)) + { + Int64 total_amount = total_memory_tracker.get(); + if (amount > total_amount) + { + set(total_amount); + will_be = size + total_amount; + } + } + std::bernoulli_distribution fault(fault_probability); if (unlikely(fault_probability && fault(thread_local_rng)) && memoryTrackerCanThrow(level, true) && throw_if_memory_exceeded) { @@ -197,21 +203,7 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT allocation_traced = true; } - Int64 amount_to_check = will_be; - bool used_rss_counter = false; - /// For Global memory tracker, additionally check RSS. - /// It is needed to avoid possible OOM. - /// We can't track all memory allocations from external libraries (yet). - if (level == VariableContext::Global) - { - if (Int64 current_rss = size + rss.fetch_add(size, std::memory_order_relaxed); current_rss > will_be) - { - used_rss_counter = true; - amount_to_check = current_rss; - } - } - - if (unlikely(current_hard_limit && amount_to_check > current_hard_limit) && memoryTrackerCanThrow(level, false) && throw_if_memory_exceeded) + if (unlikely(current_hard_limit && will_be > current_hard_limit) && memoryTrackerCanThrow(level, false) && throw_if_memory_exceeded) { OvercommitResult overcommit_result = OvercommitResult::NONE; if (auto * overcommit_tracker_ptr = overcommit_tracker.load(std::memory_order_relaxed); overcommit_tracker_ptr != nullptr && query_tracker != nullptr) @@ -225,11 +217,10 @@ void MemoryTracker::allocImpl(Int64 size, bool throw_if_memory_exceeded, MemoryT const auto * description = description_ptr.load(std::memory_order_relaxed); throw DB::Exception( DB::ErrorCodes::MEMORY_LIMIT_EXCEEDED, - "Memory limit{}{} {}exceeded: would use {} (attempt to allocate chunk of {} bytes), maximum: {}. OvercommitTracker decision: {}.", + "Memory limit{}{} exceeded: would use {} (attempt to allocate chunk of {} bytes), maximum: {}. OvercommitTracker decision: {}.", description ? " " : "", description ? description : "", - used_rss_counter ? "(RSS) " : "", - formatReadableSizeWithBinarySuffix(amount_to_check), + formatReadableSizeWithBinarySuffix(will_be), size, formatReadableSizeWithBinarySuffix(current_hard_limit), toDescription(overcommit_result)); @@ -295,20 +286,8 @@ bool MemoryTracker::updatePeak(Int64 will_be, bool log_memory_usage) void MemoryTracker::free(Int64 size) { - if (level == VariableContext::Global) - rss.fetch_sub(size, std::memory_order_relaxed); - if (MemoryTrackerBlockerInThread::isBlocked(level)) { - if (level == VariableContext::Global) - { - /// For global memory tracker always update memory usage. - amount.fetch_sub(size, std::memory_order_relaxed); - auto metric_loaded = metric.load(std::memory_order_relaxed); - if (metric_loaded != CurrentMetrics::end()) - CurrentMetrics::sub(metric_loaded, size); - } - /// Since the MemoryTrackerBlockerInThread should respect the level, we should go to the next parent. if (auto * loaded_next = parent.load(std::memory_order_relaxed)) loaded_next->free(size); @@ -323,7 +302,7 @@ void MemoryTracker::free(Int64 size) } Int64 accounted_size = size; - if (level == VariableContext::Thread || level == VariableContext::Global) + if (level == VariableContext::Thread) { /// Could become negative if memory allocated in this thread is freed in another one amount.fetch_sub(accounted_size, std::memory_order_relaxed); @@ -394,9 +373,12 @@ void MemoryTracker::reset() } -void MemoryTracker::setRSS(Int64 to) +void MemoryTracker::set(Int64 to) { - rss.store(to, std::memory_order_relaxed); + amount.store(to, std::memory_order_relaxed); + + bool log_memory_usage = true; + updatePeak(to, log_memory_usage); } diff --git a/src/Common/MemoryTracker.h b/src/Common/MemoryTracker.h index 8a4edb0d5b4..d9dd55a3a50 100644 --- a/src/Common/MemoryTracker.h +++ b/src/Common/MemoryTracker.h @@ -56,8 +56,6 @@ private: std::atomic hard_limit {0}; std::atomic profiler_limit {0}; - static std::atomic rss; - Int64 profiler_step = 0; /// To test exception safety of calling code, memory tracker throws an exception on each memory allocation with specified probability. @@ -201,8 +199,8 @@ public: /// Reset the accumulated data. void reset(); - /// Update RSS. - static void setRSS(Int64 to); + /// Reset current counter to a new value. + void set(Int64 to); /// Prints info about peak memory consumption into log. void logPeakMemoryUsage(); diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index b528df10087..f9bc22dd110 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -34,6 +34,12 @@ #endif +namespace CurrentMetrics +{ + extern const Metric MemoryTracking; +} + + namespace DB { @@ -385,7 +391,7 @@ uint64_t updateJemallocEpoch() } template -static Value saveJemallocMetricImpl(AsynchronousMetricValues & values, +static void saveJemallocMetricImpl(AsynchronousMetricValues & values, const std::string & jemalloc_full_name, const std::string & clickhouse_full_name) { @@ -393,23 +399,22 @@ static Value saveJemallocMetricImpl(AsynchronousMetricValues & values, size_t size = sizeof(value); mallctl(jemalloc_full_name.c_str(), &value, &size, nullptr, 0); values[clickhouse_full_name] = value; - return value; } template -static Value saveJemallocMetric(AsynchronousMetricValues & values, +static void saveJemallocMetric(AsynchronousMetricValues & values, const std::string & metric_name) { - return saveJemallocMetricImpl(values, + saveJemallocMetricImpl(values, fmt::format("stats.{}", metric_name), fmt::format("jemalloc.{}", metric_name)); } template -static Value saveAllArenasMetric(AsynchronousMetricValues & values, +static void saveAllArenasMetric(AsynchronousMetricValues & values, const std::string & metric_name) { - return saveJemallocMetricImpl(values, + saveJemallocMetricImpl(values, fmt::format("stats.arenas.{}.{}", MALLCTL_ARENAS_ALL, metric_name), fmt::format("jemalloc.arenas.all.{}", metric_name)); } @@ -650,31 +655,6 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti } } -#if USE_JEMALLOC - // 'epoch' is a special mallctl -- it updates the statistics. Without it, all - // the following calls will return stale values. It increments and returns - // the current epoch number, which might be useful to log as a sanity check. - auto epoch = updateJemallocEpoch(); - new_values["jemalloc.epoch"] = epoch; - - // Collect the statistics themselves. - [[maybe_unused]] size_t je_malloc_allocated = saveJemallocMetric(new_values, "allocated"); - saveJemallocMetric(new_values, "active"); - saveJemallocMetric(new_values, "metadata"); - saveJemallocMetric(new_values, "metadata_thp"); - saveJemallocMetric(new_values, "resident"); - [[maybe_unused]] size_t je_malloc_mapped = saveJemallocMetric(new_values, "mapped"); - saveJemallocMetric(new_values, "retained"); - saveJemallocMetric(new_values, "background_thread.num_threads"); - saveJemallocMetric(new_values, "background_thread.num_runs"); - saveJemallocMetric(new_values, "background_thread.run_intervals"); - saveAllArenasMetric(new_values, "pactive"); - saveAllArenasMetric(new_values, "pdirty"); - saveAllArenasMetric(new_values, "pmuzzy"); - saveAllArenasMetric(new_values, "dirty_purged"); - saveAllArenasMetric(new_values, "muzzy_purged"); -#endif - /// Process process memory usage according to OS #if defined(OS_LINUX) || defined(OS_FREEBSD) { @@ -694,34 +674,21 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti { Int64 amount = total_memory_tracker.get(); Int64 peak = total_memory_tracker.getPeak(); - Int64 rss = data.resident; + Int64 new_amount = data.resident; - new_values["MemoryTrackingPeak"] = peak; - -#if USE_JEMALLOC - /// This is a memory which is kept by allocator. - /// Remove it from RSS to decrease memory drift. - rss -= je_malloc_mapped - je_malloc_allocated; -#endif - /// In theory, the difference between RSS and tracked memory should be caused by - /// external libraries which allocation we can't track. - Int64 rss_drift = rss - amount; - Int64 difference = rss_drift - last_logged_rss_drift; + Int64 difference = new_amount - amount; /// Log only if difference is high. This is for convenience. The threshold is arbitrary. if (difference >= 1048576 || difference <= -1048576) - { LOG_TRACE(log, - "MemoryTracking: allocated {}, peak {}, RSS (adjusted) {}, difference: {}", + "MemoryTracking: was {}, peak {}, will set to {} (RSS), difference: {}", ReadableSize(amount), ReadableSize(peak), - ReadableSize(rss), - ReadableSize(rss_drift)); + ReadableSize(new_amount), + ReadableSize(difference)); - last_logged_rss_drift = rss_drift; - } - - total_memory_tracker.setRSS(rss); + total_memory_tracker.set(new_amount); + CurrentMetrics::set(CurrentMetrics::MemoryTracking, new_amount); } } #endif @@ -1592,6 +1559,31 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti } #endif +#if USE_JEMALLOC + // 'epoch' is a special mallctl -- it updates the statistics. Without it, all + // the following calls will return stale values. It increments and returns + // the current epoch number, which might be useful to log as a sanity check. + auto epoch = updateJemallocEpoch(); + new_values["jemalloc.epoch"] = epoch; + + // Collect the statistics themselves. + saveJemallocMetric(new_values, "allocated"); + saveJemallocMetric(new_values, "active"); + saveJemallocMetric(new_values, "metadata"); + saveJemallocMetric(new_values, "metadata_thp"); + saveJemallocMetric(new_values, "resident"); + saveJemallocMetric(new_values, "mapped"); + saveJemallocMetric(new_values, "retained"); + saveJemallocMetric(new_values, "background_thread.num_threads"); + saveJemallocMetric(new_values, "background_thread.num_runs"); + saveJemallocMetric(new_values, "background_thread.run_intervals"); + saveAllArenasMetric(new_values, "pactive"); + saveAllArenasMetric(new_values, "pdirty"); + saveAllArenasMetric(new_values, "pmuzzy"); + saveAllArenasMetric(new_values, "dirty_purged"); + saveAllArenasMetric(new_values, "muzzy_purged"); +#endif + /// Add more metrics as you wish. new_values["AsynchronousMetricsCalculationTimeSpent"] = watch.elapsedSeconds(); diff --git a/src/Interpreters/AsynchronousMetrics.h b/src/Interpreters/AsynchronousMetrics.h index 3ba84219cb2..e4bcb2890f3 100644 --- a/src/Interpreters/AsynchronousMetrics.h +++ b/src/Interpreters/AsynchronousMetrics.h @@ -78,7 +78,6 @@ private: #if defined(OS_LINUX) || defined(OS_FREEBSD) MemoryStatisticsOS memory_stat; - Int64 last_logged_rss_drift = 0; #endif #if defined(OS_LINUX) diff --git a/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py b/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py index 35c29959a43..c95bbfda708 100644 --- a/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py +++ b/tests/integration/test_input_format_parallel_parsing_memory_tracking/test.py @@ -42,8 +42,7 @@ def test_memory_tracking_total(): "bash", "-c", "clickhouse local -q \"SELECT arrayStringConcat(arrayMap(x->toString(cityHash64(x)), range(1000)), ' ') from numbers(10000)\" > data.json", - ], - user="root", + ] ) for it in range(0, 20): diff --git a/tests/queries/0_stateless/00725_memory_tracking.sql b/tests/queries/0_stateless/00725_memory_tracking.sql index ee81502ad83..b7356f0a6aa 100644 --- a/tests/queries/0_stateless/00725_memory_tracking.sql +++ b/tests/queries/0_stateless/00725_memory_tracking.sql @@ -1,4 +1,4 @@ --- Tags: no-replicated-database, no-tsan, no-asan, no-msan +-- Tags: no-replicated-database SELECT least(value, 0) FROM system.metrics WHERE metric = 'MemoryTracking'; SELECT length(range(100000000)); From 001aca3b4776e929de93adf44d1629edae70b55b Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 09:17:02 +0200 Subject: [PATCH 633/672] ProfileEvents for incomplete data due to query complexity settings --- src/Common/ProfileEvents.cpp | 4 +++- src/QueryPipeline/ExecutionSpeedLimits.cpp | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Common/ProfileEvents.cpp b/src/Common/ProfileEvents.cpp index eb144402b52..1d58ed84a6c 100644 --- a/src/Common/ProfileEvents.cpp +++ b/src/Common/ProfileEvents.cpp @@ -360,7 +360,9 @@ M(KeeperSnapshotApplysFailed, "Number of failed snapshot applying")\ M(KeeperReadSnapshot, "Number of snapshot read(serialization)")\ M(KeeperSaveSnapshot, "Number of snapshot save")\ - + \ + M(OverflowBreak, "Number of times, data processing was cancelled by query complexity limitation with setting '*_overflow_mode' = 'break' and the result is incomplete.") \ + M(OverflowThrow, "Number of times, data processing was cancelled by query complexity limitation with setting '*_overflow_mode' = 'throw' and exception was thrown.") \ namespace ProfileEvents { diff --git a/src/QueryPipeline/ExecutionSpeedLimits.cpp b/src/QueryPipeline/ExecutionSpeedLimits.cpp index d66344d5a05..b88e70cd14f 100644 --- a/src/QueryPipeline/ExecutionSpeedLimits.cpp +++ b/src/QueryPipeline/ExecutionSpeedLimits.cpp @@ -9,6 +9,8 @@ namespace ProfileEvents { extern const Event ThrottlerSleepMicroseconds; + extern const Event OverflowBreak; + extern const Event OverflowThrow; } @@ -104,8 +106,10 @@ static bool handleOverflowMode(OverflowMode mode, int code, fmt::format_string(args)...); case OverflowMode::BREAK: + ProfileEvents::increment(ProfileEvents::OverflowBreak); return false; default: throw Exception("Logical error: unknown overflow mode", ErrorCodes::LOGICAL_ERROR); From 8dab7908ade34633a97dcbe6a000cf76a01b62d8 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 09:35:00 +0200 Subject: [PATCH 634/672] Add a test --- .../02385_profile_events_overflow.reference | 3 +++ .../02385_profile_events_overflow.sql | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/queries/0_stateless/02385_profile_events_overflow.reference create mode 100644 tests/queries/0_stateless/02385_profile_events_overflow.sql diff --git a/tests/queries/0_stateless/02385_profile_events_overflow.reference b/tests/queries/0_stateless/02385_profile_events_overflow.reference new file mode 100644 index 00000000000..e8183f05f5d --- /dev/null +++ b/tests/queries/0_stateless/02385_profile_events_overflow.reference @@ -0,0 +1,3 @@ +1 +1 +1 diff --git a/tests/queries/0_stateless/02385_profile_events_overflow.sql b/tests/queries/0_stateless/02385_profile_events_overflow.sql new file mode 100644 index 00000000000..518efab6d48 --- /dev/null +++ b/tests/queries/0_stateless/02385_profile_events_overflow.sql @@ -0,0 +1,20 @@ +-- Tags: no-parallel +SET system_events_show_zero_values = 1; + +CREATE TEMPORARY TABLE t (x UInt64); +INSERT INTO t SELECT value FROM system.events WHERE event = 'OverflowBreak'; +SELECT count() FROM system.numbers FORMAT Null SETTINGS max_rows_to_read = 1, read_overflow_mode = 'break'; +INSERT INTO t SELECT value FROM system.events WHERE event = 'OverflowBreak'; +SELECT max(x) - min(x) FROM t; + +TRUNCATE TABLE t; +INSERT INTO t SELECT value FROM system.events WHERE event = 'OverflowThrow'; +SELECT count() FROM system.numbers SETTINGS max_rows_to_read = 1, read_overflow_mode = 'throw'; -- { serverError 158 } +INSERT INTO t SELECT value FROM system.events WHERE event = 'OverflowThrow'; +SELECT max(x) - min(x) FROM t; + +TRUNCATE TABLE t; +INSERT INTO t SELECT value FROM system.events WHERE event = 'OverflowAny'; +SELECT number, count() FROM numbers(100000) GROUP BY number FORMAT Null SETTINGS max_rows_to_group_by = 1, group_by_overflow_mode = 'any'; +INSERT INTO t SELECT value FROM system.events WHERE event = 'OverflowAny'; +SELECT max(x) - min(x) FROM t; From 1a8ddf2956ce306c91f27378e4877ab8f81c46a4 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 14 Aug 2022 09:35:22 +0200 Subject: [PATCH 635/672] Addition to prev. revision --- src/Common/ProfileEvents.cpp | 1 + src/Interpreters/Aggregator.cpp | 16 +++++++++++----- src/QueryPipeline/SizeLimits.cpp | 22 +++++++++++++++++++--- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/Common/ProfileEvents.cpp b/src/Common/ProfileEvents.cpp index 1d58ed84a6c..00ccaca00d4 100644 --- a/src/Common/ProfileEvents.cpp +++ b/src/Common/ProfileEvents.cpp @@ -363,6 +363,7 @@ \ M(OverflowBreak, "Number of times, data processing was cancelled by query complexity limitation with setting '*_overflow_mode' = 'break' and the result is incomplete.") \ M(OverflowThrow, "Number of times, data processing was cancelled by query complexity limitation with setting '*_overflow_mode' = 'throw' and exception was thrown.") \ + M(OverflowAny, "Number of times approximate GROUP BY was in effect: when aggregation was performed only on top of first 'max_rows_to_group_by' unique keys and other keys were ignored due to 'group_by_overflow_mode' = 'any'.") \ namespace ProfileEvents { diff --git a/src/Interpreters/Aggregator.cpp b/src/Interpreters/Aggregator.cpp index 07360a70595..6e101005599 100644 --- a/src/Interpreters/Aggregator.cpp +++ b/src/Interpreters/Aggregator.cpp @@ -34,11 +34,14 @@ namespace ProfileEvents { -extern const Event ExternalAggregationWritePart; -extern const Event ExternalAggregationCompressedBytes; -extern const Event ExternalAggregationUncompressedBytes; -extern const Event AggregationPreallocatedElementsInHashTables; -extern const Event AggregationHashTablesInitializedAsTwoLevel; + extern const Event ExternalAggregationWritePart; + extern const Event ExternalAggregationCompressedBytes; + extern const Event ExternalAggregationUncompressedBytes; + extern const Event AggregationPreallocatedElementsInHashTables; + extern const Event AggregationHashTablesInitializedAsTwoLevel; + extern const Event OverflowThrow; + extern const Event OverflowBreak; + extern const Event OverflowAny; } namespace @@ -1667,14 +1670,17 @@ bool Aggregator::checkLimits(size_t result_size, bool & no_more_keys) const switch (params.group_by_overflow_mode) { case OverflowMode::THROW: + ProfileEvents::increment(ProfileEvents::OverflowThrow); throw Exception("Limit for rows to GROUP BY exceeded: has " + toString(result_size) + " rows, maximum: " + toString(params.max_rows_to_group_by), ErrorCodes::TOO_MANY_ROWS); case OverflowMode::BREAK: + ProfileEvents::increment(ProfileEvents::OverflowBreak); return false; case OverflowMode::ANY: + ProfileEvents::increment(ProfileEvents::OverflowAny); no_more_keys = true; break; } diff --git a/src/QueryPipeline/SizeLimits.cpp b/src/QueryPipeline/SizeLimits.cpp index 3fe73f61402..76832b1f951 100644 --- a/src/QueryPipeline/SizeLimits.cpp +++ b/src/QueryPipeline/SizeLimits.cpp @@ -1,9 +1,17 @@ #include #include #include +#include #include +namespace ProfileEvents +{ + extern const Event OverflowThrow; + extern const Event OverflowBreak; +} + + namespace DB { @@ -12,20 +20,26 @@ bool SizeLimits::check(UInt64 rows, UInt64 bytes, const char * what, int too_man if (overflow_mode == OverflowMode::THROW) { if (max_rows && rows > max_rows) + { + ProfileEvents::increment(ProfileEvents::OverflowThrow); throw Exception( too_many_rows_exception_code, "Limit for {} exceeded, max rows: {}, current rows: {}", what, formatReadableQuantity(max_rows), formatReadableQuantity(rows)); + } if (max_bytes && bytes > max_bytes) + { + ProfileEvents::increment(ProfileEvents::OverflowThrow); throw Exception( too_many_bytes_exception_code, "Limit for {} exceeded, max bytes: {}, current bytes: {}", what, ReadableSize(max_bytes), ReadableSize(bytes)); + } return true; } @@ -37,10 +51,12 @@ bool SizeLimits::softCheck(UInt64 rows, UInt64 bytes) const { /// For result_overflow_mode = 'break', we check for >= to tell that no more data is needed. /// Last chunk will be processed. - if (max_rows && rows >= max_rows) - return false; - if (max_bytes && bytes >= max_bytes) + if ((max_rows && rows >= max_rows) + || (max_bytes && bytes >= max_bytes)) + { + ProfileEvents::increment(ProfileEvents::OverflowBreak); return false; + } return true; } From 71dd2a19fc6ee68ff0f182c5ad1db715222c210e Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Sun, 14 Aug 2022 16:14:13 +0800 Subject: [PATCH 636/672] fix arrayDiff --- src/Functions/array/arrayDifference.cpp | 6 +++++- .../0_stateless/01716_array_difference_overflow.reference | 4 ++++ .../queries/0_stateless/01716_array_difference_overflow.sql | 6 ++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Functions/array/arrayDifference.cpp b/src/Functions/array/arrayDifference.cpp index a19f04f4e02..24050c7db4e 100644 --- a/src/Functions/array/arrayDifference.cpp +++ b/src/Functions/array/arrayDifference.cpp @@ -84,7 +84,7 @@ struct ArrayDifferenceImpl } else { - dst[pos] = curr - prev; + dst[pos] = static_cast(curr) - static_cast(prev); } prev = curr; @@ -102,7 +102,11 @@ struct ArrayDifferenceImpl const ColVecType * column = checkAndGetColumn(&*mapped); if (!column) + { + fmt::print(stderr, "nope\n"); return false; + } + fmt::print(stderr, "boom\n"); const IColumn::Offsets & offsets = array.getOffsets(); const typename ColVecType::Container & data = column->getData(); diff --git a/tests/queries/0_stateless/01716_array_difference_overflow.reference b/tests/queries/0_stateless/01716_array_difference_overflow.reference index 5297534679e..a4e34800849 100644 --- a/tests/queries/0_stateless/01716_array_difference_overflow.reference +++ b/tests/queries/0_stateless/01716_array_difference_overflow.reference @@ -1 +1,5 @@ [0,9223372036854710272] +[0,-9] +[0,-9] +[0,-9] +[0,-9] diff --git a/tests/queries/0_stateless/01716_array_difference_overflow.sql b/tests/queries/0_stateless/01716_array_difference_overflow.sql index 3d153725294..747e0ad757d 100644 --- a/tests/queries/0_stateless/01716_array_difference_overflow.sql +++ b/tests/queries/0_stateless/01716_array_difference_overflow.sql @@ -1,2 +1,8 @@ -- Overflow is Ok and behaves as the CPU does it. SELECT arrayDifference([65536, -9223372036854775808]); + +-- Diff of unsigned int -> int +SELECT arrayDifference( cast([10, 1], 'Array(UInt8)')); +SELECT arrayDifference( cast([10, 1], 'Array(UInt16)')); +SELECT arrayDifference( cast([10, 1], 'Array(UInt32)')); +SELECT arrayDifference( cast([10, 1], 'Array(UInt64)')); From 272447c5dc77df552d2308b670a7cd1e1a637cb4 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Sun, 14 Aug 2022 16:19:09 +0800 Subject: [PATCH 637/672] remove junk log --- src/Functions/array/arrayDifference.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Functions/array/arrayDifference.cpp b/src/Functions/array/arrayDifference.cpp index 24050c7db4e..118fe87270b 100644 --- a/src/Functions/array/arrayDifference.cpp +++ b/src/Functions/array/arrayDifference.cpp @@ -102,11 +102,7 @@ struct ArrayDifferenceImpl const ColVecType * column = checkAndGetColumn(&*mapped); if (!column) - { - fmt::print(stderr, "nope\n"); return false; - } - fmt::print(stderr, "boom\n"); const IColumn::Offsets & offsets = array.getOffsets(); const typename ColVecType::Container & data = column->getData(); From 297e49ff6cc3e8b5505f6650c87c6f0b9e59f6e0 Mon Sep 17 00:00:00 2001 From: Lorenzo Mangani Date: Sun, 14 Aug 2022 10:53:51 +0200 Subject: [PATCH 638/672] Add metrico applications (qryn, clickhouse-mate) --- docs/en/interfaces/third-party/gui.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/en/interfaces/third-party/gui.md b/docs/en/interfaces/third-party/gui.md index aefd763b21e..b67bf2a081a 100644 --- a/docs/en/interfaces/third-party/gui.md +++ b/docs/en/interfaces/third-party/gui.md @@ -73,6 +73,18 @@ Features: ClickHouse datasource plugin provides a support for ClickHouse as a backend database. +### qryn (#qryn) + +[qryn](https://metrico.in) is a polyglot, high-performance observability stack for ClickHouse _(formerly cLoki)_ with native Grafana integrations allowing users to ingest and analyze logs, metrics and telemetry traces from any agent supporting Loki/LogQL, Prometheus/PromQL, OTLP/Tempo, Elastic, InfluxDB and many more. + +Features: + +- Built in Explore UI and LogQL CLI for querying, extracting and visualizing data +- Native Grafana APIs support for querying, processing, ingesting, tracing and alerting without plugins +- Powerful pipeline to dynamically search, filter and extract data from logs, events, traces and beyond +- Ingestion and PUSH APIs transparently compatible with LogQL, PromQL, InfluxDB, Elastic and many more +- Ready to use with Agents such as Promtail, Grafana-Agent, Vector, Logstash, Telegraf and many others + ### DBeaver {#dbeaver} [DBeaver](https://dbeaver.io/) - universal desktop database client with ClickHouse support. @@ -169,6 +181,20 @@ Features: - Supports alarm configuration - Support permission granularity to library and table permission configuration +### ClickHouse-Mate {#clickmate} + +[ClickHouse-Mate](https://github.com/metrico/clickhouse-mate) is an angular web client + user interface to search and explore data in ClickHouse. + +Features: + +- ClickHouse SQL Query autocompletion +- Fast Database and Table tree navigation +- Advanced result Filtering and Sorting +- Inline ClickHouse SQL documentation +- Query Presets and History +- 100% browser based, no server/backend + + ## Commercial {#commercial} ### DataGrip {#datagrip} From 5f90de93e357912e3d752446f511875962353deb Mon Sep 17 00:00:00 2001 From: Lorenzo Mangani Date: Sun, 14 Aug 2022 11:00:57 +0200 Subject: [PATCH 639/672] Update gui.md --- docs/en/interfaces/third-party/gui.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/interfaces/third-party/gui.md b/docs/en/interfaces/third-party/gui.md index b67bf2a081a..7bcc8832da2 100644 --- a/docs/en/interfaces/third-party/gui.md +++ b/docs/en/interfaces/third-party/gui.md @@ -194,6 +194,8 @@ Features: - Query Presets and History - 100% browser based, no server/backend +The client is available for instant usage through github pages: https://metrico.github.io/clickhouse-mate/ + ## Commercial {#commercial} From 2cb78c7220d56e357a00b1c428219bb81c023d63 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Mon, 15 Aug 2022 00:36:25 +0000 Subject: [PATCH 640/672] Detailed comment about overflow check --- src/Functions/FunctionBinaryArithmetic.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Functions/FunctionBinaryArithmetic.h b/src/Functions/FunctionBinaryArithmetic.h index 91878f6e192..fa13008f352 100644 --- a/src/Functions/FunctionBinaryArithmetic.h +++ b/src/Functions/FunctionBinaryArithmetic.h @@ -1184,9 +1184,13 @@ public: { if (context->getSettingsRef().decimal_check_overflow) { - /// relay on "knowledge" of current decimal division implementation to avoid overflow upfront based on operand's scale - /// for more detains see issue #30341 - /// TODO: comment with more detailed explanation + /// Check overflow by using operands scale (based on big decimal division implementation details): + /// big decimal arithmetic is based on big integers, decimal operands are converted to big integers + /// i.e. int_operand = decimal_operand*10^scale + /// For division, left operand will be scaled by right operand scale also to do big integer division, + /// BigInt result = left*10^(left_scale + right_scale) / right * 10^right_scale + /// So, we can check upfront possible overflow just by checking max scale used for left operand + /// Note: it doesn't detect all possible overflow during big decimal division if (left.getScale() + right.getScale() > ResultDataType::maxPrecision()) throw Exception("Overflow during decimal division", ErrorCodes::DECIMAL_OVERFLOW); } From 24371f6cf0b246b641fb7d28c45ab18594a2eaa1 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 15 Aug 2022 06:56:29 +0200 Subject: [PATCH 641/672] Simplify the code and check what will happen --- src/Interpreters/ExpressionAnalyzer.h | 6 ++---- src/Storages/StorageJoin.h | 6 +----- src/Storages/StorageSet.cpp | 10 +++------- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/Interpreters/ExpressionAnalyzer.h b/src/Interpreters/ExpressionAnalyzer.h index f158df30204..ddb41a00f84 100644 --- a/src/Interpreters/ExpressionAnalyzer.h +++ b/src/Interpreters/ExpressionAnalyzer.h @@ -139,14 +139,12 @@ public: void makeWindowDescriptionFromAST(const Context & context, const WindowDescriptions & existing_descriptions, WindowDescription & desc, const IAST * ast); void makeWindowDescriptions(ActionsDAGPtr actions); - /** - * Create Set from a subquery or a table expression in the query. The created set is suitable for using the index. + /** Create Set from a subquery or a table expression in the query. The created set is suitable for using the index. * The set will not be created if its size hits the limit. */ void tryMakeSetForIndexFromSubquery(const ASTPtr & subquery_or_table_name, const SelectQueryOptions & query_options = {}); - /** - * Checks if subquery is not a plain StorageSet. + /** Checks if subquery is not a plain StorageSet. * Because while making set we will read data from StorageSet which is not allowed. * Returns valid SetPtr from StorageSet if the latter is used after IN or nullptr otherwise. */ diff --git a/src/Storages/StorageJoin.h b/src/Storages/StorageJoin.h index 2a28ff6d01b..390af09422c 100644 --- a/src/Storages/StorageJoin.h +++ b/src/Storages/StorageJoin.h @@ -76,14 +76,10 @@ public: Block getRightSampleBlock() const { auto metadata_snapshot = getInMemoryMetadataPtr(); - Block block = metadata_snapshot->getSampleBlock().sortColumns(); + Block block = metadata_snapshot->getSampleBlock(); if (use_nulls && isLeftOrFull(kind)) - { for (auto & col : block) - { JoinCommon::convertColumnToNullable(col); - } - } return block; } diff --git a/src/Storages/StorageSet.cpp b/src/Storages/StorageSet.cpp index 2f586a3c26c..48f8adfece2 100644 --- a/src/Storages/StorageSet.cpp +++ b/src/Storages/StorageSet.cpp @@ -81,12 +81,11 @@ SetOrJoinSink::SetOrJoinSink( void SetOrJoinSink::consume(Chunk chunk) { - /// Sort columns in the block. This is necessary, since Set and Join count on the same column order in different blocks. - Block sorted_block = getHeader().cloneWithColumns(chunk.detachColumns()).sortColumns(); + Block block = getHeader().cloneWithColumns(chunk.detachColumns()); - table.insertBlock(sorted_block, getContext()); + table.insertBlock(block, getContext()); if (persistent) - backup_stream.write(sorted_block); + backup_stream.write(block); } void SetOrJoinSink::onFinish() @@ -147,9 +146,7 @@ StorageSet::StorageSet( : StorageSetOrJoinBase{disk_, relative_path_, table_id_, columns_, constraints_, comment, persistent_} , set(std::make_shared(SizeLimits(), false, true)) { - Block header = getInMemoryMetadataPtr()->getSampleBlock(); - header = header.sortColumns(); set->setHeader(header.getColumnsWithTypeAndName()); restore(); @@ -170,7 +167,6 @@ void StorageSet::truncate(const ASTPtr &, const StorageMetadataPtr & metadata_sn disk->createDirectories(fs::path(path) / "tmp/"); Block header = metadata_snapshot->getSampleBlock(); - header = header.sortColumns(); increment = 0; set = std::make_shared(SizeLimits(), false, true); From 1bbe19d794225fb8c632605637c44f076d2d24f2 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Mon, 15 Aug 2022 14:04:07 +0800 Subject: [PATCH 642/672] Fix test --- tests/queries/0_stateless/02210_processors_profile_log_2.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02210_processors_profile_log_2.sh b/tests/queries/0_stateless/02210_processors_profile_log_2.sh index ab4a7309266..9bb2daf9d04 100755 --- a/tests/queries/0_stateless/02210_processors_profile_log_2.sh +++ b/tests/queries/0_stateless/02210_processors_profile_log_2.sh @@ -11,7 +11,7 @@ QUERY_ID=$(${CLICKHOUSE_CLIENT} -q "select lower(hex(reverse(reinterpretAsString ${CLICKHOUSE_CLIENT} --query_id "${QUERY_ID}" < Date: Mon, 15 Aug 2022 10:04:26 +0200 Subject: [PATCH 643/672] Add a test --- .../02386_set_columns_order.reference | 2 ++ .../0_stateless/02386_set_columns_order.sql | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/queries/0_stateless/02386_set_columns_order.reference create mode 100644 tests/queries/0_stateless/02386_set_columns_order.sql diff --git a/tests/queries/0_stateless/02386_set_columns_order.reference b/tests/queries/0_stateless/02386_set_columns_order.reference new file mode 100644 index 00000000000..6312b4b05a9 --- /dev/null +++ b/tests/queries/0_stateless/02386_set_columns_order.reference @@ -0,0 +1,2 @@ +3 Mary +1 diff --git a/tests/queries/0_stateless/02386_set_columns_order.sql b/tests/queries/0_stateless/02386_set_columns_order.sql new file mode 100644 index 00000000000..dab5ad30579 --- /dev/null +++ b/tests/queries/0_stateless/02386_set_columns_order.sql @@ -0,0 +1,22 @@ +DROP TABLE IF EXISTS userid_set; +DROP TABLE IF EXISTS userid_test; +DROP TABLE IF EXISTS userid_set2; + +CREATE TABLE userid_set(userid UInt64, name String) ENGINE = Set; +INSERT INTO userid_set VALUES (1, 'Mary'),(2, 'Jane'),(3, 'Mary'),(4, 'Jack'); + +CREATE TABLE userid_test (userid UInt64, name String) ENGINE = MergeTree() PARTITION BY (intDiv(userid, 500)) ORDER BY (userid) SETTINGS index_granularity = 8192; +INSERT INTO userid_test VALUES (1, 'Jack'),(2, 'Mary'),(3, 'Mary'),(4, 'John'),(5, 'Mary'); + +SELECT * FROM userid_test WHERE (userid, name) IN (userid_set); + +CREATE TABLE userid_set2(userid UInt64, name String, birthdate Date) ENGINE = Set; +INSERT INTO userid_set2 values (1,'John', '1990-01-01'); + +WITH 'John' AS name, toDate('1990-01-01') AS birthdate +SELECT * FROM numbers(10) +WHERE (number, name, birthdate) IN (userid_set2); + +DROP TABLE userid_set; +DROP TABLE userid_test; +DROP TABLE userid_set2; From 0341e16dc56d5c9d129c4e714f352075954e7e47 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Mon, 15 Aug 2022 18:22:53 +0800 Subject: [PATCH 644/672] Fix again --- .../02210_processors_profile_log_2.reference | 10 ++++++++-- .../0_stateless/02210_processors_profile_log_2.sh | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/queries/0_stateless/02210_processors_profile_log_2.reference b/tests/queries/0_stateless/02210_processors_profile_log_2.reference index b5d3e561efb..5467c7ef2ba 100644 --- a/tests/queries/0_stateless/02210_processors_profile_log_2.reference +++ b/tests/queries/0_stateless/02210_processors_profile_log_2.reference @@ -1,5 +1,11 @@ 499999500000 -AggregatingTransform 1000002 8000016 2 16 +AggregatingTransform 1000001 8000008 1 8 +ConvertingAggregatedToChunksTransform 0 0 1 8 ExpressionTransform 1 8 1 8 ExpressionTransform 1000000 8000000 1000000 8000000 -NumbersMt 3 24 1000003 8000024 +LazyOutputFormat 1 8 0 0 +LimitsCheckingTransform 1 8 1 8 +NullSource 0 0 0 0 +NumbersMt 0 0 1000000 8000000 +Resize 1 8 1 8 +Resize 1 8 1 8 diff --git a/tests/queries/0_stateless/02210_processors_profile_log_2.sh b/tests/queries/0_stateless/02210_processors_profile_log_2.sh index 9bb2daf9d04..93eabc2f0fe 100755 --- a/tests/queries/0_stateless/02210_processors_profile_log_2.sh +++ b/tests/queries/0_stateless/02210_processors_profile_log_2.sh @@ -16,4 +16,4 @@ EOF ${CLICKHOUSE_CLIENT} -q "SYSTEM FLUSH LOGS" -${CLICKHOUSE_CLIENT} -q "select any(name) name, sum(input_rows), sum(input_bytes), sum(output_rows), sum(output_bytes) from system.processors_profile_log where query_id = '${QUERY_ID}' group by plan_step, plan_group order by name, sum(input_rows), sum(input_bytes), sum(output_rows), sum(output_bytes)" +${CLICKHOUSE_CLIENT} -q "select name, sum(input_rows), sum(input_bytes), sum(output_rows), sum(output_bytes) from system.processors_profile_log where query_id = '${QUERY_ID}' group by name, plan_step, plan_group order by name, sum(input_rows), sum(input_bytes), sum(output_rows), sum(output_bytes)" From 6f5a7c3bf79d2f7f2a400a021cb6c06d2b26e6fb Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 15 Aug 2022 12:30:47 +0200 Subject: [PATCH 645/672] fix a bug with symlinks detection --- programs/install/Install.cpp | 5 ++-- src/Common/DateLUT.cpp | 5 ++-- src/Common/filesystemHelpers.cpp | 20 +++++++++++++ src/Common/filesystemHelpers.h | 5 ++++ src/Databases/DatabaseAtomic.cpp | 5 ++-- src/Databases/DatabaseFactory.cpp | 49 +++++++++++++++++++++++-------- utils/check-style/check-style | 5 ++++ 7 files changed, 75 insertions(+), 19 deletions(-) diff --git a/programs/install/Install.cpp b/programs/install/Install.cpp index da133096baf..45c7c9a912e 100644 --- a/programs/install/Install.cpp +++ b/programs/install/Install.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -378,10 +379,10 @@ int mainEntryClickHouseInstall(int argc, char ** argv) if (fs::exists(symlink_path)) { - bool is_symlink = fs::is_symlink(symlink_path); + bool is_symlink = FS::isSymlink(symlink_path); fs::path points_to; if (is_symlink) - points_to = fs::weakly_canonical(fs::read_symlink(symlink_path)); + points_to = fs::weakly_canonical(FS::readSymlink(symlink_path)); if (is_symlink && points_to == main_bin_path) { diff --git a/src/Common/DateLUT.cpp b/src/Common/DateLUT.cpp index d14b63cd70a..1206015b764 100644 --- a/src/Common/DateLUT.cpp +++ b/src/Common/DateLUT.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -64,9 +65,9 @@ std::string determineDefaultTimeZone() /// /etc/localtime -> /usr/share/zoneinfo//UTC /// /usr/share/zoneinfo//UTC -> UCT /// But the preferred time zone name is pointed by the first link (UTC), and the second link is just an internal detail. - if (fs::is_symlink(tz_file_path)) + if (FS::isSymlink(tz_file_path)) { - tz_file_path = fs::read_symlink(tz_file_path); + tz_file_path = FS::readSymlink(tz_file_path); /// If it's relative - make it absolute. if (tz_file_path.is_relative()) tz_file_path = (fs::path("/etc/") / tz_file_path).lexically_normal(); diff --git a/src/Common/filesystemHelpers.cpp b/src/Common/filesystemHelpers.cpp index 1e8e53bf1ea..59fdf197b1e 100644 --- a/src/Common/filesystemHelpers.cpp +++ b/src/Common/filesystemHelpers.cpp @@ -351,4 +351,24 @@ void setModificationTime(const std::string & path, time_t time) if (utime(path.c_str(), &tb) != 0) DB::throwFromErrnoWithPath("Cannot set modification time for file: " + path, path, DB::ErrorCodes::PATH_ACCESS_DENIED); } + +bool isSymlink(const fs::path & path) +{ + /// Remove trailing stash before checking if file is symlink. + /// Let /path/to/link is a symlink to /path/to/target/dir/ directory. + /// In this case is_symlink("/path/to/link") is true, + /// but is_symlink("/path/to/link/") is false (it's a directory) + if (path.filename().empty()) + return fs::is_symlink(path.parent_path()); /// STYLE_CHECK_ALLOW_STD_FS_SYMLINK + return fs::is_symlink(path); /// STYLE_CHECK_ALLOW_STD_FS_SYMLINK +} + +fs::path readSymlink(const fs::path & path) +{ + /// See the comment for isSymlink + if (path.filename().empty()) + return fs::read_symlink(path.parent_path()); /// STYLE_CHECK_ALLOW_STD_FS_SYMLINK + return fs::read_symlink(path); /// STYLE_CHECK_ALLOW_STD_FS_SYMLINK +} + } diff --git a/src/Common/filesystemHelpers.h b/src/Common/filesystemHelpers.h index 2be3ea748fa..f96fe269eab 100644 --- a/src/Common/filesystemHelpers.h +++ b/src/Common/filesystemHelpers.h @@ -9,6 +9,7 @@ #include #include +namespace fs = std::filesystem; namespace DB { @@ -89,4 +90,8 @@ Poco::Timestamp getModificationTimestamp(const std::string & path); void setModificationTime(const std::string & path, time_t time); /// st_ctime time_t getChangeTime(const std::string & path); + +bool isSymlink(const fs::path & path); +fs::path readSymlink(const fs::path & path); + } diff --git a/src/Databases/DatabaseAtomic.cpp b/src/Databases/DatabaseAtomic.cpp index 92dae025dae..1d7ff40135c 100644 --- a/src/Databases/DatabaseAtomic.cpp +++ b/src/Databases/DatabaseAtomic.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -424,7 +425,7 @@ void DatabaseAtomic::beforeLoadingMetadata(ContextMutablePtr /*context*/, bool f /// Recreate symlinks to table data dirs in case of force restore, because some of them may be broken for (const auto & table_path : fs::directory_iterator(path_to_table_symlinks)) { - if (!fs::is_symlink(table_path)) + if (!FS::isSymlink(table_path)) { throw Exception(ErrorCodes::ABORTED, "'{}' is not a symlink. Atomic database should contains only symlinks.", std::string(table_path.path())); @@ -495,7 +496,7 @@ void DatabaseAtomic::tryCreateMetadataSymlink() fs::path metadata_symlink(path_to_metadata_symlink); if (fs::exists(metadata_symlink)) { - if (!fs::is_symlink(metadata_symlink)) + if (!FS::isSymlink(metadata_symlink)) throw Exception(ErrorCodes::FILE_ALREADY_EXISTS, "Directory {} exists", path_to_metadata_symlink); } else diff --git a/src/Databases/DatabaseFactory.cpp b/src/Databases/DatabaseFactory.cpp index 6213fa62e3c..c16de2d33a5 100644 --- a/src/Databases/DatabaseFactory.cpp +++ b/src/Databases/DatabaseFactory.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "config_core.h" @@ -60,8 +61,43 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; } +void cckMetadataPathForOrdinary(const ASTCreateQuery & create, const String & metadata_path) +{ + const String & engine_name = create.storage->engine->name; + const String & database_name = create.getDatabase(); + + if (engine_name != "Ordinary") + return; + + if (!FS::isSymlink(metadata_path)) + return; + + String target_path = FS::readSymlink(metadata_path).string(); + fs::path path_to_remove = metadata_path; + if (path_to_remove.filename().empty()) + path_to_remove = path_to_remove.parent_path(); + + /// Before 20.7 metadata/db_name.sql file might absent and Ordinary database was attached if there's metadata/db_name/ dir. + /// Between 20.7 and 22.7 metadata/db_name.sql was created in this case as well. + /// Since 20.7 `default` database is created with Atomic engine on the very first server run. + /// The problem is that if server crashed during the very first run and metadata/db_name/ -> store/whatever symlink was created + /// then it's considered as Ordinary database. And it even works somehow + /// until background task tries to remove unused dir from store/... + throw Exception(ErrorCodes::CANNOT_CREATE_DATABASE, + "Metadata directory {} for Ordinary database {} is a symbolic link to {}. " + "It may be a result of manual intervention, crash on very first server start or a bug. " + "Database cannot be attached (it's kind of protection from potential data loss). " + "Metadata directory must not be a symlink and must contain tables metadata files itself. " + "You have to resolve this manually. It can be done like this: rm {}; sudo -u clickhouse mv {} {};", + metadata_path, database_name, target_path, + quoteString(path_to_remove.string()), quoteString(target_path), quoteString(path_to_remove.string())); + +} + DatabasePtr DatabaseFactory::get(const ASTCreateQuery & create, const String & metadata_path, ContextPtr context) { + cckMetadataPathForOrdinary(create, metadata_path); + /// Creates store/xxx/ for Atomic fs::create_directories(fs::path(metadata_path).parent_path()); @@ -127,19 +163,6 @@ DatabasePtr DatabaseFactory::getImpl(const ASTCreateQuery & create, const String throw Exception(ErrorCodes::UNKNOWN_DATABASE_ENGINE, "Ordinary database engine is deprecated (see also allow_deprecated_database_ordinary setting)"); - /// Before 20.7 metadata/db_name.sql file might absent and Ordinary database was attached if there's metadata/db_name/ dir. - /// Between 20.7 and 22.7 metadata/db_name.sql was created in this case as well. - /// Since 20.7 `default` database is created with Atomic engine on the very first server run. - /// The problem is that if server crashed during the very first run and metadata/db_name/ -> store/whatever symlink was created - /// then it's considered as Ordinary database. And it even works somehow - /// until background task tries to remove onused dir from store/... - if (fs::is_symlink(metadata_path)) - throw Exception(ErrorCodes::CANNOT_CREATE_DATABASE, "Metadata directory {} for Ordinary database {} is a symbolic link to {}. " - "It may be a result of manual intervention, crash on very first server start or a bug. " - "Database cannot be attached (it's kind of protection from potential data loss). " - "Metadata directory must not be a symlink and must contain tables metadata files itself. " - "You have to resolve this manually.", - metadata_path, database_name, fs::read_symlink(metadata_path).string()); return std::make_shared(database_name, metadata_path, context); } diff --git a/utils/check-style/check-style b/utils/check-style/check-style index e540e0b126c..dac016003d5 100755 --- a/utils/check-style/check-style +++ b/utils/check-style/check-style @@ -346,3 +346,8 @@ fi # Forbid files that differ only by character case find $ROOT_PATH | sort -f | uniq -i -c | awk '{ if ($1 > 1) print }' + +# Forbid std::filesystem::is_symlink and std::filesystem::read_symlink, because it's easy to use them incorrectly +find $ROOT_PATH/{src,programs,utils} -name '*.h' -or -name '*.cpp' | + grep -vP $EXCLUDE_DIRS | + xargs grep -P '::(is|read)_symlink' | grep -v "STYLE_CHECK_ALLOW_STD_FS_SYMLINK" && echo "Use DB::FS::isSymlink and DB::FS::readSymlink instead" From d3cc2349868de3add3885e8973e314446f4d219e Mon Sep 17 00:00:00 2001 From: Nikita Mikhaylov Date: Mon, 15 Aug 2022 12:41:17 +0200 Subject: [PATCH 646/672] Parallel distributed insert select from *Cluster table functions (#39107) --- src/Interpreters/InterpreterInsertQuery.cpp | 2 +- src/Storages/HDFS/StorageHDFSCluster.cpp | 33 ++- src/Storages/HDFS/StorageHDFSCluster.h | 12 +- src/Storages/IStorageCluster.h | 28 +++ src/Storages/StorageDistributed.cpp | 175 ++++++++++++---- src/Storages/StorageDistributed.h | 4 + src/Storages/StorageReplicatedMergeTree.cpp | 105 ++++++++++ src/Storages/StorageReplicatedMergeTree.h | 5 + src/Storages/StorageS3Cluster.cpp | 32 ++- src/Storages/StorageS3Cluster.h | 12 +- .../test_s3_cluster/configs/cluster.xml | 17 +- tests/integration/test_s3_cluster/test.py | 189 +++++++++++++++--- 12 files changed, 526 insertions(+), 88 deletions(-) create mode 100644 src/Storages/IStorageCluster.h diff --git a/src/Interpreters/InterpreterInsertQuery.cpp b/src/Interpreters/InterpreterInsertQuery.cpp index 7b6066575ae..4c7823ddc4e 100644 --- a/src/Interpreters/InterpreterInsertQuery.cpp +++ b/src/Interpreters/InterpreterInsertQuery.cpp @@ -326,7 +326,7 @@ BlockIO InterpreterInsertQuery::execute() if (!query.table_function) getContext()->checkAccess(AccessType::INSERT, query.table_id, query_sample_block.getNames()); - if (query.select && table->isRemote() && settings.parallel_distributed_insert_select) + if (query.select && settings.parallel_distributed_insert_select) // Distributed INSERT SELECT distributed_pipeline = table->distributedWrite(query, getContext()); diff --git a/src/Storages/HDFS/StorageHDFSCluster.cpp b/src/Storages/HDFS/StorageHDFSCluster.cpp index 47a6fbf5eaa..200c8cb3320 100644 --- a/src/Storages/HDFS/StorageHDFSCluster.cpp +++ b/src/Storages/HDFS/StorageHDFSCluster.cpp @@ -41,7 +41,7 @@ StorageHDFSCluster::StorageHDFSCluster( const ColumnsDescription & columns_, const ConstraintsDescription & constraints_, const String & compression_method_) - : IStorage(table_id_) + : IStorageCluster(table_id_) , cluster_name(cluster_name_) , uri(uri_) , format_name(format_name_) @@ -74,13 +74,7 @@ Pipe StorageHDFSCluster::read( size_t /*max_block_size*/, unsigned /*num_streams*/) { - auto cluster = context->getCluster(cluster_name)->getClusterWithReplicasAsShards(context->getSettingsRef()); - - auto iterator = std::make_shared(context, uri); - auto callback = std::make_shared([iterator]() mutable -> String - { - return iterator->next(); - }); + createIteratorAndCallback(context); /// Calculate the header. This is significant, because some columns could be thrown away in some cases like query with count(*) Block header = @@ -140,6 +134,29 @@ QueryProcessingStage::Enum StorageHDFSCluster::getQueryProcessingStage( } +void StorageHDFSCluster::createIteratorAndCallback(ContextPtr context) const +{ + cluster = context->getCluster(cluster_name)->getClusterWithReplicasAsShards(context->getSettingsRef()); + + iterator = std::make_shared(context, uri); + callback = std::make_shared([iter = this->iterator]() mutable -> String { return iter->next(); }); +} + + +RemoteQueryExecutor::Extension StorageHDFSCluster::getTaskIteratorExtension(ContextPtr context) const +{ + createIteratorAndCallback(context); + return RemoteQueryExecutor::Extension{.task_iterator = callback}; +} + + +ClusterPtr StorageHDFSCluster::getCluster(ContextPtr context) const +{ + createIteratorAndCallback(context); + return cluster; +} + + NamesAndTypesList StorageHDFSCluster::getVirtuals() const { return NamesAndTypesList{ diff --git a/src/Storages/HDFS/StorageHDFSCluster.h b/src/Storages/HDFS/StorageHDFSCluster.h index 21ae73c11ea..64b5fa86e05 100644 --- a/src/Storages/HDFS/StorageHDFSCluster.h +++ b/src/Storages/HDFS/StorageHDFSCluster.h @@ -9,6 +9,7 @@ #include #include +#include #include namespace DB @@ -16,7 +17,7 @@ namespace DB class Context; -class StorageHDFSCluster : public IStorage +class StorageHDFSCluster : public IStorageCluster { public: StorageHDFSCluster( @@ -39,11 +40,20 @@ public: NamesAndTypesList getVirtuals() const override; + ClusterPtr getCluster(ContextPtr context) const override; + RemoteQueryExecutor::Extension getTaskIteratorExtension(ContextPtr context) const override; + private: String cluster_name; String uri; String format_name; String compression_method; + + mutable ClusterPtr cluster; + mutable std::shared_ptr iterator; + mutable std::shared_ptr callback; + + void createIteratorAndCallback(ContextPtr context) const; }; diff --git a/src/Storages/IStorageCluster.h b/src/Storages/IStorageCluster.h new file mode 100644 index 00000000000..ecab7266153 --- /dev/null +++ b/src/Storages/IStorageCluster.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +namespace DB +{ + + +/** + * Base cluster for Storages used in table functions like s3Cluster and hdfsCluster + * Needed for code simplification around parallel_distributed_insert_select + */ +class IStorageCluster: public IStorage +{ +public: + + explicit IStorageCluster(const StorageID & table_id_) : IStorage(table_id_) {} + + virtual ClusterPtr getCluster(ContextPtr context) const = 0; + virtual RemoteQueryExecutor::Extension getTaskIteratorExtension(ContextPtr context) const = 0; + + bool isRemote() const override { return true; } +}; + + +} diff --git a/src/Storages/StorageDistributed.cpp b/src/Storages/StorageDistributed.cpp index b3ea2cb9f5b..cc55b2ae271 100644 --- a/src/Storages/StorageDistributed.cpp +++ b/src/Storages/StorageDistributed.cpp @@ -59,6 +59,8 @@ #include #include +#include + #include #include #include @@ -759,55 +761,35 @@ SinkToStoragePtr StorageDistributed::write(const ASTPtr &, const StorageMetadata } -std::optional StorageDistributed::distributedWrite(const ASTInsertQuery & query, ContextPtr local_context) +std::optional StorageDistributed::distributedWriteBetweenDistributedTables(const StorageDistributed & src_distributed, const ASTInsertQuery & query, ContextPtr local_context) const { - QueryPipeline pipeline; - - const Settings & settings = local_context->getSettingsRef(); - if (settings.max_distributed_depth && local_context->getClientInfo().distributed_depth >= settings.max_distributed_depth) - throw Exception("Maximum distributed depth exceeded", ErrorCodes::TOO_LARGE_DISTRIBUTED_DEPTH); - - std::shared_ptr storage_src; - auto & select = query.select->as(); + const auto & settings = local_context->getSettingsRef(); auto new_query = std::dynamic_pointer_cast(query.clone()); - if (select.list_of_selects->children.size() == 1) + + /// Unwrap view() function. + if (src_distributed.remote_table_function_ptr) { - if (auto * select_query = select.list_of_selects->children.at(0)->as()) - { - JoinedTables joined_tables(Context::createCopy(local_context), *select_query); + const TableFunctionPtr src_table_function = + TableFunctionFactory::instance().get(src_distributed.remote_table_function_ptr, local_context); + const TableFunctionView * view_function = + assert_cast(src_table_function.get()); + new_query->select = view_function->getSelectQuery().clone(); + } + else + { + const auto select_with_union_query = std::make_shared(); + select_with_union_query->list_of_selects = std::make_shared(); - if (joined_tables.tablesCount() == 1) - { - storage_src = std::dynamic_pointer_cast(joined_tables.getLeftTableStorage()); - if (storage_src) - { - /// Unwrap view() function. - if (storage_src->remote_table_function_ptr) - { - const TableFunctionPtr src_table_function = - TableFunctionFactory::instance().get(storage_src->remote_table_function_ptr, local_context); - const TableFunctionView * view_function = - assert_cast(src_table_function.get()); - new_query->select = view_function->getSelectQuery().clone(); - } - else - { - const auto select_with_union_query = std::make_shared(); - select_with_union_query->list_of_selects = std::make_shared(); + auto * select = query.select->as().list_of_selects->children.at(0)->as(); + auto new_select_query = std::dynamic_pointer_cast(select->clone()); + select_with_union_query->list_of_selects->children.push_back(new_select_query); - auto new_select_query = std::dynamic_pointer_cast(select_query->clone()); - select_with_union_query->list_of_selects->children.push_back(new_select_query); + new_select_query->replaceDatabaseAndTable(src_distributed.getRemoteDatabaseName(), src_distributed.getRemoteTableName()); - new_select_query->replaceDatabaseAndTable(storage_src->getRemoteDatabaseName(), storage_src->getRemoteTableName()); - - new_query->select = select_with_union_query; - } - } - } - } + new_query->select = select_with_union_query; } - const Cluster::AddressesWithFailover & src_addresses = storage_src ? storage_src->getCluster()->getShardsAddresses() : Cluster::AddressesWithFailover{}; + const Cluster::AddressesWithFailover & src_addresses = src_distributed.getCluster()->getShardsAddresses(); const Cluster::AddressesWithFailover & dst_addresses = getCluster()->getShardsAddresses(); /// Compare addresses instead of cluster name, to handle remote()/cluster(). /// (since for remote()/cluster() the getClusterName() is empty string) @@ -822,7 +804,7 @@ std::optional StorageDistributed::distributedWrite(const ASTInser LOG_WARNING(log, "Parallel distributed INSERT SELECT is not possible " "(source cluster={} ({} addresses), destination cluster={} ({} addresses))", - storage_src ? storage_src->getClusterName() : "", + src_distributed.getClusterName(), src_addresses.size(), getClusterName(), dst_addresses.size()); @@ -849,6 +831,7 @@ std::optional StorageDistributed::distributedWrite(const ASTInser new_query_str = buf.str(); } + QueryPipeline pipeline; ContextMutablePtr query_context = Context::createCopy(local_context); ++query_context->getClientInfo().distributed_depth; @@ -882,6 +865,114 @@ std::optional StorageDistributed::distributedWrite(const ASTInser } +std::optional StorageDistributed::distributedWriteFromClusterStorage(const IStorageCluster & src_storage_cluster, const ASTInsertQuery & query, ContextPtr local_context) const +{ + const auto & settings = local_context->getSettingsRef(); + auto extension = src_storage_cluster.getTaskIteratorExtension(local_context); + + auto dst_cluster = getCluster(); + + auto new_query = std::dynamic_pointer_cast(query.clone()); + if (settings.parallel_distributed_insert_select == PARALLEL_DISTRIBUTED_INSERT_SELECT_ALL) + { + new_query->table_id = StorageID(getRemoteDatabaseName(), getRemoteTableName()); + /// Reset table function for INSERT INTO remote()/cluster() + new_query->table_function.reset(); + } + + String new_query_str; + { + WriteBufferFromOwnString buf; + IAST::FormatSettings ast_format_settings(buf, /*one_line*/ true); + ast_format_settings.always_quote_identifiers = true; + new_query->IAST::format(ast_format_settings); + new_query_str = buf.str(); + } + + QueryPipeline pipeline; + ContextMutablePtr query_context = Context::createCopy(local_context); + ++query_context->getClientInfo().distributed_depth; + + /// Here we take addresses from destination cluster and assume source table exists on these nodes + for (const auto & replicas : getCluster()->getShardsAddresses()) + { + /// There will be only one replica, because we consider each replica as a shard + for (const auto & node : replicas) + { + auto connection = std::make_shared( + node.host_name, node.port, query_context->getGlobalContext()->getCurrentDatabase(), + node.user, node.password, node.quota_key, node.cluster, node.cluster_secret, + "ParallelInsertSelectInititiator", + node.compression, + node.secure + ); + + auto remote_query_executor = std::make_shared( + connection, + new_query_str, + Block{}, + query_context, + /*throttler=*/nullptr, + Scalars{}, + Tables{}, + QueryProcessingStage::Complete, + extension); + + QueryPipeline remote_pipeline(std::make_shared(remote_query_executor, false, settings.async_socket_for_remote)); + remote_pipeline.complete(std::make_shared(remote_query_executor->getHeader())); + + pipeline.addCompletedPipeline(std::move(remote_pipeline)); + } + } + + return pipeline; +} + + +std::optional StorageDistributed::distributedWrite(const ASTInsertQuery & query, ContextPtr local_context) +{ + const Settings & settings = local_context->getSettingsRef(); + if (settings.max_distributed_depth && local_context->getClientInfo().distributed_depth >= settings.max_distributed_depth) + throw Exception("Maximum distributed depth exceeded", ErrorCodes::TOO_LARGE_DISTRIBUTED_DEPTH); + + auto & select = query.select->as(); + + StoragePtr src_storage; + + if (select.list_of_selects->children.size() == 1) + { + if (auto * select_query = select.list_of_selects->children.at(0)->as()) + { + JoinedTables joined_tables(Context::createCopy(local_context), *select_query); + + if (joined_tables.tablesCount() == 1) + { + src_storage = joined_tables.getLeftTableStorage(); + } + } + } + + if (!src_storage) + return {}; + + if (auto src_distributed = std::dynamic_pointer_cast(src_storage)) + { + return distributedWriteBetweenDistributedTables(*src_distributed, query, local_context); + } + else if (auto src_storage_cluster = std::dynamic_pointer_cast(src_storage)) + { + return distributedWriteFromClusterStorage(*src_storage_cluster, query, local_context); + } + else if (local_context->getClientInfo().distributed_depth == 0) + { + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Parallel distributed INSERT SELECT is not possible. "\ + "Reason: distributed reading is supported only from Distributed engine or *Cluster table functions, but got {} storage", src_storage->getName()); + } + + return {}; +} + + void StorageDistributed::checkAlterIsPossible(const AlterCommands & commands, ContextPtr local_context) const { auto name_deps = getDependentViewsByColumn(local_context); diff --git a/src/Storages/StorageDistributed.h b/src/Storages/StorageDistributed.h index 7cb25ae46ab..3161f4b50f6 100644 --- a/src/Storages/StorageDistributed.h +++ b/src/Storages/StorageDistributed.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -207,6 +208,9 @@ private: void delayInsertOrThrowIfNeeded() const; + std::optional distributedWriteFromClusterStorage(const IStorageCluster & src_storage_cluster, const ASTInsertQuery & query, ContextPtr context) const; + std::optional distributedWriteBetweenDistributedTables(const StorageDistributed & src_distributed, const ASTInsertQuery & query, ContextPtr context) const; + String remote_database; String remote_table; ASTPtr remote_table_function_ptr; diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 5089bc28e3b..90b51632000 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -44,11 +44,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -58,6 +60,7 @@ #include #include #include +#include #include #include @@ -72,6 +75,7 @@ #include #include #include +#include #include #include @@ -158,6 +162,7 @@ namespace ErrorCodes extern const int BAD_ARGUMENTS; extern const int CONCURRENT_ACCESS_NOT_SUPPORTED; extern const int CHECKSUM_DOESNT_MATCH; + extern const int TOO_LARGE_DISTRIBUTED_DEPTH; } namespace ActionLocks @@ -4467,6 +4472,106 @@ SinkToStoragePtr StorageReplicatedMergeTree::write(const ASTPtr & /*query*/, con } +std::optional StorageReplicatedMergeTree::distributedWriteFromClusterStorage(const std::shared_ptr & src_storage_cluster, const ASTInsertQuery & query, ContextPtr local_context) +{ + const auto & settings = local_context->getSettingsRef(); + auto extension = src_storage_cluster->getTaskIteratorExtension(local_context); + + /// Here we won't check that the cluster formed from table replicas is a subset of a cluster specified in s3Cluster/hdfsCluster table function + auto src_cluster = src_storage_cluster->getCluster(local_context); + + /// Actually the query doesn't change, we just serialize it to string + String query_str; + { + WriteBufferFromOwnString buf; + IAST::FormatSettings ast_format_settings(buf, /*one_line*/ true); + ast_format_settings.always_quote_identifiers = true; + query.IAST::format(ast_format_settings); + query_str = buf.str(); + } + + QueryPipeline pipeline; + ContextMutablePtr query_context = Context::createCopy(local_context); + ++query_context->getClientInfo().distributed_depth; + + for (const auto & replicas : src_cluster->getShardsAddresses()) + { + /// There will be only one replica, because we consider each replica as a shard + for (const auto & node : replicas) + { + auto connection = std::make_shared( + node.host_name, node.port, query_context->getGlobalContext()->getCurrentDatabase(), + node.user, node.password, node.quota_key, node.cluster, node.cluster_secret, + "ParallelInsertSelectInititiator", + node.compression, + node.secure + ); + + auto remote_query_executor = std::make_shared( + connection, + query_str, + Block{}, + query_context, + /*throttler=*/nullptr, + Scalars{}, + Tables{}, + QueryProcessingStage::Complete, + extension); + + QueryPipeline remote_pipeline(std::make_shared(remote_query_executor, false, settings.async_socket_for_remote)); + remote_pipeline.complete(std::make_shared(remote_query_executor->getHeader())); + + pipeline.addCompletedPipeline(std::move(remote_pipeline)); + } + } + + return pipeline; +} + +std::optional StorageReplicatedMergeTree::distributedWrite(const ASTInsertQuery & query, ContextPtr local_context) +{ + /// Do not enable parallel distributed INSERT SELECT in case when query probably comes from another server + if (local_context->getClientInfo().query_kind != ClientInfo::QueryKind::INITIAL_QUERY) + return {}; + + const Settings & settings = local_context->getSettingsRef(); + if (settings.max_distributed_depth && local_context->getClientInfo().distributed_depth >= settings.max_distributed_depth) + throw Exception("Maximum distributed depth exceeded", ErrorCodes::TOO_LARGE_DISTRIBUTED_DEPTH); + + auto & select = query.select->as(); + + StoragePtr src_storage; + + if (select.list_of_selects->children.size() == 1) + { + if (auto * select_query = select.list_of_selects->children.at(0)->as()) + { + JoinedTables joined_tables(Context::createCopy(local_context), *select_query); + + if (joined_tables.tablesCount() == 1) + { + src_storage = joined_tables.getLeftTableStorage(); + } + } + } + + if (!src_storage) + return {}; + + if (auto src_distributed = std::dynamic_pointer_cast(src_storage)) + { + return distributedWriteFromClusterStorage(src_distributed, query, local_context); + } + else if (local_context->getClientInfo().distributed_depth == 0) + { + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Parallel distributed INSERT SELECT is not possible. Reason: distributed "\ + "reading into Replicated table is supported only from *Cluster table functions, but got {} storage", src_storage->getName()); + } + + return {}; +} + + bool StorageReplicatedMergeTree::optimize( const ASTPtr &, const StorageMetadataPtr &, diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index c35e2d5cf5c..c6b0a7f47fc 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -136,6 +137,8 @@ public: SinkToStoragePtr write(const ASTPtr & query, const StorageMetadataPtr & /*metadata_snapshot*/, ContextPtr context) override; + std::optional distributedWrite(const ASTInsertQuery & /*query*/, ContextPtr /*context*/) override; + bool optimize( const ASTPtr & query, const StorageMetadataPtr & metadata_snapshot, @@ -465,6 +468,8 @@ private: std::mutex last_broken_disks_mutex; std::set last_broken_disks; + static std::optional distributedWriteFromClusterStorage(const std::shared_ptr & src_storage_cluster, const ASTInsertQuery & query, ContextPtr context); + template void foreachActiveParts(Func && func, bool select_sequential_consistency) const; diff --git a/src/Storages/StorageS3Cluster.cpp b/src/Storages/StorageS3Cluster.cpp index a3f368effa7..0c5e69cb906 100644 --- a/src/Storages/StorageS3Cluster.cpp +++ b/src/Storages/StorageS3Cluster.cpp @@ -56,7 +56,7 @@ StorageS3Cluster::StorageS3Cluster( const ConstraintsDescription & constraints_, ContextPtr context_, const String & compression_method_) - : IStorage(table_id_) + : IStorageCluster(table_id_) , s3_configuration{S3::URI{Poco::URI{filename_}}, access_key_id_, secret_access_key_, {}, {}, S3Settings::ReadWriteSettings(context_->getSettingsRef())} , filename(filename_) , cluster_name(cluster_name_) @@ -105,12 +105,7 @@ Pipe StorageS3Cluster::read( unsigned /*num_streams*/) { StorageS3::updateS3Configuration(context, s3_configuration); - - auto cluster = context->getCluster(cluster_name)->getClusterWithReplicasAsShards(context->getSettingsRef()); - - auto iterator = std::make_shared( - *s3_configuration.client, s3_configuration.uri, query_info.query, virtual_block, context); - auto callback = std::make_shared([iterator]() mutable -> String { return iterator->next(); }); + createIteratorAndCallback(query_info.query, context); /// Calculate the header. This is significant, because some columns could be thrown away in some cases like query with count(*) Block header = @@ -170,6 +165,29 @@ QueryProcessingStage::Enum StorageS3Cluster::getQueryProcessingStage( } +void StorageS3Cluster::createIteratorAndCallback(ASTPtr query, ContextPtr context) const +{ + cluster = context->getCluster(cluster_name)->getClusterWithReplicasAsShards(context->getSettingsRef()); + iterator = std::make_shared( + *s3_configuration.client, s3_configuration.uri, query, virtual_block, context); + callback = std::make_shared([iter = this->iterator]() mutable -> String { return iter->next(); }); +} + + +RemoteQueryExecutor::Extension StorageS3Cluster::getTaskIteratorExtension(ContextPtr context) const +{ + createIteratorAndCallback(/*query=*/nullptr, context); + return RemoteQueryExecutor::Extension{.task_iterator = callback}; +} + + +ClusterPtr StorageS3Cluster::getCluster(ContextPtr context) const +{ + createIteratorAndCallback(/*query=*/nullptr, context); + return cluster; +} + + NamesAndTypesList StorageS3Cluster::getVirtuals() const { return virtual_columns; diff --git a/src/Storages/StorageS3Cluster.h b/src/Storages/StorageS3Cluster.h index f823d1fdf04..e18c33e79da 100644 --- a/src/Storages/StorageS3Cluster.h +++ b/src/Storages/StorageS3Cluster.h @@ -10,6 +10,7 @@ #include "Client/Connection.h" #include #include +#include #include namespace DB @@ -17,7 +18,7 @@ namespace DB class Context; -class StorageS3Cluster : public IStorage +class StorageS3Cluster : public IStorageCluster { public: StorageS3Cluster( @@ -42,15 +43,22 @@ public: NamesAndTypesList getVirtuals() const override; + RemoteQueryExecutor::Extension getTaskIteratorExtension(ContextPtr context) const override; + ClusterPtr getCluster(ContextPtr context) const override; private: StorageS3::S3Configuration s3_configuration; - String filename; String cluster_name; String format_name; String compression_method; NamesAndTypesList virtual_columns; Block virtual_block; + + mutable ClusterPtr cluster; + mutable std::shared_ptr iterator; + mutable std::shared_ptr callback; + + void createIteratorAndCallback(ASTPtr query, ContextPtr context) const; }; diff --git a/tests/integration/test_s3_cluster/configs/cluster.xml b/tests/integration/test_s3_cluster/configs/cluster.xml index 18f15763633..39275e99abd 100644 --- a/tests/integration/test_s3_cluster/configs/cluster.xml +++ b/tests/integration/test_s3_cluster/configs/cluster.xml @@ -20,8 +20,23 @@ + + + + + + s0_0_0 + 9000 + + + s0_0_1 + 9000 + + + + cluster_simple - \ No newline at end of file + diff --git a/tests/integration/test_s3_cluster/test.py b/tests/integration/test_s3_cluster/test.py index 2cbb36fcf06..2384aa6e059 100644 --- a/tests/integration/test_s3_cluster/test.py +++ b/tests/integration/test_s3_cluster/test.py @@ -34,10 +34,24 @@ def started_cluster(): try: cluster = ClickHouseCluster(__file__) cluster.add_instance( - "s0_0_0", main_configs=["configs/cluster.xml"], with_minio=True + "s0_0_0", + main_configs=["configs/cluster.xml"], + macros={"replica": "node1", "shard": "shard1"}, + with_minio=True, + with_zookeeper=True, + ) + cluster.add_instance( + "s0_0_1", + main_configs=["configs/cluster.xml"], + macros={"replica": "replica2", "shard": "shard1"}, + with_zookeeper=True, + ) + cluster.add_instance( + "s0_1_0", + main_configs=["configs/cluster.xml"], + macros={"replica": "replica1", "shard": "shard2"}, + with_zookeeper=True, ) - cluster.add_instance("s0_0_1", main_configs=["configs/cluster.xml"]) - cluster.add_instance("s0_1_0", main_configs=["configs/cluster.xml"]) logging.info("Starting cluster...") cluster.start() @@ -55,17 +69,17 @@ def test_select_all(started_cluster): pure_s3 = node.query( """ SELECT * from s3( - 'http://minio1:9001/root/data/{clickhouse,database}/*', - 'minio', 'minio123', 'CSV', - 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') + 'http://minio1:9001/root/data/{clickhouse,database}/*', + 'minio', 'minio123', 'CSV', + 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') ORDER BY (name, value, polygon)""" ) # print(pure_s3) s3_distibuted = node.query( """ SELECT * from s3Cluster( - 'cluster_simple', - 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', + 'cluster_simple', + 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') ORDER BY (name, value, polygon)""" ) # print(s3_distibuted) @@ -78,15 +92,15 @@ def test_count(started_cluster): pure_s3 = node.query( """ SELECT count(*) from s3( - 'http://minio1:9001/root/data/{clickhouse,database}/*', - 'minio', 'minio123', 'CSV', + 'http://minio1:9001/root/data/{clickhouse,database}/*', + 'minio', 'minio123', 'CSV', 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))')""" ) # print(pure_s3) s3_distibuted = node.query( """ SELECT count(*) from s3Cluster( - 'cluster_simple', 'http://minio1:9001/root/data/{clickhouse,database}/*', + 'cluster_simple', 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))')""" ) @@ -125,13 +139,13 @@ def test_union_all(started_cluster): SELECT * FROM ( SELECT * from s3( - 'http://minio1:9001/root/data/{clickhouse,database}/*', - 'minio', 'minio123', 'CSV', - 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') + 'http://minio1:9001/root/data/{clickhouse,database}/*', + 'minio', 'minio123', 'CSV', + 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') UNION ALL SELECT * from s3( - 'http://minio1:9001/root/data/{clickhouse,database}/*', - 'minio', 'minio123', 'CSV', + 'http://minio1:9001/root/data/{clickhouse,database}/*', + 'minio', 'minio123', 'CSV', 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') ) ORDER BY (name, value, polygon) @@ -143,13 +157,13 @@ def test_union_all(started_cluster): SELECT * FROM ( SELECT * from s3Cluster( - 'cluster_simple', - 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', + 'cluster_simple', + 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') UNION ALL SELECT * from s3Cluster( - 'cluster_simple', - 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', + 'cluster_simple', + 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') ) ORDER BY (name, value, polygon) @@ -166,12 +180,12 @@ def test_wrong_cluster(started_cluster): """ SELECT count(*) from s3Cluster( 'non_existent_cluster', - 'http://minio1:9001/root/data/{clickhouse,database}/*', + 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') UNION ALL SELECT count(*) from s3Cluster( 'non_existent_cluster', - 'http://minio1:9001/root/data/{clickhouse,database}/*', + 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') """ ) @@ -184,14 +198,137 @@ def test_ambiguous_join(started_cluster): result = node.query( """ SELECT l.name, r.value from s3Cluster( - 'cluster_simple', - 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', + 'cluster_simple', + 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') as l JOIN s3Cluster( - 'cluster_simple', - 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', + 'cluster_simple', + 'http://minio1:9001/root/data/{clickhouse,database}/*', 'minio', 'minio123', 'CSV', 'name String, value UInt32, polygon Array(Array(Tuple(Float64, Float64)))') as r ON l.name = r.name """ ) assert "AMBIGUOUS_COLUMN_NAME" not in result + + +def test_distributed_insert_select(started_cluster): + first_replica_first_shard = started_cluster.instances["s0_0_0"] + second_replica_first_shard = started_cluster.instances["s0_0_1"] + first_replica_second_shard = started_cluster.instances["s0_1_0"] + + first_replica_first_shard.query( + """ + CREATE TABLE insert_select_local ON CLUSTER 'cluster_simple' (a String, b UInt64) + ENGINE=ReplicatedMergeTree('/clickhouse/tables/{shard}/insert_select', '{replica}') + ORDER BY (a, b); + """ + ) + + first_replica_first_shard.query( + """ + CREATE TABLE insert_select_distributed ON CLUSTER 'cluster_simple' as insert_select_local + ENGINE = Distributed('cluster_simple', default, insert_select_local, b % 2); + """ + ) + + for file_number in range(100): + first_replica_first_shard.query( + """ + INSERT INTO TABLE FUNCTION s3('http://minio1:9001/root/data/generated/file_{}.csv', 'minio', 'minio123', 'CSV','a String, b UInt64') + SELECT repeat('{}', 10), number from numbers(100); + """.format( + file_number, file_number + ) + ) + + first_replica_first_shard.query( + """ + INSERT INTO insert_select_distributed SELECT * FROM s3Cluster( + 'cluster_simple', + 'http://minio1:9001/root/data/generated/*.csv', 'minio', 'minio123', 'CSV','a String, b UInt64' + ) SETTINGS parallel_distributed_insert_select=1; + """ + ) + + for line in ( + first_replica_first_shard.query("""SELECT * FROM insert_select_local;""") + .strip() + .split("\n") + ): + _, b = line.split() + assert int(b) % 2 == 0 + + for line in ( + second_replica_first_shard.query("""SELECT * FROM insert_select_local;""") + .strip() + .split("\n") + ): + _, b = line.split() + assert int(b) % 2 == 0 + + for line in ( + first_replica_second_shard.query("""SELECT * FROM insert_select_local;""") + .strip() + .split("\n") + ): + _, b = line.split() + assert int(b) % 2 == 1 + + +def test_distributed_insert_select_with_replicated(started_cluster): + first_replica_first_shard = started_cluster.instances["s0_0_0"] + second_replica_first_shard = started_cluster.instances["s0_0_1"] + + first_replica_first_shard.query( + """ + CREATE TABLE insert_select_replicated_local ON CLUSTER 'first_shard' (a String, b UInt64) + ENGINE=ReplicatedMergeTree('/clickhouse/tables/{shard}/insert_select_with_replicated', '{replica}') + ORDER BY (a, b); + """ + ) + + for replica in [first_replica_first_shard, second_replica_first_shard]: + replica.query( + """ + SYSTEM STOP FETCHES; + """ + ) + replica.query( + """ + SYSTEM STOP MERGES; + """ + ) + + for file_number in range(100): + first_replica_first_shard.query( + """ + INSERT INTO TABLE FUNCTION s3('http://minio1:9001/root/data/generated_replicated/file_{}.csv', 'minio', 'minio123', 'CSV','a String, b UInt64') + SELECT repeat('{}', 10), number from numbers(100); + """.format( + file_number, file_number + ) + ) + + first_replica_first_shard.query( + """ + INSERT INTO insert_select_replicated_local SELECT * FROM s3Cluster( + 'first_shard', + 'http://minio1:9001/root/data/generated_replicated/*.csv', 'minio', 'minio123', 'CSV','a String, b UInt64' + ) SETTINGS parallel_distributed_insert_select=1; + """ + ) + + first = int( + first_replica_first_shard.query( + """SELECT count(*) FROM insert_select_replicated_local""" + ).strip() + ) + second = int( + second_replica_first_shard.query( + """SELECT count(*) FROM insert_select_replicated_local""" + ).strip() + ) + + assert first != 0 + assert second != 0 + assert first + second == 100 * 100 From 0e22b4b800101072c882f1f47e9c2a2e63f55293 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 15 Aug 2022 14:25:25 +0300 Subject: [PATCH 647/672] Update src/Common/filesystemHelpers.cpp Co-authored-by: alesapin --- src/Common/filesystemHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/filesystemHelpers.cpp b/src/Common/filesystemHelpers.cpp index 59fdf197b1e..610608cd312 100644 --- a/src/Common/filesystemHelpers.cpp +++ b/src/Common/filesystemHelpers.cpp @@ -354,7 +354,7 @@ void setModificationTime(const std::string & path, time_t time) bool isSymlink(const fs::path & path) { - /// Remove trailing stash before checking if file is symlink. + /// Remove trailing slash before checking if file is symlink. /// Let /path/to/link is a symlink to /path/to/target/dir/ directory. /// In this case is_symlink("/path/to/link") is true, /// but is_symlink("/path/to/link/") is false (it's a directory) From edaff7001097993e9f42bed6d1459a367d8bff57 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 15 Aug 2022 13:53:14 +0200 Subject: [PATCH 648/672] better error message when restoring covered parts --- docker/test/stress/run.sh | 1 + src/Storages/MergeTree/MergeTreeData.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index 6c32e9a73ea..db9933c7907 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -391,6 +391,7 @@ else -e "Missing columns: 'v3' while processing query: 'v3, k, v1, v2, p'" \ -e "This engine is deprecated and is not supported in transactions" \ -e "[Queue = DB::MergeMutateRuntimeQueue]: Code: 235. DB::Exception: Part" \ + -e "The set of parts restored in place of" \ /var/log/clickhouse-server/clickhouse-server.backward.clean.log | zgrep -Fa "" > /test_output/bc_check_error_messages.txt \ && echo -e 'Backward compatibility check: Error message in clickhouse-server.log (see bc_check_error_messages.txt)\tFAIL' >> /test_output/test_results.tsv \ || echo -e 'Backward compatibility check: No Error messages in clickhouse-server.log\tOK' >> /test_output/test_results.tsv diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 07f73759014..6f3e052cd58 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -3195,8 +3195,10 @@ void MergeTreeData::forgetPartAndMoveToDetached(const MergeTreeData::DataPartPtr pos = (*it)->info.max_block + 1; restored.push_back((*it)->name); } - else + else if ((*it)->info.partition_id == part->info.partition_id) update_error(it); + else + error = true; } else error = true; @@ -3217,7 +3219,7 @@ void MergeTreeData::forgetPartAndMoveToDetached(const MergeTreeData::DataPartPtr update_error(it); if ((*it)->getState() != DataPartState::Active) - activate_part(it); + activate_part(it); pos = (*it)->info.max_block + 1; restored.push_back((*it)->name); From 4ee1a645d50d747fbbe78bc15af3d71a110f0b98 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Mon, 15 Aug 2022 12:03:21 +0000 Subject: [PATCH 649/672] style: rename helper method reserve() to resize() --- src/Columns/ColumnVector.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Columns/ColumnVector.cpp b/src/Columns/ColumnVector.cpp index dc242b6efa2..d89917fc6c2 100644 --- a/src/Columns/ColumnVector.cpp +++ b/src/Columns/ColumnVector.cpp @@ -517,7 +517,7 @@ inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_ali namespace { template -void reserve(Container & res_data, size_t reserve_size) +void resize(Container & res_data, size_t reserve_size) { #if defined(MEMORY_SANITIZER) res_data.resize_fill(reserve_size, static_cast(0)); // MSan doesn't recognize that all allocated memory is written by AVX-512 intrinsics. @@ -560,7 +560,7 @@ inline void doFilterAligned(const UInt8 *& filt_pos, const UInt8 *& filt_end_ali if (reserve_size - current_offset < SIMD_BYTES) { reserve_size += alloc_size; - reserve(res_data, reserve_size); + resize(res_data, reserve_size); alloc_size *= 2; } From ced68e9298b896dda249178cac9ed806498c1667 Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Mon, 15 Aug 2022 09:27:20 -0400 Subject: [PATCH 650/672] add title frontmatter --- docs/en/sql-reference/statements/create/user.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/sql-reference/statements/create/user.md b/docs/en/sql-reference/statements/create/user.md index feda8459104..6c52c0e5e4e 100644 --- a/docs/en/sql-reference/statements/create/user.md +++ b/docs/en/sql-reference/statements/create/user.md @@ -1,6 +1,7 @@ --- sidebar_position: 39 sidebar_label: USER +title: create user --- # CREATE USER From 71409f8f67e0ed5971fc9fff82a75cdd33aa2aa7 Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Mon, 15 Aug 2022 11:08:46 -0400 Subject: [PATCH 651/672] trying tags with Algolia --- docs/en/sql-reference/statements/create/user.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/sql-reference/statements/create/user.md b/docs/en/sql-reference/statements/create/user.md index 6c52c0e5e4e..3837c60deb1 100644 --- a/docs/en/sql-reference/statements/create/user.md +++ b/docs/en/sql-reference/statements/create/user.md @@ -1,7 +1,9 @@ --- sidebar_position: 39 sidebar_label: USER -title: create user +tags: + - create user + - add user --- # CREATE USER From ca0d883c0f0006e52b6a69c7e650aecacc021377 Mon Sep 17 00:00:00 2001 From: avogar Date: Mon, 15 Aug 2022 15:36:18 +0000 Subject: [PATCH 652/672] Fix possible segfault in CapnProto input format --- .../Formats/Impl/CapnProtoRowInputFormat.cpp | 5 +++ ...2_capnp_format_segments_overflow.reference | 1 + .../02402_capnp_format_segments_overflow.sh | 23 ++++++++++++++ .../0_stateless/data_capnp/overflow.capnp | Bin 0 -> 250176 bytes .../format_schemas/02402_overflow.capnp | 30 ++++++++++++++++++ 5 files changed, 59 insertions(+) create mode 100644 tests/queries/0_stateless/02402_capnp_format_segments_overflow.reference create mode 100755 tests/queries/0_stateless/02402_capnp_format_segments_overflow.sh create mode 100644 tests/queries/0_stateless/data_capnp/overflow.capnp create mode 100644 tests/queries/0_stateless/format_schemas/02402_overflow.capnp diff --git a/src/Processors/Formats/Impl/CapnProtoRowInputFormat.cpp b/src/Processors/Formats/Impl/CapnProtoRowInputFormat.cpp index 0dadbb75532..05675929536 100644 --- a/src/Processors/Formats/Impl/CapnProtoRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/CapnProtoRowInputFormat.cpp @@ -49,6 +49,11 @@ kj::Array CapnProtoRowInputFormat::readMessage() { uint32_t segment_count; in->readStrict(reinterpret_cast(&segment_count), sizeof(uint32_t)); + /// Dont' allow large amount of segments as it's done in capnproto library: + /// https://github.com/capnproto/capnproto/blob/931074914eda9ca574b5c24d1169c0f7a5156594/c%2B%2B/src/capnp/serialize.c%2B%2B#L181 + /// Large amount of segments can indicate that corruption happened. + if (segment_count >= 512) + throw Exception(ErrorCodes::INCORRECT_DATA, "Message has too many segments. Most likely, data was corrupted"); // one for segmentCount and one because segmentCount starts from 0 const auto prefix_size = (2 + segment_count) * sizeof(uint32_t); diff --git a/tests/queries/0_stateless/02402_capnp_format_segments_overflow.reference b/tests/queries/0_stateless/02402_capnp_format_segments_overflow.reference new file mode 100644 index 00000000000..d86bac9de59 --- /dev/null +++ b/tests/queries/0_stateless/02402_capnp_format_segments_overflow.reference @@ -0,0 +1 @@ +OK diff --git a/tests/queries/0_stateless/02402_capnp_format_segments_overflow.sh b/tests/queries/0_stateless/02402_capnp_format_segments_overflow.sh new file mode 100755 index 00000000000..244b94d9189 --- /dev/null +++ b/tests/queries/0_stateless/02402_capnp_format_segments_overflow.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-parallel, no-replicated-database + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +USER_FILES_PATH=$(clickhouse-client --query "select _path,_file from file('nonexist.txt', 'CSV', 'val1 char')" 2>&1 | grep Exception | awk '{gsub("/nonexist.txt","",$9); print $9}') +mkdir -p $USER_FILES_PATH/test_02402 +cp $CURDIR/data_capnp/overflow.capnp $USER_FILES_PATH/test_02402/ + +SCHEMADIR=$(clickhouse-client --query "select * from file('test_02402/overflow.capnp', 'CapnProto', 'val1 char') settings format_schema='nonexist:Message'" 2>&1 | grep Exception | grep -oP "file \K.*(?=/nonexist.capnp)") + +CLIENT_SCHEMADIR=$CURDIR/format_schemas +SERVER_SCHEMADIR=test_02402 + +mkdir -p $SCHEMADIR/$SERVER_SCHEMADIR +cp -r $CLIENT_SCHEMADIR/02402_* $SCHEMADIR/$SERVER_SCHEMADIR/ + +$CLICKHOUSE_CLIENT --query="SELECT * FROM file('test_02402/overflow.capnp', 'CapnProto') SETTINGS format_schema='$SERVER_SCHEMADIR/02402_overflow:CapnProto'" 2>&1 | grep -F -q "INCORRECT_DATA" && echo 'OK' || echo 'FAIL'; + +rm -rf $USER_FILES_PATH/test_02402 +rm -rf ${SCHEMADIR:?}/${SERVER_SCHEMADIR:?} diff --git a/tests/queries/0_stateless/data_capnp/overflow.capnp b/tests/queries/0_stateless/data_capnp/overflow.capnp new file mode 100644 index 0000000000000000000000000000000000000000..8bf1eb8848cd4897d0a5d58504d77f9d3a604ef9 GIT binary patch literal 250176 zcmeIuOAf*y07cPAZN_-T& Date: Mon, 15 Aug 2022 13:22:10 -0400 Subject: [PATCH 653/672] promote to H1 to fix case in title --- docs/en/sql-reference/aggregate-functions/reference/anylast.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/sql-reference/aggregate-functions/reference/anylast.md b/docs/en/sql-reference/aggregate-functions/reference/anylast.md index 06d7603853f..44697359405 100644 --- a/docs/en/sql-reference/aggregate-functions/reference/anylast.md +++ b/docs/en/sql-reference/aggregate-functions/reference/anylast.md @@ -2,7 +2,7 @@ sidebar_position: 104 --- -## anyLast +# anyLast Selects the last value encountered. The result is just as indeterminate as for the [any](../../../sql-reference/aggregate-functions/reference/any.md) function. From 2c5c0d6d474f7e4be65ee2c32cc18e82fd74aa9f Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 15 Aug 2022 19:55:28 +0200 Subject: [PATCH 654/672] Fix typo --- src/Processors/Formats/Impl/CapnProtoRowInputFormat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Processors/Formats/Impl/CapnProtoRowInputFormat.cpp b/src/Processors/Formats/Impl/CapnProtoRowInputFormat.cpp index 05675929536..3916a8e4ba6 100644 --- a/src/Processors/Formats/Impl/CapnProtoRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/CapnProtoRowInputFormat.cpp @@ -49,7 +49,7 @@ kj::Array CapnProtoRowInputFormat::readMessage() { uint32_t segment_count; in->readStrict(reinterpret_cast(&segment_count), sizeof(uint32_t)); - /// Dont' allow large amount of segments as it's done in capnproto library: + /// Don't allow large amount of segments as it's done in capnproto library: /// https://github.com/capnproto/capnproto/blob/931074914eda9ca574b5c24d1169c0f7a5156594/c%2B%2B/src/capnp/serialize.c%2B%2B#L181 /// Large amount of segments can indicate that corruption happened. if (segment_count >= 512) From 73f51a0f191d5c083eca359088fd5be60a0f6367 Mon Sep 17 00:00:00 2001 From: Denny Crane Date: Mon, 15 Aug 2022 16:40:48 -0300 Subject: [PATCH 655/672] doc array-join --- docs/en/sql-reference/functions/array-join.md | 115 +++++++++++++++++- docs/ru/sql-reference/functions/array-join.md | 115 +++++++++++++++++- 2 files changed, 220 insertions(+), 10 deletions(-) diff --git a/docs/en/sql-reference/functions/array-join.md b/docs/en/sql-reference/functions/array-join.md index ed1199a45d8..07259149866 100644 --- a/docs/en/sql-reference/functions/array-join.md +++ b/docs/en/sql-reference/functions/array-join.md @@ -9,15 +9,11 @@ This is a very unusual function. Normal functions do not change a set of rows, but just change the values in each row (map). Aggregate functions compress a set of rows (fold or reduce). -The ‘arrayJoin’ function takes each row and generates a set of rows (unfold). +The `arrayJoin` function takes each row and generates a set of rows (unfold). This function takes an array as an argument, and propagates the source row to multiple rows for the number of elements in the array. All the values in columns are simply copied, except the values in the column where this function is applied; it is replaced with the corresponding array value. -A query can use multiple `arrayJoin` functions. In this case, the transformation is performed multiple times. - -Note the ARRAY JOIN syntax in the SELECT query, which provides broader possibilities. - Example: ``` sql @@ -32,3 +28,112 @@ SELECT arrayJoin([1, 2, 3] AS src) AS dst, 'Hello', src └─────┴───────────┴─────────┘ ``` +The `arrayJoin` function affects all sections of the query, including the `WHERE` section. Notice the result 2, even though the subquery returned 1 row. + +Example: + +```sql +SELECT sum(1) AS impressions +FROM +( + SELECT ['Istambul', 'Berlin', 'Bobruisk'] AS cities +) +WHERE arrayJoin(cities) IN ['Istambul', 'Berlin']; +``` + +``` text +┌─impressions─┐ +│ 2 │ +└─────────────┘ +``` + +A query can use multiple `arrayJoin` functions. In this case, the transformation is performed multiple times and the rows are multiplied. + +Example: + +```sql +SELECT + sum(1) AS impressions, + arrayJoin(cities) AS city, + arrayJoin(browsers) AS browser +FROM +( + SELECT + ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Firefox', 'Chrome', 'Chrome'] AS browsers +) +GROUP BY + 2, + 3 +``` + +``` text +┌─impressions─┬─city─────┬─browser─┐ +│ 2 │ Istambul │ Chrome │ +│ 1 │ Istambul │ Firefox │ +│ 2 │ Berlin │ Chrome │ +│ 1 │ Berlin │ Firefox │ +│ 2 │ Bobruisk │ Chrome │ +│ 1 │ Bobruisk │ Firefox │ +└─────────────┴──────────┴─────────┘ +``` + +Note the [ARRAY JOIN](../statements/select/array-join.md) syntax in the SELECT query, which provides broader possibilities. +`ARRAY JOIN` allows you to convert multiple arrays with the same number of elements at a time. + +Example: + +```sql +SELECT + sum(1) AS impressions, + city, + browser +FROM +( + SELECT + ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Firefox', 'Chrome', 'Chrome'] AS browsers +) +ARRAY JOIN + cities AS city, + browsers AS browser +GROUP BY + 2, + 3 +``` + +``` text +┌─impressions─┬─city─────┬─browser─┐ +│ 1 │ Istambul │ Firefox │ +│ 1 │ Berlin │ Chrome │ +│ 1 │ Bobruisk │ Chrome │ +└─────────────┴──────────┴─────────┘ +``` + +Or you can use [Tuple](../data-types/tuple.md) + +Example: + +```sql +SELECT + sum(1) AS impressions, + (arrayJoin(arrayZip(cities, browsers)) AS t).1 AS city, + t.2 AS browser +FROM +( + SELECT + ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Firefox', 'Chrome', 'Chrome'] AS browsers +) +GROUP BY + 2, + 3 +``` + +``` text +┌─impressions─┬─city─────┬─browser─┐ +│ 1 │ Istambul │ Firefox │ +│ 1 │ Berlin │ Chrome │ +│ 1 │ Bobruisk │ Chrome │ +└─────────────┴──────────┴─────────┘ +``` diff --git a/docs/ru/sql-reference/functions/array-join.md b/docs/ru/sql-reference/functions/array-join.md index c35821b7f48..9c4cfbd360a 100644 --- a/docs/ru/sql-reference/functions/array-join.md +++ b/docs/ru/sql-reference/functions/array-join.md @@ -9,15 +9,11 @@ sidebar_label: "Функция ArrayJoin" Обычные функции не изменяют множество строк, а лишь изменяют значения в каждой строке (map). Агрегатные функции выполняют свёртку множества строк (fold, reduce). -Функция arrayJoin выполняет размножение каждой строки в множество строк (unfold). +Функция `arrayJoin` выполняет размножение каждой строки в множество строк (unfold). Функция принимает в качестве аргумента массив, и размножает исходную строку в несколько строк - по числу элементов массива. Все значения в столбцах просто копируются, кроме значения в столбце с применением этой функции - он заменяется на соответствующее значение массива. -В запросе может быть использовано несколько функций `arrayJoin`. В этом случае, соответствующее преобразование делается несколько раз. - -Обратите внимание на синтаксис ARRAY JOIN в запросе SELECT, который предоставляет более широкие возможности. - Пример: ``` sql @@ -32,3 +28,112 @@ SELECT arrayJoin([1, 2, 3] AS src) AS dst, 'Hello', src └─────┴───────────┴─────────┘ ``` +Функция `arrayJoin` влияет на все секции запроса, включая секцию `WHERE`. Обратите внимание на результат 2, хотя подзапрос вернул 1 строку. + +Пример: + +```sql +SELECT sum(1) AS impressions +FROM +( + SELECT ['Istambul', 'Berlin', 'Bobruisk'] AS cities +) +WHERE arrayJoin(cities) IN ['Istambul', 'Berlin']; +``` + +``` text +┌─impressions─┐ +│ 2 │ +└─────────────┘ +``` + +В запросе может быть использовано несколько функций `arrayJoin`. В этом случае, соответствующее преобразование делается несколько раз и строки перемножаются. + +Пример: + +```sql +SELECT + sum(1) AS impressions, + arrayJoin(cities) AS city, + arrayJoin(browsers) AS browser +FROM +( + SELECT + ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Firefox', 'Chrome', 'Chrome'] AS browsers +) +GROUP BY + 2, + 3 +``` + +``` text +┌─impressions─┬─city─────┬─browser─┐ +│ 2 │ Istambul │ Chrome │ +│ 1 │ Istambul │ Firefox │ +│ 2 │ Berlin │ Chrome │ +│ 1 │ Berlin │ Firefox │ +│ 2 │ Bobruisk │ Chrome │ +│ 1 │ Bobruisk │ Firefox │ +└─────────────┴──────────┴─────────┘ +``` + +Обратите внимание на синтаксис [ARRAY JOIN](../statements/select/array-join.md) в запросе SELECT, который предоставляет более широкие возможности. +`ARRAY JOIN` позволяет преобразовать несколько массивов с одинаковым количеством элементов за раз. + +Пример: + +```sql +SELECT + sum(1) AS impressions, + city, + browser +FROM +( + SELECT + ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Firefox', 'Chrome', 'Chrome'] AS browsers +) +ARRAY JOIN + cities AS city, + browsers AS browser +GROUP BY + 2, + 3 +``` + +``` text +┌─impressions─┬─city─────┬─browser─┐ +│ 1 │ Istambul │ Firefox │ +│ 1 │ Berlin │ Chrome │ +│ 1 │ Bobruisk │ Chrome │ +└─────────────┴──────────┴─────────┘ +``` + +Или можно использовать [Tuple](../data-types/tuple.md) + +Пример: + +```sql +SELECT + sum(1) AS impressions, + (arrayJoin(arrayZip(cities, browsers)) AS t).1 AS city, + t.2 AS browser +FROM +( + SELECT + ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Firefox', 'Chrome', 'Chrome'] AS browsers +) +GROUP BY + 2, + 3 +``` + +``` text +┌─impressions─┬─city─────┬─browser─┐ +│ 1 │ Istambul │ Firefox │ +│ 1 │ Berlin │ Chrome │ +│ 1 │ Bobruisk │ Chrome │ +└─────────────┴──────────┴─────────┘ +``` From f3b349fd18eda24e04d87004eb694e774e9004e6 Mon Sep 17 00:00:00 2001 From: Denny Crane Date: Mon, 15 Aug 2022 16:42:55 -0300 Subject: [PATCH 656/672] Update array-join.md --- docs/en/sql-reference/functions/array-join.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/en/sql-reference/functions/array-join.md b/docs/en/sql-reference/functions/array-join.md index 07259149866..58182d75042 100644 --- a/docs/en/sql-reference/functions/array-join.md +++ b/docs/en/sql-reference/functions/array-join.md @@ -59,7 +59,7 @@ SELECT FROM ( SELECT - ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Istanbul', 'Berlin', 'Bobruisk'] AS cities, ['Firefox', 'Chrome', 'Chrome'] AS browsers ) GROUP BY @@ -69,8 +69,8 @@ GROUP BY ``` text ┌─impressions─┬─city─────┬─browser─┐ -│ 2 │ Istambul │ Chrome │ -│ 1 │ Istambul │ Firefox │ +│ 2 │ Istanbul │ Chrome │ +│ 1 │ Istanbul │ Firefox │ │ 2 │ Berlin │ Chrome │ │ 1 │ Berlin │ Firefox │ │ 2 │ Bobruisk │ Chrome │ @@ -91,7 +91,7 @@ SELECT FROM ( SELECT - ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Istanbul', 'Berlin', 'Bobruisk'] AS cities, ['Firefox', 'Chrome', 'Chrome'] AS browsers ) ARRAY JOIN @@ -104,7 +104,7 @@ GROUP BY ``` text ┌─impressions─┬─city─────┬─browser─┐ -│ 1 │ Istambul │ Firefox │ +│ 1 │ Istanbul │ Firefox │ │ 1 │ Berlin │ Chrome │ │ 1 │ Bobruisk │ Chrome │ └─────────────┴──────────┴─────────┘ @@ -122,7 +122,7 @@ SELECT FROM ( SELECT - ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Istanbul', 'Berlin', 'Bobruisk'] AS cities, ['Firefox', 'Chrome', 'Chrome'] AS browsers ) GROUP BY @@ -132,7 +132,7 @@ GROUP BY ``` text ┌─impressions─┬─city─────┬─browser─┐ -│ 1 │ Istambul │ Firefox │ +│ 1 │ Istanbul │ Firefox │ │ 1 │ Berlin │ Chrome │ │ 1 │ Bobruisk │ Chrome │ └─────────────┴──────────┴─────────┘ From c7aaf9bbc6814c879e9d10f6d3d3d9408b2d7530 Mon Sep 17 00:00:00 2001 From: Denny Crane Date: Mon, 15 Aug 2022 16:44:27 -0300 Subject: [PATCH 657/672] Update array-join.md --- docs/ru/sql-reference/functions/array-join.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/ru/sql-reference/functions/array-join.md b/docs/ru/sql-reference/functions/array-join.md index 9c4cfbd360a..330a22f6553 100644 --- a/docs/ru/sql-reference/functions/array-join.md +++ b/docs/ru/sql-reference/functions/array-join.md @@ -36,9 +36,9 @@ SELECT arrayJoin([1, 2, 3] AS src) AS dst, 'Hello', src SELECT sum(1) AS impressions FROM ( - SELECT ['Istambul', 'Berlin', 'Bobruisk'] AS cities + SELECT ['Istanbul', 'Berlin', 'Bobruisk'] AS cities ) -WHERE arrayJoin(cities) IN ['Istambul', 'Berlin']; +WHERE arrayJoin(cities) IN ['Istanbul', 'Berlin']; ``` ``` text @@ -59,7 +59,7 @@ SELECT FROM ( SELECT - ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Istanbul', 'Berlin', 'Bobruisk'] AS cities, ['Firefox', 'Chrome', 'Chrome'] AS browsers ) GROUP BY @@ -69,8 +69,8 @@ GROUP BY ``` text ┌─impressions─┬─city─────┬─browser─┐ -│ 2 │ Istambul │ Chrome │ -│ 1 │ Istambul │ Firefox │ +│ 2 │ Istanbul │ Chrome │ +│ 1 │ Istanbul │ Firefox │ │ 2 │ Berlin │ Chrome │ │ 1 │ Berlin │ Firefox │ │ 2 │ Bobruisk │ Chrome │ @@ -91,7 +91,7 @@ SELECT FROM ( SELECT - ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Istanbul', 'Berlin', 'Bobruisk'] AS cities, ['Firefox', 'Chrome', 'Chrome'] AS browsers ) ARRAY JOIN @@ -104,7 +104,7 @@ GROUP BY ``` text ┌─impressions─┬─city─────┬─browser─┐ -│ 1 │ Istambul │ Firefox │ +│ 1 │ Istanbul │ Firefox │ │ 1 │ Berlin │ Chrome │ │ 1 │ Bobruisk │ Chrome │ └─────────────┴──────────┴─────────┘ @@ -122,7 +122,7 @@ SELECT FROM ( SELECT - ['Istambul', 'Berlin', 'Bobruisk'] AS cities, + ['Istanbul', 'Berlin', 'Bobruisk'] AS cities, ['Firefox', 'Chrome', 'Chrome'] AS browsers ) GROUP BY @@ -132,7 +132,7 @@ GROUP BY ``` text ┌─impressions─┬─city─────┬─browser─┐ -│ 1 │ Istambul │ Firefox │ +│ 1 │ Istanbul │ Firefox │ │ 1 │ Berlin │ Chrome │ │ 1 │ Bobruisk │ Chrome │ └─────────────┴──────────┴─────────┘ From f574b49825530df5ca1ab699e90ded8e43ba5add Mon Sep 17 00:00:00 2001 From: Denny Crane Date: Mon, 15 Aug 2022 16:45:07 -0300 Subject: [PATCH 658/672] Update array-join.md --- docs/en/sql-reference/functions/array-join.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/sql-reference/functions/array-join.md b/docs/en/sql-reference/functions/array-join.md index 58182d75042..0eb1d1b431e 100644 --- a/docs/en/sql-reference/functions/array-join.md +++ b/docs/en/sql-reference/functions/array-join.md @@ -36,9 +36,9 @@ Example: SELECT sum(1) AS impressions FROM ( - SELECT ['Istambul', 'Berlin', 'Bobruisk'] AS cities + SELECT ['Istanbul', 'Berlin', 'Bobruisk'] AS cities ) -WHERE arrayJoin(cities) IN ['Istambul', 'Berlin']; +WHERE arrayJoin(cities) IN ['Istanbul', 'Berlin']; ``` ``` text From 26c2a0ce5ef0a35d51bef215065bcdc6af243380 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 15 Aug 2022 22:12:07 +0200 Subject: [PATCH 659/672] try to print stacktraces if query timeouts --- tests/integration/helpers/client.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/tests/integration/helpers/client.py b/tests/integration/helpers/client.py index f03a6d2ab23..a29a204a1c5 100644 --- a/tests/integration/helpers/client.py +++ b/tests/integration/helpers/client.py @@ -4,6 +4,7 @@ import tempfile import logging from threading import Timer +DEFAULT_QUERY_TIMEOUT=600 class Client: def __init__(self, host, port=9000, command="/usr/bin/clickhouse-client"): @@ -16,6 +17,23 @@ class Client: self.command += ["--host", self.host, "--port", str(self.port), "--stacktrace"] + @staticmethod + def stacktraces_on_timeout_decorator(func): + def wrap(self, *args, **kwargs): + try: + return func(self, *args, **kwargs) + except sp.TimeoutExpired: + # I failed to make pytest print stacktraces using print(...) or logging.debug(...), so... + self.get_query_request( + "INSERT INTO TABLE FUNCTION file('stacktraces_on_timeout.txt', 'TSVRaw', 'tn String, tid UInt64, qid String, st String') " + "SELECT thread_name, thread_id, query_id, arrayStringConcat(arrayMap(x -> demangle(addressToSymbol(x)), trace), '\n') AS res FROM system.stack_trace " + "SETTINGS allow_introspection_functions=1", + timeout=60, + ).get_answer_and_error() + raise + return wrap + + @stacktraces_on_timeout_decorator def query( self, sql, @@ -80,6 +98,7 @@ class Client: return CommandRequest(command, stdin, timeout, ignore_error) + @stacktraces_on_timeout_decorator def query_and_get_error( self, sql, @@ -100,6 +119,7 @@ class Client: database=database, ).get_error() + @stacktraces_on_timeout_decorator def query_and_get_answer_with_error( self, sql, @@ -170,7 +190,7 @@ class CommandRequest: self.timer.start() def get_answer(self): - self.process.wait() + self.process.wait(timeout=DEFAULT_QUERY_TIMEOUT) self.stdout_file.seek(0) self.stderr_file.seek(0) @@ -197,7 +217,7 @@ class CommandRequest: return stdout def get_error(self): - self.process.wait() + self.process.wait(timeout=DEFAULT_QUERY_TIMEOUT) self.stdout_file.seek(0) self.stderr_file.seek(0) @@ -221,7 +241,7 @@ class CommandRequest: return stderr def get_answer_and_error(self): - self.process.wait() + self.process.wait(timeout=DEFAULT_QUERY_TIMEOUT) self.stdout_file.seek(0) self.stderr_file.seek(0) From 1b11a5c6fe276603d690f27746ddd029e6d7ab28 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 15 Aug 2022 20:19:41 +0000 Subject: [PATCH 660/672] Automatic style fix --- tests/integration/helpers/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/helpers/client.py b/tests/integration/helpers/client.py index a29a204a1c5..005f5e53291 100644 --- a/tests/integration/helpers/client.py +++ b/tests/integration/helpers/client.py @@ -4,7 +4,8 @@ import tempfile import logging from threading import Timer -DEFAULT_QUERY_TIMEOUT=600 +DEFAULT_QUERY_TIMEOUT = 600 + class Client: def __init__(self, host, port=9000, command="/usr/bin/clickhouse-client"): @@ -31,6 +32,7 @@ class Client: timeout=60, ).get_answer_and_error() raise + return wrap @stacktraces_on_timeout_decorator From b865787b3ca5f9aaaa6ebf47977027af2836c950 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy <99031427+yakov-olkhovskiy@users.noreply.github.com> Date: Mon, 15 Aug 2022 19:05:40 -0400 Subject: [PATCH 661/672] add Unit tests to Mergeable --- tests/ci/ci_config.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index 5b8f3b4227e..4b2cf4df743 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -354,4 +354,9 @@ REQUIRED_CHECKS = [ "ClickHouse special build check", "Stateful tests (release)", "Stateless tests (release)", + "Unit tests (release-clang)", + "Unit tests (asan)", + "Unit tests (msan)", + "Unit tests (tsan)", + "Unit tests (ubsan)", ] From 1e6f7a7c526fcc52050066169793385318f659ff Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 16 Aug 2022 09:35:45 +0200 Subject: [PATCH 662/672] make it work with python3.8 --- tests/integration/helpers/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/helpers/client.py b/tests/integration/helpers/client.py index 005f5e53291..a4407d5b442 100644 --- a/tests/integration/helpers/client.py +++ b/tests/integration/helpers/client.py @@ -18,7 +18,6 @@ class Client: self.command += ["--host", self.host, "--port", str(self.port), "--stacktrace"] - @staticmethod def stacktraces_on_timeout_decorator(func): def wrap(self, *args, **kwargs): try: From b33f3a4e160b8ec0847e567346da990591d1561d Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 16 Aug 2022 09:32:01 +0000 Subject: [PATCH 663/672] Extract common KV storage logic --- src/Storages/KVStorageUtils.cpp | 182 +++++++++++++++++ src/Storages/KVStorageUtils.h | 48 +++++ .../RocksDB/StorageEmbeddedRocksDB.cpp | 189 +----------------- 3 files changed, 231 insertions(+), 188 deletions(-) create mode 100644 src/Storages/KVStorageUtils.cpp create mode 100644 src/Storages/KVStorageUtils.h diff --git a/src/Storages/KVStorageUtils.cpp b/src/Storages/KVStorageUtils.cpp new file mode 100644 index 00000000000..41aa91eef31 --- /dev/null +++ b/src/Storages/KVStorageUtils.cpp @@ -0,0 +1,182 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +namespace +{ +// returns keys may be filter by condition +bool traverseASTFilter( + const std::string & primary_key, const DataTypePtr & primary_key_type, const ASTPtr & elem, const PreparedSetsPtr & prepared_sets, const ContextPtr & context, FieldVectorPtr & res) +{ + const auto * function = elem->as(); + if (!function) + return false; + + if (function->name == "and") + { + // one child has the key filter condition is ok + for (const auto & child : function->arguments->children) + if (traverseASTFilter(primary_key, primary_key_type, child, prepared_sets, context, res)) + return true; + return false; + } + else if (function->name == "or") + { + // make sure every child has the key filter condition + for (const auto & child : function->arguments->children) + if (!traverseASTFilter(primary_key, primary_key_type, child, prepared_sets, context, res)) + return false; + return true; + } + else if (function->name == "equals" || function->name == "in") + { + const auto & args = function->arguments->as(); + const ASTIdentifier * ident; + std::shared_ptr value; + + if (args.children.size() != 2) + return false; + + if (function->name == "in") + { + if (!prepared_sets) + return false; + + ident = args.children.at(0)->as(); + if (!ident) + return false; + + if (ident->name() != primary_key) + return false; + value = args.children.at(1); + + PreparedSetKey set_key; + if ((value->as() || value->as())) + set_key = PreparedSetKey::forSubquery(*value); + else + set_key = PreparedSetKey::forLiteral(*value, {primary_key_type}); + + SetPtr set = prepared_sets->get(set_key); + if (!set) + return false; + + if (!set->hasExplicitSetElements()) + return false; + + set->checkColumnsNumber(1); + const auto & set_column = *set->getSetElements()[0]; + for (size_t row = 0; row < set_column.size(); ++row) + res->push_back(set_column[row]); + return true; + } + else + { + if ((ident = args.children.at(0)->as())) + value = args.children.at(1); + else if ((ident = args.children.at(1)->as())) + value = args.children.at(0); + else + return false; + + if (ident->name() != primary_key) + return false; + + const auto node = evaluateConstantExpressionAsLiteral(value, context); + /// function->name == "equals" + if (const auto * literal = node->as()) + { + auto converted_field = convertFieldToType(literal->value, *primary_key_type); + if (!converted_field.isNull()) + res->push_back(converted_field); + return true; + } + } + } + return false; +} +} + +std::pair getFilterKeys( + const String & primary_key, const DataTypePtr & primary_key_type, const SelectQueryInfo & query_info, const ContextPtr & context) +{ + const auto & select = query_info.query->as(); + if (!select.where()) + return {{}, true}; + + FieldVectorPtr res = std::make_shared(); + auto matched_keys = traverseASTFilter(primary_key, primary_key_type, select.where(), query_info.prepared_sets, context, res); + return std::make_pair(res, !matched_keys); +} + +std::vector serializeKeysToRawString( + FieldVector::const_iterator & it, + FieldVector::const_iterator end, + DataTypePtr key_column_type, + size_t max_block_size) +{ + size_t num_keys = end - it; + + std::vector result; + result.reserve(num_keys); + + size_t rows_processed = 0; + while (it < end && (max_block_size == 0 || rows_processed < max_block_size)) + { + std::string & serialized_key = result.emplace_back(); + WriteBufferFromString wb(serialized_key); + key_column_type->getDefaultSerialization()->serializeBinary(*it, wb); + wb.finalize(); + + ++it; + ++rows_processed; + } + return result; +} + +std::vector serializeKeysToRawString(const ColumnWithTypeAndName & keys) +{ + if (!keys.column) + return {}; + + size_t num_keys = keys.column->size(); + std::vector result; + result.reserve(num_keys); + + for (size_t i = 0; i < num_keys; ++i) + { + std::string & serialized_key = result.emplace_back(); + WriteBufferFromString wb(serialized_key); + Field field; + keys.column->get(i, field); + /// TODO(@vdimir): use serializeBinaryBulk + keys.type->getDefaultSerialization()->serializeBinary(field, wb); + wb.finalize(); + } + return result; +} + +/// In current implementation rocks db can have key with only one column. +size_t getPrimaryKeyPos(const Block & header, const Names & primary_key) +{ + if (primary_key.size() != 1) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Only one primary key is supported"); + return header.getPositionByName(primary_key[0]); +} + +} diff --git a/src/Storages/KVStorageUtils.h b/src/Storages/KVStorageUtils.h new file mode 100644 index 00000000000..e3216164869 --- /dev/null +++ b/src/Storages/KVStorageUtils.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +namespace DB +{ + +using FieldVectorPtr = std::shared_ptr; + +class IDataType; +using DataTypePtr = std::shared_ptr; + +/** Retrieve from the query a condition of the form `key = 'key'`, `key in ('xxx_'), from conjunctions in the WHERE clause. + * TODO support key like search + */ +std::pair getFilterKeys( + const std::string & primary_key, const DataTypePtr & primary_key_type, const SelectQueryInfo & query_info, const ContextPtr & context); + +template +void fillColumns(const K & key, const V & value, size_t key_pos, const Block & header, MutableColumns & columns) +{ + ReadBufferFromString key_buffer(key); + ReadBufferFromString value_buffer(value); + for (size_t i = 0; i < header.columns(); ++i) + { + const auto & serialization = header.getByPosition(i).type->getDefaultSerialization(); + serialization->deserializeBinary(*columns[i], i == key_pos ? key_buffer : value_buffer); + } +} + +std::vector serializeKeysToRawString( + FieldVector::const_iterator & it, + FieldVector::const_iterator end, + DataTypePtr key_column_type, + size_t max_block_size); + +std::vector serializeKeysToRawString(const ColumnWithTypeAndName & keys); + +/// In current implementation key with only column is supported. +size_t getPrimaryKeyPos(const Block & header, const Names & primary_key); + +} diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp index bab254daa87..24a2db33dcf 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp @@ -3,30 +3,17 @@ #include -#include #include +#include -#include -#include -#include -#include -#include -#include #include -#include -#include - #include #include #include #include -#include -#include #include -#include -#include #include #include @@ -75,179 +62,6 @@ static RocksDBOptions getOptionsFromConfig(const Poco::Util::AbstractConfigurati return options; } -// returns keys may be filter by condition -static bool traverseASTFilter( - const String & primary_key, const DataTypePtr & primary_key_type, const ASTPtr & elem, const PreparedSetsPtr & prepared_sets, const ContextPtr & context, FieldVectorPtr & res) -{ - const auto * function = elem->as(); - if (!function) - return false; - - if (function->name == "and") - { - // one child has the key filter condition is ok - for (const auto & child : function->arguments->children) - if (traverseASTFilter(primary_key, primary_key_type, child, prepared_sets, context, res)) - return true; - return false; - } - else if (function->name == "or") - { - // make sure every child has the key filter condition - for (const auto & child : function->arguments->children) - if (!traverseASTFilter(primary_key, primary_key_type, child, prepared_sets, context, res)) - return false; - return true; - } - else if (function->name == "equals" || function->name == "in") - { - const auto & args = function->arguments->as(); - const ASTIdentifier * ident; - std::shared_ptr value; - - if (args.children.size() != 2) - return false; - - if (function->name == "in") - { - if (!prepared_sets) - return false; - - ident = args.children.at(0)->as(); - if (!ident) - return false; - - if (ident->name() != primary_key) - return false; - value = args.children.at(1); - - PreparedSetKey set_key; - if ((value->as() || value->as())) - set_key = PreparedSetKey::forSubquery(*value); - else - set_key = PreparedSetKey::forLiteral(*value, {primary_key_type}); - - SetPtr set = prepared_sets->get(set_key); - if (!set) - return false; - - if (!set->hasExplicitSetElements()) - return false; - - set->checkColumnsNumber(1); - const auto & set_column = *set->getSetElements()[0]; - for (size_t row = 0; row < set_column.size(); ++row) - res->push_back(set_column[row]); - return true; - } - else - { - if ((ident = args.children.at(0)->as())) - value = args.children.at(1); - else if ((ident = args.children.at(1)->as())) - value = args.children.at(0); - else - return false; - - if (ident->name() != primary_key) - return false; - - const auto node = evaluateConstantExpressionAsLiteral(value, context); - /// function->name == "equals" - if (const auto * literal = node->as()) - { - auto converted_field = convertFieldToType(literal->value, *primary_key_type); - if (!converted_field.isNull()) - res->push_back(converted_field); - return true; - } - } - } - return false; -} - -/** Retrieve from the query a condition of the form `key = 'key'`, `key in ('xxx_'), from conjunctions in the WHERE clause. - * TODO support key like search - */ -static std::pair getFilterKeys( - const String & primary_key, const DataTypePtr & primary_key_type, const SelectQueryInfo & query_info, const ContextPtr & context) -{ - const auto & select = query_info.query->as(); - if (!select.where()) - return {{}, true}; - - FieldVectorPtr res = std::make_shared(); - auto matched_keys = traverseASTFilter(primary_key, primary_key_type, select.where(), query_info.prepared_sets, context, res); - return std::make_pair(res, !matched_keys); -} - -template -static void fillColumns(const K & key, const V & value, size_t key_pos, const Block & header, MutableColumns & columns) -{ - ReadBufferFromString key_buffer(key); - ReadBufferFromString value_buffer(value); - for (size_t i = 0; i < header.columns(); ++i) - { - const auto & serialization = header.getByPosition(i).type->getDefaultSerialization(); - serialization->deserializeBinary(*columns[i], i == key_pos ? key_buffer : value_buffer); - } -} - -static std::vector serializeKeysToRawString( - FieldVector::const_iterator & it, - FieldVector::const_iterator end, - DataTypePtr key_column_type, - size_t max_block_size) -{ - size_t num_keys = end - it; - - std::vector result; - result.reserve(num_keys); - - size_t rows_processed = 0; - while (it < end && (max_block_size == 0 || rows_processed < max_block_size)) - { - std::string & serialized_key = result.emplace_back(); - WriteBufferFromString wb(serialized_key); - key_column_type->getDefaultSerialization()->serializeBinary(*it, wb); - wb.finalize(); - - ++it; - ++rows_processed; - } - return result; -} - -static std::vector serializeKeysToRawString(const ColumnWithTypeAndName & keys) -{ - if (!keys.column) - return {}; - - size_t num_keys = keys.column->size(); - std::vector result; - result.reserve(num_keys); - - for (size_t i = 0; i < num_keys; ++i) - { - std::string & serialized_key = result.emplace_back(); - WriteBufferFromString wb(serialized_key); - Field field; - keys.column->get(i, field); - /// TODO(@vdimir): use serializeBinaryBulk - keys.type->getDefaultSerialization()->serializeBinary(field, wb); - wb.finalize(); - } - return result; -} - -/// In current implementation rocks db can have key with only one column. -static size_t getPrimaryKeyPos(const Block & header, const Names & primary_key) -{ - if (primary_key.size() != 1) - throw Exception(ErrorCodes::LOGICAL_ERROR, "RocksDB: only one primary key is supported"); - return header.getPositionByName(primary_key[0]); -} - class EmbeddedRocksDBSource : public ISource { public: @@ -597,7 +411,6 @@ Chunk StorageEmbeddedRocksDB::getBySerializedKeys( for (const auto & key : keys) slices_keys.emplace_back(key); - auto statuses = multiGet(slices_keys, values); if (null_map) { From 3901778d499a378f97e2c34a63b28be4160c81ac Mon Sep 17 00:00:00 2001 From: kssenii Date: Tue, 16 Aug 2022 11:32:00 +0200 Subject: [PATCH 664/672] Add more current metrics for cache --- src/Common/CurrentMetrics.cpp | 2 ++ src/Common/LRUFileCachePriority.cpp | 44 +++++++++++++++++++++++++++++ src/Common/LRUFileCachePriority.h | 27 ++++-------------- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/Common/CurrentMetrics.cpp b/src/Common/CurrentMetrics.cpp index f479e4cc140..c46342f6ade 100644 --- a/src/Common/CurrentMetrics.cpp +++ b/src/Common/CurrentMetrics.cpp @@ -92,6 +92,8 @@ M(FilesystemCacheReadBuffers, "Number of active cache buffers") \ M(CacheFileSegments, "Number of existing cache file segments") \ M(CacheDetachedFileSegments, "Number of existing detached cache file segments") \ + M(FilesystemCacheSize, "Filesystem cache size in bytes") \ + M(FilesystemCacheElements, "Filesystem cache elements (file segments)") \ M(S3Requests, "S3 requests") \ M(KeeperAliveConnections, "Number of alive connections") \ M(KeeperOutstandingRequets, "Number of outstanding requests") \ diff --git a/src/Common/LRUFileCachePriority.cpp b/src/Common/LRUFileCachePriority.cpp index b4c4bfa338b..91addc92501 100644 --- a/src/Common/LRUFileCachePriority.cpp +++ b/src/Common/LRUFileCachePriority.cpp @@ -1,4 +1,11 @@ #include +#include + +namespace CurrentMetrics +{ + extern const Metric FilesystemCacheSize; + extern const Metric FilesystemCacheElements; +} namespace DB { @@ -22,8 +29,13 @@ IFileCachePriority::WriteIterator LRUFileCachePriority::add(const Key & key, siz entry.size); } #endif + auto iter = queue.insert(queue.end(), FileCacheRecord(key, offset, size)); cache_size += size; + + CurrentMetrics::add(CurrentMetrics::FilesystemCacheSize, size); + CurrentMetrics::add(CurrentMetrics::FilesystemCacheElements); + return std::make_shared(this, iter); } @@ -39,10 +51,19 @@ bool LRUFileCachePriority::contains(const Key & key, size_t offset, std::lock_gu void LRUFileCachePriority::removeAll(std::lock_guard &) { + CurrentMetrics::sub(CurrentMetrics::FilesystemCacheSize, cache_size); + CurrentMetrics::sub(CurrentMetrics::FilesystemCacheElements, queue.size()); + queue.clear(); cache_size = 0; } +LRUFileCachePriority::LRUFileCacheIterator::LRUFileCacheIterator( + LRUFileCachePriority * cache_priority_, LRUFileCachePriority::LRUQueueIterator queue_iter_) + : cache_priority(cache_priority_), queue_iter(queue_iter_) +{ +} + IFileCachePriority::ReadIterator LRUFileCachePriority::getLowestPriorityReadIterator(std::lock_guard &) { return std::make_unique(this, queue.begin()); @@ -58,4 +79,27 @@ size_t LRUFileCachePriority::getElementsNum(std::lock_guard &) const return queue.size(); } +void LRUFileCachePriority::LRUFileCacheIterator::removeAndGetNext(std::lock_guard &) +{ + cache_priority->cache_size -= queue_iter->size; + + CurrentMetrics::sub(CurrentMetrics::FilesystemCacheSize, queue_iter->size); + CurrentMetrics::sub(CurrentMetrics::FilesystemCacheElements); + + queue_iter = cache_priority->queue.erase(queue_iter); +} + +void LRUFileCachePriority::LRUFileCacheIterator::incrementSize(size_t size_increment, std::lock_guard &) +{ + cache_priority->cache_size += size_increment; + CurrentMetrics::add(CurrentMetrics::FilesystemCacheSize, size_increment); + queue_iter->size += size_increment; +} + +void LRUFileCachePriority::LRUFileCacheIterator::use(std::lock_guard &) +{ + queue_iter->hits++; + cache_priority->queue.splice(cache_priority->queue.end(), cache_priority->queue, queue_iter); +} + }; diff --git a/src/Common/LRUFileCachePriority.h b/src/Common/LRUFileCachePriority.h index 0f5755e1cb8..7ea35e9a5eb 100644 --- a/src/Common/LRUFileCachePriority.h +++ b/src/Common/LRUFileCachePriority.h @@ -37,14 +37,11 @@ private: class LRUFileCachePriority::LRUFileCacheIterator : public IFileCachePriority::IIterator { public: - LRUFileCacheIterator(LRUFileCachePriority * file_cache_, LRUFileCachePriority::LRUQueueIterator queue_iter_) - : file_cache(file_cache_), queue_iter(queue_iter_) - { - } + LRUFileCacheIterator(LRUFileCachePriority * cache_priority_, LRUFileCachePriority::LRUQueueIterator queue_iter_); void next() const override { queue_iter++; } - bool valid() const override { return queue_iter != file_cache->queue.end(); } + bool valid() const override { return queue_iter != cache_priority->queue.end(); } const Key & key() const override { return queue_iter->key; } @@ -54,26 +51,14 @@ public: size_t hits() const override { return queue_iter->hits; } - void removeAndGetNext(std::lock_guard &) override - { - file_cache->cache_size -= queue_iter->size; - queue_iter = file_cache->queue.erase(queue_iter); - } + void removeAndGetNext(std::lock_guard &) override; - void incrementSize(size_t size_increment, std::lock_guard &) override - { - file_cache->cache_size += size_increment; - queue_iter->size += size_increment; - } + void incrementSize(size_t size_increment, std::lock_guard &) override; - void use(std::lock_guard &) override - { - queue_iter->hits++; - file_cache->queue.splice(file_cache->queue.end(), file_cache->queue, queue_iter); - } + void use(std::lock_guard &) override; private: - LRUFileCachePriority * file_cache; + LRUFileCachePriority * cache_priority; mutable LRUFileCachePriority::LRUQueueIterator queue_iter; }; From 6fd4d2cfb3492cf12e87ac6d1cab5f8f4ea2b5cc Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 16 Aug 2022 15:32:50 +0300 Subject: [PATCH 665/672] Revert "tests/performance: cover sparse_hashed dictionary (#40027)" This reverts commit 6a30c23252a41ada572bad7e54e13535d295a0df. --- tests/performance/hashed_dictionary.xml | 193 ++++++++++++------------ 1 file changed, 93 insertions(+), 100 deletions(-) diff --git a/tests/performance/hashed_dictionary.xml b/tests/performance/hashed_dictionary.xml index 01ee35c8ed4..cf1cdac6df1 100644 --- a/tests/performance/hashed_dictionary.xml +++ b/tests/performance/hashed_dictionary.xml @@ -1,4 +1,72 @@ + + CREATE TABLE simple_key_hashed_dictionary_source_table + ( + id UInt64, + value_int UInt64, + value_string String, + value_decimal Decimal64(8), + value_string_nullable Nullable(String) + ) ENGINE = Memory; + + + + CREATE TABLE complex_key_hashed_dictionary_source_table + ( + id UInt64, + id_key String, + value_int UInt64, + value_string String, + value_decimal Decimal64(8), + value_string_nullable Nullable(String) + ) ENGINE = Memory; + + + + CREATE DICTIONARY simple_key_hashed_dictionary + ( + id UInt64, + value_int UInt64, + value_string String, + value_decimal Decimal64(8), + value_string_nullable Nullable(String) + ) + PRIMARY KEY id + SOURCE(CLICKHOUSE(DB 'default' TABLE 'simple_key_hashed_dictionary_source_table')) + LAYOUT(HASHED()) + LIFETIME(MIN 0 MAX 1000); + + + + CREATE DICTIONARY complex_key_hashed_dictionary + ( + id UInt64, + id_key String, + value_int UInt64, + value_string String, + value_decimal Decimal64(8), + value_string_nullable Nullable(String) + ) + PRIMARY KEY id, id_key + SOURCE(CLICKHOUSE(DB 'default' TABLE 'complex_key_hashed_dictionary_source_table')) + LAYOUT(COMPLEX_KEY_HASHED()) + LIFETIME(MIN 0 MAX 1000); + + + + INSERT INTO simple_key_hashed_dictionary_source_table + SELECT number, number, toString(number), toDecimal64(number, 8), toString(number) + FROM system.numbers + LIMIT 5000000; + + + + INSERT INTO complex_key_hashed_dictionary_source_table + SELECT number, toString(number), number, toString(number), toDecimal64(number, 8), toString(number) + FROM system.numbers + LIMIT 5000000; + + column_name @@ -17,129 +85,54 @@ 7500000 - - - layout_suffix - - HASHED - SPARSE_HASHED - - - - CREATE TABLE simple_key_dictionary_source_table - ( - id UInt64, - value_int UInt64, - value_string String, - value_decimal Decimal64(8), - value_string_nullable Nullable(String) - ) ENGINE = Memory - - - - CREATE TABLE complex_key_dictionary_source_table - ( - id UInt64, - id_key String, - value_int UInt64, - value_string String, - value_decimal Decimal64(8), - value_string_nullable Nullable(String) - ) ENGINE = Memory - - - - CREATE DICTIONARY IF NOT EXISTS simple_key_{layout_suffix}_dictionary - ( - id UInt64, - value_int UInt64, - value_string String, - value_decimal Decimal64(8), - value_string_nullable Nullable(String) - ) - PRIMARY KEY id - SOURCE(CLICKHOUSE(TABLE 'simple_key_dictionary_source_table')) - LAYOUT({layout_suffix}()) - LIFETIME(0) - - - - CREATE DICTIONARY IF NOT EXISTS complex_key_{layout_suffix}_dictionary - ( - id UInt64, - id_key String, - value_int UInt64, - value_string String, - value_decimal Decimal64(8), - value_string_nullable Nullable(String) - ) - PRIMARY KEY id, id_key - SOURCE(CLICKHOUSE(TABLE 'complex_key_dictionary_source_table')) - LAYOUT(COMPLEX_KEY_{layout_suffix}()) - LIFETIME(0) - - - - INSERT INTO simple_key_dictionary_source_table - SELECT number, number, toString(number), toDecimal64(number, 8), toString(number) - FROM system.numbers - LIMIT 5000000 - - - - INSERT INTO complex_key_dictionary_source_table - SELECT number, toString(number), number, toString(number), toDecimal64(number, 8), toString(number) - FROM system.numbers - LIMIT 5000000 - - - SYSTEM RELOAD DICTIONARY simple_key_{layout_suffix}_dictionary - SYSTEM RELOAD DICTIONARY complex_key_{layout_suffix}_dictionary - - SYSTEM RELOAD DICTIONARY simple_key_{layout_suffix}_dictionary - SYSTEM RELOAD DICTIONARY complex_key_{layout_suffix}_dictionary - WITH rand64() % toUInt64({elements_count}) as key - SELECT dictGet('default.simple_key_{layout_suffix}_dictionary', {column_name}, key) + SELECT dictGet('default.simple_key_hashed_dictionary', {column_name}, key) FROM system.numbers LIMIT {elements_count} - FORMAT Null + FORMAT Null; WITH rand64() % toUInt64({elements_count}) as key - SELECT dictHas('default.simple_key_{layout_suffix}_dictionary', key) + SELECT dictHas('default.simple_key_hashed_dictionary', key) FROM system.numbers LIMIT {elements_count} - FORMAT Null + FORMAT Null; - SELECT * FROM simple_key_{layout_suffix}_dictionary FORMAT Null - - WITH (rand64() % toUInt64({elements_count}), toString(rand64() % toUInt64({elements_count}))) as key - SELECT dictGet('default.complex_key_{layout_suffix}_dictionary', {column_name}, key) - FROM system.numbers - LIMIT {elements_count} - FORMAT Null + SELECT * FROM simple_key_hashed_dictionary + FORMAT Null; WITH (rand64() % toUInt64({elements_count}), toString(rand64() % toUInt64({elements_count}))) as key - SELECT dictHas('default.complex_key_{layout_suffix}_dictionary', key) + SELECT dictGet('default.complex_key_hashed_dictionary', {column_name}, key) FROM system.numbers LIMIT {elements_count} - FORMAT Null + FORMAT Null; - SELECT * FROM complex_key_{layout_suffix}_dictionary FORMAT Null + + WITH (rand64() % toUInt64({elements_count}), toString(rand64() % toUInt64({elements_count}))) as key + SELECT dictHas('default.complex_key_hashed_dictionary', key) + FROM system.numbers + LIMIT {elements_count} + FORMAT Null; + - DROP TABLE IF EXISTS simple_key_dictionary_source_table - DROP TABLE IF EXISTS complex_key_dictionary_source_table + + SELECT * FROM complex_key_hashed_dictionary + FORMAT Null; + + + DROP TABLE IF EXISTS simple_key_hashed_dictionary_source_table; + DROP TABLE IF EXISTS complex_key_hashed_dictionary_source_table; + + DROP DICTIONARY IF EXISTS simple_key_hashed_dictionary; + DROP DICTIONARY IF EXISTS complex_key_hashed_dictionary; - DROP DICTIONARY IF EXISTS simple_key_{layout_suffix}_dictionary - DROP DICTIONARY IF EXISTS complex_key_{layout_suffix}_dictionary From dfbbc51045b56f55e12174452ff5020a5f73edb5 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy <99031427+yakov-olkhovskiy@users.noreply.github.com> Date: Tue, 16 Aug 2022 09:02:03 -0400 Subject: [PATCH 666/672] add update_mergeable_check trigger --- tests/ci/unit_tests_check.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ci/unit_tests_check.py b/tests/ci/unit_tests_check.py index eadb07c729c..6df7499275f 100644 --- a/tests/ci/unit_tests_check.py +++ b/tests/ci/unit_tests_check.py @@ -4,6 +4,7 @@ import logging import os import sys import subprocess +import atexit from github import Github @@ -14,7 +15,7 @@ from pr_info import PRInfo from build_download_helper import download_unit_tests from upload_result_helper import upload_results from docker_pull_helper import get_image_with_version -from commit_status_helper import post_commit_status +from commit_status_helper import post_commit_status, update_mergeable_check from clickhouse_helper import ( ClickHouseHelper, mark_flaky_tests, @@ -115,6 +116,8 @@ if __name__ == "__main__": pr_info = PRInfo() gh = Github(get_best_robot_token(), per_page=100) + + atexit.register(update_mergeable_check, gh, pr_info, check_name) rerun_helper = RerunHelper(gh, pr_info, check_name) if rerun_helper.is_already_finished_by_status(): From 15bbb21a09575145b38ccf76e96e36efec600bcf Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 16 Aug 2022 13:10:28 +0000 Subject: [PATCH 667/672] Automatic style fix --- tests/ci/unit_tests_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/unit_tests_check.py b/tests/ci/unit_tests_check.py index 6df7499275f..c2dfab9dddc 100644 --- a/tests/ci/unit_tests_check.py +++ b/tests/ci/unit_tests_check.py @@ -116,7 +116,7 @@ if __name__ == "__main__": pr_info = PRInfo() gh = Github(get_best_robot_token(), per_page=100) - + atexit.register(update_mergeable_check, gh, pr_info, check_name) rerun_helper = RerunHelper(gh, pr_info, check_name) From 6539273c86ef117c74a374430f335d4315b44328 Mon Sep 17 00:00:00 2001 From: Suzy Wang Date: Tue, 16 Aug 2022 06:50:37 -0700 Subject: [PATCH 668/672] update lz4.c from upstream, cve fixes --- contrib/librdkafka | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/librdkafka b/contrib/librdkafka index 6062e711a91..ff32b4e9eea 160000 --- a/contrib/librdkafka +++ b/contrib/librdkafka @@ -1 +1 @@ -Subproject commit 6062e711a919fb3b669b243b7dceabd045d0e4a2 +Subproject commit ff32b4e9eeafd0b276f010ee969179e4e9e6d0b2 From 0136262a2ab6535fdf8928b43f851863f9f0c224 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 16 Aug 2022 18:03:02 +0200 Subject: [PATCH 669/672] fix --- src/Interpreters/DDLWorker.cpp | 2 ++ src/Interpreters/DDLWorker.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Interpreters/DDLWorker.cpp b/src/Interpreters/DDLWorker.cpp index 0e1aaa2ff20..6ec20ab5f5f 100644 --- a/src/Interpreters/DDLWorker.cpp +++ b/src/Interpreters/DDLWorker.cpp @@ -112,6 +112,8 @@ DDLWorker::DDLWorker( void DDLWorker::startup() { + [[maybe_unused]] bool prev_stop_flag = stop_flag.exchange(false); + chassert(true); main_thread = ThreadFromGlobalPool(&DDLWorker::runMainThread, this); cleanup_thread = ThreadFromGlobalPool(&DDLWorker::runCleanupThread, this); } diff --git a/src/Interpreters/DDLWorker.h b/src/Interpreters/DDLWorker.h index 5dc6d4acbe5..7ddcc80c02a 100644 --- a/src/Interpreters/DDLWorker.h +++ b/src/Interpreters/DDLWorker.h @@ -138,7 +138,7 @@ protected: std::shared_ptr queue_updated_event = std::make_shared(); std::shared_ptr cleanup_event = std::make_shared(); std::atomic initialized = false; - std::atomic stop_flag = false; + std::atomic stop_flag = true; ThreadFromGlobalPool main_thread; ThreadFromGlobalPool cleanup_thread; From 89bf69c35fae4eaf526ded7580f0fd8ee0b4062f Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Mon, 15 Aug 2022 18:58:46 +0000 Subject: [PATCH 670/672] style: improve code aesthetics and make it slightly safer --- src/Functions/MatchImpl.h | 6 +- src/Functions/MultiMatchAllIndicesImpl.h | 4 +- src/Functions/MultiMatchAnyImpl.h | 6 +- src/Functions/Regexps.h | 102 +++++++++++------------ 4 files changed, 55 insertions(+), 63 deletions(-) diff --git a/src/Functions/MatchImpl.h b/src/Functions/MatchImpl.h index 78ce2627c35..d5465dc3498 100644 --- a/src/Functions/MatchImpl.h +++ b/src/Functions/MatchImpl.h @@ -476,8 +476,7 @@ struct MatchImpl } else { - cache.getOrSet(needle, regexp); - + regexp = cache.getOrSet(needle); regexp->getAnalyzeResult(required_substr, is_trivial, required_substring_is_prefix); if (required_substr.empty()) @@ -589,8 +588,7 @@ struct MatchImpl } else { - cache.getOrSet(needle, regexp); - + regexp = cache.getOrSet(needle); regexp->getAnalyzeResult(required_substr, is_trivial, required_substring_is_prefix); if (required_substr.empty()) diff --git a/src/Functions/MultiMatchAllIndicesImpl.h b/src/Functions/MultiMatchAllIndicesImpl.h index 5c5de846a3f..29a8d250830 100644 --- a/src/Functions/MultiMatchAllIndicesImpl.h +++ b/src/Functions/MultiMatchAllIndicesImpl.h @@ -86,7 +86,7 @@ struct MultiMatchAllIndicesImpl return; } - const auto & hyperscan_regex = MultiRegexps::get(needles, edit_distance); + const auto * hyperscan_regex = MultiRegexps::get(needles, edit_distance); hs_scratch_t * scratch = nullptr; hs_error_t err = hs_clone_scratch(hyperscan_regex->getScratch(), &scratch); @@ -197,7 +197,7 @@ struct MultiMatchAllIndicesImpl checkHyperscanRegexp(needles, max_hyperscan_regexp_length, max_hyperscan_regexp_total_length); - const auto & hyperscan_regex = MultiRegexps::get(needles, edit_distance); + const auto * hyperscan_regex = MultiRegexps::get(needles, edit_distance); hs_scratch_t * scratch = nullptr; hs_error_t err = hs_clone_scratch(hyperscan_regex->getScratch(), &scratch); diff --git a/src/Functions/MultiMatchAnyImpl.h b/src/Functions/MultiMatchAnyImpl.h index 0490a81d707..cb4cfa854aa 100644 --- a/src/Functions/MultiMatchAnyImpl.h +++ b/src/Functions/MultiMatchAnyImpl.h @@ -100,7 +100,7 @@ struct MultiMatchAnyImpl return; } #if USE_VECTORSCAN - const auto & hyperscan_regex = MultiRegexps::get(needles, edit_distance); + const auto * hyperscan_regex = MultiRegexps::get(needles, edit_distance); hs_scratch_t * scratch = nullptr; hs_error_t err = hs_clone_scratch(hyperscan_regex->getScratch(), &scratch); @@ -155,7 +155,7 @@ struct MultiMatchAnyImpl memset(accum.data(), 0, accum.size()); for (size_t j = 0; j < needles.size(); ++j) { - MatchImpl::vectorConstant(haystack_data, haystack_offsets, std::string(needles[j].data(), needles[j].size()), nullptr, accum); + MatchImpl::vectorConstant(haystack_data, haystack_offsets, String(needles[j].data(), needles[j].size()), nullptr, accum); for (size_t i = 0; i < res.size(); ++i) { if constexpr (FindAny) @@ -224,7 +224,7 @@ struct MultiMatchAnyImpl checkHyperscanRegexp(needles, max_hyperscan_regexp_length, max_hyperscan_regexp_total_length); - const auto & hyperscan_regex = MultiRegexps::get(needles, edit_distance); + const auto * hyperscan_regex = MultiRegexps::get(needles, edit_distance); hs_scratch_t * scratch = nullptr; hs_error_t err = hs_clone_scratch(hyperscan_regex->getScratch(), &scratch); diff --git a/src/Functions/Regexps.h b/src/Functions/Regexps.h index b932b14a6a9..20a0a128f7b 100644 --- a/src/Functions/Regexps.h +++ b/src/Functions/Regexps.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "config_functions.h" @@ -41,7 +42,7 @@ using Regexp = OptimizedRegularExpressionSingleThreaded; using RegexpPtr = std::shared_ptr; template -inline Regexp createRegexp(const std::string & pattern) +inline Regexp createRegexp(const String & pattern) { int flags = OptimizedRegularExpression::RE_DOT_NL; if constexpr (no_capture) @@ -66,39 +67,32 @@ public: using RegexpPtr = std::shared_ptr; template - void getOrSet(const String & pattern, RegexpPtr & regexp) + RegexpPtr getOrSet(const String & pattern) { - StringAndRegexp & bucket = known_regexps[hasher(pattern) % max_regexp_cache_size]; + Bucket & bucket = known_regexps[hasher(pattern) % CACHE_SIZE]; - if (likely(bucket.regexp != nullptr)) - { - if (pattern == bucket.pattern) - regexp = bucket.regexp; - else - { - regexp = std::make_shared(createRegexp(pattern)); - bucket = {pattern, regexp}; - } - } + if (bucket.regexp == nullptr) [[unlikely]] + /// insert new entry + bucket = {pattern, std::make_shared(createRegexp(pattern))}; else - { - regexp = std::make_shared(createRegexp(pattern)); - bucket = {pattern, regexp}; - } + if (pattern != bucket.pattern) + /// replace existing entry + bucket = {pattern, std::make_shared(createRegexp(pattern))}; + + return bucket.regexp; } private: - constexpr static size_t max_regexp_cache_size = 100; // collision probability + constexpr static size_t CACHE_SIZE = 100; // collision probability - std::hash hasher; - struct StringAndRegexp + std::hash hasher; + struct Bucket { - std::string pattern; - RegexpPtr regexp; + String pattern; /// key + RegexpPtr regexp; /// value }; - using CacheTable = std::array; + using CacheTable = std::array; CacheTable known_regexps; - }; } @@ -136,25 +130,23 @@ private: ScratchPtr scratch; }; -class RegexpsConstructor +class DeferredConstructedRegexps { public: - RegexpsConstructor() = default; - void setConstructor(std::function constructor_) { constructor = std::move(constructor_); } - Regexps * operator()() + Regexps * get() { std::lock_guard lock(mutex); - if (regexp) - return &*regexp; - regexp = constructor(); - return &*regexp; + if (regexps) + return &*regexps; + regexps = constructor(); + return &*regexps; } private: std::function constructor; - std::optional regexp; + std::optional regexps TSA_GUARDED_BY(mutex); std::mutex mutex; }; @@ -162,8 +154,8 @@ struct Pool { /// Mutex for finding in map. std::mutex mutex; - /// Patterns + possible edit_distance to database and scratch. - std::map, std::optional>, RegexpsConstructor> storage; + /// (Patterns + possible edit_distance) -> (database + scratch area) + std::map, std::optional>, DeferredConstructedRegexps> storage; }; template @@ -186,9 +178,9 @@ inline Regexps constructRegexps(const std::vector & str_patterns, [[mayb ext_exprs_ptrs.reserve(str_patterns.size()); } - for (const StringRef ref : str_patterns) + for (std::string_view ref : str_patterns) { - patterns.push_back(ref.data); + patterns.push_back(ref.data()); /* Flags below are the pattern matching flags. * HS_FLAG_DOTALL is a compile flag where matching a . will not exclude newlines. This is a good * performance practice according to Hyperscan API. https://intel.github.io/hyperscan/dev-reference/performance.html#dot-all-mode @@ -251,11 +243,9 @@ inline Regexps constructRegexps(const std::vector & str_patterns, [[mayb CompilerError error(compile_error); if (error->expression < 0) - throw Exception(String(error->message), ErrorCodes::LOGICAL_ERROR); + throw Exception(ErrorCodes::LOGICAL_ERROR, String(error->message)); else - throw Exception( - "Pattern '" + str_patterns[error->expression] + "' failed with error '" + String(error->message), - ErrorCodes::BAD_ARGUMENTS); + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Pattern '{}' failed with error '{}'", str_patterns[error->expression], String(error->message)); } ProfileEvents::increment(ProfileEvents::RegexpCreated); @@ -267,7 +257,7 @@ inline Regexps constructRegexps(const std::vector & str_patterns, [[mayb /// If not HS_SUCCESS, it is guaranteed that the memory would not be allocated for scratch. if (err != HS_SUCCESS) - throw Exception("Could not allocate scratch space for hyperscan", ErrorCodes::CANNOT_ALLOCATE_MEMORY); + throw Exception(ErrorCodes::CANNOT_ALLOCATE_MEMORY, "Could not allocate scratch space for hyperscan"); return {db, scratch}; } @@ -284,28 +274,32 @@ inline Regexps * get(const std::vector & patterns, std::option std::vector str_patterns; str_patterns.reserve(patterns.size()); for (const auto & pattern : patterns) - str_patterns.emplace_back(std::string(pattern.data(), pattern.size())); + str_patterns.emplace_back(String(pattern)); - /// Get the lock for finding database. + /// Lock pool to find compiled regexp for given pattern + edit distance. std::unique_lock lock(known_regexps.mutex); auto it = known_regexps.storage.find({str_patterns, edit_distance}); - /// If not found, compile and let other threads wait. if (known_regexps.storage.end() == it) { - it = known_regexps.storage - .emplace(std::piecewise_construct, std::make_tuple(std::move(str_patterns), edit_distance), std::make_tuple()) - .first; - it->second.setConstructor([&str_patterns = it->first.first, edit_distance]() - { - return constructRegexps(str_patterns, edit_distance); - }); + /// Not found. Pattern compilation is expensive and we don't want to block other threads reading from /inserting into the pool while + /// we hold the pool lock during pattern compilation. Therefore, only set the constructor method and compile outside the pool lock. + it = known_regexps.storage.emplace( + std::piecewise_construct, + std::make_tuple(std::move(str_patterns), edit_distance), + std::make_tuple()) + .first; + it->second.setConstructor( + [&str_patterns = it->first.first, edit_distance]() + { + return constructRegexps(str_patterns, edit_distance); + } + ); } - /// Unlock before possible construction. lock.unlock(); - return it->second(); + return it->second.get(); /// Possibly compile pattern now. } } From df889351ad3321ea2c0f4219f9a14d7fc8f7f3a8 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Tue, 16 Aug 2022 09:56:53 +0000 Subject: [PATCH 671/672] feat: replace unbounded vectorscan cache by bounded cache VectorScan patterns can grow large (up to multiple MBs) and queries involving different VectorScan patterns lead to an ever increasing pattern cache size. With this commit, the unbounded cache for vectorscan patterns is replaced by a bounded cache with "CacheTable" eviction strategy, similar to what we have for re2 patterns. The cache size is currently hard-coded to 500 entries. Fixes #19869 --- src/Functions/MultiMatchAllIndicesImpl.h | 14 +-- src/Functions/MultiMatchAnyImpl.h | 15 ++-- src/Functions/Regexps.h | 110 +++++++++++++++-------- 3 files changed, 91 insertions(+), 48 deletions(-) diff --git a/src/Functions/MultiMatchAllIndicesImpl.h b/src/Functions/MultiMatchAllIndicesImpl.h index 29a8d250830..e19d1691c6a 100644 --- a/src/Functions/MultiMatchAllIndicesImpl.h +++ b/src/Functions/MultiMatchAllIndicesImpl.h @@ -86,9 +86,10 @@ struct MultiMatchAllIndicesImpl return; } - const auto * hyperscan_regex = MultiRegexps::get(needles, edit_distance); + MultiRegexps::DeferredConstructedRegexpsPtr deferred_constructed_regexps = MultiRegexps::getOrSet(needles, edit_distance); + MultiRegexps::Regexps * regexps = deferred_constructed_regexps->get(); hs_scratch_t * scratch = nullptr; - hs_error_t err = hs_clone_scratch(hyperscan_regex->getScratch(), &scratch); + hs_error_t err = hs_clone_scratch(regexps->getScratch(), &scratch); if (err != HS_SUCCESS) throw Exception("Could not clone scratch space for hyperscan", ErrorCodes::CANNOT_ALLOCATE_MEMORY); @@ -114,7 +115,7 @@ struct MultiMatchAllIndicesImpl throw Exception("Too long string to search", ErrorCodes::TOO_MANY_BYTES); /// scan, check, update the offsets array and the offset of haystack. err = hs_scan( - hyperscan_regex->getDB(), + regexps->getDB(), reinterpret_cast(haystack_data.data()) + offset, length, 0, @@ -197,9 +198,10 @@ struct MultiMatchAllIndicesImpl checkHyperscanRegexp(needles, max_hyperscan_regexp_length, max_hyperscan_regexp_total_length); - const auto * hyperscan_regex = MultiRegexps::get(needles, edit_distance); + MultiRegexps::DeferredConstructedRegexpsPtr deferred_constructed_regexps = MultiRegexps::getOrSet(needles, edit_distance); + MultiRegexps::Regexps * regexps = deferred_constructed_regexps->get(); hs_scratch_t * scratch = nullptr; - hs_error_t err = hs_clone_scratch(hyperscan_regex->getScratch(), &scratch); + hs_error_t err = hs_clone_scratch(regexps->getScratch(), &scratch); if (err != HS_SUCCESS) throw Exception("Could not clone scratch space for hyperscan", ErrorCodes::CANNOT_ALLOCATE_MEMORY); @@ -224,7 +226,7 @@ struct MultiMatchAllIndicesImpl /// scan, check, update the offsets array and the offset of haystack. err = hs_scan( - hyperscan_regex->getDB(), + regexps->getDB(), reinterpret_cast(haystack_data.data()) + prev_haystack_offset, cur_haystack_length, 0, diff --git a/src/Functions/MultiMatchAnyImpl.h b/src/Functions/MultiMatchAnyImpl.h index cb4cfa854aa..a5d5a354290 100644 --- a/src/Functions/MultiMatchAnyImpl.h +++ b/src/Functions/MultiMatchAnyImpl.h @@ -100,9 +100,11 @@ struct MultiMatchAnyImpl return; } #if USE_VECTORSCAN - const auto * hyperscan_regex = MultiRegexps::get(needles, edit_distance); + MultiRegexps::DeferredConstructedRegexpsPtr deferred_constructed_regexps = MultiRegexps::getOrSet(needles, edit_distance); + MultiRegexps::Regexps * regexps = deferred_constructed_regexps->get(); + hs_scratch_t * scratch = nullptr; - hs_error_t err = hs_clone_scratch(hyperscan_regex->getScratch(), &scratch); + hs_error_t err = hs_clone_scratch(regexps->getScratch(), &scratch); if (err != HS_SUCCESS) throw Exception("Could not clone scratch space for vectorscan", ErrorCodes::CANNOT_ALLOCATE_MEMORY); @@ -133,7 +135,7 @@ struct MultiMatchAnyImpl /// zero the result, scan, check, update the offset. res[i] = 0; err = hs_scan( - hyperscan_regex->getDB(), + regexps->getDB(), reinterpret_cast(haystack_data.data()) + offset, length, 0, @@ -224,9 +226,10 @@ struct MultiMatchAnyImpl checkHyperscanRegexp(needles, max_hyperscan_regexp_length, max_hyperscan_regexp_total_length); - const auto * hyperscan_regex = MultiRegexps::get(needles, edit_distance); + MultiRegexps::DeferredConstructedRegexpsPtr deferred_constructed_regexps = MultiRegexps::getOrSet(needles, edit_distance); + MultiRegexps::Regexps * regexps = deferred_constructed_regexps->get(); hs_scratch_t * scratch = nullptr; - hs_error_t err = hs_clone_scratch(hyperscan_regex->getScratch(), &scratch); + hs_error_t err = hs_clone_scratch(regexps->getScratch(), &scratch); if (err != HS_SUCCESS) throw Exception("Could not clone scratch space for vectorscan", ErrorCodes::CANNOT_ALLOCATE_MEMORY); @@ -256,7 +259,7 @@ struct MultiMatchAnyImpl /// zero the result, scan, check, update the offset. res[i] = 0; err = hs_scan( - hyperscan_regex->getDB(), + regexps->getDB(), reinterpret_cast(haystack_data.data()) + prev_haystack_offset, cur_haystack_length, 0, diff --git a/src/Functions/Regexps.h b/src/Functions/Regexps.h index 20a0a128f7b..08c819fae99 100644 --- a/src/Functions/Regexps.h +++ b/src/Functions/Regexps.h @@ -14,6 +14,7 @@ #include #include #include +#include #include "config_functions.h" @@ -83,7 +84,7 @@ public: } private: - constexpr static size_t CACHE_SIZE = 100; // collision probability + constexpr static size_t CACHE_SIZE = 100; /// collision probability std::hash hasher; struct Bucket @@ -133,7 +134,9 @@ private: class DeferredConstructedRegexps { public: - void setConstructor(std::function constructor_) { constructor = std::move(constructor_); } + explicit DeferredConstructedRegexps(std::function constructor_) + : constructor(std::move(constructor_)) + {} Regexps * get() { @@ -145,18 +148,12 @@ public: } private: - std::function constructor; + std::function constructor TSA_GUARDED_BY(mutex); std::optional regexps TSA_GUARDED_BY(mutex); std::mutex mutex; }; -struct Pool -{ - /// Mutex for finding in map. - std::mutex mutex; - /// (Patterns + possible edit_distance) -> (database + scratch area) - std::map, std::optional>, DeferredConstructedRegexps> storage; -}; +using DeferredConstructedRegexpsPtr = std::shared_ptr; template inline Regexps constructRegexps(const std::vector & str_patterns, [[maybe_unused]] std::optional edit_distance) @@ -262,44 +259,85 @@ inline Regexps constructRegexps(const std::vector & str_patterns, [[mayb return {db, scratch}; } -/// If WithEditDistance is False, edit_distance must be nullopt -/// Also, we use templates here because each instantiation of function -/// template has its own copy of local static variables which must not be the same -/// for different hyperscan compilations. -template -inline Regexps * get(const std::vector & patterns, std::optional edit_distance) +/// Maps string pattern vectors + edit distance to compiled vectorscan regexps. Uses the same eviction mechanism as the LocalCacheTable for +/// re2 patterns. Because vectorscan regexes are overall more heavy-weight (more expensive compilation, regexes can grow up to multiple +/// MBs, usage of scratch space), 1. GlobalCacheTable is a global singleton and, as a result, needs locking 2. the pattern compilation is +/// done outside GlobalCacheTable's lock, at the cost of another level of locking. +struct GlobalCacheTable { - static Pool known_regexps; /// Different variables for different pattern parameters, thread-safe in C++11 + constexpr static size_t CACHE_SIZE = 500; /// collision probability + + struct Bucket + { + std::vector patterns; /// key + std::optional edit_distance; /// key + /// The compiled patterns and their state (vectorscan 'database' + scratch space) are wrapped in a shared_ptr. Refcounting guarantees + /// that eviction of a pattern does not affect parallel threads still using the pattern. + DeferredConstructedRegexpsPtr regexps; /// value + }; + + std::array known_regexps TSA_GUARDED_BY(mutex); + std::mutex mutex; + + static size_t getBucketIndexFor(const std::vector patterns, std::optional edit_distance) + { + size_t hash = 0; + for (const auto & pattern : patterns) + boost::hash_combine(hash, pattern); + boost::hash_combine(hash, edit_distance); + return hash % CACHE_SIZE; + } +}; + +/// If WithEditDistance is False, edit_distance must be nullopt. Also, we use templates here because each instantiation of function template +/// has its own copy of local static variables which must not be the same for different hyperscan compilations. +template +inline DeferredConstructedRegexpsPtr getOrSet(const std::vector & patterns, std::optional edit_distance) +{ + static GlobalCacheTable pool; /// Different variables for different pattern parameters, thread-safe in C++11 std::vector str_patterns; str_patterns.reserve(patterns.size()); for (const auto & pattern : patterns) str_patterns.emplace_back(String(pattern)); - /// Lock pool to find compiled regexp for given pattern + edit distance. - std::unique_lock lock(known_regexps.mutex); + size_t bucket_idx = GlobalCacheTable::getBucketIndexFor(str_patterns, edit_distance); - auto it = known_regexps.storage.find({str_patterns, edit_distance}); + /// Lock cache to find compiled regexp for given pattern vector + edit distance. + std::lock_guard lock(pool.mutex); - if (known_regexps.storage.end() == it) + GlobalCacheTable::Bucket & bucket = pool.known_regexps[bucket_idx]; + + /// Pattern compilation is expensive and we don't want to block other threads reading from / inserting into the cache while we hold the + /// cache lock during pattern compilation. Therefore, when a cache entry is created or replaced, only set the regexp constructor method + /// and compile outside the cache lock. + /// Note that the string patterns and the edit distance is passed into the constructor lambda by value, i.e. copied - it is not an + /// option to reference the corresponding string patterns / edit distance key in the cache table bucket because the cache entry may + /// already be evicted at the time the compilation starts. + + if (bucket.regexps == nullptr) [[unlikely]] { - /// Not found. Pattern compilation is expensive and we don't want to block other threads reading from /inserting into the pool while - /// we hold the pool lock during pattern compilation. Therefore, only set the constructor method and compile outside the pool lock. - it = known_regexps.storage.emplace( - std::piecewise_construct, - std::make_tuple(std::move(str_patterns), edit_distance), - std::make_tuple()) - .first; - it->second.setConstructor( - [&str_patterns = it->first.first, edit_distance]() - { - return constructRegexps(str_patterns, edit_distance); - } - ); + /// insert new entry + auto deferred_constructed_regexps = std::make_shared( + [str_patterns, edit_distance]() + { + return constructRegexps(str_patterns, edit_distance); + }); + bucket = {std::move(str_patterns), edit_distance, deferred_constructed_regexps}; } + else + if (bucket.patterns != str_patterns || bucket.edit_distance != edit_distance) + { + /// replace existing entry + auto deferred_constructed_regexps = std::make_shared( + [str_patterns, edit_distance]() + { + return constructRegexps(str_patterns, edit_distance); + }); + bucket = {std::move(str_patterns), edit_distance, deferred_constructed_regexps}; + } - lock.unlock(); - return it->second.get(); /// Possibly compile pattern now. + return bucket.regexps; } } From a3a124cc349adf95fff5016eac176bf33d2d3c67 Mon Sep 17 00:00:00 2001 From: Rich Raposa Date: Tue, 16 Aug 2022 16:20:10 -0600 Subject: [PATCH 672/672] Added --ask-password option to documentation (#40289) --- docs/en/interfaces/cli.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/en/interfaces/cli.md b/docs/en/interfaces/cli.md index e0882d1f9e1..b113d180a36 100644 --- a/docs/en/interfaces/cli.md +++ b/docs/en/interfaces/cli.md @@ -22,7 +22,9 @@ Connected to ClickHouse server version 20.13.1 revision 54442. Different client and server versions are compatible with one another, but some features may not be available in older clients. We recommend using the same version of the client as the server app. When you try to use a client of the older version, then the server, `clickhouse-client` displays the message: - ClickHouse client version is older than ClickHouse server. It may lack support for new features. +```response +ClickHouse client version is older than ClickHouse server. It may lack support for new features. +``` ## Usage {#cli_usage} @@ -116,6 +118,7 @@ You can pass parameters to `clickhouse-client` (all parameters have a default va - `--port` – The port to connect to. Default value: 9000. Note that the HTTP interface and the native interface use different ports. - `--user, -u` – The username. Default value: default. - `--password` – The password. Default value: empty string. +- `--ask-password` - Prompt the user to enter a password. - `--query, -q` – The query to process when using non-interactive mode. You must specify either `query` or `queries-file` option. - `--queries-file` – file path with queries to execute. You must specify either `query` or `queries-file` option. - `--database, -d` – Select the current default database. Default value: the current database from the server settings (‘default’ by default). @@ -182,6 +185,6 @@ This feature can be used to generate URLs to facilitate profiling of queries. If the configuration above is applied, the ID of a query is shown in the following format: -``` text +```response speedscope:http://speedscope-host/#profileURL=qp%3Fid%3Dc8ecc783-e753-4b38-97f1-42cddfb98b7d ```